@x12i/ai-tools 1.0.2 → 1.0.3
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 +114 -12
- package/dist/{AiModelsCatalogClient-CSVlKql5.d.cts → AiModelsCatalogClient-CNeqFiFs.d.cts} +2 -1
- package/dist/{AiModelsCatalogClient-B-dNLXX0.d.ts → AiModelsCatalogClient-nwFoEaqL.d.ts} +2 -1
- package/dist/aliases/index.d.cts +4 -3
- package/dist/aliases/index.d.ts +4 -3
- package/dist/catalog/index.cjs +30 -0
- package/dist/catalog/index.cjs.map +1 -0
- package/dist/catalog/index.d.cts +100 -0
- package/dist/catalog/index.d.ts +100 -0
- package/dist/catalog/index.js +30 -0
- package/dist/catalog/index.js.map +1 -0
- package/dist/catalox/index.cjs +2 -2
- package/dist/catalox/index.d.cts +7 -19
- package/dist/catalox/index.d.ts +7 -19
- package/dist/catalox/index.js +1 -1
- package/dist/chunk-C3H7RTFR.cjs +1 -0
- package/dist/chunk-C3H7RTFR.cjs.map +1 -0
- package/dist/{chunk-ONA73BU6.cjs → chunk-DKHGWHXP.cjs} +21 -12
- package/dist/chunk-DKHGWHXP.cjs.map +1 -0
- package/dist/{chunk-HHNHWYTP.cjs → chunk-FGP3QXWL.cjs} +94 -36
- package/dist/chunk-FGP3QXWL.cjs.map +1 -0
- package/dist/chunk-HS74X2OJ.cjs +172 -0
- package/dist/chunk-HS74X2OJ.cjs.map +1 -0
- package/dist/chunk-HYGXZY25.js +163 -0
- package/dist/chunk-HYGXZY25.js.map +1 -0
- package/dist/chunk-M5TMA73F.js +1 -0
- package/dist/chunk-M5TMA73F.js.map +1 -0
- package/dist/chunk-MX3AMQFC.js +172 -0
- package/dist/chunk-MX3AMQFC.js.map +1 -0
- package/dist/{chunk-MLRHYOCD.js → chunk-VRFVF5RH.js} +21 -12
- package/dist/chunk-VRFVF5RH.js.map +1 -0
- package/dist/cli/index.cjs +133 -30
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +134 -31
- package/dist/cli/index.js.map +1 -1
- package/dist/cost/index.d.cts +4 -3
- package/dist/cost/index.d.ts +4 -3
- package/dist/index.cjs +17 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -16
- package/dist/index.d.ts +10 -16
- package/dist/index.js +22 -11
- package/dist/{modelNameResolver-Bn8QnkSj.d.ts → modelNameResolver-D9V_GfUK.d.cts} +3 -27
- package/dist/{modelNameResolver-bZD-eBSJ.d.cts → modelNameResolver-DqFt7g6W.d.ts} +3 -27
- package/dist/models/index.d.cts +3 -2
- package/dist/models/index.d.ts +3 -2
- package/dist/sync/index.cjs +3 -3
- package/dist/sync/index.d.cts +6 -3
- package/dist/sync/index.d.ts +6 -3
- package/dist/sync/index.js +2 -2
- package/dist/syncAiModelsCatalog-CnXRLm2c.d.cts +32 -0
- package/dist/syncAiModelsCatalog-DpkN_w7S.d.ts +32 -0
- package/dist/types-BYXnCvKx.d.cts +137 -0
- package/dist/types-BYXnCvKx.d.ts +137 -0
- package/dist/types-CX6QFNNy.d.cts +144 -0
- package/dist/types-CuiPDcVs.d.ts +144 -0
- package/dist/upsertAiModelRecord-C831wOIF.d.ts +35 -0
- package/dist/upsertAiModelRecord-CjY-sny0.d.cts +35 -0
- package/package.json +8 -1
- package/dist/chunk-HHNHWYTP.cjs.map +0 -1
- package/dist/chunk-ML2FRR4L.js +0 -105
- package/dist/chunk-ML2FRR4L.js.map +0 -1
- package/dist/chunk-MLRHYOCD.js.map +0 -1
- package/dist/chunk-ONA73BU6.cjs.map +0 -1
- package/dist/types-DdGB3YaA.d.cts +0 -278
- package/dist/types-DdGB3YaA.d.ts +0 -278
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import {
|
|
2
|
+
OpenRouterSyncProvider,
|
|
3
|
+
ensureAiModelsCatalog,
|
|
4
|
+
syncAiModelsCatalog
|
|
5
|
+
} from "./chunk-VRFVF5RH.js";
|
|
6
|
+
import {
|
|
7
|
+
decodeModelFirestoreDocId,
|
|
8
|
+
encodeModelFirestoreDocId
|
|
9
|
+
} from "./chunk-HYGXZY25.js";
|
|
10
|
+
import {
|
|
11
|
+
AiModelsCatalogClient
|
|
12
|
+
} from "./chunk-KQOALKKX.js";
|
|
13
|
+
import {
|
|
14
|
+
AI_MODELS_CATALOG_ID,
|
|
15
|
+
AI_MODELS_DESCRIPTOR,
|
|
16
|
+
AI_TOOLS_APP_ID,
|
|
17
|
+
invalidateModelsCache
|
|
18
|
+
} from "./chunk-DJ5SWJDY.js";
|
|
19
|
+
|
|
20
|
+
// src/catalog/verifyAiModelsCatalog.ts
|
|
21
|
+
function sample(ids, limit) {
|
|
22
|
+
return ids.length <= limit ? ids : ids.slice(0, limit);
|
|
23
|
+
}
|
|
24
|
+
async function verifyAiModelsCatalog(options) {
|
|
25
|
+
const start = Date.now();
|
|
26
|
+
const appId = options.appId ?? AI_TOOLS_APP_ID;
|
|
27
|
+
const catalogId = options.catalogId ?? AI_MODELS_CATALOG_ID;
|
|
28
|
+
const sampleLimit = options.sampleLimit ?? 20;
|
|
29
|
+
if (options.ensureDescriptor !== false) {
|
|
30
|
+
await ensureAiModelsCatalog(options.catalox, { appId, catalogId });
|
|
31
|
+
}
|
|
32
|
+
const orIds = options.expectedModelIds ?? new Set(
|
|
33
|
+
(options.openRouterModels ?? await new OpenRouterSyncProvider({
|
|
34
|
+
apiKey: options.openRouterApiKey,
|
|
35
|
+
query: options.openRouterQuery ?? { output_modalities: "all" }
|
|
36
|
+
}).fetchModels()).map((m) => m.modelId)
|
|
37
|
+
);
|
|
38
|
+
invalidateModelsCache(appId);
|
|
39
|
+
const client = new AiModelsCatalogClient({ catalox: options.catalox, appId, catalogId });
|
|
40
|
+
const catMap = await client.getAllModels();
|
|
41
|
+
const catIds = new Set(catMap.keys());
|
|
42
|
+
const missingInCatalox = [...orIds].filter((id) => !catIds.has(id));
|
|
43
|
+
const extraInCatalox = [...catIds].filter((id) => !orIds.has(id));
|
|
44
|
+
let supportsReasoningMissing = 0;
|
|
45
|
+
let openRouterMirrorMissing = 0;
|
|
46
|
+
for (const m of catMap.values()) {
|
|
47
|
+
if (m.supportsReasoning === void 0) supportsReasoningMissing++;
|
|
48
|
+
if (!m.openRouter?.id) openRouterMirrorMissing++;
|
|
49
|
+
}
|
|
50
|
+
const descriptor = await options.catalox.getCatalogDescriptor(
|
|
51
|
+
{ appId, superAdmin: true },
|
|
52
|
+
catalogId
|
|
53
|
+
);
|
|
54
|
+
const remoteKeys = descriptor?.queryableFields?.map((f) => f.key) ?? [];
|
|
55
|
+
const localKeys = AI_MODELS_DESCRIPTOR.queryableFields.map((f) => f.key);
|
|
56
|
+
const descriptorKeysMatch = remoteKeys.length === localKeys.length && remoteKeys.every((k, i) => k === localKeys[i]);
|
|
57
|
+
const ok = missingInCatalox.length === 0 && extraInCatalox.length === 0 && supportsReasoningMissing === 0 && openRouterMirrorMissing === 0 && descriptorKeysMatch;
|
|
58
|
+
return {
|
|
59
|
+
ok,
|
|
60
|
+
openRouterCount: orIds.size,
|
|
61
|
+
cataloxCount: catIds.size,
|
|
62
|
+
missingInCatalox: sample(missingInCatalox, sampleLimit),
|
|
63
|
+
extraInCatalox: sample(extraInCatalox, sampleLimit),
|
|
64
|
+
supportsReasoningMissing,
|
|
65
|
+
openRouterMirrorMissing,
|
|
66
|
+
descriptorKeysMatch,
|
|
67
|
+
durationMs: Date.now() - start
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/catalog/pruneStaleCatalogModels.ts
|
|
72
|
+
import {
|
|
73
|
+
flatNativeItemsCollectionRef,
|
|
74
|
+
legacyNativeItemsCollectionRef,
|
|
75
|
+
resolveNativeItemsLayout
|
|
76
|
+
} from "@x12i/catalox/firebase";
|
|
77
|
+
var DELETE_BATCH = 400;
|
|
78
|
+
async function pruneStaleCatalogModels(options) {
|
|
79
|
+
const { firestore, catalogId, activeModelIds, dryRun } = options;
|
|
80
|
+
const layout = await resolveNativeItemsLayout(firestore, catalogId);
|
|
81
|
+
const col = layout === "legacy" ? legacyNativeItemsCollectionRef(firestore, catalogId) : flatNativeItemsCollectionRef(firestore, catalogId);
|
|
82
|
+
const snapshot = await col.get();
|
|
83
|
+
const toDelete = [];
|
|
84
|
+
for (const doc of snapshot.docs) {
|
|
85
|
+
const modelId = decodeModelFirestoreDocId(doc.id);
|
|
86
|
+
if (!activeModelIds.has(modelId)) {
|
|
87
|
+
toDelete.push(modelId);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (!dryRun && toDelete.length > 0) {
|
|
91
|
+
for (let i = 0; i < toDelete.length; i += DELETE_BATCH) {
|
|
92
|
+
const chunk = toDelete.slice(i, i + DELETE_BATCH);
|
|
93
|
+
const batch = firestore.batch();
|
|
94
|
+
for (const modelId of chunk) {
|
|
95
|
+
batch.delete(col.doc(encodeModelFirestoreDocId(modelId)));
|
|
96
|
+
}
|
|
97
|
+
await batch.commit();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
scanned: snapshot.size,
|
|
102
|
+
pruned: toDelete.length,
|
|
103
|
+
prunedModelIds: toDelete
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// src/catalog/runAiModelsCatalogSync.ts
|
|
108
|
+
var CatalogSyncJobError = class extends Error {
|
|
109
|
+
constructor(message, sync, verify) {
|
|
110
|
+
super(message);
|
|
111
|
+
this.sync = sync;
|
|
112
|
+
this.verify = verify;
|
|
113
|
+
this.name = "CatalogSyncJobError";
|
|
114
|
+
}
|
|
115
|
+
sync;
|
|
116
|
+
verify;
|
|
117
|
+
};
|
|
118
|
+
async function runAiModelsCatalogSync(options) {
|
|
119
|
+
const verifyAfter = options.verifyAfter !== false;
|
|
120
|
+
const failOnVerifyError = options.failOnVerifyError !== false;
|
|
121
|
+
const appId = options.appId ?? AI_TOOLS_APP_ID;
|
|
122
|
+
const catalogId = options.catalogId ?? AI_MODELS_CATALOG_ID;
|
|
123
|
+
const ctx = { appId, superAdmin: true };
|
|
124
|
+
const sync = await syncAiModelsCatalog(options);
|
|
125
|
+
let prune;
|
|
126
|
+
if (options.pruneStale && !options.dryRun) {
|
|
127
|
+
prune = await pruneStaleCatalogModels({
|
|
128
|
+
firestore: options.firestore,
|
|
129
|
+
catalogId,
|
|
130
|
+
context: ctx,
|
|
131
|
+
activeModelIds: new Set(sync.syncedModelIds),
|
|
132
|
+
dryRun: options.dryRun
|
|
133
|
+
});
|
|
134
|
+
invalidateModelsCache(appId);
|
|
135
|
+
}
|
|
136
|
+
let verify;
|
|
137
|
+
if (verifyAfter) {
|
|
138
|
+
verify = await verifyAiModelsCatalog({
|
|
139
|
+
catalox: options.catalox,
|
|
140
|
+
appId,
|
|
141
|
+
catalogId,
|
|
142
|
+
expectedModelIds: new Set(sync.syncedModelIds),
|
|
143
|
+
ensureDescriptor: false
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
const syncFailed = sync.errors.length > 0 || sync.upserted < sync.fetched;
|
|
147
|
+
const verifyFailed = verify !== void 0 && !verify.ok;
|
|
148
|
+
const ok = !syncFailed && !verifyFailed;
|
|
149
|
+
if (!ok && failOnVerifyError) {
|
|
150
|
+
const parts = [];
|
|
151
|
+
if (syncFailed) {
|
|
152
|
+
parts.push(
|
|
153
|
+
`sync incomplete: upserted ${sync.upserted}/${sync.fetched}, ${sync.errors.length} errors`
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
if (verifyFailed && verify) {
|
|
157
|
+
parts.push(
|
|
158
|
+
`verify failed: catalox ${verify.cataloxCount} vs openrouter ${verify.openRouterCount}, missing ${verify.missingInCatalox.length}, extra ${verify.extraInCatalox.length}`
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
throw new CatalogSyncJobError(parts.join("; "), sync, verify);
|
|
162
|
+
}
|
|
163
|
+
return { sync, verify, prune, ok };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export {
|
|
167
|
+
verifyAiModelsCatalog,
|
|
168
|
+
pruneStaleCatalogModels,
|
|
169
|
+
CatalogSyncJobError,
|
|
170
|
+
runAiModelsCatalogSync
|
|
171
|
+
};
|
|
172
|
+
//# sourceMappingURL=chunk-MX3AMQFC.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/catalog/verifyAiModelsCatalog.ts","../src/catalog/pruneStaleCatalogModels.ts","../src/catalog/runAiModelsCatalogSync.ts"],"sourcesContent":["import type { Catalox } from \"@x12i/catalox\";\nimport { invalidateModelsCache } from \"../cache/modelCache.js\";\nimport { AiModelsCatalogClient } from \"../catalox/AiModelsCatalogClient.js\";\nimport type { AiModelRecord } from \"../models/types.js\";\nimport { OpenRouterSyncProvider } from \"../sync/OpenRouterSyncProvider.js\";\nimport type { OpenRouterModelsQuery } from \"../models/openrouter.types.js\";\nimport {\n AI_MODELS_CATALOG_ID,\n AI_MODELS_DESCRIPTOR,\n AI_TOOLS_APP_ID,\n} from \"./aiModelsCatalogDescriptor.js\";\nimport { ensureAiModelsCatalog } from \"./ensureAiModelsCatalog.js\";\n\nexport type CatalogVerifyOptions = {\n catalox: Catalox;\n appId?: string;\n catalogId?: string;\n /** When set, compare against this id set (e.g. from a sync that just ran). */\n expectedModelIds?: Set<string>;\n /** When set, compare against this list instead of fetching OpenRouter. */\n openRouterModels?: AiModelRecord[];\n openRouterApiKey?: string;\n openRouterQuery?: OpenRouterModelsQuery;\n /** Ensure descriptor is registered before reading (default true). */\n ensureDescriptor?: boolean;\n /** Max model ids to include in report samples (default 20). */\n sampleLimit?: number;\n};\n\nexport type CatalogVerifyReport = {\n ok: boolean;\n openRouterCount: number;\n cataloxCount: number;\n missingInCatalox: string[];\n extraInCatalox: string[];\n supportsReasoningMissing: number;\n openRouterMirrorMissing: number;\n descriptorKeysMatch: boolean;\n durationMs: number;\n};\n\nfunction sample(ids: string[], limit: number): string[] {\n return ids.length <= limit ? ids : ids.slice(0, limit);\n}\n\n/**\n * Compare the live OpenRouter catalog with Catalox/Firestore ai-models items.\n * Use after sync in CI/cron, or standalone health checks.\n */\nexport async function verifyAiModelsCatalog(\n options: CatalogVerifyOptions,\n): Promise<CatalogVerifyReport> {\n const start = Date.now();\n const appId = options.appId ?? AI_TOOLS_APP_ID;\n const catalogId = options.catalogId ?? AI_MODELS_CATALOG_ID;\n const sampleLimit = options.sampleLimit ?? 20;\n\n if (options.ensureDescriptor !== false) {\n await ensureAiModelsCatalog(options.catalox, { appId, catalogId });\n }\n\n const orIds =\n options.expectedModelIds ??\n new Set(\n (\n options.openRouterModels ??\n (await new OpenRouterSyncProvider({\n apiKey: options.openRouterApiKey,\n query: options.openRouterQuery ?? { output_modalities: \"all\" },\n }).fetchModels())\n ).map((m) => m.modelId),\n );\n\n invalidateModelsCache(appId);\n const client = new AiModelsCatalogClient({ catalox: options.catalox, appId, catalogId });\n const catMap = await client.getAllModels();\n const catIds = new Set(catMap.keys());\n\n const missingInCatalox = [...orIds].filter((id) => !catIds.has(id));\n const extraInCatalox = [...catIds].filter((id) => !orIds.has(id));\n\n let supportsReasoningMissing = 0;\n let openRouterMirrorMissing = 0;\n for (const m of catMap.values()) {\n if (m.supportsReasoning === undefined) supportsReasoningMissing++;\n if (!m.openRouter?.id) openRouterMirrorMissing++;\n }\n\n const descriptor = await options.catalox.getCatalogDescriptor(\n { appId, superAdmin: true },\n catalogId,\n );\n const remoteKeys = descriptor?.queryableFields?.map((f) => f.key) ?? [];\n const localKeys = AI_MODELS_DESCRIPTOR.queryableFields.map((f) => f.key);\n const descriptorKeysMatch =\n remoteKeys.length === localKeys.length &&\n remoteKeys.every((k, i) => k === localKeys[i]);\n\n const ok =\n missingInCatalox.length === 0 &&\n extraInCatalox.length === 0 &&\n supportsReasoningMissing === 0 &&\n openRouterMirrorMissing === 0 &&\n descriptorKeysMatch;\n\n return {\n ok,\n openRouterCount: orIds.size,\n cataloxCount: catIds.size,\n missingInCatalox: sample(missingInCatalox, sampleLimit),\n extraInCatalox: sample(extraInCatalox, sampleLimit),\n supportsReasoningMissing,\n openRouterMirrorMissing,\n descriptorKeysMatch,\n durationMs: Date.now() - start,\n };\n}\n","import type { Firestore } from \"firebase-admin/firestore\";\nimport type { CataloxContext } from \"@x12i/catalox\";\nimport {\n flatNativeItemsCollectionRef,\n legacyNativeItemsCollectionRef,\n resolveNativeItemsLayout,\n} from \"@x12i/catalox/firebase\";\nimport { decodeModelFirestoreDocId, encodeModelFirestoreDocId } from \"../catalox/modelDocId.js\";\n\nexport type PruneStaleCatalogModelsOptions = {\n firestore: Firestore;\n catalogId: string;\n context: CataloxContext;\n /** Canonical model ids from the latest OpenRouter fetch. */\n activeModelIds: Set<string>;\n dryRun?: boolean;\n};\n\nexport type PruneStaleCatalogModelsResult = {\n scanned: number;\n pruned: number;\n prunedModelIds: string[];\n};\n\nconst DELETE_BATCH = 400;\n\n/**\n * Remove catalog documents that are no longer listed on OpenRouter.\n * Off by default — enable explicitly when running production sync jobs.\n */\nexport async function pruneStaleCatalogModels(\n options: PruneStaleCatalogModelsOptions,\n): Promise<PruneStaleCatalogModelsResult> {\n const { firestore, catalogId, activeModelIds, dryRun } = options;\n const layout = await resolveNativeItemsLayout(firestore, catalogId);\n const col =\n layout === \"legacy\"\n ? legacyNativeItemsCollectionRef(firestore, catalogId)\n : flatNativeItemsCollectionRef(firestore, catalogId);\n\n const snapshot = await col.get();\n const toDelete: string[] = [];\n\n for (const doc of snapshot.docs) {\n const modelId = decodeModelFirestoreDocId(doc.id);\n if (!activeModelIds.has(modelId)) {\n toDelete.push(modelId);\n }\n }\n\n if (!dryRun && toDelete.length > 0) {\n for (let i = 0; i < toDelete.length; i += DELETE_BATCH) {\n const chunk = toDelete.slice(i, i + DELETE_BATCH);\n const batch = firestore.batch();\n for (const modelId of chunk) {\n batch.delete(col.doc(encodeModelFirestoreDocId(modelId)));\n }\n await batch.commit();\n }\n }\n\n return {\n scanned: snapshot.size,\n pruned: toDelete.length,\n prunedModelIds: toDelete,\n };\n}\n","import { invalidateModelsCache } from \"../cache/modelCache.js\";\nimport { syncAiModelsCatalog } from \"../sync/syncAiModelsCatalog.js\";\nimport type { SyncOptions, SyncResult } from \"../sync/syncAiModelsCatalog.js\";\nimport { pruneStaleCatalogModels } from \"./pruneStaleCatalogModels.js\";\nimport type { CatalogVerifyReport } from \"./verifyAiModelsCatalog.js\";\nimport { verifyAiModelsCatalog } from \"./verifyAiModelsCatalog.js\";\nimport { AI_MODELS_CATALOG_ID, AI_TOOLS_APP_ID } from \"./aiModelsCatalogDescriptor.js\";\n\nexport type CatalogSyncJobOptions = SyncOptions & {\n /**\n * After upsert, compare Catalox to OpenRouter (default true).\n * Set false only when you will verify separately.\n */\n verifyAfter?: boolean;\n /** Fail the job when verification does not pass (default true). */\n failOnVerifyError?: boolean;\n /** Delete Firestore rows not present in the latest OpenRouter list (default false). */\n pruneStale?: boolean;\n};\n\nexport type CatalogSyncJobResult = {\n sync: SyncResult;\n verify?: CatalogVerifyReport;\n prune?: {\n scanned: number;\n pruned: number;\n prunedModelIds: string[];\n };\n ok: boolean;\n};\n\nexport class CatalogSyncJobError extends Error {\n constructor(\n message: string,\n public readonly sync: SyncResult,\n public readonly verify?: CatalogVerifyReport,\n ) {\n super(message);\n this.name = \"CatalogSyncJobError\";\n }\n}\n\n/**\n * Production entry point: sync OpenRouter → Catalox, optionally prune stale rows, then verify.\n * Suitable for cron, Cloud Run jobs, and `ai-tools sync`.\n */\nexport async function runAiModelsCatalogSync(\n options: CatalogSyncJobOptions,\n): Promise<CatalogSyncJobResult> {\n const verifyAfter = options.verifyAfter !== false;\n const failOnVerifyError = options.failOnVerifyError !== false;\n const appId = options.appId ?? AI_TOOLS_APP_ID;\n const catalogId = options.catalogId ?? AI_MODELS_CATALOG_ID;\n const ctx = { appId, superAdmin: true as const };\n\n const sync = await syncAiModelsCatalog(options);\n\n let prune: CatalogSyncJobResult[\"prune\"];\n\n if (options.pruneStale && !options.dryRun) {\n prune = await pruneStaleCatalogModels({\n firestore: options.firestore,\n catalogId,\n context: ctx,\n activeModelIds: new Set(sync.syncedModelIds),\n dryRun: options.dryRun,\n });\n invalidateModelsCache(appId);\n }\n\n let verify: CatalogVerifyReport | undefined;\n if (verifyAfter) {\n verify = await verifyAiModelsCatalog({\n catalox: options.catalox,\n appId,\n catalogId,\n expectedModelIds: new Set(sync.syncedModelIds),\n ensureDescriptor: false,\n });\n }\n\n const syncFailed = sync.errors.length > 0 || sync.upserted < sync.fetched;\n const verifyFailed = verify !== undefined && !verify.ok;\n const ok = !syncFailed && !verifyFailed;\n\n if (!ok && failOnVerifyError) {\n const parts: string[] = [];\n if (syncFailed) {\n parts.push(\n `sync incomplete: upserted ${sync.upserted}/${sync.fetched}, ${sync.errors.length} errors`,\n );\n }\n if (verifyFailed && verify) {\n parts.push(\n `verify failed: catalox ${verify.cataloxCount} vs openrouter ${verify.openRouterCount}, ` +\n `missing ${verify.missingInCatalox.length}, extra ${verify.extraInCatalox.length}`,\n );\n }\n throw new CatalogSyncJobError(parts.join(\"; \"), sync, verify);\n }\n\n return { sync, verify, prune, ok };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
batchUpsertAiModelRecords
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-HYGXZY25.js";
|
|
4
4
|
import {
|
|
5
5
|
normalizeOpenRouterModel
|
|
6
6
|
} from "./chunk-6QGDZTGH.js";
|
|
@@ -126,28 +126,37 @@ async function syncAiModelsCatalog(options) {
|
|
|
126
126
|
if (options.verbose) {
|
|
127
127
|
console.log(`[sync] fetched ${models.length} models from ${provider.getModelsUrl()}`);
|
|
128
128
|
}
|
|
129
|
+
const syncedModelIds = models.map((m) => m.modelId);
|
|
129
130
|
if (options.dryRun) {
|
|
130
131
|
return {
|
|
131
132
|
fetched: models.length,
|
|
132
133
|
upserted: 0,
|
|
133
134
|
skipped: models.length,
|
|
134
135
|
errors: [],
|
|
136
|
+
syncedModelIds,
|
|
135
137
|
durationMs: Date.now() - start
|
|
136
138
|
};
|
|
137
139
|
}
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
140
|
+
const upsert = await batchUpsertAiModelRecords(
|
|
141
|
+
options.firestore,
|
|
142
|
+
catalogId,
|
|
143
|
+
ctx,
|
|
144
|
+
models,
|
|
145
|
+
{ onProgress: options.onProgress }
|
|
146
|
+
);
|
|
145
147
|
invalidateModelsCache(appId);
|
|
148
|
+
if (upsert.failed.length > 0 && upsert.upserted === 0) {
|
|
149
|
+
throw new SyncError(
|
|
150
|
+
"SYNC_UPSERT_FAILED",
|
|
151
|
+
`All ${upsert.failed.length} model upserts failed. First error: ${upsert.failed[0].error}`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
146
154
|
return {
|
|
147
155
|
fetched: models.length,
|
|
148
|
-
upserted:
|
|
149
|
-
skipped:
|
|
150
|
-
errors,
|
|
156
|
+
upserted: upsert.upserted,
|
|
157
|
+
skipped: models.length - upsert.upserted,
|
|
158
|
+
errors: upsert.failed,
|
|
159
|
+
syncedModelIds,
|
|
151
160
|
durationMs: Date.now() - start
|
|
152
161
|
};
|
|
153
162
|
}
|
|
@@ -157,4 +166,4 @@ export {
|
|
|
157
166
|
OpenRouterSyncProvider,
|
|
158
167
|
syncAiModelsCatalog
|
|
159
168
|
};
|
|
160
|
-
//# sourceMappingURL=chunk-
|
|
169
|
+
//# sourceMappingURL=chunk-VRFVF5RH.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/catalog/ensureAiModelsCatalog.ts","../src/sync/OpenRouterSyncProvider.ts","../src/sync/syncAiModelsCatalog.ts"],"sourcesContent":["import type { Catalox, CataloxContext } from \"@x12i/catalox\";\nimport { AiToolsError } from \"../errors.js\";\nimport {\n AI_MODELS_CATALOG_ID,\n AI_MODELS_DESCRIPTOR,\n AI_TOOLS_APP_ID,\n} from \"./aiModelsCatalogDescriptor.js\";\n\nexport type EnsureAiModelsCatalogOptions = {\n appId?: string;\n catalogId?: string;\n};\n\nexport async function ensureAiModelsCatalog(\n catalox: Catalox,\n options: EnsureAiModelsCatalogOptions = {},\n): Promise<void> {\n const appId = options.appId ?? AI_TOOLS_APP_ID;\n const catalogId = options.catalogId ?? AI_MODELS_CATALOG_ID;\n\n const ctx: CataloxContext = { appId, superAdmin: true };\n\n try {\n await catalox.ensureCatalog(ctx, {\n catalogId,\n name: AI_MODELS_DESCRIPTOR.label,\n status: \"active\",\n });\n\n await catalox.bindCatalogToApp(ctx, {\n appId,\n catalogId,\n access: { canRead: true, canWrite: true, canAdmin: true },\n });\n\n await catalox.upsertCatalogDescriptor(ctx, catalogId, {\n descriptorVersion: \"1.0.0\",\n descriptor: AI_MODELS_DESCRIPTOR,\n });\n } catch (cause) {\n throw new AiToolsError(\n \"CATALOG_BOOTSTRAP_FAILED\",\n `Failed to bootstrap catalog \"${catalogId}\" for app \"${appId}\".`,\n cause,\n );\n }\n}\n","import { SyncError } from \"../errors.js\";\nimport { normalizeOpenRouterModel } from \"../models/normalizeOpenRouterModel.js\";\nimport type { AiModelRecord } from \"../models/types.js\";\nimport type {\n OpenRouterModelsQuery,\n OpenRouterModelsResponse,\n} from \"../models/openrouter.types.js\";\n\nexport type OpenRouterSyncProviderOptions = {\n /** Optional — public GET /models needs no key. */\n apiKey?: string;\n baseUrl?: string;\n /** Query params passed to OpenRouter (default: all modalities). */\n query?: OpenRouterModelsQuery;\n};\n\nfunction buildFetchHeaders(apiKey?: string): Record<string, string> {\n const headers: Record<string, string> = { Accept: \"application/json\" };\n if (apiKey?.trim()) headers.Authorization = `Bearer ${apiKey.trim()}`;\n return headers;\n}\n\nfunction buildModelsUrl(baseUrl: string, query?: OpenRouterModelsQuery): string {\n const url = new URL(`${baseUrl.replace(/\\/$/, \"\")}/models`);\n const q = { output_modalities: \"all\", ...query };\n for (const [key, value] of Object.entries(q)) {\n if (value !== undefined && value !== \"\") url.searchParams.set(key, value);\n }\n return url.toString();\n}\n\nexport class OpenRouterSyncProvider {\n private readonly apiKey?: string;\n private readonly baseUrl: string;\n private readonly query?: OpenRouterModelsQuery;\n\n constructor(options: OpenRouterSyncProviderOptions = {}) {\n this.apiKey = options.apiKey;\n this.baseUrl = options.baseUrl ?? \"https://openrouter.ai/api/v1\";\n this.query = options.query ?? { output_modalities: \"all\" };\n }\n\n /**\n * Fetches the full OpenRouter model catalog (public, no API key required).\n * @see https://openrouter.ai/api/v1/models\n */\n async fetchModels(): Promise<AiModelRecord[]> {\n const url = buildModelsUrl(this.baseUrl, this.query);\n let response: Response;\n\n try {\n response = await fetch(url, { headers: buildFetchHeaders(this.apiKey) });\n } catch (cause) {\n throw new SyncError(\"OPENROUTER_MODELS_FETCH_FAILED\", `Failed to fetch OpenRouter models from ${url}`, cause);\n }\n\n if (response.status === 401 || response.status === 403) {\n throw new SyncError(\n \"OPENROUTER_AUTH_FAILED\",\n this.apiKey\n ? \"OpenRouter API key is invalid or unauthorized.\"\n : \"OpenRouter returned unauthorized — remove OPENROUTER_API_KEY to use the public models endpoint.\",\n );\n }\n\n if (!response.ok) {\n const body = await response.text().catch(() => \"\");\n throw new SyncError(\n \"OPENROUTER_MODELS_FETCH_FAILED\",\n `OpenRouter models fetch failed (${response.status}): ${body.slice(0, 200)}`,\n );\n }\n\n const json = (await response.json()) as OpenRouterModelsResponse;\n const syncedAt = new Date().toISOString();\n return (json.data ?? []).map((row) => normalizeOpenRouterModel(row, syncedAt));\n }\n\n /** Build the URL used for the last fetch pattern (for debugging). */\n getModelsUrl(): string {\n return buildModelsUrl(this.baseUrl, this.query);\n }\n}\n","import type { Catalox, CataloxContext } from \"@x12i/catalox\";\nimport type { Firestore } from \"firebase-admin/firestore\";\nimport { ensureAiModelsCatalog } from \"../catalog/ensureAiModelsCatalog.js\";\nimport { AI_MODELS_CATALOG_ID, AI_TOOLS_APP_ID } from \"../catalog/aiModelsCatalogDescriptor.js\";\nimport { invalidateModelsCache } from \"../cache/modelCache.js\";\nimport {\n batchUpsertAiModelRecords,\n type BatchUpsertProgress,\n} from \"../catalox/upsertAiModelRecord.js\";\nimport { SyncError } from \"../errors.js\";\nimport type { AiModelRecord } from \"../models/types.js\";\nimport type { OpenRouterModelsQuery } from \"../models/openrouter.types.js\";\nimport { OpenRouterSyncProvider } from \"./OpenRouterSyncProvider.js\";\n\nexport type SyncOptions = {\n catalox: Catalox;\n firestore: Firestore;\n openRouterApiKey?: string;\n openRouterQuery?: OpenRouterModelsQuery;\n appId?: string;\n catalogId?: string;\n dryRun?: boolean;\n verbose?: boolean;\n forceCache?: boolean;\n onProgress?: (progress: BatchUpsertProgress) => void;\n};\n\nexport type SyncResult = {\n fetched: number;\n upserted: number;\n skipped: number;\n errors: Array<{ modelId: string; error: string }>;\n durationMs: number;\n /** Model ids written (or that would be written on dry-run). */\n syncedModelIds: string[];\n};\n\nexport async function syncAiModelsCatalog(options: SyncOptions): Promise<SyncResult> {\n const start = Date.now();\n const appId = options.appId ?? AI_TOOLS_APP_ID;\n const catalogId = options.catalogId ?? AI_MODELS_CATALOG_ID;\n const ctx: CataloxContext = { appId, superAdmin: true };\n\n if (options.forceCache) {\n invalidateModelsCache(appId);\n }\n\n await ensureAiModelsCatalog(options.catalox, { appId, catalogId });\n\n const provider = new OpenRouterSyncProvider({\n apiKey: options.openRouterApiKey,\n query: options.openRouterQuery ?? { output_modalities: \"all\" },\n });\n\n let models: AiModelRecord[];\n try {\n models = await provider.fetchModels();\n } catch (error) {\n if (error instanceof SyncError) throw error;\n throw new SyncError(\"SYNC_FETCH_FAILED\", \"Failed to fetch models from OpenRouter.\", error);\n }\n\n if (options.verbose) {\n console.log(`[sync] fetched ${models.length} models from ${provider.getModelsUrl()}`);\n }\n\n const syncedModelIds = models.map((m) => m.modelId);\n\n if (options.dryRun) {\n return {\n fetched: models.length,\n upserted: 0,\n skipped: models.length,\n errors: [],\n syncedModelIds,\n durationMs: Date.now() - start,\n };\n }\n\n const upsert = await batchUpsertAiModelRecords(\n options.firestore,\n catalogId,\n ctx,\n models,\n { onProgress: options.onProgress },\n );\n\n invalidateModelsCache(appId);\n\n if (upsert.failed.length > 0 && upsert.upserted === 0) {\n throw new SyncError(\n \"SYNC_UPSERT_FAILED\",\n `All ${upsert.failed.length} model upserts failed. First error: ${upsert.failed[0]!.error}`,\n );\n }\n\n return {\n fetched: models.length,\n upserted: upsert.upserted,\n skipped: models.length - upsert.upserted,\n errors: upsert.failed,\n syncedModelIds,\n durationMs: Date.now() - start,\n };\n}\n"],"mappings":"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","names":[]}
|
package/dist/cli/index.cjs
CHANGED
|
@@ -5,15 +5,19 @@ var _chunkHN6UAQAEcjs = require('../chunk-HN6UAQAE.cjs');
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
var
|
|
9
|
-
require('../chunk-HHNHWYTP.cjs');
|
|
8
|
+
var _chunkQWAX7VQOcjs = require('../chunk-QWAX7VQO.cjs');
|
|
10
9
|
|
|
11
10
|
|
|
11
|
+
var _chunkQCRLKVB3cjs = require('../chunk-QCRLKVB3.cjs');
|
|
12
12
|
|
|
13
|
-
var _chunkQWAX7VQOcjs = require('../chunk-QWAX7VQO.cjs');
|
|
14
13
|
|
|
15
14
|
|
|
16
|
-
|
|
15
|
+
|
|
16
|
+
var _chunkHS74X2OJcjs = require('../chunk-HS74X2OJ.cjs');
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
var _chunkDKHGWHXPcjs = require('../chunk-DKHGWHXP.cjs');
|
|
20
|
+
require('../chunk-FGP3QXWL.cjs');
|
|
17
21
|
require('../chunk-AV6OE2YQ.cjs');
|
|
18
22
|
|
|
19
23
|
|
|
@@ -179,15 +183,91 @@ ${report.total} total \xB7 ${report.ok} ok \xB7 ${report.unknown} unknown \
|
|
|
179
183
|
});
|
|
180
184
|
}
|
|
181
185
|
|
|
186
|
+
// src/cli/report.ts
|
|
187
|
+
function emitJson(data) {
|
|
188
|
+
console.log(JSON.stringify(data, null, 2));
|
|
189
|
+
}
|
|
190
|
+
function emitHuman(lines) {
|
|
191
|
+
for (const line of lines) {
|
|
192
|
+
console.log(line);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function formatSyncJobReport(result) {
|
|
196
|
+
const lines = [
|
|
197
|
+
"",
|
|
198
|
+
result.ok ? "\u2714 Catalog sync job succeeded" : "\u2717 Catalog sync job failed",
|
|
199
|
+
"",
|
|
200
|
+
"Sync",
|
|
201
|
+
` Fetched: ${result.sync.fetched}`,
|
|
202
|
+
` Upserted: ${result.sync.upserted}`,
|
|
203
|
+
` Skipped: ${result.sync.skipped}`,
|
|
204
|
+
` Errors: ${result.sync.errors.length}`,
|
|
205
|
+
` Duration: ${result.sync.durationMs}ms`
|
|
206
|
+
];
|
|
207
|
+
if (result.verify) {
|
|
208
|
+
lines.push(
|
|
209
|
+
"",
|
|
210
|
+
"Verify",
|
|
211
|
+
` Status: ${result.verify.ok ? "ok" : "FAILED"}`,
|
|
212
|
+
` OpenRouter: ${result.verify.openRouterCount}`,
|
|
213
|
+
` Catalox: ${result.verify.cataloxCount}`,
|
|
214
|
+
` Missing: ${result.verify.missingInCatalox.length}`,
|
|
215
|
+
` Extra: ${result.verify.extraInCatalox.length}`,
|
|
216
|
+
` Duration: ${result.verify.durationMs}ms`
|
|
217
|
+
);
|
|
218
|
+
if (result.verify.missingInCatalox.length > 0) {
|
|
219
|
+
lines.push(` Sample missing: ${result.verify.missingInCatalox.join(", ")}`);
|
|
220
|
+
}
|
|
221
|
+
if (result.verify.extraInCatalox.length > 0) {
|
|
222
|
+
lines.push(` Sample extra: ${result.verify.extraInCatalox.join(", ")}`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (result.prune) {
|
|
226
|
+
lines.push("", "Prune", ` Scanned: ${result.prune.scanned}`, ` Removed: ${result.prune.pruned}`);
|
|
227
|
+
}
|
|
228
|
+
return lines;
|
|
229
|
+
}
|
|
230
|
+
|
|
182
231
|
// src/cli/commands/catalog.ts
|
|
183
232
|
function registerCatalogCommand(program2) {
|
|
184
|
-
const catalog = program2.command("catalog").description("Catalog bootstrap commands");
|
|
185
|
-
catalog.command("ensure").description("Create catalog, descriptor, and app binding if missing").option("--app <appId>", "Override appId").option("--catalog <id>", "Override catalogId").action(async (opts) => {
|
|
186
|
-
|
|
233
|
+
const catalog = program2.command("catalog").description("Catalog bootstrap and health commands");
|
|
234
|
+
catalog.command("ensure").description("Create catalog, descriptor, and app binding if missing").option("--app <appId>", "Override appId").option("--catalog <id>", "Override catalogId").option("--json", "Machine-readable JSON on stdout").action(async (opts) => {
|
|
235
|
+
const appId = _nullishCoalesce(opts.app, () => ( envAppId()));
|
|
236
|
+
const catalogId = _nullishCoalesce(opts.catalog, () => ( envCatalogId()));
|
|
237
|
+
await _chunkDKHGWHXPcjs.ensureAiModelsCatalog.call(void 0, getCatalox(), { appId, catalogId });
|
|
238
|
+
if (opts.json) {
|
|
239
|
+
emitJson({ ok: true, appId, catalogId, action: "ensure" });
|
|
240
|
+
} else {
|
|
241
|
+
console.log("\u2714 ai-models catalog ensured");
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
catalog.command("verify").description("Health check: compare Catalox ai-models against the live OpenRouter catalog").option("--app <appId>", "Override appId").option("--catalog <id>", "Override catalogId").option("--json", "Machine-readable JSON on stdout").action(async (opts) => {
|
|
245
|
+
const report = await _chunkHS74X2OJcjs.verifyAiModelsCatalog.call(void 0, {
|
|
246
|
+
catalox: getCatalox(),
|
|
187
247
|
appId: _nullishCoalesce(opts.app, () => ( envAppId())),
|
|
188
248
|
catalogId: _nullishCoalesce(opts.catalog, () => ( envCatalogId()))
|
|
189
249
|
});
|
|
190
|
-
|
|
250
|
+
if (opts.json) {
|
|
251
|
+
emitJson(report);
|
|
252
|
+
} else {
|
|
253
|
+
emitHuman([
|
|
254
|
+
report.ok ? "\u2714 Catalog verification passed" : "\u2717 Catalog verification FAILED",
|
|
255
|
+
` OpenRouter models: ${report.openRouterCount}`,
|
|
256
|
+
` Catalox models: ${report.cataloxCount}`,
|
|
257
|
+
` Missing in Catalox: ${report.missingInCatalox.length}`,
|
|
258
|
+
` Extra in Catalox: ${report.extraInCatalox.length}`,
|
|
259
|
+
` Duration: ${report.durationMs}ms`
|
|
260
|
+
]);
|
|
261
|
+
if (report.missingInCatalox.length > 0) {
|
|
262
|
+
console.log(` Sample missing: ${report.missingInCatalox.join(", ")}`);
|
|
263
|
+
}
|
|
264
|
+
if (report.extraInCatalox.length > 0) {
|
|
265
|
+
console.log(` Sample extra: ${report.extraInCatalox.join(", ")}`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (!report.ok) {
|
|
269
|
+
process.exit(1);
|
|
270
|
+
}
|
|
191
271
|
});
|
|
192
272
|
}
|
|
193
273
|
|
|
@@ -362,30 +442,53 @@ ${rows.length} shown \xB7 ${total} matching`);
|
|
|
362
442
|
|
|
363
443
|
// src/cli/commands/sync.ts
|
|
364
444
|
function registerSyncCommand(program2) {
|
|
365
|
-
program2.command("sync").description(
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
445
|
+
program2.command("sync").description(
|
|
446
|
+
"Production sync: fetch OpenRouter models, upsert into Catalox/Firestore, verify completeness"
|
|
447
|
+
).option("--dry-run", "Fetch and validate without writing to Firestore").option("--no-verify", "Skip post-sync verification (not recommended in production)").option("--prune-stale", "Remove catalog rows no longer on OpenRouter").option("--json", "Machine-readable JSON on stdout").option("--verbose", "Log fetch URL and upsert progress").option("--app <appId>", "Override appId").option("--catalog <id>", "Override catalogId").option("--force-cache", "Invalidate nx-cache before running").action(
|
|
448
|
+
async (opts) => {
|
|
449
|
+
const verifyAfter = opts.verify !== false;
|
|
450
|
+
try {
|
|
451
|
+
const result = await _chunkHS74X2OJcjs.runAiModelsCatalogSync.call(void 0, {
|
|
452
|
+
catalox: getCatalox(),
|
|
453
|
+
firestore: getFirestore(),
|
|
454
|
+
openRouterApiKey: process.env.OPENROUTER_API_KEY,
|
|
455
|
+
appId: _nullishCoalesce(opts.app, () => ( envAppId())),
|
|
456
|
+
catalogId: _nullishCoalesce(opts.catalog, () => ( envCatalogId())),
|
|
457
|
+
dryRun: opts.dryRun,
|
|
458
|
+
verbose: opts.verbose,
|
|
459
|
+
forceCache: opts.forceCache,
|
|
460
|
+
verifyAfter,
|
|
461
|
+
pruneStale: opts.pruneStale,
|
|
462
|
+
onProgress: opts.verbose ? ({ completed, total, phase }) => {
|
|
463
|
+
console.log(`[sync] ${phase} ${completed}/${total}`);
|
|
464
|
+
} : void 0
|
|
465
|
+
});
|
|
466
|
+
if (opts.json) {
|
|
467
|
+
emitJson(result);
|
|
468
|
+
} else {
|
|
469
|
+
emitHuman(formatSyncJobReport(result));
|
|
470
|
+
}
|
|
471
|
+
if (!result.ok) {
|
|
472
|
+
process.exit(1);
|
|
473
|
+
}
|
|
474
|
+
} catch (error) {
|
|
475
|
+
if (error instanceof _chunkHS74X2OJcjs.CatalogSyncJobError) {
|
|
476
|
+
const payload = {
|
|
477
|
+
ok: false,
|
|
478
|
+
sync: error.sync,
|
|
479
|
+
verify: error.verify
|
|
480
|
+
};
|
|
481
|
+
if (opts.json) {
|
|
482
|
+
emitJson(payload);
|
|
483
|
+
} else {
|
|
484
|
+
emitHuman(formatSyncJobReport(payload));
|
|
485
|
+
}
|
|
486
|
+
process.exit(1);
|
|
487
|
+
}
|
|
488
|
+
throw error;
|
|
385
489
|
}
|
|
386
|
-
process.exit(1);
|
|
387
490
|
}
|
|
388
|
-
|
|
491
|
+
);
|
|
389
492
|
}
|
|
390
493
|
|
|
391
494
|
// src/cli/index.ts
|