@dereekb/dbx-cli 13.14.0 → 13.16.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.
@@ -284,29 +284,20 @@ function findFunctionsClassName(sourceFile) {
284
284
  return result;
285
285
  }
286
286
  function inferGroupName(sourceFile) {
287
+ return findTypeAliasStem(sourceFile, "ModelCrudFunctionsConfig") ?? findTypeAliasStem(sourceFile, "FunctionTypeMap");
288
+ }
289
+ function findTypeAliasStem(sourceFile, ending) {
287
290
  let result;
288
291
  for (const alias of sourceFile.getTypeAliases()) {
289
292
  const name = alias.getName();
290
- if (name.endsWith("ModelCrudFunctionsConfig")) {
291
- const stem = name.slice(0, -"ModelCrudFunctionsConfig".length);
293
+ if (name.endsWith(ending)) {
294
+ const stem = name.slice(0, -ending.length);
292
295
  if (stem.length > 0) {
293
296
  result = stem;
294
297
  break;
295
298
  }
296
299
  }
297
300
  }
298
- if (result === void 0) {
299
- for (const alias of sourceFile.getTypeAliases()) {
300
- const name = alias.getName();
301
- if (name.endsWith("FunctionTypeMap")) {
302
- const stem = name.slice(0, -"FunctionTypeMap".length);
303
- if (stem.length > 0) {
304
- result = stem;
305
- break;
306
- }
307
- }
308
- }
309
- }
310
301
  return result;
311
302
  }
312
303
  function isNullLiteralType(node) {
@@ -402,44 +393,44 @@ function typeNodeName(node) {
402
393
  return result;
403
394
  }
404
395
  function readTypeDocs(sourceFile, typeName) {
405
- let result;
406
396
  const interfaceDecl = sourceFile.getInterface(typeName);
397
+ let result;
407
398
  if (interfaceDecl) {
408
- const typeDescription = readJsDocSummary(interfaceDecl);
409
- const hasApiParamsTag = hasJsDocFlag(interfaceDecl, "dbxModelApiParams");
410
- const fields = [];
411
- for (const property of interfaceDecl.getProperties()) {
412
- const fieldName = property.getName();
413
- const description = readJsDocSummary(property);
414
- const typeNode = property.getTypeNode();
415
- const typeText = typeNode?.getText().trim() ?? "";
416
- const adminOnly = hasJsDocFlag(property, "dbxModelApiAdminOnly");
417
- const field = {
418
- name: fieldName,
419
- typeText,
420
- ...description ? { description } : {},
421
- ...adminOnly ? { accessLevel: "adminOnly" } : {}
422
- };
423
- fields.push(field);
424
- }
425
- if (typeDescription || fields.length > 0 || hasApiParamsTag) {
426
- result = {
427
- ...typeDescription ? { typeDescription } : {},
428
- ...fields.length > 0 ? { fields } : {},
429
- hasApiParamsTag
430
- };
431
- }
399
+ result = readInterfaceTypeDocs(interfaceDecl);
432
400
  } else {
433
401
  const typeAlias = sourceFile.getTypeAlias(typeName);
434
- if (typeAlias) {
435
- const typeDescription = readJsDocSummary(typeAlias);
436
- if (typeDescription) {
437
- result = { typeDescription };
438
- }
439
- }
402
+ result = typeAlias ? readTypeAliasDocs(typeAlias) : void 0;
403
+ }
404
+ return result;
405
+ }
406
+ function readInterfaceTypeDocs(interfaceDecl) {
407
+ const typeDescription = readJsDocSummary(interfaceDecl);
408
+ const hasApiParamsTag = hasJsDocFlag(interfaceDecl, "dbxModelApiParams");
409
+ const fields = interfaceDecl.getProperties().map((property) => readInterfaceField(property));
410
+ let result;
411
+ if (typeDescription || fields.length > 0 || hasApiParamsTag) {
412
+ result = {
413
+ ...typeDescription ? { typeDescription } : {},
414
+ ...fields.length > 0 ? { fields } : {},
415
+ hasApiParamsTag
416
+ };
440
417
  }
441
418
  return result;
442
419
  }
420
+ function readInterfaceField(property) {
421
+ const description = readJsDocSummary(property);
422
+ const adminOnly = hasJsDocFlag(property, "dbxModelApiAdminOnly");
423
+ return {
424
+ name: property.getName(),
425
+ typeText: property.getTypeNode()?.getText().trim() ?? "",
426
+ ...description ? { description } : {},
427
+ ...adminOnly ? { accessLevel: "adminOnly" } : {}
428
+ };
429
+ }
430
+ function readTypeAliasDocs(typeAlias) {
431
+ const typeDescription = readJsDocSummary(typeAlias);
432
+ return typeDescription ? { typeDescription } : void 0;
433
+ }
443
434
  function hasJsDocFlag(node, tagName) {
444
435
  let result = false;
445
436
  for (const doc of node.getJsDocs()) {
@@ -468,8 +459,8 @@ function readJsDocTagValue(node, tagName) {
468
459
  function readJsDocSummary(node) {
469
460
  let result;
470
461
  const docs = node.getJsDocs();
471
- if (docs.length > 0) {
472
- const last = docs[docs.length - 1];
462
+ const last = docs.at(-1);
463
+ if (last) {
473
464
  const description = last.getDescription().trim();
474
465
  if (description.length > 0) {
475
466
  result = description;
@@ -762,38 +753,48 @@ function readNestedFromExpression(expr) {
762
753
  if (Node3.isCallExpression(expr)) {
763
754
  const fnName = expr.getExpression().getText();
764
755
  if (fnName === SUB_OBJECT_FN || fnName === OBJECT_ARRAY_FN) {
765
- const args = expr.getArguments();
766
- if (args.length > 0) {
767
- const config = args[0];
768
- if (Node3.isObjectLiteralExpression(config)) {
769
- const objectField = readPropertyValue(config, OBJECT_FIELD_KEY);
770
- if (objectField) {
771
- const isArray = fnName === OBJECT_ARRAY_FN;
772
- if (Node3.isIdentifier(objectField)) {
773
- result = { ref: objectField.getText(), isArray };
774
- } else if (Node3.isObjectLiteralExpression(objectField)) {
775
- const fieldsLiteral = readObjectProperty(objectField, FIELDS_LITERAL_KEY);
776
- if (fieldsLiteral) {
777
- const inlineFields = readFieldEntries(fieldsLiteral);
778
- result = {
779
- inline: {
780
- converterConst: void 0,
781
- factory: fnName,
782
- interfaceName: readGenericInterfaceName(expr),
783
- fields: inlineFields,
784
- line: expr.getStartLineNumber()
785
- },
786
- isArray
787
- };
788
- }
789
- }
790
- }
791
- }
756
+ result = readNestedConverterCall(expr, fnName);
757
+ }
758
+ }
759
+ return result;
760
+ }
761
+ function readNestedConverterCall(call, fnName) {
762
+ let result;
763
+ const args = call.getArguments();
764
+ if (args.length > 0) {
765
+ const config = args[0];
766
+ if (Node3.isObjectLiteralExpression(config)) {
767
+ const objectField = readPropertyValue(config, OBJECT_FIELD_KEY);
768
+ if (objectField) {
769
+ result = buildNestedConverterMatch({ objectField, call, fnName });
792
770
  }
793
771
  }
794
772
  }
795
773
  return result;
796
774
  }
775
+ function buildNestedConverterMatch(input) {
776
+ const { objectField, call, fnName } = input;
777
+ const isArray = fnName === OBJECT_ARRAY_FN;
778
+ let result;
779
+ if (Node3.isIdentifier(objectField)) {
780
+ result = { ref: objectField.getText(), isArray };
781
+ } else if (Node3.isObjectLiteralExpression(objectField)) {
782
+ const fieldsLiteral = readObjectProperty(objectField, FIELDS_LITERAL_KEY);
783
+ if (fieldsLiteral) {
784
+ result = {
785
+ inline: {
786
+ converterConst: void 0,
787
+ factory: fnName,
788
+ interfaceName: readGenericInterfaceName(call),
789
+ fields: readFieldEntries(fieldsLiteral),
790
+ line: call.getStartLineNumber()
791
+ },
792
+ isArray
793
+ };
794
+ }
795
+ }
796
+ return result;
797
+ }
797
798
  function readPropertyValue(literal, key) {
798
799
  const property = literal.getProperty(key);
799
800
  let result;
@@ -1086,28 +1087,32 @@ function buildEntryForIdentity(input) {
1086
1087
  depth: 0,
1087
1088
  visitedConverters: /* @__PURE__ */ new Set()
1088
1089
  });
1089
- const modelGroup = registries.groupByModelName.get(modelName);
1090
- const serviceFactory = registries.serviceFactoryByModelType.get(identity.modelType);
1091
- result = {
1092
- modelType: identity.modelType,
1093
- modelName,
1094
- ...modelGroup ? { modelGroup } : {},
1095
- identityConst: identity.identityConst,
1096
- collectionPrefix: identity.collectionPrefix,
1097
- ...identity.parentIdentityConst ? { parentIdentityConst: identity.parentIdentityConst } : {},
1098
- ...iface.description ? { description: iface.description } : {},
1099
- sourcePackage: source.sourcePackage,
1100
- sourceFile: source.sourceFile,
1101
- fields,
1102
- ...iface.mcpToolNameSegment ? { mcpToolNameSegment: iface.mcpToolNameSegment } : {},
1103
- ...iface.dbxModelRead ? { read: iface.dbxModelRead } : {},
1104
- ...serviceFactory ? { serviceFactory } : {}
1105
- };
1090
+ result = buildManifestEntry({ identity, modelName, collectionPrefix: identity.collectionPrefix, iface, fields, source, registries });
1106
1091
  }
1107
1092
  }
1108
1093
  }
1109
1094
  return result;
1110
1095
  }
1096
+ function buildManifestEntry(input) {
1097
+ const { identity, modelName, collectionPrefix, iface, fields, source, registries } = input;
1098
+ const modelGroup = registries.groupByModelName.get(modelName);
1099
+ const serviceFactory = registries.serviceFactoryByModelType.get(identity.modelType);
1100
+ return {
1101
+ modelType: identity.modelType,
1102
+ modelName,
1103
+ ...modelGroup ? { modelGroup } : {},
1104
+ identityConst: identity.identityConst,
1105
+ collectionPrefix,
1106
+ ...identity.parentIdentityConst ? { parentIdentityConst: identity.parentIdentityConst } : {},
1107
+ ...iface.description ? { description: iface.description } : {},
1108
+ sourcePackage: source.sourcePackage,
1109
+ sourceFile: source.sourceFile,
1110
+ fields,
1111
+ ...iface.mcpToolNameSegment ? { mcpToolNameSegment: iface.mcpToolNameSegment } : {},
1112
+ ...iface.dbxModelRead ? { read: iface.dbxModelRead } : {},
1113
+ ...serviceFactory ? { serviceFactory } : {}
1114
+ };
1115
+ }
1111
1116
  function findConverterForInterface(extraction, interfaceName) {
1112
1117
  return extraction.converters.find((c) => c.interfaceName === interfaceName);
1113
1118
  }
@@ -1158,21 +1163,10 @@ function buildField(input) {
1158
1163
  return out;
1159
1164
  }
1160
1165
  function resolveNestedFields(input) {
1161
- const { field } = input;
1162
1166
  let result;
1163
1167
  if (input.depth < MAX_NESTED_DEPTH) {
1164
- let nestedConverter;
1165
- let aborted = false;
1166
- if (field.nestedConverterInline) {
1167
- nestedConverter = field.nestedConverterInline;
1168
- } else if (field.nestedConverterRef) {
1169
- if (input.visitedConverters.has(field.nestedConverterRef)) {
1170
- aborted = true;
1171
- } else {
1172
- nestedConverter = input.converterRegistry.get(field.nestedConverterRef);
1173
- }
1174
- }
1175
- if (!aborted && nestedConverter) {
1168
+ const nestedConverter = selectNestedConverter(input);
1169
+ if (nestedConverter) {
1176
1170
  const nextVisited = new Set(input.visitedConverters);
1177
1171
  if (nestedConverter.converterConst) nextVisited.add(nestedConverter.converterConst);
1178
1172
  const nestedIface = nestedConverter.interfaceName ? input.interfaceRegistry.get(nestedConverter.interfaceName) : void 0;
@@ -1185,11 +1179,21 @@ function resolveNestedFields(input) {
1185
1179
  depth: input.depth + 1,
1186
1180
  visitedConverters: nextVisited
1187
1181
  });
1188
- result = { fields, isArray: field.nestedIsArray ?? false };
1182
+ result = { fields, isArray: input.field.nestedIsArray ?? false };
1189
1183
  }
1190
1184
  }
1191
1185
  return result;
1192
1186
  }
1187
+ function selectNestedConverter(input) {
1188
+ const { field } = input;
1189
+ let result;
1190
+ if (field.nestedConverterInline) {
1191
+ result = field.nestedConverterInline;
1192
+ } else if (field.nestedConverterRef && !input.visitedConverters.has(field.nestedConverterRef)) {
1193
+ result = input.converterRegistry.get(field.nestedConverterRef);
1194
+ }
1195
+ return result;
1196
+ }
1193
1197
  function collectAncestors(iface, registry) {
1194
1198
  const out = [];
1195
1199
  const visited = /* @__PURE__ */ new Set([iface.name]);
@@ -1261,31 +1265,42 @@ function findIdentifierInBarrelChain(filePath, identifier, visited) {
1261
1265
  let result = false;
1262
1266
  if (!visited.has(filePath)) {
1263
1267
  visited.add(filePath);
1264
- let text;
1265
- try {
1266
- text = readFileSync5(filePath, "utf8");
1267
- } catch {
1268
- text = void 0;
1269
- }
1268
+ const text = readFileTextSafe(filePath);
1270
1269
  if (text !== void 0) {
1271
- for (const pattern of EXPORT_DECL_PATTERNS) {
1272
- const re = new RegExp(pattern.source.replace("IDENT", escapeRegExp(identifier)));
1273
- if (re.test(text)) {
1274
- result = true;
1275
- break;
1276
- }
1277
- }
1278
- if (!result) {
1279
- const dir = dirname2(filePath);
1280
- for (const reExportTarget of collectReExportTargets(text)) {
1281
- const resolved = resolveReExport(dir, reExportTarget);
1282
- if (!resolved) continue;
1283
- if (findIdentifierInBarrelChain(resolved, identifier, visited)) {
1284
- result = true;
1285
- break;
1286
- }
1287
- }
1288
- }
1270
+ result = barrelTextDeclaresIdentifier(text, identifier) || reExportChainHasIdentifier({ fromFile: filePath, text, identifier, visited });
1271
+ }
1272
+ }
1273
+ return result;
1274
+ }
1275
+ function readFileTextSafe(filePath) {
1276
+ let text;
1277
+ try {
1278
+ text = readFileSync5(filePath, "utf8");
1279
+ } catch {
1280
+ text = void 0;
1281
+ }
1282
+ return text;
1283
+ }
1284
+ function barrelTextDeclaresIdentifier(text, identifier) {
1285
+ let result = false;
1286
+ for (const pattern of EXPORT_DECL_PATTERNS) {
1287
+ const re = new RegExp(pattern.source.replace("IDENT", escapeRegExp(identifier)));
1288
+ if (re.test(text)) {
1289
+ result = true;
1290
+ break;
1291
+ }
1292
+ }
1293
+ return result;
1294
+ }
1295
+ function reExportChainHasIdentifier(input) {
1296
+ const { fromFile, text, identifier, visited } = input;
1297
+ let result = false;
1298
+ const dir = dirname2(fromFile);
1299
+ for (const reExportTarget of collectReExportTargets(text)) {
1300
+ const resolved = resolveReExport(dir, reExportTarget);
1301
+ if (resolved && findIdentifierInBarrelChain(resolved, identifier, visited)) {
1302
+ result = true;
1303
+ break;
1289
1304
  }
1290
1305
  }
1291
1306
  return result;
@@ -1402,7 +1417,7 @@ function renderEntry({ entry, validatorName }) {
1402
1417
  entry.mcpResultTypeDescription ? `mcpResultTypeDescription: ${JSON.stringify(entry.mcpResultTypeDescription)}` : void 0,
1403
1418
  entry.mcpResultFields && entry.mcpResultFields.length > 0 ? `mcpResultFields: ${renderDocFields(entry.mcpResultFields)}` : void 0
1404
1419
  ];
1405
- return ` { ${fields.filter((v) => Boolean(v)).join(", ")} }`;
1420
+ return ` { ${fields.filter(Boolean).join(", ")} }`;
1406
1421
  }
1407
1422
  function renderDocFields(fields) {
1408
1423
  const items = fields.map((field) => {
@@ -1432,7 +1447,7 @@ function renderModelEntry(entry, emitConverters) {
1432
1447
  entry.read ? `read: ${JSON.stringify(entry.read)}` : void 0,
1433
1448
  entry.serviceFactory ? `serviceFactory: { exportName: ${JSON.stringify(entry.serviceFactory.exportName)}, sourceFile: ${JSON.stringify(entry.serviceFactory.sourceFile)} }` : void 0
1434
1449
  ];
1435
- return ` { ${fields.filter((v) => Boolean(v)).join(", ")} }`;
1450
+ return ` { ${fields.filter(Boolean).join(", ")} }`;
1436
1451
  }
1437
1452
  function renderModelFields(fields, emitConverters) {
1438
1453
  let result;
@@ -1458,7 +1473,7 @@ function renderModelField(field, emitConverters) {
1458
1473
  field.nestedFields ? `nestedFields: ${renderModelFields(field.nestedFields, emitConverters)}` : void 0,
1459
1474
  field.nestedFields ? `nestedIsArray: ${nestedIsArrayLiteral}` : void 0
1460
1475
  ];
1461
- return `{ ${parts.filter((v) => Boolean(v)).join(", ")} }`;
1476
+ return `{ ${parts.filter(Boolean).join(", ")} }`;
1462
1477
  }
1463
1478
 
1464
1479
  // packages/dbx-cli/firebase-api-manifest/src/generate-api-manifest/main.ts
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@dereekb/dbx-cli-firebase-api-manifest",
3
- "version": "13.14.0",
3
+ "version": "13.16.0",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "devDependencies": {
7
7
  "ts-morph": "^21.0.0"
8
8
  },
9
9
  "peerDependencies": {
10
- "@dereekb/dbx-cli": "13.14.0",
11
- "@dereekb/util": "13.14.0",
10
+ "@dereekb/dbx-cli": "13.16.0",
11
+ "@dereekb/util": "13.16.0",
12
12
  "prettier": "3.8.3"
13
13
  }
14
14
  }
@@ -5,14 +5,14 @@ const require = __createRequire(import.meta.url);
5
5
  // packages/dbx-cli/generate-firestore-indexes/package.json
6
6
  var package_default = {
7
7
  name: "@dereekb/dbx-cli-generate-firestore-indexes",
8
- version: "13.14.0",
8
+ version: "13.16.0",
9
9
  private: true,
10
10
  type: "module",
11
11
  devDependencies: {
12
12
  eslint: "10.4.0"
13
13
  },
14
14
  peerDependencies: {
15
- "@dereekb/dbx-cli": "13.14.0"
15
+ "@dereekb/dbx-cli": "13.16.0"
16
16
  }
17
17
  };
18
18
 
@@ -2109,38 +2109,51 @@ function parseArgv(argv) {
2109
2109
  return { component, output, check, json, help, error };
2110
2110
  }
2111
2111
  async function readExistingIndexes(input) {
2112
+ const text = await readExistingIndexesText(input);
2113
+ let result2;
2114
+ if (text !== void 0) {
2115
+ result2 = parseExistingIndexesJson(text, input.outputAbs, input.stderr);
2116
+ }
2117
+ return result2;
2118
+ }
2119
+ async function readExistingIndexesText(input) {
2112
2120
  const { outputAbs, readFile, stderr } = input;
2113
- let text = null;
2114
- let readFailed = false;
2121
+ let text;
2115
2122
  try {
2116
2123
  text = await readFile(outputAbs);
2117
2124
  } catch (err) {
2118
- readFailed = true;
2119
2125
  const code = err.code;
2120
2126
  if (code !== "ENOENT") {
2121
2127
  stderr(`generate-firestore-indexes: could not read existing ${outputAbs}: ${err instanceof Error ? err.message : String(err)}`);
2122
2128
  }
2129
+ text = void 0;
2123
2130
  }
2131
+ return text;
2132
+ }
2133
+ function parseExistingIndexesJson(text, outputAbs, stderr) {
2124
2134
  let result2;
2125
- if (!readFailed && text !== null) {
2126
- let parsed;
2127
- let parseFailed = false;
2128
- try {
2129
- parsed = JSON.parse(text);
2130
- } catch (err) {
2131
- parseFailed = true;
2132
- stderr(`generate-firestore-indexes: existing ${outputAbs} is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
2133
- }
2134
- if (!parseFailed) {
2135
- if (parsed === null || typeof parsed !== "object") {
2136
- stderr(`generate-firestore-indexes: existing ${outputAbs} top-level value is not an object`);
2137
- } else {
2138
- const raw = parsed;
2139
- const indexes = Array.isArray(raw.indexes) ? raw.indexes : [];
2140
- const fieldOverrides = Array.isArray(raw.fieldOverrides) ? raw.fieldOverrides : [];
2141
- result2 = { indexes, fieldOverrides };
2142
- }
2143
- }
2135
+ let parsed;
2136
+ let parseFailed = false;
2137
+ try {
2138
+ parsed = JSON.parse(text);
2139
+ } catch (err) {
2140
+ parseFailed = true;
2141
+ stderr(`generate-firestore-indexes: existing ${outputAbs} is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
2142
+ }
2143
+ if (!parseFailed) {
2144
+ result2 = coerceFirestoreIndexesJson(parsed, outputAbs, stderr);
2145
+ }
2146
+ return result2;
2147
+ }
2148
+ function coerceFirestoreIndexesJson(parsed, outputAbs, stderr) {
2149
+ let result2;
2150
+ if (parsed === null || typeof parsed !== "object") {
2151
+ stderr(`generate-firestore-indexes: existing ${outputAbs} top-level value is not an object`);
2152
+ } else {
2153
+ const raw = parsed;
2154
+ const indexes = Array.isArray(raw.indexes) ? raw.indexes : [];
2155
+ const fieldOverrides = Array.isArray(raw.fieldOverrides) ? raw.fieldOverrides : [];
2156
+ result2 = { indexes, fieldOverrides };
2144
2157
  }
2145
2158
  return result2;
2146
2159
  }
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@dereekb/dbx-cli-generate-firestore-indexes",
3
- "version": "13.14.0",
3
+ "version": "13.16.0",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "devDependencies": {
7
7
  "eslint": "10.4.0"
8
8
  },
9
9
  "peerDependencies": {
10
- "@dereekb/dbx-cli": "13.14.0"
10
+ "@dereekb/dbx-cli": "13.16.0"
11
11
  }
12
12
  }
@@ -725,9 +725,7 @@ var EMPTY_ROLE_CONST_RESOLUTION = { roles: [], unresolved: [] };
725
725
  function resolveRoleConstByName(name, ctx) {
726
726
  let result;
727
727
  const scalar = ctx.knownRoles.get(name);
728
- if (scalar !== void 0) {
729
- result = { roles: [scalar], unresolved: [] };
730
- } else {
728
+ if (scalar === void 0) {
731
729
  const arrayExpr = ctx.localArrayRoles.get(name);
732
730
  if (arrayExpr === void 0) {
733
731
  result = { roles: [], unresolved: [name] };
@@ -738,6 +736,8 @@ function resolveRoleConstByName(name, ctx) {
738
736
  nextSeen.add(name);
739
737
  result = resolveRoleArrayLiteral(arrayExpr, { ...ctx, seen: nextSeen });
740
738
  }
739
+ } else {
740
+ result = { roles: [scalar], unresolved: [] };
741
741
  }
742
742
  return result;
743
743
  }
@@ -1015,53 +1015,65 @@ function renderMcpManifest(input, now = /* @__PURE__ */ new Date()) {
1015
1015
  const warnings = [];
1016
1016
  const errors = [];
1017
1017
  const seenNames = /* @__PURE__ */ new Map();
1018
+ const segments = buildSegmentMap(input.modelManifest);
1019
+ const toolEntries = input.apiManifest.filter((entry) => entry.verb !== "standalone");
1020
+ const nameCounts = countToolNames(toolEntries, segments);
1021
+ for (const entry of toolEntries) {
1022
+ registerToolEntry({ entry, segments, nameCounts, tools, seenNames, warnings, errors });
1023
+ }
1024
+ const models = input.modelManifest != null && input.modelManifest.length > 0 ? input.modelManifest.map(projectModelEntry) : void 0;
1025
+ const auth = input.auth == null ? void 0 : projectAuthSection(input.auth.registry, input.auth.app);
1026
+ const base = { version: MCP_MANIFEST_VERSION, generatedAt: now.toISOString(), tools };
1027
+ const manifest = {
1028
+ ...base,
1029
+ ...models == null ? {} : { models },
1030
+ ...auth == null ? {} : { auth }
1031
+ };
1032
+ return { manifest, warnings, errors };
1033
+ }
1034
+ function buildSegmentMap(modelManifest) {
1018
1035
  const segments = /* @__PURE__ */ new Map();
1019
- if (input.modelManifest != null) {
1020
- for (const model of input.modelManifest) {
1036
+ if (modelManifest != null) {
1037
+ for (const model of modelManifest) {
1021
1038
  if (model.mcpToolNameSegment != null && model.mcpToolNameSegment.length > 0) {
1022
1039
  segments.set(model.modelType, model.mcpToolNameSegment);
1023
1040
  }
1024
1041
  }
1025
1042
  }
1026
- const toolEntries = input.apiManifest.filter((entry) => entry.verb !== "standalone");
1043
+ return segments;
1044
+ }
1045
+ function countToolNames(toolEntries, segments) {
1027
1046
  const nameCounts = /* @__PURE__ */ new Map();
1028
1047
  for (const entry of toolEntries) {
1029
1048
  const segment = segments.get(entry.model) ?? entry.model;
1030
1049
  const baseName = buildMcpToolName(segment, entry.verb, entry.specifier);
1031
1050
  nameCounts.set(baseName, (nameCounts.get(baseName) ?? 0) + 1);
1032
1051
  }
1033
- for (const entry of toolEntries) {
1034
- const key = mcpManifestKey(entry.model, entry.verb, entry.specifier);
1035
- tools[key] = buildToolEntry(entry);
1036
- const segment = segments.get(entry.model) ?? entry.model;
1037
- const baseName = buildMcpToolName(segment, entry.verb, entry.specifier);
1038
- const clashes = (nameCounts.get(baseName) ?? 0) > 1;
1039
- const toolName = clashes ? buildDisambiguatedMcpToolName(segment, entry.verb, entry.specifier) : baseName;
1040
- if (toolName !== baseName) {
1041
- warnings.push(`Tool name "${baseName}" is produced by more than one entry; re-derived as "${toolName}" (${key}) with the abbreviated call type to disambiguate.`);
1042
- }
1043
- const validation = validateMcpToolName(toolName);
1044
- if (validation.level === "error") {
1045
- errors.push(`Tool name "${toolName}" is ${validation.length} chars, over the ${MCP_TOOL_NAME_MAX_LENGTH}-char MCP cap (${key}). Shorten the model/specifier, set a per-model mcpToolNameSegment, or hide the tool.`);
1046
- } else if (validation.level === "warn") {
1047
- warnings.push(`Tool name "${toolName}" is ${validation.length} chars, over the ${MCP_TOOL_NAME_WARN_LENGTH}-char soft limit (${key}).`);
1048
- }
1049
- const priorKey = seenNames.get(toolName);
1050
- if (priorKey != null) {
1051
- warnings.push(`Tool name "${toolName}" is still produced by more than one entry (${priorKey} and ${key}) after disambiguation; one shadows the other unless hidden or renamed at runtime.`);
1052
- } else {
1053
- seenNames.set(toolName, key);
1054
- }
1052
+ return nameCounts;
1053
+ }
1054
+ function registerToolEntry(input) {
1055
+ const { entry, segments, nameCounts, tools, seenNames, warnings, errors } = input;
1056
+ const key = mcpManifestKey(entry.model, entry.verb, entry.specifier);
1057
+ tools[key] = buildToolEntry(entry);
1058
+ const segment = segments.get(entry.model) ?? entry.model;
1059
+ const baseName = buildMcpToolName(segment, entry.verb, entry.specifier);
1060
+ const clashes = (nameCounts.get(baseName) ?? 0) > 1;
1061
+ const toolName = clashes ? buildDisambiguatedMcpToolName(segment, entry.verb, entry.specifier) : baseName;
1062
+ if (toolName !== baseName) {
1063
+ warnings.push(`Tool name "${baseName}" is produced by more than one entry; re-derived as "${toolName}" (${key}) with the abbreviated call type to disambiguate.`);
1064
+ }
1065
+ const validation = validateMcpToolName(toolName);
1066
+ if (validation.level === "error") {
1067
+ errors.push(`Tool name "${toolName}" is ${validation.length} chars, over the ${MCP_TOOL_NAME_MAX_LENGTH}-char MCP cap (${key}). Shorten the model/specifier, set a per-model mcpToolNameSegment, or hide the tool.`);
1068
+ } else if (validation.level === "warn") {
1069
+ warnings.push(`Tool name "${toolName}" is ${validation.length} chars, over the ${MCP_TOOL_NAME_WARN_LENGTH}-char soft limit (${key}).`);
1070
+ }
1071
+ const priorKey = seenNames.get(toolName);
1072
+ if (priorKey == null) {
1073
+ seenNames.set(toolName, key);
1074
+ } else {
1075
+ warnings.push(`Tool name "${toolName}" is still produced by more than one entry (${priorKey} and ${key}) after disambiguation; one shadows the other unless hidden or renamed at runtime.`);
1055
1076
  }
1056
- const models = input.modelManifest != null && input.modelManifest.length > 0 ? input.modelManifest.map(projectModelEntry) : void 0;
1057
- const auth = input.auth == null ? void 0 : projectAuthSection(input.auth.registry, input.auth.app);
1058
- const base = { version: MCP_MANIFEST_VERSION, generatedAt: now.toISOString(), tools };
1059
- const manifest = {
1060
- ...base,
1061
- ...models == null ? {} : { models },
1062
- ...auth == null ? {} : { auth }
1063
- };
1064
- return { manifest, warnings, errors };
1065
1077
  }
1066
1078
  function projectAuthSection(registry, appSlug) {
1067
1079
  const primary = registry.findApp(appSlug);
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@dereekb/dbx-cli-generate-mcp-manifest",
3
- "version": "13.14.0",
3
+ "version": "13.16.0",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "peerDependencies": {
7
- "@dereekb/dbx-cli": "13.14.0",
8
- "@dereekb/model": "13.14.0",
7
+ "@dereekb/dbx-cli": "13.16.0",
8
+ "@dereekb/model": "13.16.0",
9
9
  "arktype": "^2.2.0",
10
10
  "jiti": "2.6.1"
11
11
  }