@docyrus/docyrus 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +11 -0
  2. package/main.js +363 -24
  3. package/main.js.map +3 -3
  4. package/package.json +1 -1
package/README.md CHANGED
@@ -97,6 +97,17 @@ Download current tenant OpenAPI spec:
97
97
  docyrus discover api --json
98
98
  ```
99
99
 
100
+ Discover from downloaded tenant OpenAPI spec:
101
+
102
+ ```bash
103
+ docyrus discover namespaces --json
104
+ docyrus discover path /v1/users --json
105
+ docyrus discover endpoint /v1/users/me --json
106
+ docyrus discover endpoint [PUT]/v1/users/me/photo --json
107
+ docyrus discover entity UserEntity --json
108
+ docyrus discover search users,UserEntity --json
109
+ ```
110
+
100
111
  Work with accounts and tenants:
101
112
 
102
113
  ```bash
package/main.js CHANGED
@@ -39952,7 +39952,7 @@ function buildInputSchema(args, env, options) {
39952
39952
  // package.json
39953
39953
  var package_default = {
39954
39954
  name: "@docyrus/docyrus",
39955
- version: "0.0.1",
39955
+ version: "0.0.2",
39956
39956
  private: false,
39957
39957
  description: "Docyrus API CLI",
39958
39958
  main: "./main.js",
@@ -40752,6 +40752,185 @@ function createDsCli(dependencies) {
40752
40752
  }
40753
40753
 
40754
40754
  // src/commands/discoverCommands.ts
40755
+ var import_promises2 = require("node:fs/promises");
40756
+ var SUPPORTED_HTTP_METHODS2 = [
40757
+ "GET",
40758
+ "POST",
40759
+ "PUT",
40760
+ "PATCH",
40761
+ "DELETE",
40762
+ "HEAD",
40763
+ "OPTIONS",
40764
+ "TRACE"
40765
+ ];
40766
+ function isRecord(value) {
40767
+ return typeof value === "object" && value !== null;
40768
+ }
40769
+ function isEnoentError(error48) {
40770
+ return typeof error48 === "object" && error48 !== null && "code" in error48 && error48.code === "ENOENT";
40771
+ }
40772
+ function normalizePathInput(pathValue) {
40773
+ const trimmed = pathValue.trim();
40774
+ if (!trimmed) {
40775
+ throw new UserInputError("Path is required.");
40776
+ }
40777
+ return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
40778
+ }
40779
+ function buildPathCandidates(pathValue) {
40780
+ const normalized = normalizePathInput(pathValue);
40781
+ const candidates = /* @__PURE__ */ new Set([normalized]);
40782
+ if (normalized === "/v1") {
40783
+ candidates.add("/");
40784
+ } else if (normalized.startsWith("/v1/")) {
40785
+ candidates.add(normalized.substring(3));
40786
+ } else {
40787
+ candidates.add(`/v1${normalized}`);
40788
+ }
40789
+ return Array.from(candidates.values());
40790
+ }
40791
+ function toOutputPath(pathValue) {
40792
+ const normalized = normalizePathInput(pathValue);
40793
+ if (normalized === "/") {
40794
+ return "/v1";
40795
+ }
40796
+ if (normalized.startsWith("/v1/") || normalized === "/v1") {
40797
+ return normalized;
40798
+ }
40799
+ return `/v1${normalized}`;
40800
+ }
40801
+ function parseEndpointSelector(input) {
40802
+ const trimmed = input.trim();
40803
+ if (!trimmed) {
40804
+ throw new UserInputError("Endpoint selector is required.");
40805
+ }
40806
+ const methodPathMatch = trimmed.match(/^\[([a-z]+)\](\/.*)$/i);
40807
+ if (!methodPathMatch) {
40808
+ return {
40809
+ path: trimmed,
40810
+ method: "GET"
40811
+ };
40812
+ }
40813
+ return {
40814
+ method: methodPathMatch[1].toUpperCase(),
40815
+ path: methodPathMatch[2]
40816
+ };
40817
+ }
40818
+ function getPaths(document) {
40819
+ const rawPaths = document.paths;
40820
+ return isRecord(rawPaths) ? rawPaths : {};
40821
+ }
40822
+ function getEntities(document) {
40823
+ const components = isRecord(document.components) ? document.components : null;
40824
+ if (components && isRecord(components.schemas)) {
40825
+ return components.schemas;
40826
+ }
40827
+ const definitions = document.definitions;
40828
+ if (isRecord(definitions)) {
40829
+ return definitions;
40830
+ }
40831
+ return {};
40832
+ }
40833
+ function extractNamespaces(paths) {
40834
+ const namespaces = /* @__PURE__ */ new Set();
40835
+ for (const path3 of Object.keys(paths)) {
40836
+ const segments = path3.split("/").map((item) => item.trim()).filter((item) => item.length > 0);
40837
+ if (segments.length === 0) {
40838
+ continue;
40839
+ }
40840
+ const namespace = segments[0] === "v1" ? segments[1] : segments[0];
40841
+ if (namespace) {
40842
+ namespaces.add(`/v1/${namespace}`);
40843
+ }
40844
+ }
40845
+ return Array.from(namespaces.values()).sort((left, right) => left.localeCompare(right));
40846
+ }
40847
+ function getPathOperation(pathItem, method) {
40848
+ if (!isRecord(pathItem)) {
40849
+ return null;
40850
+ }
40851
+ const normalizedMethod = method.toLowerCase();
40852
+ const operation = pathItem[normalizedMethod];
40853
+ return isRecord(operation) ? operation : null;
40854
+ }
40855
+ function toEndpointMethodEntries(paths, pathFilter) {
40856
+ const entries = [];
40857
+ const sortedPaths = Object.keys(paths).filter(pathFilter).sort((left, right) => left.localeCompare(right));
40858
+ for (const path3 of sortedPaths) {
40859
+ const pathItem = paths[path3];
40860
+ if (!isRecord(pathItem)) {
40861
+ continue;
40862
+ }
40863
+ for (const method of SUPPORTED_HTTP_METHODS2) {
40864
+ const operation = getPathOperation(pathItem, method);
40865
+ if (!operation) {
40866
+ continue;
40867
+ }
40868
+ const descriptionValue = operation.description;
40869
+ const summaryValue = operation.summary;
40870
+ const description = typeof descriptionValue === "string" ? descriptionValue : typeof summaryValue === "string" ? summaryValue : void 0;
40871
+ entries.push({
40872
+ path: toOutputPath(path3),
40873
+ method,
40874
+ description
40875
+ });
40876
+ }
40877
+ }
40878
+ return entries;
40879
+ }
40880
+ function parseOpenApiDocument(raw) {
40881
+ let parsed;
40882
+ try {
40883
+ parsed = JSON.parse(raw);
40884
+ } catch (error48) {
40885
+ throw new UserInputError("Stored OpenAPI spec is invalid JSON. Run 'docyrus discover api' to re-download.", {
40886
+ cause: error48
40887
+ });
40888
+ }
40889
+ if (!isRecord(parsed)) {
40890
+ throw new UserInputError("Stored OpenAPI spec must be a JSON object. Run 'docyrus discover api' to re-download.");
40891
+ }
40892
+ return parsed;
40893
+ }
40894
+ async function loadOpenApiSpec(dependencies, tenantId) {
40895
+ let filePath = dependencies.tenantOpenApiService.getTenantOpenApiFilePath(tenantId);
40896
+ let downloaded = false;
40897
+ let sourceUrl;
40898
+ let content;
40899
+ try {
40900
+ content = await (0, import_promises2.readFile)(filePath, "utf8");
40901
+ } catch (error48) {
40902
+ if (!isEnoentError(error48)) {
40903
+ throw new UserInputError("Failed to read downloaded OpenAPI spec file.", {
40904
+ filePath,
40905
+ cause: error48
40906
+ });
40907
+ }
40908
+ const downloadResult = await dependencies.tenantOpenApiService.downloadTenantOpenApi(tenantId);
40909
+ downloaded = true;
40910
+ sourceUrl = downloadResult.sourceUrl;
40911
+ filePath = downloadResult.filePath;
40912
+ content = await (0, import_promises2.readFile)(filePath, "utf8");
40913
+ }
40914
+ return {
40915
+ filePath,
40916
+ downloaded,
40917
+ sourceUrl,
40918
+ document: parseOpenApiDocument(content)
40919
+ };
40920
+ }
40921
+ async function withActiveTenantSpec(dependencies) {
40922
+ const apiBaseUrl = await dependencies.environmentConfigService.getActiveApiBaseUrl();
40923
+ const activeProfile = await dependencies.authStore.getActiveProfile(apiBaseUrl);
40924
+ if (!activeProfile) {
40925
+ throw new AuthSessionError("No active session found. Run 'docyrus auth login'.");
40926
+ }
40927
+ const spec = await loadOpenApiSpec(dependencies, activeProfile.tenantId);
40928
+ return {
40929
+ apiBaseUrl,
40930
+ activeProfile,
40931
+ spec
40932
+ };
40933
+ }
40755
40934
  function createDiscoverCli(dependencies) {
40756
40935
  const discoverCli = Cli_exports.create("discover", {
40757
40936
  description: "Discovery commands",
@@ -40778,6 +40957,154 @@ function createDiscoverCli(dependencies) {
40778
40957
  });
40779
40958
  }
40780
40959
  });
40960
+ discoverCli.command("namespaces", {
40961
+ description: "List API namespaces from active tenant OpenAPI spec",
40962
+ run: async () => {
40963
+ const { apiBaseUrl, spec } = await withActiveTenantSpec(dependencies);
40964
+ const namespaces = extractNamespaces(getPaths(spec.document));
40965
+ return await injectContext({
40966
+ apiBaseUrl,
40967
+ authStore: dependencies.authStore,
40968
+ payload: {
40969
+ namespaces,
40970
+ count: namespaces.length,
40971
+ specFilePath: spec.filePath,
40972
+ downloaded: spec.downloaded,
40973
+ sourceUrl: spec.sourceUrl
40974
+ }
40975
+ });
40976
+ }
40977
+ });
40978
+ discoverCli.command("path", {
40979
+ description: "List endpoints with method and description for matching path prefix",
40980
+ args: external_exports.object({
40981
+ prefix: external_exports.string().min(1).describe("Path prefix, e.g. /v1/users")
40982
+ }),
40983
+ run: async (context) => {
40984
+ const { apiBaseUrl, spec } = await withActiveTenantSpec(dependencies);
40985
+ const paths = getPaths(spec.document);
40986
+ const candidates = buildPathCandidates(context.args.prefix);
40987
+ const endpoints = toEndpointMethodEntries(
40988
+ paths,
40989
+ (path3) => candidates.some((candidate) => path3.startsWith(candidate))
40990
+ );
40991
+ return await injectContext({
40992
+ apiBaseUrl,
40993
+ authStore: dependencies.authStore,
40994
+ payload: {
40995
+ prefix: context.args.prefix,
40996
+ normalizedPrefixes: candidates,
40997
+ endpoints,
40998
+ count: endpoints.length,
40999
+ specFilePath: spec.filePath,
41000
+ downloaded: spec.downloaded,
41001
+ sourceUrl: spec.sourceUrl
41002
+ }
41003
+ });
41004
+ }
41005
+ });
41006
+ discoverCli.command("endpoint", {
41007
+ description: "Return full endpoint object for a path and HTTP method",
41008
+ args: external_exports.object({
41009
+ selector: external_exports.string().min(1).describe("Endpoint selector, e.g. /v1/users/me or [PUT]/v1/users/me/photo")
41010
+ }),
41011
+ run: async (context) => {
41012
+ const { apiBaseUrl, spec } = await withActiveTenantSpec(dependencies);
41013
+ const paths = getPaths(spec.document);
41014
+ const selector = parseEndpointSelector(context.args.selector);
41015
+ const candidates = buildPathCandidates(selector.path);
41016
+ const resolvedPath = candidates.find((candidate) => candidate in paths);
41017
+ if (!resolvedPath) {
41018
+ throw new UserInputError(`Endpoint '${selector.path}' was not found in downloaded OpenAPI spec.`);
41019
+ }
41020
+ if (!SUPPORTED_HTTP_METHODS2.includes(selector.method)) {
41021
+ throw new UserInputError(`Unsupported HTTP method '${selector.method}'.`);
41022
+ }
41023
+ const operation = getPathOperation(paths[resolvedPath], selector.method);
41024
+ if (!operation) {
41025
+ throw new UserInputError(
41026
+ `Method '${selector.method}' is not available for endpoint '${toOutputPath(resolvedPath)}'.`
41027
+ );
41028
+ }
41029
+ return await injectContext({
41030
+ apiBaseUrl,
41031
+ authStore: dependencies.authStore,
41032
+ payload: {
41033
+ requestedSelector: context.args.selector,
41034
+ method: selector.method,
41035
+ resolvedPath: toOutputPath(resolvedPath),
41036
+ endpoint: operation,
41037
+ specFilePath: spec.filePath,
41038
+ downloaded: spec.downloaded,
41039
+ sourceUrl: spec.sourceUrl
41040
+ }
41041
+ });
41042
+ }
41043
+ });
41044
+ discoverCli.command("entity", {
41045
+ description: "Return full entity object by name",
41046
+ args: external_exports.object({
41047
+ name: external_exports.string().min(1).describe("Entity name, e.g. UserEntity")
41048
+ }),
41049
+ run: async (context) => {
41050
+ const { apiBaseUrl, spec } = await withActiveTenantSpec(dependencies);
41051
+ const entities = getEntities(spec.document);
41052
+ const requestedName = context.args.name.trim();
41053
+ if (!(requestedName in entities)) {
41054
+ throw new UserInputError(`Entity '${context.args.name}' was not found in downloaded OpenAPI spec.`);
41055
+ }
41056
+ return await injectContext({
41057
+ apiBaseUrl,
41058
+ authStore: dependencies.authStore,
41059
+ payload: {
41060
+ entityName: requestedName,
41061
+ entity: entities[requestedName],
41062
+ specFilePath: spec.filePath,
41063
+ downloaded: spec.downloaded,
41064
+ sourceUrl: spec.sourceUrl
41065
+ }
41066
+ });
41067
+ }
41068
+ });
41069
+ discoverCli.command("search", {
41070
+ description: "Search endpoint paths and entity names (endpoint results include method and description)",
41071
+ args: external_exports.object({
41072
+ query: external_exports.string().min(1).describe("One or more comma-separated search strings")
41073
+ }),
41074
+ run: async (context) => {
41075
+ const { apiBaseUrl, spec } = await withActiveTenantSpec(dependencies);
41076
+ const terms = context.args.query.split(",").map((term) => term.trim()).filter((term) => term.length > 0);
41077
+ if (terms.length === 0) {
41078
+ throw new UserInputError("Provide at least one non-empty search term.");
41079
+ }
41080
+ const entityNames = Object.keys(getEntities(spec.document));
41081
+ const pathMap = getPaths(spec.document);
41082
+ const results = terms.map((term) => {
41083
+ const loweredTerm = term.toLowerCase();
41084
+ const matchingEndpoints = toEndpointMethodEntries(
41085
+ pathMap,
41086
+ (path3) => path3.toLowerCase().includes(loweredTerm)
41087
+ );
41088
+ const matchingEntities = entityNames.filter((entityName) => entityName.toLowerCase().includes(loweredTerm));
41089
+ return {
41090
+ term,
41091
+ endpoints: matchingEndpoints,
41092
+ entityNames: matchingEntities.sort((left, right) => left.localeCompare(right))
41093
+ };
41094
+ });
41095
+ return await injectContext({
41096
+ apiBaseUrl,
41097
+ authStore: dependencies.authStore,
41098
+ payload: {
41099
+ terms,
41100
+ results,
41101
+ specFilePath: spec.filePath,
41102
+ downloaded: spec.downloaded,
41103
+ sourceUrl: spec.sourceUrl
41104
+ }
41105
+ });
41106
+ }
41107
+ });
40781
41108
  return discoverCli;
40782
41109
  }
