@docyrus/docyrus 0.0.1 → 0.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.
Files changed (4) hide show
  1. package/README.md +44 -0
  2. package/main.js +1492 -58
  3. package/main.js.map +4 -4
  4. package/package.json +1 -1
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.3",
39956
39956
  private: false,
39957
39957
  description: "Docyrus API CLI",
39958
39958
  main: "./main.js",
@@ -40428,6 +40428,8 @@ function createAuthCli(dependencies) {
40428
40428
  }
40429
40429
 
40430
40430
  // src/services/inputReader.ts
40431
+ var import_promises2 = require("node:fs/promises");
40432
+ var import_node_path4 = require("node:path");
40431
40433
  async function readStdinText() {
40432
40434
  if (process.stdin.isTTY) {
40433
40435
  return "";
@@ -40451,20 +40453,99 @@ function parseJsonData(raw, source) {
40451
40453
  });
40452
40454
  }
40453
40455
  }
40454
- async function readJsonInput(params) {
40455
- const { data, readStdin = readStdinText } = params;
40456
- let rawJson = data?.trim();
40457
- if (!rawJson) {
40458
- rawJson = (await readStdin()).trim();
40456
+ function parseCsvRow(line) {
40457
+ const values = [];
40458
+ let current = "";
40459
+ let inQuotes = false;
40460
+ for (let index = 0; index < line.length; index += 1) {
40461
+ const char = line[index];
40462
+ const next = line[index + 1];
40463
+ if (char === '"') {
40464
+ if (inQuotes && next === '"') {
40465
+ current += '"';
40466
+ index += 1;
40467
+ } else {
40468
+ inQuotes = !inQuotes;
40469
+ }
40470
+ continue;
40471
+ }
40472
+ if (char === "," && !inQuotes) {
40473
+ values.push(current.trim());
40474
+ current = "";
40475
+ continue;
40476
+ }
40477
+ current += char;
40459
40478
  }
40460
- if (!rawJson) {
40461
- throw new UserInputError("JSON input is required. Provide --data or pipe JSON via stdin.");
40479
+ if (inQuotes) {
40480
+ throw new UserInputError("Invalid CSV. Unterminated quoted value detected.");
40462
40481
  }
40463
- const parsed = parseJsonData(rawJson, data ? "--data" : "stdin");
40464
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
40465
- throw new UserInputError("Expected a JSON object.");
40482
+ values.push(current.trim());
40483
+ return values;
40484
+ }
40485
+ function parseCsvData(raw, source) {
40486
+ const trimmed = raw.trim();
40487
+ if (!trimmed) {
40488
+ throw new UserInputError(`CSV input is empty in ${source}.`);
40489
+ }
40490
+ const lines = trimmed.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
40491
+ if (lines.length < 2) {
40492
+ throw new UserInputError(`CSV input in ${source} must include a header row and at least one data row.`);
40493
+ }
40494
+ const header = parseCsvRow(lines[0]);
40495
+ if (header.length === 0 || header.some((name) => name.length === 0)) {
40496
+ throw new UserInputError(`CSV header in ${source} contains empty column names.`);
40497
+ }
40498
+ const records = [];
40499
+ for (let rowIndex = 1; rowIndex < lines.length; rowIndex += 1) {
40500
+ const rowValues = parseCsvRow(lines[rowIndex]);
40501
+ if (rowValues.length !== header.length) {
40502
+ throw new UserInputError(
40503
+ `CSV row ${rowIndex + 1} in ${source} has ${rowValues.length} values but header has ${header.length}.`
40504
+ );
40505
+ }
40506
+ const record2 = {};
40507
+ for (let columnIndex = 0; columnIndex < header.length; columnIndex += 1) {
40508
+ record2[header[columnIndex]] = rowValues[columnIndex];
40509
+ }
40510
+ records.push(record2);
40466
40511
  }
40467
- return parsed;
40512
+ return records;
40513
+ }
40514
+ async function readDataInput(params) {
40515
+ const {
40516
+ data,
40517
+ fromFile,
40518
+ readStdin = readStdinText,
40519
+ readFileFn = async (path3, encoding) => await (0, import_promises2.readFile)(path3, encoding)
40520
+ } = params;
40521
+ const trimmedData = data?.trim();
40522
+ const trimmedFromFile = fromFile?.trim();
40523
+ if (trimmedData && trimmedFromFile) {
40524
+ throw new UserInputError("Provide either --data or --from-file, not both.");
40525
+ }
40526
+ if (trimmedFromFile) {
40527
+ let content;
40528
+ try {
40529
+ content = await readFileFn(trimmedFromFile, "utf8");
40530
+ } catch (error48) {
40531
+ throw new UserInputError(`Unable to read file '${trimmedFromFile}'.`, {
40532
+ cause: error48
40533
+ });
40534
+ }
40535
+ const extension = (0, import_node_path4.extname)(trimmedFromFile).toLowerCase();
40536
+ if (extension === ".csv") {
40537
+ return parseCsvData(content, "--from-file");
40538
+ }
40539
+ return parseJsonData(content, "--from-file");
40540
+ }
40541
+ if (trimmedData) {
40542
+ return parseJsonData(trimmedData, "--data");
40543
+ }
40544
+ const stdinContent = (await readStdin()).trim();
40545
+ if (!stdinContent) {
40546
+ throw new UserInputError("JSON input is required. Provide --data, --from-file, or pipe JSON via stdin.");
40547
+ }
40548
+ return parseJsonData(stdinContent, "stdin");
40468
40549
  }
40469
40550
 
40470
40551
  // src/commands/curlCommand.ts
@@ -40610,6 +40691,42 @@ function createCurlCli(dependencies) {
40610
40691
  }
40611
40692
 
40612
40693
  // src/commands/dsCommands.ts
40694
+ var BULK_OPERATION_LIMIT = 50;
40695
+ function isRecord(value) {
40696
+ return typeof value === "object" && value !== null && !Array.isArray(value);
40697
+ }
40698
+ function toBulkPayload(payload, mode) {
40699
+ if (!Array.isArray(payload)) {
40700
+ return null;
40701
+ }
40702
+ if (payload.length === 0) {
40703
+ throw new UserInputError("Batch payload cannot be empty.");
40704
+ }
40705
+ if (payload.length > BULK_OPERATION_LIMIT) {
40706
+ throw new UserInputError(`Batch payload cannot exceed ${BULK_OPERATION_LIMIT} items.`);
40707
+ }
40708
+ const records = [];
40709
+ for (let index = 0; index < payload.length; index += 1) {
40710
+ const item = payload[index];
40711
+ if (!isRecord(item)) {
40712
+ throw new UserInputError(`Batch item at index ${index} must be a JSON object.`);
40713
+ }
40714
+ if (mode === "update") {
40715
+ const itemId = item.id;
40716
+ if (itemId === void 0 || itemId === null || typeof itemId === "string" && itemId.trim().length === 0) {
40717
+ throw new UserInputError(`Batch update item at index ${index} is missing required 'id'.`);
40718
+ }
40719
+ }
40720
+ records.push(item);
40721
+ }
40722
+ return records;
40723
+ }
40724
+ function toSinglePayload(payload) {
40725
+ if (!isRecord(payload)) {
40726
+ throw new UserInputError("Expected a JSON object for single-item operation.");
40727
+ }
40728
+ return payload;
40729
+ }
40613
40730
  function createDsCli(dependencies) {
40614
40731
  const dsCli = Cli_exports.create("ds", {
40615
40732
  description: "Data source commands",
@@ -40683,16 +40800,25 @@ function createDsCli(dependencies) {
40683
40800
  dataSourceSlug: external_exports.string().min(1)
40684
40801
  }),
40685
40802
  options: external_exports.object({
40686
- data: external_exports.string().optional().describe("JSON payload for record fields")
40803
+ data: external_exports.string().optional().describe("JSON payload for record fields"),
40804
+ fromFile: external_exports.string().optional().describe("Path to JSON or CSV payload file")
40687
40805
  }),
40688
40806
  run: async (context) => {
40689
40807
  const apiBaseUrl = await dependencies.environmentConfigService.getActiveApiBaseUrl();
40690
40808
  const apiClient = dependencies.createApiClient(apiBaseUrl);
40691
- const payload = await readJsonInput({ data: context.options.data });
40692
- const response = await apiClient.request({
40809
+ const payload = await readDataInput({
40810
+ data: context.options.data,
40811
+ fromFile: context.options.fromFile
40812
+ });
40813
+ const bulkPayload = toBulkPayload(payload, "create");
40814
+ const response = bulkPayload ? await apiClient.request({
40815
+ method: "POST",
40816
+ path: `/apps/${context.args.appSlug}/data-sources/${context.args.dataSourceSlug}/items/bulk`,
40817
+ body: bulkPayload
40818
+ }) : await apiClient.request({
40693
40819
  method: "POST",
40694
40820
  path: `/apps/${context.args.appSlug}/data-sources/${context.args.dataSourceSlug}/items`,
40695
- body: payload
40821
+ body: toSinglePayload(payload)
40696
40822
  });
40697
40823
  return await injectContext({
40698
40824
  apiBaseUrl,
@@ -40706,20 +40832,37 @@ function createDsCli(dependencies) {
40706
40832
  args: external_exports.object({
40707
40833
  appSlug: external_exports.string().min(1),
40708
40834
  dataSourceSlug: external_exports.string().min(1),
40709
- recordId: external_exports.string().min(1)
40835
+ recordId: external_exports.string().min(1).optional()
40710
40836
  }),
40711
40837
  options: external_exports.object({
40712
- data: external_exports.string().optional().describe("JSON payload for record fields")
40838
+ data: external_exports.string().optional().describe("JSON payload for record fields"),
40839
+ fromFile: external_exports.string().optional().describe("Path to JSON or CSV payload file")
40713
40840
  }),
40714
40841
  run: async (context) => {
40715
40842
  const apiBaseUrl = await dependencies.environmentConfigService.getActiveApiBaseUrl();
40716
40843
  const apiClient = dependencies.createApiClient(apiBaseUrl);
40717
- const payload = await readJsonInput({ data: context.options.data });
40718
- const response = await apiClient.request({
40719
- method: "PATCH",
40720
- path: `/apps/${context.args.appSlug}/data-sources/${context.args.dataSourceSlug}/items/${context.args.recordId}`,
40721
- body: payload
40844
+ const payload = await readDataInput({
40845
+ data: context.options.data,
40846
+ fromFile: context.options.fromFile
40722
40847
  });
40848
+ const bulkPayload = toBulkPayload(payload, "update");
40849
+ if (bulkPayload && context.args.recordId) {
40850
+ throw new UserInputError("Do not provide recordId for batch update. Include 'id' in each item instead.");
40851
+ }
40852
+ const response = bulkPayload ? await apiClient.request({
40853
+ method: "PATCH",
40854
+ path: `/apps/${context.args.appSlug}/data-sources/${context.args.dataSourceSlug}/items/bulk`,
40855
+ body: bulkPayload
40856
+ }) : await (() => {
40857
+ if (!context.args.recordId) {
40858
+ throw new UserInputError("recordId is required for single-item update.");
40859
+ }
40860
+ return apiClient.request({
40861
+ method: "PATCH",
40862
+ path: `/apps/${context.args.appSlug}/data-sources/${context.args.dataSourceSlug}/items/${context.args.recordId}`,
40863
+ body: toSinglePayload(payload)
40864
+ });
40865
+ })();
40723
40866
  return await injectContext({
40724
40867
  apiBaseUrl,
40725
40868
  authStore: dependencies.authStore,
@@ -40752,6 +40895,185 @@ function createDsCli(dependencies) {
40752
40895
  }
40753
40896
 
40754
40897
  // src/commands/discoverCommands.ts
40898
+ var import_promises3 = require("node:fs/promises");
40899
+ var SUPPORTED_HTTP_METHODS2 = [
40900
+ "GET",
40901
+ "POST",
40902
+ "PUT",
40903
+ "PATCH",
40904
+ "DELETE",
40905
+ "HEAD",
40906
+ "OPTIONS",
40907
+ "TRACE"
40908
+ ];
40909
+ function isRecord2(value) {
40910
+ return typeof value === "object" && value !== null;
40911
+ }
40912
+ function isEnoentError(error48) {
40913
+ return typeof error48 === "object" && error48 !== null && "code" in error48 && error48.code === "ENOENT";
40914
+ }
40915
+ function normalizePathInput(pathValue) {
40916
+ const trimmed = pathValue.trim();
40917
+ if (!trimmed) {
40918
+ throw new UserInputError("Path is required.");
40919
+ }
40920
+ return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
40921
+ }
40922
+ function buildPathCandidates(pathValue) {
40923
+ const normalized = normalizePathInput(pathValue);
40924
+ const candidates = /* @__PURE__ */ new Set([normalized]);
40925
+ if (normalized === "/v1") {
40926
+ candidates.add("/");
40927
+ } else if (normalized.startsWith("/v1/")) {
40928
+ candidates.add(normalized.substring(3));
40929
+ } else {
40930
+ candidates.add(`/v1${normalized}`);
40931
+ }
40932
+ return Array.from(candidates.values());
40933
+ }
40934
+ function toOutputPath(pathValue) {
40935
+ const normalized = normalizePathInput(pathValue);
40936
+ if (normalized === "/") {
40937
+ return "/v1";
40938
+ }
40939
+ if (normalized.startsWith("/v1/") || normalized === "/v1") {
40940
+ return normalized;
40941
+ }
40942
+ return `/v1${normalized}`;
40943
+ }
40944
+ function parseEndpointSelector(input) {
40945
+ const trimmed = input.trim();
40946
+ if (!trimmed) {
40947
+ throw new UserInputError("Endpoint selector is required.");
40948
+ }
40949
+ const methodPathMatch = trimmed.match(/^\[([a-z]+)\](\/.*)$/i);
40950
+ if (!methodPathMatch) {
40951
+ return {
40952
+ path: trimmed,
40953
+ method: "GET"
40954
+ };
40955
+ }
40956
+ return {
40957
+ method: methodPathMatch[1].toUpperCase(),
40958
+ path: methodPathMatch[2]
40959
+ };
40960
+ }
40961
+ function getPaths(document) {
40962
+ const rawPaths = document.paths;
40963
+ return isRecord2(rawPaths) ? rawPaths : {};
40964
+ }
40965
+ function getEntities(document) {
40966
+ const components = isRecord2(document.components) ? document.components : null;
40967
+ if (components && isRecord2(components.schemas)) {
40968
+ return components.schemas;
40969
+ }
40970
+ const definitions = document.definitions;
40971
+ if (isRecord2(definitions)) {
40972
+ return definitions;
40973
+ }
40974
+ return {};
40975
+ }
40976
+ function extractNamespaces(paths) {
40977
+ const namespaces = /* @__PURE__ */ new Set();
40978
+ for (const path3 of Object.keys(paths)) {
40979
+ const segments = path3.split("/").map((item) => item.trim()).filter((item) => item.length > 0);
40980
+ if (segments.length === 0) {
40981
+ continue;
40982
+ }
40983
+ const namespace = segments[0] === "v1" ? segments[1] : segments[0];
40984
+ if (namespace) {
40985
+ namespaces.add(`/v1/${namespace}`);
40986
+ }
40987
+ }
40988
+ return Array.from(namespaces.values()).sort((left, right) => left.localeCompare(right));
40989
+ }
40990
+ function getPathOperation(pathItem, method) {
40991
+ if (!isRecord2(pathItem)) {
40992
+ return null;
40993
+ }
40994
+ const normalizedMethod = method.toLowerCase();
40995
+ const operation = pathItem[normalizedMethod];
40996
+ return isRecord2(operation) ? operation : null;
40997
+ }
40998
+ function toEndpointMethodEntries(paths, pathFilter) {
40999
+ const entries = [];
41000
+ const sortedPaths = Object.keys(paths).filter(pathFilter).sort((left, right) => left.localeCompare(right));
41001
+ for (const path3 of sortedPaths) {
41002
+ const pathItem = paths[path3];
41003
+ if (!isRecord2(pathItem)) {
41004
+ continue;
41005
+ }
41006
+ for (const method of SUPPORTED_HTTP_METHODS2) {
41007
+ const operation = getPathOperation(pathItem, method);
41008
+ if (!operation) {
41009
+ continue;
41010
+ }
41011
+ const descriptionValue = operation.description;
41012
+ const summaryValue = operation.summary;
41013
+ const description = typeof descriptionValue === "string" ? descriptionValue : typeof summaryValue === "string" ? summaryValue : void 0;
41014
+ entries.push({
41015
+ path: toOutputPath(path3),
41016
+ method,
41017
+ description
41018
+ });
41019
+ }
41020
+ }
41021
+ return entries;
41022
+ }
41023
+ function parseOpenApiDocument(raw) {
41024
+ let parsed;
41025
+ try {
41026
+ parsed = JSON.parse(raw);
41027
+ } catch (error48) {
41028
+ throw new UserInputError("Stored OpenAPI spec is invalid JSON. Run 'docyrus discover api' to re-download.", {
41029
+ cause: error48
41030
+ });
41031
+ }
41032
+ if (!isRecord2(parsed)) {
41033
+ throw new UserInputError("Stored OpenAPI spec must be a JSON object. Run 'docyrus discover api' to re-download.");
41034
+ }
41035
+ return parsed;
41036
+ }
41037
+ async function loadOpenApiSpec(dependencies, tenantId) {
41038
+ let filePath = dependencies.tenantOpenApiService.getTenantOpenApiFilePath(tenantId);
41039
+ let downloaded = false;
41040
+ let sourceUrl;
41041
+ let content;
41042
+ try {
41043
+ content = await (0, import_promises3.readFile)(filePath, "utf8");
41044
+ } catch (error48) {
41045
+ if (!isEnoentError(error48)) {
41046
+ throw new UserInputError("Failed to read downloaded OpenAPI spec file.", {
41047
+ filePath,
41048
+ cause: error48
41049
+ });
41050
+ }
41051
+ const downloadResult = await dependencies.tenantOpenApiService.downloadTenantOpenApi(tenantId);
41052
+ downloaded = true;
41053
+ sourceUrl = downloadResult.sourceUrl;
41054
+ filePath = downloadResult.filePath;
41055
+ content = await (0, import_promises3.readFile)(filePath, "utf8");
41056
+ }
41057
+ return {
41058
+ filePath,
41059
+ downloaded,
41060
+ sourceUrl,
41061
+ document: parseOpenApiDocument(content)
41062
+ };
41063
+ }
41064
+ async function withActiveTenantSpec(dependencies) {
41065
+ const apiBaseUrl = await dependencies.environmentConfigService.getActiveApiBaseUrl();
41066
+ const activeProfile = await dependencies.authStore.getActiveProfile(apiBaseUrl);
41067
+ if (!activeProfile) {
41068
+ throw new AuthSessionError("No active session found. Run 'docyrus auth login'.");
41069
+ }
41070
+ const spec = await loadOpenApiSpec(dependencies, activeProfile.tenantId);
41071
+ return {
41072
+ apiBaseUrl,
41073
+ activeProfile,
41074
+ spec
41075
+ };
41076
+ }
40755
41077
  function createDiscoverCli(dependencies) {
40756
41078
  const discoverCli = Cli_exports.create("discover", {
40757
41079
  description: "Discovery commands",
@@ -40778,6 +41100,154 @@ function createDiscoverCli(dependencies) {
40778
41100
  });
40779
41101
  }
40780
41102
  });
41103
+ discoverCli.command("namespaces", {
41104
+ description: "List API namespaces from active tenant OpenAPI spec",
41105
+ run: async () => {
41106
+ const { apiBaseUrl, spec } = await withActiveTenantSpec(dependencies);
41107
+ const namespaces = extractNamespaces(getPaths(spec.document));
41108
+ return await injectContext({
41109
+ apiBaseUrl,
41110
+ authStore: dependencies.authStore,
41111
+ payload: {
41112
+ namespaces,
41113
+ count: namespaces.length,
41114
+ specFilePath: spec.filePath,
41115
+ downloaded: spec.downloaded,
41116
+ sourceUrl: spec.sourceUrl
41117
+ }
41118
+ });
41119
+ }
41120
+ });
41121
+ discoverCli.command("path", {
41122
+ description: "List endpoints with method and description for matching path prefix",
41123
+ args: external_exports.object({
41124
+ prefix: external_exports.string().min(1).describe("Path prefix, e.g. /v1/users")
41125
+ }),
41126
+ run: async (context) => {
41127
+ const { apiBaseUrl, spec } = await withActiveTenantSpec(dependencies);
41128
+ const paths = getPaths(spec.document);
41129
+ const candidates = buildPathCandidates(context.args.prefix);
41130
+ const endpoints = toEndpointMethodEntries(
41131
+ paths,
41132
+ (path3) => candidates.some((candidate) => path3.startsWith(candidate))
41133
+ );
41134
+ return await injectContext({
41135
+ apiBaseUrl,
41136
+ authStore: dependencies.authStore,
41137
+ payload: {
41138
+ prefix: context.args.prefix,
41139
+ normalizedPrefixes: candidates,
41140
+ endpoints,
41141
+ count: endpoints.length,
41142
+ specFilePath: spec.filePath,
41143
+ downloaded: spec.downloaded,
41144
+ sourceUrl: spec.sourceUrl
41145
+ }
41146
+ });
41147
+ }
41148
+ });
41149
+ discoverCli.command("endpoint", {
41150
+ description: "Return full endpoint object for a path and HTTP method",
41151
+ args: external_exports.object({
41152
+ selector: external_exports.string().min(1).describe("Endpoint selector, e.g. /v1/users/me or [PUT]/v1/users/me/photo")
41153
+ }),
41154
+ run: async (context) => {
41155
+ const { apiBaseUrl, spec } = await withActiveTenantSpec(dependencies);
41156
+ const paths = getPaths(spec.document);
41157
+ const selector = parseEndpointSelector(context.args.selector);
41158
+ const candidates = buildPathCandidates(selector.path);
41159
+ const resolvedPath = candidates.find((candidate) => candidate in paths);
41160
+ if (!resolvedPath) {
41161
+ throw new UserInputError(`Endpoint '${selector.path}' was not found in downloaded OpenAPI spec.`);
41162
+ }
41163
+ if (!SUPPORTED_HTTP_METHODS2.includes(selector.method)) {
41164
+ throw new UserInputError(`Unsupported HTTP method '${selector.method}'.`);
41165
+ }
41166
+ const operation = getPathOperation(paths[resolvedPath], selector.method);
41167
+ if (!operation) {
41168
+ throw new UserInputError(
41169
+ `Method '${selector.method}' is not available for endpoint '${toOutputPath(resolvedPath)}'.`
41170
+ );
41171
+ }
41172
+ return await injectContext({
41173
+ apiBaseUrl,
41174
+ authStore: dependencies.authStore,
41175
+ payload: {
41176
+ requestedSelector: context.args.selector,
41177
+ method: selector.method,
41178
+ resolvedPath: toOutputPath(resolvedPath),
41179
+ endpoint: operation,
41180
+ specFilePath: spec.filePath,
41181
+ downloaded: spec.downloaded,
41182
+ sourceUrl: spec.sourceUrl
41183
+ }
41184
+ });
41185
+ }
41186
+ });
41187
+ discoverCli.command("entity", {
41188
+ description: "Return full entity object by name",
41189
+ args: external_exports.object({
41190
+ name: external_exports.string().min(1).describe("Entity name, e.g. UserEntity")
41191
+ }),
41192
+ run: async (context) => {
41193
+ const { apiBaseUrl, spec } = await withActiveTenantSpec(dependencies);
41194
+ const entities = getEntities(spec.document);
41195
+ const requestedName = context.args.name.trim();
41196
+ if (!(requestedName in entities)) {
41197
+ throw new UserInputError(`Entity '${context.args.name}' was not found in downloaded OpenAPI spec.`);
41198
+ }
41199
+ return await injectContext({
41200
+ apiBaseUrl,
41201
+ authStore: dependencies.authStore,
41202
+ payload: {
41203
+ entityName: requestedName,
41204
+ entity: entities[requestedName],
41205
+ specFilePath: spec.filePath,
41206
+ downloaded: spec.downloaded,
41207
+ sourceUrl: spec.sourceUrl
41208
+ }
41209
+ });
41210
+ }
41211
+ });
41212
+ discoverCli.command("search", {
41213
+ description: "Search endpoint paths and entity names (endpoint results include method and description)",
41214
+ args: external_exports.object({
41215
+ query: external_exports.string().min(1).describe("One or more comma-separated search strings")
41216
+ }),
41217
+ run: async (context) => {
41218
+ const { apiBaseUrl, spec } = await withActiveTenantSpec(dependencies);
41219
+ const terms = context.args.query.split(",").map((term) => term.trim()).filter((term) => term.length > 0);
41220
+ if (terms.length === 0) {
41221
+ throw new UserInputError("Provide at least one non-empty search term.");
41222
+ }
41223
+ const entityNames = Object.keys(getEntities(spec.document));
41224
+ const pathMap = getPaths(spec.document);
41225
+ const results = terms.map((term) => {
41226
+ const loweredTerm = term.toLowerCase();
41227
+ const matchingEndpoints = toEndpointMethodEntries(
41228
+ pathMap,
41229
+ (path3) => path3.toLowerCase().includes(loweredTerm)
41230
+ );
41231
+ const matchingEntities = entityNames.filter((entityName) => entityName.toLowerCase().includes(loweredTerm));
41232
+ return {
41233
+ term,
41234
+ endpoints: matchingEndpoints,
41235
+ entityNames: matchingEntities.sort((left, right) => left.localeCompare(right))
41236
+ };
41237
+ });
41238
+ return await injectContext({
41239
+ apiBaseUrl,
41240
+ authStore: dependencies.authStore,
41241
+ payload: {
41242
+ terms,
41243
+ results,
41244
+ specFilePath: spec.filePath,
41245
+ downloaded: spec.downloaded,
41246
+ sourceUrl: spec.sourceUrl
41247
+ }
41248
+ });
41249
+ }
41250
+ });
40781
41251
  return discoverCli;
40782
41252
  }
40783
41253
 
@@ -40819,6 +41289,952 @@ function createEnvCli(dependencies) {
40819
41289
  return envCli;
40820
41290
  }
40821
41291
 
41292
+ // src/services/studioPayload.ts
41293
+ var import_promises4 = require("node:fs/promises");
41294
+ var import_node_path5 = require("node:path");
41295
+ async function readStdinText2() {
41296
+ if (process.stdin.isTTY) {
41297
+ return "";
41298
+ }
41299
+ const chunks = [];
41300
+ for await (const chunk of process.stdin) {
41301
+ if (typeof chunk === "string") {
41302
+ chunks.push(Buffer.from(chunk));
41303
+ } else {
41304
+ chunks.push(Buffer.from(chunk));
41305
+ }
41306
+ }
41307
+ return Buffer.concat(chunks).toString("utf8").trim();
41308
+ }
41309
+ function isRecord3(value) {
41310
+ return typeof value === "object" && value !== null && !Array.isArray(value);
41311
+ }
41312
+ async function readStudioWriteInput(params) {
41313
+ const {
41314
+ data,
41315
+ fromFile,
41316
+ readStdin = readStdinText2,
41317
+ readFileFn = async (path3, encoding) => await (0, import_promises4.readFile)(path3, encoding)
41318
+ } = params;
41319
+ const trimmedData = data?.trim();
41320
+ const trimmedFromFile = fromFile?.trim();
41321
+ if (trimmedData && trimmedFromFile) {
41322
+ throw new UserInputError("Provide either --data or --from-file, not both.");
41323
+ }
41324
+ if (trimmedFromFile) {
41325
+ const extension = (0, import_node_path5.extname)(trimmedFromFile).toLowerCase();
41326
+ if (extension && extension !== ".json") {
41327
+ throw new UserInputError("Studio commands support only JSON files in --from-file.");
41328
+ }
41329
+ let content;
41330
+ try {
41331
+ content = await readFileFn(trimmedFromFile, "utf8");
41332
+ } catch (error48) {
41333
+ throw new UserInputError(`Unable to read file '${trimmedFromFile}'.`, {
41334
+ cause: error48
41335
+ });
41336
+ }
41337
+ return parseJsonData(content, "--from-file");
41338
+ }
41339
+ if (trimmedData) {
41340
+ return parseJsonData(trimmedData, "--data");
41341
+ }
41342
+ const stdinContent = (await readStdin()).trim();
41343
+ if (!stdinContent) {
41344
+ return {};
41345
+ }
41346
+ return parseJsonData(stdinContent, "stdin");
41347
+ }
41348
+ function ensureObjectPayload(payload, label) {
41349
+ if (!isRecord3(payload)) {
41350
+ throw new UserInputError(`${label} expects a JSON object payload.`);
41351
+ }
41352
+ return payload;
41353
+ }
41354
+ function mergeObjectWithFlags(basePayload, overrides) {
41355
+ const merged = {
41356
+ ...basePayload
41357
+ };
41358
+ for (const [key, value] of Object.entries(overrides)) {
41359
+ if (value === void 0) {
41360
+ continue;
41361
+ }
41362
+ merged[key] = value;
41363
+ }
41364
+ return merged;
41365
+ }
41366
+ function normalizeBatchPayload(payload, key) {
41367
+ if (Array.isArray(payload)) {
41368
+ return {
41369
+ [key]: payload
41370
+ };
41371
+ }
41372
+ if (isRecord3(payload)) {
41373
+ if (key in payload) {
41374
+ return payload;
41375
+ }
41376
+ if (Object.keys(payload).length === 0) {
41377
+ throw new UserInputError(`Batch payload is required. Provide --data/--from-file with '${key}'.`);
41378
+ }
41379
+ }
41380
+ throw new UserInputError(`Batch payload must be an array or object containing '${key}'.`);
41381
+ }
41382
+
41383
+ // src/services/studioResolver.ts
41384
+ function isRecord4(value) {
41385
+ return typeof value === "object" && value !== null;
41386
+ }
41387
+ function extractString(record2, key) {
41388
+ const value = record2[key];
41389
+ return typeof value === "string" && value.length > 0 ? value : void 0;
41390
+ }
41391
+ function extractArray(payload) {
41392
+ if (Array.isArray(payload)) {
41393
+ return payload.filter((item) => isRecord4(item));
41394
+ }
41395
+ if (isRecord4(payload) && Array.isArray(payload.data)) {
41396
+ return payload.data.filter((item) => isRecord4(item));
41397
+ }
41398
+ return [];
41399
+ }
41400
+ function ensureExclusiveSelector(label, idValue, slugValue) {
41401
+ if (idValue && slugValue) {
41402
+ throw new UserInputError(`Provide either --${label}Id or --${label}Slug, not both.`);
41403
+ }
41404
+ if (!idValue && !slugValue) {
41405
+ throw new UserInputError(`Provide --${label}Id or --${label}Slug.`);
41406
+ }
41407
+ }
41408
+ function normalizeOptional(value) {
41409
+ const trimmed = value?.trim();
41410
+ return trimmed && trimmed.length > 0 ? trimmed : void 0;
41411
+ }
41412
+ function resolveBySlug(label, items, slug) {
41413
+ const matches = items.filter((item) => extractString(item, "slug") === slug);
41414
+ if (matches.length === 0) {
41415
+ throw new UserInputError(`${label} slug '${slug}' was not found.`);
41416
+ }
41417
+ if (matches.length > 1) {
41418
+ const matchingIds = matches.map((item) => extractString(item, "id")).filter((id) => Boolean(id));
41419
+ throw new UserInputError(`${label} slug '${slug}' is ambiguous. Matching IDs: ${matchingIds.join(", ")}`);
41420
+ }
41421
+ const matchId = extractString(matches[0], "id");
41422
+ if (!matchId) {
41423
+ throw new UserInputError(`${label} slug '${slug}' resolved to an invalid item without id.`);
41424
+ }
41425
+ return matchId;
41426
+ }
41427
+ var StudioResolver = class {
41428
+ constructor(apiClient) {
41429
+ this.apiClient = apiClient;
41430
+ }
41431
+ #appsCache = /* @__PURE__ */ new Map();
41432
+ #dataSourcesByAppId = /* @__PURE__ */ new Map();
41433
+ #fieldsByAppAndDataSource = /* @__PURE__ */ new Map();
41434
+ async resolveAppId(options) {
41435
+ const appId = normalizeOptional(options.appId);
41436
+ const appSlug = normalizeOptional(options.appSlug);
41437
+ ensureExclusiveSelector("app", appId, appSlug);
41438
+ if (appId) {
41439
+ return appId;
41440
+ }
41441
+ const apps = await this.#getApps();
41442
+ return resolveBySlug("App", apps, appSlug);
41443
+ }
41444
+ async resolveDataSourceId(options) {
41445
+ const dataSourceId = normalizeOptional(options.dataSourceId);
41446
+ const dataSourceSlug = normalizeOptional(options.dataSourceSlug);
41447
+ ensureExclusiveSelector("dataSource", dataSourceId, dataSourceSlug);
41448
+ if (dataSourceId) {
41449
+ return dataSourceId;
41450
+ }
41451
+ const dataSources = await this.getDataSourcesForApp(options.appId);
41452
+ return resolveBySlug("Data source", dataSources, dataSourceSlug);
41453
+ }
41454
+ async resolveFieldId(options) {
41455
+ const fieldId = normalizeOptional(options.fieldId);
41456
+ const fieldSlug = normalizeOptional(options.fieldSlug);
41457
+ ensureExclusiveSelector("field", fieldId, fieldSlug);
41458
+ if (fieldId) {
41459
+ return fieldId;
41460
+ }
41461
+ const fields = await this.getFieldsForDataSource(options.appId, options.dataSourceId);
41462
+ return resolveBySlug("Field", fields, fieldSlug);
41463
+ }
41464
+ async getDataSourcesForApp(appId) {
41465
+ const cacheKey = appId;
41466
+ const cached2 = this.#dataSourcesByAppId.get(cacheKey);
41467
+ if (cached2) {
41468
+ return cached2;
41469
+ }
41470
+ const response = await this.apiClient.request({
41471
+ method: "GET",
41472
+ path: `/dev/apps/${appId}/data-sources`
41473
+ });
41474
+ const resolved = extractArray(response.data);
41475
+ this.#dataSourcesByAppId.set(cacheKey, resolved);
41476
+ return resolved;
41477
+ }
41478
+ async getFieldsForDataSource(appId, dataSourceId) {
41479
+ const cacheKey = `${appId}::${dataSourceId}`;
41480
+ const cached2 = this.#fieldsByAppAndDataSource.get(cacheKey);
41481
+ if (cached2) {
41482
+ return cached2;
41483
+ }
41484
+ const response = await this.apiClient.request({
41485
+ method: "GET",
41486
+ path: `/dev/apps/${appId}/data-sources/${dataSourceId}/fields`
41487
+ });
41488
+ const resolved = extractArray(response.data);
41489
+ this.#fieldsByAppAndDataSource.set(cacheKey, resolved);
41490
+ return resolved;
41491
+ }
41492
+ async #getApps() {
41493
+ const cacheKey = "all";
41494
+ const cached2 = this.#appsCache.get(cacheKey);
41495
+ if (cached2) {
41496
+ return cached2;
41497
+ }
41498
+ const response = await this.apiClient.request({
41499
+ method: "GET",
41500
+ path: "/dev/apps"
41501
+ });
41502
+ const resolved = extractArray(response.data);
41503
+ this.#appsCache.set(cacheKey, resolved);
41504
+ return resolved;
41505
+ }
41506
+ };
41507
+
41508
+ // src/commands/studioCommands.ts
41509
+ function parseOptionalJsonFlag(raw, source) {
41510
+ if (!raw || raw.trim().length === 0) {
41511
+ return void 0;
41512
+ }
41513
+ return parseJsonData(raw, source);
41514
+ }
41515
+ function wrapStudioPayload(apiBaseUrl, dependencies, payload) {
41516
+ return injectContext({
41517
+ apiBaseUrl,
41518
+ authStore: dependencies.authStore,
41519
+ payload
41520
+ });
41521
+ }
41522
+ async function getStudioRunContext(dependencies) {
41523
+ const apiBaseUrl = await dependencies.environmentConfigService.getActiveApiBaseUrl();
41524
+ const apiClient = dependencies.createApiClient(apiBaseUrl);
41525
+ const resolver = new StudioResolver(apiClient);
41526
+ return {
41527
+ apiBaseUrl,
41528
+ apiClient,
41529
+ resolver
41530
+ };
41531
+ }
41532
+ function dataSourceFlags(options) {
41533
+ return {
41534
+ title: options.title,
41535
+ name: options.name,
41536
+ slug: options.slug,
41537
+ type: options.type,
41538
+ icon: options.icon,
41539
+ data_sharing: options.dataSharing,
41540
+ meta: parseOptionalJsonFlag(options.meta, "--meta")
41541
+ };
41542
+ }
41543
+ function fieldFlags(options) {
41544
+ return {
41545
+ name: options.name,
41546
+ slug: options.slug,
41547
+ type: options.type,
41548
+ read_only: options.readOnly,
41549
+ status: options.status,
41550
+ default_value: options.defaultValue,
41551
+ relation_data_source_id: options.relationDataSourceId,
41552
+ sort_order: options.sortOrder,
41553
+ tenant_enum_set_id: options.tenantEnumSetId,
41554
+ options: parseOptionalJsonFlag(options.options, "--options"),
41555
+ validations: parseOptionalJsonFlag(options.validations, "--validations")
41556
+ };
41557
+ }
41558
+ function requireNonEmptyObject(payload, label) {
41559
+ if (Object.keys(payload).length === 0) {
41560
+ throw new UserInputError(`${label} payload is empty. Provide flags, --data, or --from-file.`);
41561
+ }
41562
+ }
41563
+ function createStudioCli(dependencies) {
41564
+ const studioCli = Cli_exports.create("studio", {
41565
+ description: "Studio (dev app data source CRUD) commands",
41566
+ env: EnvSchema
41567
+ });
41568
+ studioCli.command("list-data-sources", {
41569
+ description: "List data sources for an app",
41570
+ options: external_exports.object({
41571
+ appId: external_exports.string().optional().describe("App ID"),
41572
+ appSlug: external_exports.string().optional().describe("App slug"),
41573
+ expand: external_exports.string().optional().describe("Optional comma-separated expansions, e.g. fields")
41574
+ }),
41575
+ run: async (context) => {
41576
+ const studio = await getStudioRunContext(dependencies);
41577
+ const appId = await studio.resolver.resolveAppId({
41578
+ appId: context.options.appId,
41579
+ appSlug: context.options.appSlug
41580
+ });
41581
+ const response = await studio.apiClient.request({
41582
+ method: "GET",
41583
+ path: `/dev/apps/${appId}/data-sources`,
41584
+ query: {
41585
+ expand: context.options.expand
41586
+ }
41587
+ });
41588
+ return await wrapStudioPayload(studio.apiBaseUrl, dependencies, response.data);
41589
+ }
41590
+ });
41591
+ studioCli.command("get-data-source", {
41592
+ description: "Get a single data source",
41593
+ options: external_exports.object({
41594
+ appId: external_exports.string().optional().describe("App ID"),
41595
+ appSlug: external_exports.string().optional().describe("App slug"),
41596
+ dataSourceId: external_exports.string().optional().describe("Data source ID"),
41597
+ dataSourceSlug: external_exports.string().optional().describe("Data source slug")
41598
+ }),
41599
+ run: async (context) => {
41600
+ const studio = await getStudioRunContext(dependencies);
41601
+ const appId = await studio.resolver.resolveAppId({
41602
+ appId: context.options.appId,
41603
+ appSlug: context.options.appSlug
41604
+ });
41605
+ const dataSourceId = await studio.resolver.resolveDataSourceId({
41606
+ appId,
41607
+ dataSourceId: context.options.dataSourceId,
41608
+ dataSourceSlug: context.options.dataSourceSlug
41609
+ });
41610
+ const response = await studio.apiClient.request({
41611
+ method: "GET",
41612
+ path: `/dev/apps/${appId}/data-sources/${dataSourceId}`
41613
+ });
41614
+ return await wrapStudioPayload(studio.apiBaseUrl, dependencies, response.data);
41615
+ }
41616
+ });
41617
+ studioCli.command("create-data-source", {
41618
+ description: "Create a data source",
41619
+ options: external_exports.object({
41620
+ appId: external_exports.string().optional().describe("App ID"),
41621
+ appSlug: external_exports.string().optional().describe("App slug"),
41622
+ data: external_exports.string().optional().describe("JSON payload"),
41623
+ fromFile: external_exports.string().optional().describe("Path to JSON payload file"),
41624
+ title: external_exports.string().optional().describe("Data source title"),
41625
+ name: external_exports.string().optional().describe("Data source name"),
41626
+ slug: external_exports.string().optional().describe("Data source slug"),
41627
+ type: external_exports.string().optional().describe("Data source type"),
41628
+ icon: external_exports.string().optional().describe("Icon"),
41629
+ dataSharing: external_exports.string().optional().describe("Data sharing value"),
41630
+ meta: external_exports.string().optional().describe("JSON meta payload")
41631
+ }),
41632
+ run: async (context) => {
41633
+ const studio = await getStudioRunContext(dependencies);
41634
+ const appId = await studio.resolver.resolveAppId({
41635
+ appId: context.options.appId,
41636
+ appSlug: context.options.appSlug
41637
+ });
41638
+ const basePayload = ensureObjectPayload(
41639
+ await readStudioWriteInput({
41640
+ data: context.options.data,
41641
+ fromFile: context.options.fromFile
41642
+ }),
41643
+ "create-data-source"
41644
+ );
41645
+ const payload = mergeObjectWithFlags(basePayload, dataSourceFlags(context.options));
41646
+ requireNonEmptyObject(payload, "create-data-source");
41647
+ const response = await studio.apiClient.request({
41648
+ method: "POST",
41649
+ path: `/dev/apps/${appId}/data-sources`,
41650
+ body: payload
41651
+ });
41652
+ return await wrapStudioPayload(studio.apiBaseUrl, dependencies, response.data);
41653
+ }
41654
+ });
41655
+ studioCli.command("update-data-source", {
41656
+ description: "Update a data source",
41657
+ options: external_exports.object({
41658
+ appId: external_exports.string().optional().describe("App ID"),
41659
+ appSlug: external_exports.string().optional().describe("App slug"),
41660
+ dataSourceId: external_exports.string().optional().describe("Data source ID"),
41661
+ dataSourceSlug: external_exports.string().optional().describe("Data source slug"),
41662
+ data: external_exports.string().optional().describe("JSON payload"),
41663
+ fromFile: external_exports.string().optional().describe("Path to JSON payload file"),
41664
+ title: external_exports.string().optional().describe("Data source title"),
41665
+ name: external_exports.string().optional().describe("Data source name"),
41666
+ slug: external_exports.string().optional().describe("Data source slug"),
41667
+ type: external_exports.string().optional().describe("Data source type"),
41668
+ icon: external_exports.string().optional().describe("Icon"),
41669
+ dataSharing: external_exports.string().optional().describe("Data sharing value"),
41670
+ meta: external_exports.string().optional().describe("JSON meta payload")
41671
+ }),
41672
+ run: async (context) => {
41673
+ const studio = await getStudioRunContext(dependencies);
41674
+ const appId = await studio.resolver.resolveAppId({
41675
+ appId: context.options.appId,
41676
+ appSlug: context.options.appSlug
41677
+ });
41678
+ const dataSourceId = await studio.resolver.resolveDataSourceId({
41679
+ appId,
41680
+ dataSourceId: context.options.dataSourceId,
41681
+ dataSourceSlug: context.options.dataSourceSlug
41682
+ });
41683
+ const basePayload = ensureObjectPayload(
41684
+ await readStudioWriteInput({
41685
+ data: context.options.data,
41686
+ fromFile: context.options.fromFile
41687
+ }),
41688
+ "update-data-source"
41689
+ );
41690
+ const payload = mergeObjectWithFlags(basePayload, dataSourceFlags(context.options));
41691
+ requireNonEmptyObject(payload, "update-data-source");
41692
+ const response = await studio.apiClient.request({
41693
+ method: "PATCH",
41694
+ path: `/dev/apps/${appId}/data-sources/${dataSourceId}`,
41695
+ body: payload
41696
+ });
41697
+ return await wrapStudioPayload(studio.apiBaseUrl, dependencies, response.data);
41698
+ }
41699
+ });
41700
+ studioCli.command("delete-data-source", {
41701
+ description: "Delete a data source",
41702
+ options: external_exports.object({
41703
+ appId: external_exports.string().optional().describe("App ID"),
41704
+ appSlug: external_exports.string().optional().describe("App slug"),
41705
+ dataSourceId: external_exports.string().optional().describe("Data source ID"),
41706
+ dataSourceSlug: external_exports.string().optional().describe("Data source slug")
41707
+ }),
41708
+ run: async (context) => {
41709
+ const studio = await getStudioRunContext(dependencies);
41710
+ const appId = await studio.resolver.resolveAppId({
41711
+ appId: context.options.appId,
41712
+ appSlug: context.options.appSlug
41713
+ });
41714
+ const dataSourceId = await studio.resolver.resolveDataSourceId({
41715
+ appId,
41716
+ dataSourceId: context.options.dataSourceId,
41717
+ dataSourceSlug: context.options.dataSourceSlug
41718
+ });
41719
+ const response = await studio.apiClient.request({
41720
+ method: "DELETE",
41721
+ path: `/dev/apps/${appId}/data-sources/${dataSourceId}`
41722
+ });
41723
+ return await wrapStudioPayload(studio.apiBaseUrl, dependencies, response.data);
41724
+ }
41725
+ });
41726
+ studioCli.command("bulk-create-data-sources", {
41727
+ description: "Bulk create data sources",
41728
+ options: external_exports.object({
41729
+ appId: external_exports.string().optional().describe("App ID"),
41730
+ appSlug: external_exports.string().optional().describe("App slug"),
41731
+ data: external_exports.string().optional().describe("JSON payload"),
41732
+ fromFile: external_exports.string().optional().describe("Path to JSON payload file")
41733
+ }),
41734
+ run: async (context) => {
41735
+ const studio = await getStudioRunContext(dependencies);
41736
+ const appId = await studio.resolver.resolveAppId({
41737
+ appId: context.options.appId,
41738
+ appSlug: context.options.appSlug
41739
+ });
41740
+ const batchPayload = normalizeBatchPayload(
41741
+ await readStudioWriteInput({
41742
+ data: context.options.data,
41743
+ fromFile: context.options.fromFile
41744
+ }),
41745
+ "dataSources"
41746
+ );
41747
+ const response = await studio.apiClient.request({
41748
+ method: "POST",
41749
+ path: `/dev/apps/${appId}/data-sources/bulk`,
41750
+ body: batchPayload
41751
+ });
41752
+ return await wrapStudioPayload(studio.apiBaseUrl, dependencies, response.data);
41753
+ }
41754
+ });
41755
+ studioCli.command("list-fields", {
41756
+ description: "List fields for a data source",
41757
+ options: external_exports.object({
41758
+ appId: external_exports.string().optional().describe("App ID"),
41759
+ appSlug: external_exports.string().optional().describe("App slug"),
41760
+ dataSourceId: external_exports.string().optional().describe("Data source ID"),
41761
+ dataSourceSlug: external_exports.string().optional().describe("Data source slug")
41762
+ }),
41763
+ run: async (context) => {
41764
+ const studio = await getStudioRunContext(dependencies);
41765
+ const appId = await studio.resolver.resolveAppId({
41766
+ appId: context.options.appId,
41767
+ appSlug: context.options.appSlug
41768
+ });
41769
+ const dataSourceId = await studio.resolver.resolveDataSourceId({
41770
+ appId,
41771
+ dataSourceId: context.options.dataSourceId,
41772
+ dataSourceSlug: context.options.dataSourceSlug
41773
+ });
41774
+ const response = await studio.apiClient.request({
41775
+ method: "GET",
41776
+ path: `/dev/apps/${appId}/data-sources/${dataSourceId}/fields`
41777
+ });
41778
+ return await wrapStudioPayload(studio.apiBaseUrl, dependencies, response.data);
41779
+ }
41780
+ });
41781
+ studioCli.command("get-field", {
41782
+ description: "Get a single field",
41783
+ options: external_exports.object({
41784
+ appId: external_exports.string().optional().describe("App ID"),
41785
+ appSlug: external_exports.string().optional().describe("App slug"),
41786
+ dataSourceId: external_exports.string().optional().describe("Data source ID"),
41787
+ dataSourceSlug: external_exports.string().optional().describe("Data source slug"),
41788
+ fieldId: external_exports.string().optional().describe("Field ID"),
41789
+ fieldSlug: external_exports.string().optional().describe("Field slug")
41790
+ }),
41791
+ run: async (context) => {
41792
+ const studio = await getStudioRunContext(dependencies);
41793
+ const appId = await studio.resolver.resolveAppId({
41794
+ appId: context.options.appId,
41795
+ appSlug: context.options.appSlug
41796
+ });
41797
+ const dataSourceId = await studio.resolver.resolveDataSourceId({
41798
+ appId,
41799
+ dataSourceId: context.options.dataSourceId,
41800
+ dataSourceSlug: context.options.dataSourceSlug
41801
+ });
41802
+ const fieldId = await studio.resolver.resolveFieldId({
41803
+ appId,
41804
+ dataSourceId,
41805
+ fieldId: context.options.fieldId,
41806
+ fieldSlug: context.options.fieldSlug
41807
+ });
41808
+ const response = await studio.apiClient.request({
41809
+ method: "GET",
41810
+ path: `/dev/apps/${appId}/data-sources/${dataSourceId}/fields/${fieldId}`
41811
+ });
41812
+ return await wrapStudioPayload(studio.apiBaseUrl, dependencies, response.data);
41813
+ }
41814
+ });
41815
+ studioCli.command("create-field", {
41816
+ description: "Create a field",
41817
+ options: external_exports.object({
41818
+ appId: external_exports.string().optional().describe("App ID"),
41819
+ appSlug: external_exports.string().optional().describe("App slug"),
41820
+ dataSourceId: external_exports.string().optional().describe("Data source ID"),
41821
+ dataSourceSlug: external_exports.string().optional().describe("Data source slug"),
41822
+ data: external_exports.string().optional().describe("JSON payload"),
41823
+ fromFile: external_exports.string().optional().describe("Path to JSON payload file"),
41824
+ name: external_exports.string().optional().describe("Field name"),
41825
+ slug: external_exports.string().optional().describe("Field slug"),
41826
+ type: external_exports.string().optional().describe("Field type"),
41827
+ readOnly: external_exports.boolean().optional().describe("Field read only"),
41828
+ status: external_exports.number().optional().describe("Field status"),
41829
+ defaultValue: external_exports.string().optional().describe("Default value"),
41830
+ relationDataSourceId: external_exports.string().optional().describe("Relation data source ID"),
41831
+ sortOrder: external_exports.number().optional().describe("Sort order"),
41832
+ tenantEnumSetId: external_exports.string().optional().describe("Tenant enum set ID"),
41833
+ options: external_exports.string().optional().describe("JSON options"),
41834
+ validations: external_exports.string().optional().describe("JSON validations")
41835
+ }),
41836
+ run: async (context) => {
41837
+ const studio = await getStudioRunContext(dependencies);
41838
+ const appId = await studio.resolver.resolveAppId({
41839
+ appId: context.options.appId,
41840
+ appSlug: context.options.appSlug
41841
+ });
41842
+ const dataSourceId = await studio.resolver.resolveDataSourceId({
41843
+ appId,
41844
+ dataSourceId: context.options.dataSourceId,
41845
+ dataSourceSlug: context.options.dataSourceSlug
41846
+ });
41847
+ const basePayload = ensureObjectPayload(
41848
+ await readStudioWriteInput({
41849
+ data: context.options.data,
41850
+ fromFile: context.options.fromFile
41851
+ }),
41852
+ "create-field"
41853
+ );
41854
+ const payload = mergeObjectWithFlags(basePayload, fieldFlags(context.options));
41855
+ requireNonEmptyObject(payload, "create-field");
41856
+ const response = await studio.apiClient.request({
41857
+ method: "POST",
41858
+ path: `/dev/apps/${appId}/data-sources/${dataSourceId}/fields`,
41859
+ body: payload
41860
+ });
41861
+ return await wrapStudioPayload(studio.apiBaseUrl, dependencies, response.data);
41862
+ }
41863
+ });
41864
+ studioCli.command("update-field", {
41865
+ description: "Update a field",
41866
+ options: external_exports.object({
41867
+ appId: external_exports.string().optional().describe("App ID"),
41868
+ appSlug: external_exports.string().optional().describe("App slug"),
41869
+ dataSourceId: external_exports.string().optional().describe("Data source ID"),
41870
+ dataSourceSlug: external_exports.string().optional().describe("Data source slug"),
41871
+ fieldId: external_exports.string().optional().describe("Field ID"),
41872
+ fieldSlug: external_exports.string().optional().describe("Field slug"),
41873
+ data: external_exports.string().optional().describe("JSON payload"),
41874
+ fromFile: external_exports.string().optional().describe("Path to JSON payload file"),
41875
+ name: external_exports.string().optional().describe("Field name"),
41876
+ slug: external_exports.string().optional().describe("Field slug"),
41877
+ type: external_exports.string().optional().describe("Field type"),
41878
+ readOnly: external_exports.boolean().optional().describe("Field read only"),
41879
+ status: external_exports.number().optional().describe("Field status"),
41880
+ defaultValue: external_exports.string().optional().describe("Default value"),
41881
+ relationDataSourceId: external_exports.string().optional().describe("Relation data source ID"),
41882
+ sortOrder: external_exports.number().optional().describe("Sort order"),
41883
+ tenantEnumSetId: external_exports.string().optional().describe("Tenant enum set ID"),
41884
+ options: external_exports.string().optional().describe("JSON options"),
41885
+ validations: external_exports.string().optional().describe("JSON validations")
41886
+ }),
41887
+ run: async (context) => {
41888
+ const studio = await getStudioRunContext(dependencies);
41889
+ const appId = await studio.resolver.resolveAppId({
41890
+ appId: context.options.appId,
41891
+ appSlug: context.options.appSlug
41892
+ });
41893
+ const dataSourceId = await studio.resolver.resolveDataSourceId({
41894
+ appId,
41895
+ dataSourceId: context.options.dataSourceId,
41896
+ dataSourceSlug: context.options.dataSourceSlug
41897
+ });
41898
+ const fieldId = await studio.resolver.resolveFieldId({
41899
+ appId,
41900
+ dataSourceId,
41901
+ fieldId: context.options.fieldId,
41902
+ fieldSlug: context.options.fieldSlug
41903
+ });
41904
+ const basePayload = ensureObjectPayload(
41905
+ await readStudioWriteInput({
41906
+ data: context.options.data,
41907
+ fromFile: context.options.fromFile
41908
+ }),
41909
+ "update-field"
41910
+ );
41911
+ const payload = mergeObjectWithFlags(basePayload, fieldFlags(context.options));
41912
+ requireNonEmptyObject(payload, "update-field");
41913
+ const response = await studio.apiClient.request({
41914
+ method: "PATCH",
41915
+ path: `/dev/apps/${appId}/data-sources/${dataSourceId}/fields/${fieldId}`,
41916
+ body: payload
41917
+ });
41918
+ return await wrapStudioPayload(studio.apiBaseUrl, dependencies, response.data);
41919
+ }
41920
+ });
41921
+ studioCli.command("delete-field", {
41922
+ description: "Delete a field",
41923
+ options: external_exports.object({
41924
+ appId: external_exports.string().optional().describe("App ID"),
41925
+ appSlug: external_exports.string().optional().describe("App slug"),
41926
+ dataSourceId: external_exports.string().optional().describe("Data source ID"),
41927
+ dataSourceSlug: external_exports.string().optional().describe("Data source slug"),
41928
+ fieldId: external_exports.string().optional().describe("Field ID"),
41929
+ fieldSlug: external_exports.string().optional().describe("Field slug")
41930
+ }),
41931
+ run: async (context) => {
41932
+ const studio = await getStudioRunContext(dependencies);
41933
+ const appId = await studio.resolver.resolveAppId({
41934
+ appId: context.options.appId,
41935
+ appSlug: context.options.appSlug
41936
+ });
41937
+ const dataSourceId = await studio.resolver.resolveDataSourceId({
41938
+ appId,
41939
+ dataSourceId: context.options.dataSourceId,
41940
+ dataSourceSlug: context.options.dataSourceSlug
41941
+ });
41942
+ const fieldId = await studio.resolver.resolveFieldId({
41943
+ appId,
41944
+ dataSourceId,
41945
+ fieldId: context.options.fieldId,
41946
+ fieldSlug: context.options.fieldSlug
41947
+ });
41948
+ const response = await studio.apiClient.request({
41949
+ method: "DELETE",
41950
+ path: `/dev/apps/${appId}/data-sources/${dataSourceId}/fields/${fieldId}`
41951
+ });
41952
+ return await wrapStudioPayload(studio.apiBaseUrl, dependencies, response.data);
41953
+ }
41954
+ });
41955
+ studioCli.command("create-fields-batch", {
41956
+ description: "Batch create fields",
41957
+ options: external_exports.object({
41958
+ appId: external_exports.string().optional().describe("App ID"),
41959
+ appSlug: external_exports.string().optional().describe("App slug"),
41960
+ dataSourceId: external_exports.string().optional().describe("Data source ID"),
41961
+ dataSourceSlug: external_exports.string().optional().describe("Data source slug"),
41962
+ data: external_exports.string().optional().describe("JSON payload"),
41963
+ fromFile: external_exports.string().optional().describe("Path to JSON payload file")
41964
+ }),
41965
+ run: async (context) => {
41966
+ const studio = await getStudioRunContext(dependencies);
41967
+ const appId = await studio.resolver.resolveAppId({
41968
+ appId: context.options.appId,
41969
+ appSlug: context.options.appSlug
41970
+ });
41971
+ const dataSourceId = await studio.resolver.resolveDataSourceId({
41972
+ appId,
41973
+ dataSourceId: context.options.dataSourceId,
41974
+ dataSourceSlug: context.options.dataSourceSlug
41975
+ });
41976
+ const payload = normalizeBatchPayload(
41977
+ await readStudioWriteInput({
41978
+ data: context.options.data,
41979
+ fromFile: context.options.fromFile
41980
+ }),
41981
+ "fields"
41982
+ );
41983
+ const response = await studio.apiClient.request({
41984
+ method: "POST",
41985
+ path: `/dev/apps/${appId}/data-sources/${dataSourceId}/fields/batch`,
41986
+ body: payload
41987
+ });
41988
+ return await wrapStudioPayload(studio.apiBaseUrl, dependencies, response.data);
41989
+ }
41990
+ });
41991
+ studioCli.command("update-fields-batch", {
41992
+ description: "Batch update fields",
41993
+ options: external_exports.object({
41994
+ appId: external_exports.string().optional().describe("App ID"),
41995
+ appSlug: external_exports.string().optional().describe("App slug"),
41996
+ dataSourceId: external_exports.string().optional().describe("Data source ID"),
41997
+ dataSourceSlug: external_exports.string().optional().describe("Data source slug"),
41998
+ data: external_exports.string().optional().describe("JSON payload"),
41999
+ fromFile: external_exports.string().optional().describe("Path to JSON payload file")
42000
+ }),
42001
+ run: async (context) => {
42002
+ const studio = await getStudioRunContext(dependencies);
42003
+ const appId = await studio.resolver.resolveAppId({
42004
+ appId: context.options.appId,
42005
+ appSlug: context.options.appSlug
42006
+ });
42007
+ const dataSourceId = await studio.resolver.resolveDataSourceId({
42008
+ appId,
42009
+ dataSourceId: context.options.dataSourceId,
42010
+ dataSourceSlug: context.options.dataSourceSlug
42011
+ });
42012
+ const payload = normalizeBatchPayload(
42013
+ await readStudioWriteInput({
42014
+ data: context.options.data,
42015
+ fromFile: context.options.fromFile
42016
+ }),
42017
+ "fields"
42018
+ );
42019
+ const response = await studio.apiClient.request({
42020
+ method: "PATCH",
42021
+ path: `/dev/apps/${appId}/data-sources/${dataSourceId}/fields/batch`,
42022
+ body: payload
42023
+ });
42024
+ return await wrapStudioPayload(studio.apiBaseUrl, dependencies, response.data);
42025
+ }
42026
+ });
42027
+ studioCli.command("delete-fields-batch", {
42028
+ description: "Batch delete fields",
42029
+ options: external_exports.object({
42030
+ appId: external_exports.string().optional().describe("App ID"),
42031
+ appSlug: external_exports.string().optional().describe("App slug"),
42032
+ dataSourceId: external_exports.string().optional().describe("Data source ID"),
42033
+ dataSourceSlug: external_exports.string().optional().describe("Data source slug"),
42034
+ data: external_exports.string().optional().describe("JSON payload"),
42035
+ fromFile: external_exports.string().optional().describe("Path to JSON payload file")
42036
+ }),
42037
+ run: async (context) => {
42038
+ const studio = await getStudioRunContext(dependencies);
42039
+ const appId = await studio.resolver.resolveAppId({
42040
+ appId: context.options.appId,
42041
+ appSlug: context.options.appSlug
42042
+ });
42043
+ const dataSourceId = await studio.resolver.resolveDataSourceId({
42044
+ appId,
42045
+ dataSourceId: context.options.dataSourceId,
42046
+ dataSourceSlug: context.options.dataSourceSlug
42047
+ });
42048
+ const payload = normalizeBatchPayload(
42049
+ await readStudioWriteInput({
42050
+ data: context.options.data,
42051
+ fromFile: context.options.fromFile
42052
+ }),
42053
+ "fieldIds"
42054
+ );
42055
+ const response = await studio.apiClient.request({
42056
+ method: "DELETE",
42057
+ path: `/dev/apps/${appId}/data-sources/${dataSourceId}/fields/batch`,
42058
+ body: payload
42059
+ });
42060
+ return await wrapStudioPayload(studio.apiBaseUrl, dependencies, response.data);
42061
+ }
42062
+ });
42063
+ studioCli.command("list-enums", {
42064
+ description: "List enums for a field",
42065
+ options: external_exports.object({
42066
+ appId: external_exports.string().optional().describe("App ID"),
42067
+ appSlug: external_exports.string().optional().describe("App slug"),
42068
+ dataSourceId: external_exports.string().optional().describe("Data source ID"),
42069
+ dataSourceSlug: external_exports.string().optional().describe("Data source slug"),
42070
+ fieldId: external_exports.string().optional().describe("Field ID"),
42071
+ fieldSlug: external_exports.string().optional().describe("Field slug")
42072
+ }),
42073
+ run: async (context) => {
42074
+ const studio = await getStudioRunContext(dependencies);
42075
+ const appId = await studio.resolver.resolveAppId({
42076
+ appId: context.options.appId,
42077
+ appSlug: context.options.appSlug
42078
+ });
42079
+ const dataSourceId = await studio.resolver.resolveDataSourceId({
42080
+ appId,
42081
+ dataSourceId: context.options.dataSourceId,
42082
+ dataSourceSlug: context.options.dataSourceSlug
42083
+ });
42084
+ const fieldId = await studio.resolver.resolveFieldId({
42085
+ appId,
42086
+ dataSourceId,
42087
+ fieldId: context.options.fieldId,
42088
+ fieldSlug: context.options.fieldSlug
42089
+ });
42090
+ const response = await studio.apiClient.request({
42091
+ method: "GET",
42092
+ path: `/dev/apps/${appId}/data-sources/${dataSourceId}/fields/${fieldId}/enums`
42093
+ });
42094
+ return await wrapStudioPayload(studio.apiBaseUrl, dependencies, response.data);
42095
+ }
42096
+ });
42097
+ studioCli.command("create-enums", {
42098
+ description: "Create enums for a field",
42099
+ options: external_exports.object({
42100
+ appId: external_exports.string().optional().describe("App ID"),
42101
+ appSlug: external_exports.string().optional().describe("App slug"),
42102
+ dataSourceId: external_exports.string().optional().describe("Data source ID"),
42103
+ dataSourceSlug: external_exports.string().optional().describe("Data source slug"),
42104
+ fieldId: external_exports.string().optional().describe("Field ID"),
42105
+ fieldSlug: external_exports.string().optional().describe("Field slug"),
42106
+ data: external_exports.string().optional().describe("JSON payload"),
42107
+ fromFile: external_exports.string().optional().describe("Path to JSON payload file"),
42108
+ enumSetId: external_exports.string().optional().describe("Enum set ID")
42109
+ }),
42110
+ run: async (context) => {
42111
+ const studio = await getStudioRunContext(dependencies);
42112
+ const appId = await studio.resolver.resolveAppId({
42113
+ appId: context.options.appId,
42114
+ appSlug: context.options.appSlug
42115
+ });
42116
+ const dataSourceId = await studio.resolver.resolveDataSourceId({
42117
+ appId,
42118
+ dataSourceId: context.options.dataSourceId,
42119
+ dataSourceSlug: context.options.dataSourceSlug
42120
+ });
42121
+ const fieldId = await studio.resolver.resolveFieldId({
42122
+ appId,
42123
+ dataSourceId,
42124
+ fieldId: context.options.fieldId,
42125
+ fieldSlug: context.options.fieldSlug
42126
+ });
42127
+ const payload = mergeObjectWithFlags(
42128
+ normalizeBatchPayload(
42129
+ await readStudioWriteInput({
42130
+ data: context.options.data,
42131
+ fromFile: context.options.fromFile
42132
+ }),
42133
+ "enums"
42134
+ ),
42135
+ {
42136
+ enumSetId: context.options.enumSetId
42137
+ }
42138
+ );
42139
+ const response = await studio.apiClient.request({
42140
+ method: "POST",
42141
+ path: `/dev/apps/${appId}/data-sources/${dataSourceId}/fields/${fieldId}/enums`,
42142
+ body: payload
42143
+ });
42144
+ return await wrapStudioPayload(studio.apiBaseUrl, dependencies, response.data);
42145
+ }
42146
+ });
42147
+ studioCli.command("update-enums", {
42148
+ description: "Update enums for a field",
42149
+ options: external_exports.object({
42150
+ appId: external_exports.string().optional().describe("App ID"),
42151
+ appSlug: external_exports.string().optional().describe("App slug"),
42152
+ dataSourceId: external_exports.string().optional().describe("Data source ID"),
42153
+ dataSourceSlug: external_exports.string().optional().describe("Data source slug"),
42154
+ fieldId: external_exports.string().optional().describe("Field ID"),
42155
+ fieldSlug: external_exports.string().optional().describe("Field slug"),
42156
+ data: external_exports.string().optional().describe("JSON payload"),
42157
+ fromFile: external_exports.string().optional().describe("Path to JSON payload file")
42158
+ }),
42159
+ run: async (context) => {
42160
+ const studio = await getStudioRunContext(dependencies);
42161
+ const appId = await studio.resolver.resolveAppId({
42162
+ appId: context.options.appId,
42163
+ appSlug: context.options.appSlug
42164
+ });
42165
+ const dataSourceId = await studio.resolver.resolveDataSourceId({
42166
+ appId,
42167
+ dataSourceId: context.options.dataSourceId,
42168
+ dataSourceSlug: context.options.dataSourceSlug
42169
+ });
42170
+ const fieldId = await studio.resolver.resolveFieldId({
42171
+ appId,
42172
+ dataSourceId,
42173
+ fieldId: context.options.fieldId,
42174
+ fieldSlug: context.options.fieldSlug
42175
+ });
42176
+ const payload = normalizeBatchPayload(
42177
+ await readStudioWriteInput({
42178
+ data: context.options.data,
42179
+ fromFile: context.options.fromFile
42180
+ }),
42181
+ "enums"
42182
+ );
42183
+ const response = await studio.apiClient.request({
42184
+ method: "PATCH",
42185
+ path: `/dev/apps/${appId}/data-sources/${dataSourceId}/fields/${fieldId}/enums`,
42186
+ body: payload
42187
+ });
42188
+ return await wrapStudioPayload(studio.apiBaseUrl, dependencies, response.data);
42189
+ }
42190
+ });
42191
+ studioCli.command("delete-enums", {
42192
+ description: "Delete enums for a field",
42193
+ options: external_exports.object({
42194
+ appId: external_exports.string().optional().describe("App ID"),
42195
+ appSlug: external_exports.string().optional().describe("App slug"),
42196
+ dataSourceId: external_exports.string().optional().describe("Data source ID"),
42197
+ dataSourceSlug: external_exports.string().optional().describe("Data source slug"),
42198
+ fieldId: external_exports.string().optional().describe("Field ID"),
42199
+ fieldSlug: external_exports.string().optional().describe("Field slug"),
42200
+ data: external_exports.string().optional().describe("JSON payload"),
42201
+ fromFile: external_exports.string().optional().describe("Path to JSON payload file")
42202
+ }),
42203
+ run: async (context) => {
42204
+ const studio = await getStudioRunContext(dependencies);
42205
+ const appId = await studio.resolver.resolveAppId({
42206
+ appId: context.options.appId,
42207
+ appSlug: context.options.appSlug
42208
+ });
42209
+ const dataSourceId = await studio.resolver.resolveDataSourceId({
42210
+ appId,
42211
+ dataSourceId: context.options.dataSourceId,
42212
+ dataSourceSlug: context.options.dataSourceSlug
42213
+ });
42214
+ const fieldId = await studio.resolver.resolveFieldId({
42215
+ appId,
42216
+ dataSourceId,
42217
+ fieldId: context.options.fieldId,
42218
+ fieldSlug: context.options.fieldSlug
42219
+ });
42220
+ const payload = normalizeBatchPayload(
42221
+ await readStudioWriteInput({
42222
+ data: context.options.data,
42223
+ fromFile: context.options.fromFile
42224
+ }),
42225
+ "enumIds"
42226
+ );
42227
+ const response = await studio.apiClient.request({
42228
+ method: "DELETE",
42229
+ path: `/dev/apps/${appId}/data-sources/${dataSourceId}/fields/${fieldId}/enums`,
42230
+ body: payload
42231
+ });
42232
+ return await wrapStudioPayload(studio.apiBaseUrl, dependencies, response.data);
42233
+ }
42234
+ });
42235
+ return studioCli;
42236
+ }
42237
+
40822
42238
  // src/services/apiClient.ts
40823
42239
  function normalizeResponseHeaders(headers) {
40824
42240
  const normalized = {};
@@ -40972,7 +42388,7 @@ var ApiClient = class {
40972
42388
  };
40973
42389
 
40974
42390
  // src/services/authSession.ts
40975
- function isRecord(value) {
42391
+ function isRecord5(value) {
40976
42392
  return typeof value === "object" && value !== null;
40977
42393
  }
40978
42394
  function extractRecordValue(record2, keys) {
@@ -40983,7 +42399,7 @@ function extractRecordValue(record2, keys) {
40983
42399
  }
40984
42400
  return void 0;
40985
42401
  }
40986
- function extractString(record2, keys) {
42402
+ function extractString2(record2, keys) {
40987
42403
  const value = extractRecordValue(record2, keys);
40988
42404
  return typeof value === "string" && value.length > 0 ? value : void 0;
40989
42405
  }
@@ -41271,15 +42687,15 @@ var AuthSessionService = class {
41271
42687
  fetchFn: this.params.fetchFn
41272
42688
  });
41273
42689
  const payload = response.data;
41274
- const dataCandidate = isRecord(payload) && isRecord(payload.data) ? payload.data : payload;
41275
- if (!isRecord(dataCandidate)) {
42690
+ const dataCandidate = isRecord5(payload) && isRecord5(payload.data) ? payload.data : payload;
42691
+ if (!isRecord5(dataCandidate)) {
41276
42692
  throw new AuthSessionError("Unable to parse /users/me response.");
41277
42693
  }
41278
- const tenantCandidate = isRecord(dataCandidate.tenant) ? dataCandidate.tenant : void 0;
41279
- const userId = extractString(dataCandidate, ["id", "user_id"]);
41280
- const email3 = extractString(dataCandidate, ["email"]);
41281
- const tenantId = tenantCandidate ? extractString(tenantCandidate, ["id"]) : extractString(dataCandidate, ["tenant_id", "tenantId"]);
41282
- const tenantName = tenantCandidate ? extractString(tenantCandidate, ["name"]) : extractString(dataCandidate, ["tenant_name", "tenantName"]);
42694
+ const tenantCandidate = isRecord5(dataCandidate.tenant) ? dataCandidate.tenant : void 0;
42695
+ const userId = extractString2(dataCandidate, ["id", "user_id"]);
42696
+ const email3 = extractString2(dataCandidate, ["email"]);
42697
+ const tenantId = tenantCandidate ? extractString2(tenantCandidate, ["id"]) : extractString2(dataCandidate, ["tenant_id", "tenantId"]);
42698
+ const tenantName = tenantCandidate ? extractString2(tenantCandidate, ["name"]) : extractString2(dataCandidate, ["tenant_name", "tenantName"]);
41283
42699
  const tenantNo = tenantCandidate ? extractNumber(tenantCandidate, ["no", "tenant_no"]) : extractNumber(dataCandidate, ["tenant_no", "tenantNo"]);
41284
42700
  if (!userId || !email3 || !tenantId || !tenantName || !tenantNo) {
41285
42701
  throw new AuthSessionError("Incomplete identity data returned from /users/me.");
@@ -41301,17 +42717,17 @@ var AuthSessionService = class {
41301
42717
  fetchFn: this.params.fetchFn
41302
42718
  });
41303
42719
  const payload = response.data;
41304
- const listCandidate = Array.isArray(payload) ? payload : isRecord(payload) && Array.isArray(payload.data) ? payload.data : null;
42720
+ const listCandidate = Array.isArray(payload) ? payload : isRecord5(payload) && Array.isArray(payload.data) ? payload.data : null;
41305
42721
  if (!listCandidate) {
41306
42722
  throw new AuthSessionError("Unable to parse tenant catalog response.");
41307
42723
  }
41308
42724
  const mapped = [];
41309
42725
  for (const item of listCandidate) {
41310
- if (!isRecord(item)) {
42726
+ if (!isRecord5(item)) {
41311
42727
  continue;
41312
42728
  }
41313
- const tenantId = extractString(item, ["id", "tenant_id"]);
41314
- const tenantName = extractString(item, ["name"]);
42729
+ const tenantId = extractString2(item, ["id", "tenant_id"]);
42730
+ const tenantName = extractString2(item, ["name"]);
41315
42731
  const tenantNo = extractNumber(item, ["tenant_no", "tenantNo", "no"]);
41316
42732
  const logoValue = extractRecordValue(item, ["logo"]);
41317
42733
  const logo = typeof logoValue === "string" || logoValue === null ? logoValue : void 0;
@@ -41466,8 +42882,8 @@ var AuthSessionService = class {
41466
42882
  };
41467
42883
 
41468
42884
  // src/services/authStore.ts
41469
- var import_promises2 = require("node:fs/promises");
41470
- var import_node_path4 = require("node:path");
42885
+ var import_promises5 = require("node:fs/promises");
42886
+ var import_node_path6 = require("node:path");
41471
42887
  function createEmptyState() {
41472
42888
  return {
41473
42889
  version: 2,
@@ -41506,7 +42922,7 @@ var AuthStore = class {
41506
42922
  }
41507
42923
  async readState() {
41508
42924
  try {
41509
- const raw = await (0, import_promises2.readFile)(this.authFilePath, "utf8");
42925
+ const raw = await (0, import_promises5.readFile)(this.authFilePath, "utf8");
41510
42926
  const parsed = JSON.parse(raw);
41511
42927
  const legacy = LegacyAuthSessionSchema.safeParse(parsed);
41512
42928
  if (legacy.success) {
@@ -41542,17 +42958,17 @@ var AuthStore = class {
41542
42958
  });
41543
42959
  }
41544
42960
  const normalized = normalizeState(validated.data);
41545
- const directory = (0, import_node_path4.dirname)(this.authFilePath);
41546
- await (0, import_promises2.mkdir)(directory, {
42961
+ const directory = (0, import_node_path6.dirname)(this.authFilePath);
42962
+ await (0, import_promises5.mkdir)(directory, {
41547
42963
  recursive: true,
41548
42964
  mode: 448
41549
42965
  });
41550
- await (0, import_promises2.writeFile)(this.authFilePath, `${JSON.stringify(normalized, null, 2)}
42966
+ await (0, import_promises5.writeFile)(this.authFilePath, `${JSON.stringify(normalized, null, 2)}
41551
42967
  `, {
41552
42968
  encoding: "utf8",
41553
42969
  mode: 384
41554
42970
  });
41555
- await (0, import_promises2.chmod)(this.authFilePath, 384);
42971
+ await (0, import_promises5.chmod)(this.authFilePath, 384);
41556
42972
  }
41557
42973
  async getActiveProfile(apiBaseUrl) {
41558
42974
  const normalizedApiBaseUrl = normalizeApiBaseUrl(apiBaseUrl);
@@ -41749,15 +43165,15 @@ var AuthStore = class {
41749
43165
  await this.writeState(state);
41750
43166
  }
41751
43167
  async clear() {
41752
- await (0, import_promises2.rm)(this.authFilePath, {
43168
+ await (0, import_promises5.rm)(this.authFilePath, {
41753
43169
  force: true
41754
43170
  });
41755
43171
  }
41756
43172
  };
41757
43173
 
41758
43174
  // src/services/environmentConfig.ts
41759
- var import_promises3 = require("node:fs/promises");
41760
- var import_node_path5 = require("node:path");
43175
+ var import_promises6 = require("node:fs/promises");
43176
+ var import_node_path7 = require("node:path");
41761
43177
  var ENVIRONMENT_ID_ALIASES = {
41762
43178
  "local-development": "dev",
41763
43179
  prod: "live"
@@ -41843,7 +43259,7 @@ var EnvironmentConfigService = class {
41843
43259
  }
41844
43260
  async readState() {
41845
43261
  try {
41846
- const raw = await (0, import_promises3.readFile)(this.configFilePath, "utf8");
43262
+ const raw = await (0, import_promises6.readFile)(this.configFilePath, "utf8");
41847
43263
  const parsed = JSON.parse(raw);
41848
43264
  const validated = EnvironmentConfigStateSchema.safeParse(parsed);
41849
43265
  if (!validated.success) {
@@ -41877,17 +43293,17 @@ var EnvironmentConfigService = class {
41877
43293
  });
41878
43294
  }
41879
43295
  const normalized = normalizeState2(validated.data);
41880
- const directory = (0, import_node_path5.dirname)(this.configFilePath);
41881
- await (0, import_promises3.mkdir)(directory, {
43296
+ const directory = (0, import_node_path7.dirname)(this.configFilePath);
43297
+ await (0, import_promises6.mkdir)(directory, {
41882
43298
  recursive: true,
41883
43299
  mode: 448
41884
43300
  });
41885
- await (0, import_promises3.writeFile)(this.configFilePath, `${JSON.stringify(normalized, null, 2)}
43301
+ await (0, import_promises6.writeFile)(this.configFilePath, `${JSON.stringify(normalized, null, 2)}
41886
43302
  `, {
41887
43303
  encoding: "utf8",
41888
43304
  mode: 384
41889
43305
  });
41890
- await (0, import_promises3.chmod)(this.configFilePath, 384);
43306
+ await (0, import_promises6.chmod)(this.configFilePath, 384);
41891
43307
  }
41892
43308
  async getActiveEnvironment() {
41893
43309
  const state = await this.#readStateWithDefaults();
@@ -41953,8 +43369,8 @@ var EnvironmentConfigService = class {
41953
43369
  };
41954
43370
 
41955
43371
  // src/services/tenantOpenApi.ts
41956
- var import_promises4 = require("node:fs/promises");
41957
- var import_node_path6 = require("node:path");
43372
+ var import_promises7 = require("node:fs/promises");
43373
+ var import_node_path8 = require("node:path");
41958
43374
  function resolveSourceUrl(tenantId, template) {
41959
43375
  return template.replace("{tenantId}", encodeURIComponent(tenantId));
41960
43376
  }
@@ -41962,6 +43378,14 @@ var TenantOpenApiService = class {
41962
43378
  constructor(params) {
41963
43379
  this.params = params;
41964
43380
  }
43381
+ getTenantOpenApiFilePath(tenantId) {
43382
+ const normalizedTenantId = tenantId.trim();
43383
+ if (!normalizedTenantId) {
43384
+ throw new AuthSessionError("Tenant ID is required to resolve OpenAPI spec path.");
43385
+ }
43386
+ const rootPath = this.params?.rootPath || TENANT_OPENAPI_ROOT_PATH;
43387
+ return (0, import_node_path8.join)(rootPath, normalizedTenantId, "openapi.json");
43388
+ }
41965
43389
  async downloadTenantOpenApi(tenantId) {
41966
43390
  const normalizedTenantId = tenantId.trim();
41967
43391
  if (!normalizedTenantId) {
@@ -41993,18 +43417,17 @@ var TenantOpenApiService = class {
41993
43417
  cause: error48
41994
43418
  });
41995
43419
  }
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), {
43420
+ const filePath = this.getTenantOpenApiFilePath(normalizedTenantId);
43421
+ await (0, import_promises7.mkdir)((0, import_node_path8.dirname)(filePath), {
41999
43422
  recursive: true,
42000
43423
  mode: 448
42001
43424
  });
42002
- await (0, import_promises4.writeFile)(filePath, `${JSON.stringify(parsedContent, null, 2)}
43425
+ await (0, import_promises7.writeFile)(filePath, `${JSON.stringify(parsedContent, null, 2)}
42003
43426
  `, {
42004
43427
  encoding: "utf8",
42005
43428
  mode: 384
42006
43429
  });
42007
- await (0, import_promises4.chmod)(filePath, 384);
43430
+ await (0, import_promises7.chmod)(filePath, 384);
42008
43431
  return {
42009
43432
  tenantId: normalizedTenantId,
42010
43433
  sourceUrl,
@@ -42021,8 +43444,14 @@ var ROOT_HELP_COMMANDS = [
42021
43444
  { command: "auth accounts list", description: "List saved user accounts" },
42022
43445
  { command: "auth tenants use --tenantId <id>", description: "Switch active tenant for selected user" },
42023
43446
  { command: "discover api", description: "Download active tenant OpenAPI spec" },
43447
+ { command: "discover namespaces", description: "List namespaces from tenant OpenAPI spec" },
43448
+ { command: "discover path <prefix>", description: "List endpoint paths by prefix" },
43449
+ { command: "discover endpoint <path>", description: "Show endpoint object by exact path" },
43450
+ { command: "discover entity <name>", description: "Show entity object by name" },
43451
+ { command: "discover search <terms>", description: "Search in endpoint paths and entity names" },
42024
43452
  { command: "ds list <appSlug> <dataSourceSlug>", description: "List data source items" },
42025
43453
  { command: "apps list", description: "List apps" },
43454
+ { command: "studio list-data-sources --appSlug <slug>", description: "List studio data sources" },
42026
43455
  { command: "curl <path>", description: "Send arbitrary API requests" }
42027
43456
  ];
42028
43457
  function createDocyrusCli(params) {
@@ -42090,6 +43519,11 @@ function createDocyrusCli(params) {
42090
43519
  environmentConfigService,
42091
43520
  authStore
42092
43521
  }));
43522
+ cli2.command(createStudioCli({
43523
+ createApiClient,
43524
+ environmentConfigService,
43525
+ authStore
43526
+ }));
42093
43527
  cli2.command(createCurlCli({
42094
43528
  createApiClient,
42095
43529
  environmentConfigService,