40783
41110
 
@@ -40972,7 +41299,7 @@ var ApiClient = class {
40972
41299
  };
40973
41300
 
40974
41301
  // src/services/authSession.ts
40975
- function isRecord(value) {
41302
+ function isRecord2(value) {
40976
41303
  return typeof value === "object" && value !== null;
40977
41304
  }
40978
41305
  function extractRecordValue(record2, keys) {
@@ -41271,11 +41598,11 @@ var AuthSessionService = class {
41271
41598
  fetchFn: this.params.fetchFn
41272
41599
  });
41273
41600
  const payload = response.data;
41274
- const dataCandidate = isRecord(payload) && isRecord(payload.data) ? payload.data : payload;
41275
- if (!isRecord(dataCandidate)) {
41601
+ const dataCandidate = isRecord2(payload) && isRecord2(payload.data) ? payload.data : payload;
41602
+ if (!isRecord2(dataCandidate)) {
41276
41603
  throw new AuthSessionError("Unable to parse /users/me response.");
41277
41604
  }
41278
- const tenantCandidate = isRecord(dataCandidate.tenant) ? dataCandidate.tenant : void 0;
41605
+ const tenantCandidate = isRecord2(dataCandidate.tenant) ? dataCandidate.tenant : void 0;
41279
41606
  const userId = extractString(dataCandidate, ["id", "user_id"]);
41280
41607
  const email3 = extractString(dataCandidate, ["email"]);
41281
41608
  const tenantId = tenantCandidate ? extractString(tenantCandidate, ["id"]) : extractString(dataCandidate, ["tenant_id", "tenantId"]);
@@ -41301,13 +41628,13 @@ var AuthSessionService = class {
41301
41628
  fetchFn: this.params.fetchFn
41302
41629
  });
41303
41630
  const payload = response.data;
41304
- const listCandidate = Array.isArray(payload) ? payload : isRecord(payload) && Array.isArray(payload.data) ? payload.data : null;
41631
+ const listCandidate = Array.isArray(payload) ? payload : isRecord2(payload) && Array.isArray(payload.data) ? payload.data : null;
41305
41632
  if (!listCandidate) {
41306
41633
  throw new AuthSessionError("Unable to parse tenant catalog response.");
41307
41634
  }
41308
41635
  const mapped = [];
41309
41636
  for (const item of listCandidate) {
41310
- if (!isRecord(item)) {
41637
+ if (!isRecord2(item)) {
41311
41638
  continue;
41312
41639
  }
41313
41640
  const tenantId = extractString(item, ["id", "tenant_id"]);
@@ -41466,7 +41793,7 @@ var AuthSessionService = class {
41466
41793
  };
41467
41794
 
41468
41795
  // src/services/authStore.ts
41469
- var import_promises2 = require("node:fs/promises");
41796
+ var import_promises3 = require("node:fs/promises");
41470
41797
  var import_node_path4 = require("node:path");
41471
41798
  function createEmptyState() {
41472
41799
  return {
@@ -41506,7 +41833,7 @@ var AuthStore = class {
41506
41833
  }
41507
41834
  async readState() {
41508
41835
  try {
41509
- const raw = await (0, import_promises2.readFile)(this.authFilePath, "utf8");
41836
+ const raw = await (0, import_promises3.readFile)(this.authFilePath, "utf8");
41510
41837
  const parsed = JSON.parse(raw);
41511
41838
  const legacy = LegacyAuthSessionSchema.safeParse(parsed);
41512
41839
  if (legacy.success) {
@@ -41543,16 +41870,16 @@ var AuthStore = class {
41543
41870
  }
41544
41871
  const normalized = normalizeState(validated.data);
41545
41872
  const directory = (0, import_node_path4.dirname)(this.authFilePath);
41546
- await (0, import_promises2.mkdir)(directory, {
41873
+ await (0, import_promises3.mkdir)(directory, {
41547
41874
  recursive: true,
41548
41875
  mode: 448
41549
41876
  });
41550
- await (0, import_promises2.writeFile)(this.authFilePath, `${JSON.stringify(normalized, null, 2)}
41877
+ await (0, import_promises3.writeFile)(this.authFilePath, `${JSON.stringify(normalized, null, 2)}
41551
41878
  `, {
41552
41879
  encoding: "utf8",
41553
41880
  mode: 384
41554
41881
  });
41555
- await (0, import_promises2.chmod)(this.authFilePath, 384);
41882
+ await (0, import_promises3.chmod)(this.authFilePath, 384);
41556
41883
  }
41557
41884
  async getActiveProfile(apiBaseUrl) {
41558
41885
  const normalizedApiBaseUrl = normalizeApiBaseUrl(apiBaseUrl);
@@ -41749,14 +42076,14 @@ var AuthStore = class {
41749
42076
  await this.writeState(state);
41750
42077
  }
41751
42078
  async clear() {
41752
- await (0, import_promises2.rm)(this.authFilePath, {
42079
+ await (0, import_promises3.rm)(this.authFilePath, {
41753
42080
  force: true
41754
42081
  });
41755
42082
  }
41756
42083
  };
41757
42084
 
41758
42085
  // src/services/environmentConfig.ts
41759
- var import_promises3 = require("node:fs/promises");
42086
+ var import_promises4 = require("node:fs/promises");
41760
42087
  var import_node_path5 = require("node:path");
41761
42088
  var ENVIRONMENT_ID_ALIASES = {
41762
42089
  "local-development": "dev",
@@ -41843,7 +42170,7 @@ var EnvironmentConfigService = class {
41843
42170
  }
41844
42171
  async readState() {
41845
42172
  try {
41846
- const raw = await (0, import_promises3.readFile)(this.configFilePath, "utf8");
42173
+ const raw = await (0, import_promises4.readFile)(this.configFilePath, "utf8");
41847
42174
  const parsed = JSON.parse(raw);
41848
42175
  const validated = EnvironmentConfigStateSchema.safeParse(parsed);
41849
42176
  if (!validated.success) {
@@ -41878,16 +42205,16 @@ var EnvironmentConfigService = class {
41878
42205
  }
41879
42206
  const normalized = normalizeState2(validated.data);
41880
42207
  const directory = (0, import_node_path5.dirname)(this.configFilePath);
41881
- await (0, import_promises3.mkdir)(directory, {
42208
+ await (0, import_promises4.mkdir)(directory, {
41882
42209
  recursive: true,
41883
42210
  mode: 448
41884
42211
  });
41885
- await (0, import_promises3.writeFile)(this.configFilePath, `${JSON.stringify(normalized, null, 2)}
42212
+ await (0, import_promises4.writeFile)(this.configFilePath, `${JSON.stringify(normalized, null, 2)}
41886
42213
  `, {
41887
42214
  encoding: "utf8",
41888
42215
  mode: 384
41889
42216
  });
41890
- await (0, import_promises3.chmod)(this.configFilePath, 384);
42217
+ await (0, import_promises4.chmod)(this.configFilePath, 384);
41891
42218
  }
41892
42219
  async getActiveEnvironment() {
41893
42220
  const state = await this.#readStateWithDefaults();
@@ -41953,7 +42280,7 @@ var EnvironmentConfigService = class {
41953
42280
  };
41954
42281
 
41955
42282
  // src/services/tenantOpenApi.ts
41956
- var import_promises4 = require("node:fs/promises");
42283
+ var import_promises5 = require("node:fs/promises");
41957
42284
  var import_node_path6 = require("node:path");
41958
42285
  function resolveSourceUrl(tenantId, template) {
41959
42286
  return template.replace("{tenantId}", encodeURIComponent(tenantId));
@@ -41962,6 +42289,14 @@ var TenantOpenApiService = class {
41962
42289
  constructor(params) {
41963
42290
  this.params = params;
41964
42291
  }
42292
+ getTenantOpenApiFilePath(tenantId) {
42293
+ const normalizedTenantId = tenantId.trim();
42294
+ if (!normalizedTenantId) {
42295
+ throw new AuthSessionError("Tenant ID is required to resolve OpenAPI spec path.");
42296
+ }
42297
+ const rootPath = this.params?.rootPath || TENANT_OPENAPI_ROOT_PATH;
42298
+ return (0, import_node_path6.join)(rootPath, normalizedTenantId, "openapi.json");
42299
+ }
41965
42300
  async downloadTenantOpenApi(tenantId) {
41966
42301
  const normalizedTenantId = tenantId.trim();
41967
42302
  if (!normalizedTenantId) {
@@ -41993,18 +42328,17 @@ var TenantOpenApiService = class {
41993
42328
  cause: error48
41994
42329
  });
41995
42330
  }
41996
- const rootPath = this.params?.rootPath || TENANT_OPENAPI_ROOT_PATH;
41997
- const filePath = (0, import_node_path6.join)(rootPath, normalizedTenantId, "openapi.json");
41998
- await (0, import_promises4.mkdir)((0, import_node_path6.dirname)(filePath), {
42331
+ const filePath = this.getTenantOpenApiFilePath(normalizedTenantId);
42332
+ await (0, import_promises5.mkdir)((0, import_node_path6.dirname)(filePath), {
41999
42333
  recursive: true,
42000
42334
  mode: 448
42001
42335
  });
42002
- await (0, import_promises4.writeFile)(filePath, `${JSON.stringify(parsedContent, null, 2)}
42336
+ await (0, import_promises5.writeFile)(filePath, `${JSON.stringify(parsedContent, null, 2)}
42003
42337
  `, {
42004
42338
  encoding: "utf8",
42005
42339
  mode: 384
42006
42340
  });
42007
- await (0, import_promises4.chmod)(filePath, 384);
42341
+ await (0, import_promises5.chmod)(filePath, 384);
42008
42342
  return {
42009
42343
  tenantId: normalizedTenantId,
42010
42344
  sourceUrl,
@@ -42021,6 +42355,11 @@ var ROOT_HELP_COMMANDS = [
42021
42355
  { command: "auth accounts list", description: "List saved user accounts" },
42022
42356
  { command: "auth tenants use --tenantId <id>", description: "Switch active tenant for selected user" },
42023
42357
  { command: "discover api", description: "Download active tenant OpenAPI spec" },
42358
+ { command: "discover namespaces", description: "List namespaces from tenant OpenAPI spec" },
42359
+ { command: "discover path <prefix>", description: "List endpoint paths by prefix" },
42360
+ { command: "discover endpoint <path>", description: "Show endpoint object by exact path" },
42361
+ { command: "discover entity <name>", description: "Show entity object by name" },
42362
+ { command: "discover search <terms>", description: "Search in endpoint paths and entity names" },
42024
42363
  { command: "ds list <appSlug> <dataSourceSlug>", description: "List data source items" },
42025
42364
  { command: "apps list", description: "List apps" },
42026
42365
  { command: "curl <path>", description: "Send arbitrary API requests" }