bdy 1.25.3-dev → 1.25.5-dev

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 (47) hide show
  1. package/distTs/package.json +2 -1
  2. package/distTs/src/agent/socket/tunnel.js +2 -0
  3. package/distTs/src/api/client.js +22 -55
  4. package/distTs/src/cliCommand.js +52 -0
  5. package/distTs/src/cliError.js +34 -0
  6. package/distTs/src/cliIndex.js +3 -1
  7. package/distTs/src/command/agent/proxy/status.js +9 -2
  8. package/distTs/src/command/agent/status.js +22 -6
  9. package/distTs/src/command/agent/tags/get.js +9 -2
  10. package/distTs/src/command/agent/target/status.js +9 -2
  11. package/distTs/src/command/agent/tunnel/list.js +6 -1
  12. package/distTs/src/command/agent/tunnel/status.js +13 -1
  13. package/distTs/src/command/agent/version.js +7 -2
  14. package/distTs/src/command/api/info.js +50 -2
  15. package/distTs/src/command/api/list.js +18 -2
  16. package/distTs/src/command/artifact/create.js +5 -0
  17. package/distTs/src/command/artifact/publish.js +20 -22
  18. package/distTs/src/command/artifact/version/create.js +5 -0
  19. package/distTs/src/command/config/get/tunnel.js +8 -3
  20. package/distTs/src/command/config/get/tunnels.js +8 -3
  21. package/distTs/src/command/discover.js +9 -11
  22. package/distTs/src/command/distro/create.js +5 -0
  23. package/distTs/src/command/distro/route/create.js +5 -0
  24. package/distTs/src/command/distro/route/update.js +5 -0
  25. package/distTs/src/command/distro/update.js +5 -0
  26. package/distTs/src/command/domain/search.js +31 -10
  27. package/distTs/src/command/environment/create.js +5 -0
  28. package/distTs/src/command/environment/update.js +5 -0
  29. package/distTs/src/command/sandbox/app/add.js +7 -1
  30. package/distTs/src/command/sandbox/app/logs.js +9 -4
  31. package/distTs/src/command/sandbox/create.js +13 -5
  32. package/distTs/src/command/sandbox/endpoint/add.js +5 -0
  33. package/distTs/src/command/sandbox/exec/command.js +15 -30
  34. package/distTs/src/command/sandbox/exec/logs.js +4 -29
  35. package/distTs/src/command/sandbox/finishExec.js +34 -0
  36. package/distTs/src/command/sandbox/logs.js +14 -1
  37. package/distTs/src/command/sandbox/snapshot/create.js +9 -1
  38. package/distTs/src/command/target/create.js +5 -0
  39. package/distTs/src/command/target/exec/logs.js +15 -2
  40. package/distTs/src/command/target/update.js +5 -0
  41. package/distTs/src/command/yaml/validate.js +111 -52
  42. package/distTs/src/index.js +6 -1
  43. package/distTs/src/input.js +12 -11
  44. package/distTs/src/openapi.js +8 -1
  45. package/distTs/src/output.js +55 -13
  46. package/distTs/src/texts.js +272 -59
  47. package/package.json +2 -1
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "bdy",
3
3
  "preferGlobal": false,
4
- "version": "1.25.3-dev",
4
+ "version": "1.25.5-dev",
5
5
  "type": "commonjs",
6
6
  "license": "MIT",
7
7
  "homepage": "https://buddy.works/docs/cli",
@@ -16,6 +16,7 @@
16
16
  "e2e": "npm run buildTs && vitest run --config e2e/vitest.config.mts",
17
17
  "e2e:version": "vitest run --config e2e/vitest.config.mts e2e/modules/version/",
18
18
  "e2e:discover": "vitest run --config e2e/vitest.discover.config.mts",
19
+ "e2e:errors": "vitest run --config e2e/vitest.errors.config.mts",
19
20
  "e2e:tunnel-config": "vitest run --config e2e/vitest.config.mts e2e/modules/tunnel-config/",
20
21
  "e2e:auth": "vitest run --config e2e/vitest.config.mts e2e/modules/auth/",
21
22
  "e2e:workspace": "vitest run --config e2e/vitest.config.mts e2e/modules/workspace/",
@@ -30,6 +30,7 @@ class AgentSocketTunnel extends events_1.default {
30
30
  totalConnections;
31
31
  connections;
32
32
  dns;
33
+ rawStatus;
33
34
  constructor(socket, id) {
34
35
  super();
35
36
  this.id = id;
@@ -65,6 +66,7 @@ class AgentSocketTunnel extends events_1.default {
65
66
  };
66
67
  }
67
68
  onTunnelData(data) {
69
+ this.rawStatus = data;
68
70
  if (data.regionLatency)
69
71
  this.regionLatency = data.regionLatency;
70
72
  if (data.targetLatency)
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.formDataFileName = void 0;
7
7
  const texts_1 = require("../texts");
8
+ const cliError_1 = __importDefault(require("../cliError"));
8
9
  const logger_1 = __importDefault(require("../logger"));
9
10
  const utils_1 = require("../utils");
10
11
  const node_fs_1 = __importDefault(require("node:fs"));
@@ -155,7 +156,9 @@ class ApiClient {
155
156
  await (0, utils_1.sleep)(attempt * 1000);
156
157
  continue;
157
158
  }
158
- throw new Error(retriable ? texts_1.ERR_REST_API_CONNECT_ERROR : texts_1.ERR_REST_API_GENERAL_ERROR, { cause: err });
159
+ const networkCode = err.code ?? err.cause?.code;
160
+ const timeout = ['ETIMEDOUT', 'UND_ERR_CONNECT_TIMEOUT', 'UND_ERR_HEADERS_TIMEOUT', 'UND_ERR_BODY_TIMEOUT'].includes(networkCode);
161
+ throw new cliError_1.default(timeout ? 'timeout' : 'connection_error', timeout ? texts_1.ERR_REST_API_TIMEOUT : texts_1.ERR_REST_API_CONNECT_ERROR, timeout ? texts_1.TXT_CLI_RETRY_HINT : texts_1.TXT_CLI_ENDPOINT_HINT, { cause: err });
159
162
  }
160
163
  rateLimitReset = headerValue(response.headers['x-rate-limit-reset']);
161
164
  status = response.statusCode;
@@ -173,7 +176,8 @@ class ApiClient {
173
176
  this.warnRateLimitLow(headerValue(response.headers['x-rate-limit-remaining']), rateLimitReset);
174
177
  if (status === 429) {
175
178
  await responseBody.dump();
176
- throw new Error((0, texts_1.ERR_REST_API_RATE_LIMIT)(rateLimitReset));
179
+ const error = (0, texts_1.ERR_API_DETAILS)(status, undefined, path, this.baseUrl, rateLimitReset);
180
+ throw new cliError_1.default(error.code, error.message, error.hint);
177
181
  }
178
182
  if (status === 401) {
179
183
  await responseBody.dump();
@@ -194,43 +198,7 @@ class ApiClient {
194
198
  });
195
199
  }
196
200
  }
197
- throw new Error(texts_1.ERR_REST_API_WRONG_TOKEN);
198
- }
199
- if (status === 403) {
200
- let json;
201
- try {
202
- json = await responseBody.json();
203
- logger_1.default.debug('API CLIENT PARSED RESPONSE:');
204
- logger_1.default.debug(json);
205
- }
206
- catch {
207
- await responseBody.dump();
208
- throw new Error((0, texts_1.ERR_REST_API_RATE_LIMIT)(rateLimitReset));
209
- }
210
- if (json.errors && json.errors[0] && json.errors[0].message) {
211
- throw new Error((0, texts_1.ERR_API_MESSAGE_REPLACER)(json.errors[0].message, path, this.baseUrl, rateLimitReset));
212
- }
213
- throw new Error((0, texts_1.ERR_REST_API_RATE_LIMIT)(rateLimitReset));
214
- }
215
- if ([400, 404].includes(status)) {
216
- let json;
217
- try {
218
- json = await responseBody.json();
219
- }
220
- catch (err) {
221
- logger_1.default.debug('API CLIENT PARSED RESPONSE ERROR:');
222
- logger_1.default.debug(err);
223
- throw new Error(texts_1.ERR_REST_API_GENERAL_ERROR, { cause: err });
224
- }
225
- logger_1.default.debug('API CLIENT PARSED RESPONSE:');
226
- logger_1.default.debug(json);
227
- if (json.errors && json.errors[0] && json.errors[0].message) {
228
- throw new Error((0, texts_1.ERR_API_MESSAGE_REPLACER)(json.errors[0].message, path, this.baseUrl, rateLimitReset));
229
- }
230
- if (status === 404)
231
- throw new Error(texts_1.ERR_REST_API_RESOURCE_NOT_FOUND);
232
- else
233
- throw new Error((0, texts_1.ERR_REST_API_HTTP_ERROR)(status));
201
+ throw new cliError_1.default('unauthorized', texts_1.ERR_REST_API_UNAUTHORIZED, texts_1.TXT_CLI_LOGIN_HINT);
234
202
  }
235
203
  if ([200, 201].includes(status)) {
236
204
  if (parseResponseBody) {
@@ -267,26 +235,25 @@ class ApiClient {
267
235
  await responseBody.dump();
268
236
  return null;
269
237
  }
270
- else if (status === 503) {
271
- throw new Error(texts_1.ERR_REST_API_NOT_RESPONDING);
272
- }
273
238
  else {
274
- // Any other error status (409, 422, 5xx...): surface the API's own
275
- // message when the body carries one (e.g. "Concurrency limit reached")
276
- // instead of reducing it to a bare HTTP code.
277
- let json;
239
+ let message;
240
+ let nonJsonBody = false;
278
241
  try {
279
- json = await responseBody.json();
280
- }
281
- catch {
282
- throw new Error((0, texts_1.ERR_REST_API_HTTP_ERROR)(status));
242
+ const json = await responseBody.json();
243
+ logger_1.default.debug('API CLIENT PARSED RESPONSE:');
244
+ logger_1.default.debug(json);
245
+ const value = json?.errors?.[0]?.message;
246
+ if (typeof value === 'string')
247
+ message = value;
283
248
  }
284
- logger_1.default.debug('API CLIENT PARSED RESPONSE:');
285
- logger_1.default.debug(json);
286
- if (json.errors && json.errors[0] && json.errors[0].message) {
287
- throw new Error((0, texts_1.ERR_API_MESSAGE_REPLACER)(json.errors[0].message, path, this.baseUrl, rateLimitReset));
249
+ catch (err) {
250
+ nonJsonBody = true;
251
+ logger_1.default.debug(err);
288
252
  }
289
- throw new Error((0, texts_1.ERR_REST_API_HTTP_ERROR)(status));
253
+ // Edge/WAF throttling uses 403 with an HTML body. Preserve the legacy
254
+ // rate-limit classification; JSON 403 responses still report API errors.
255
+ const error = (0, texts_1.ERR_API_DETAILS)(status === 403 && nonJsonBody ? 429 : status, message, path, this.baseUrl, rateLimitReset);
256
+ throw new cliError_1.default(error.code, error.message, error.hint);
290
257
  }
291
258
  }
292
259
  // Set by the CLI bootstrap (Input.restApiTokenClient); the transport layer
@@ -1,6 +1,12 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  const commander_1 = require("commander");
7
+ const output_1 = __importDefault(require("./output"));
8
+ const cliError_1 = __importDefault(require("./cliError"));
9
+ const texts_1 = require("./texts");
4
10
  /** Command documentation shared by terminal help and machine discovery. */
5
11
  class CliCommand extends commander_1.Command {
6
12
  agentStatus;
@@ -13,11 +19,57 @@ class CliCommand extends commander_1.Command {
13
19
  hideVersionUpdate;
14
20
  agentPort;
15
21
  skipPre = false;
22
+ defaultErrorFormat = 'text';
16
23
  metadata;
17
24
  constructor(name, metadata = {}) {
18
25
  super(name);
19
26
  this.metadata = metadata;
20
27
  }
28
+ addOption(option) {
29
+ if (option.long === '--format' && !option.argChoices) {
30
+ const choices = option.flags.match(/<([^>]+)>/)?.[1].split('|');
31
+ if (choices?.every((value) => ['text', 'json', 'jsonl'].includes(value)))
32
+ option.choices(choices);
33
+ }
34
+ return super.addOption(option);
35
+ }
36
+ parseOptions(args) {
37
+ output_1.default.errorFormat = this.defaultErrorFormat;
38
+ const formatOption = this.options.find((option) => option.long === '--format');
39
+ if (formatOption && this.defaultErrorFormat === 'text') {
40
+ // Let Commander handle option syntax without running our parsers, hooks
41
+ // or actions. Even if a required value is missing, earlier format values
42
+ // are available before the real parser reports the error.
43
+ const probe = new commander_1.Command()
44
+ .exitOverride()
45
+ .configureOutput({ writeErr: () => { } });
46
+ for (const option of this.options)
47
+ probe.option(option.flags);
48
+ try {
49
+ probe.parseOptions(args);
50
+ }
51
+ catch (err) {
52
+ if (!(err instanceof commander_1.CommanderError))
53
+ throw err;
54
+ }
55
+ const format = probe.opts().format;
56
+ if (formatOption.argChoices?.includes(format) &&
57
+ (format === 'json' || format === 'jsonl'))
58
+ output_1.default.errorFormat = format;
59
+ }
60
+ this.showSuggestionAfterError(output_1.default.errorFormat === 'text');
61
+ return super.parseOptions(args);
62
+ }
63
+ error(message, options) {
64
+ if (output_1.default.errorFormat === 'text')
65
+ return super.error(message, options);
66
+ const path = [this.name()];
67
+ for (let command = this.parent; command; command = command.parent)
68
+ path.unshift(command.name());
69
+ output_1.default.exitError(new cliError_1.default(options?.code === 'commander.unknownCommand'
70
+ ? 'unknown_command'
71
+ : 'usage_error', message.replace(/^error: /, '').trim(), (0, texts_1.TXT_CLI_USAGE_HINT)(path.join(' '))));
72
+ }
21
73
  get requiredScopes() {
22
74
  return this.metadata.requiredScopes ?? null;
23
75
  }
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ class CliError extends Error {
4
+ code;
5
+ hint;
6
+ constructor(code, message, hint = null, options) {
7
+ super(message, options);
8
+ this.code = code;
9
+ this.hint = hint;
10
+ this.name = 'CliError';
11
+ }
12
+ static details(error) {
13
+ if (error instanceof CliError) {
14
+ return { code: error.code, message: error.message, hint: error.hint };
15
+ }
16
+ const { code, syscall } = error ?? {};
17
+ const missingFile = code === 'ENOENT' &&
18
+ [
19
+ 'open',
20
+ 'stat',
21
+ 'lstat',
22
+ 'access',
23
+ 'scandir',
24
+ 'readlink',
25
+ 'realpath',
26
+ ].includes(syscall ?? '');
27
+ return {
28
+ code: missingFile ? 'file_not_found' : 'command_failed',
29
+ message: error instanceof Error ? error.message : String(error),
30
+ hint: null,
31
+ };
32
+ }
33
+ }
34
+ exports.default = CliError;
@@ -30,6 +30,8 @@ const environment_1 = __importDefault(require("./command/environment"));
30
30
  const register_1 = __importDefault(require("./command/register"));
31
31
  const mcp_1 = __importDefault(require("./command/mcp"));
32
32
  const discover_1 = __importDefault(require("./command/discover"));
33
+ const cliError_1 = __importDefault(require("./cliError"));
34
+ const texts_2 = require("./texts");
33
35
  function cliIndex() {
34
36
  const program = (0, utils_1.newCommand)('bdy', texts_1.DESC_PROGRAM);
35
37
  program.hook('preAction', pre_1.default);
@@ -65,7 +67,7 @@ function cliIndex() {
65
67
  program.addCommand(discover_1.default);
66
68
  program.action((_, cmd) => {
67
69
  if (cmd.args.length > 0) {
68
- output_1.default.exitError((0, texts_1.ERR_COMMAND_NOT_FOUND)(cmd.args[0]));
70
+ output_1.default.exitError(new cliError_1.default('unknown_command', (0, texts_1.ERR_COMMAND_NOT_FOUND)(cmd.args[0]), (0, texts_2.TXT_CLI_USAGE_HINT)('bdy')));
69
71
  }
70
72
  program.help();
71
73
  });
@@ -6,8 +6,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const utils_1 = require("../../../utils");
7
7
  const texts_1 = require("../../../texts");
8
8
  const output_1 = __importDefault(require("../../../output"));
9
- const commandAgentProxyStatus = (0, utils_1.newCommand)('status', texts_1.DESC_COMMAND_AGENT_PROXY_STATUS);
10
- commandAgentProxyStatus.action(async () => {
9
+ const commandAgentProxyStatus = (0, utils_1.newCommand)('status', texts_1.DESC_COMMAND_AGENT_PROXY_STATUS, { examples: texts_1.EXAMPLE_AGENT_PROXY_STATUS });
10
+ commandAgentProxyStatus.option('--format <text|json>', texts_1.OPTION_FORMAT);
11
+ commandAgentProxyStatus.action(async (options) => {
11
12
  const ApiAgent = require('../../../tunnel/api/agent').default;
12
13
  if (!commandAgentProxyStatus.agentInstalled) {
13
14
  output_1.default.exitError(texts_1.ERR_AGENT_NOT_INSTALLED);
@@ -18,6 +19,12 @@ commandAgentProxyStatus.action(async () => {
18
19
  try {
19
20
  const api = new ApiAgent(commandAgentProxyStatus.agentPort || 0);
20
21
  const data = await api.fetchAgentProxy();
22
+ if (options.format === 'json') {
23
+ output_1.default.json(data);
24
+ if (!data.proxy)
25
+ output_1.default.exitError(texts_1.TXT_AGENT_PROXY_DISABLED);
26
+ output_1.default.exitNormal();
27
+ }
21
28
  if (data.proxy) {
22
29
  output_1.default.exitSuccess(texts_1.TXT_AGENT_PROXY_ENABLED);
23
30
  }
@@ -8,8 +8,8 @@ const node_fs_1 = require("node:fs");
8
8
  const texts_1 = require("../../texts");
9
9
  const utils_1 = require("../../utils");
10
10
  const tunnel_1 = require("../../types/tunnel");
11
- const commandAgentStatus = (0, utils_1.newCommand)('status', texts_1.DESC_COMMAND_AGENT_STATUS);
12
- const outputStatus = (installed, standalone, version, id, running, enabled, target, tunneling, proxy, status) => {
11
+ const commandAgentStatus = (0, utils_1.newCommand)('status', texts_1.DESC_COMMAND_AGENT_STATUS, { examples: texts_1.EXAMPLE_AGENT_STATUS, helpText: texts_1.HELP_AGENT_STATUS_JSON });
12
+ const outputStatus = (installed, standalone, version, id, running, enabled, target, tunneling, proxy, status, json) => {
13
13
  const AgentManager = require('../../agent/manager').default;
14
14
  if (!installed) {
15
15
  // Nothing loaded, yet there is a file where the install would be - it is unreadable or not
@@ -22,6 +22,22 @@ const outputStatus = (installed, standalone, version, id, running, enabled, targ
22
22
  }
23
23
  output_1.default.exitError(texts_1.ERR_AGENT_NOT_INSTALLED);
24
24
  }
25
+ const orphan = AgentManager.system.getStandaloneConfigPath();
26
+ const orphanedConfig = !AgentManager.system.isUserOwnedInstall() && (0, node_fs_1.existsSync)(orphan)
27
+ ? orphan
28
+ : null;
29
+ if (json) {
30
+ output_1.default.json({
31
+ ...commandAgentStatus.agentStatus,
32
+ installed,
33
+ standalone,
34
+ id: id ?? null,
35
+ version: version || null,
36
+ running,
37
+ orphaned_config: orphanedConfig,
38
+ });
39
+ output_1.default.exitNormal();
40
+ }
25
41
  let txt = 'Installation: ';
26
42
  if (standalone)
27
43
  txt += 'App @ ';
@@ -66,13 +82,13 @@ const outputStatus = (installed, standalone, version, id, running, enabled, targ
66
82
  // above: an install made before those files moved keeps its config in the system directory
67
83
  // and reports itself as an app, and a leftover in the home is just as inert next to it.
68
84
  // Inert, but only findable if it is said out loud.
69
- const orphan = AgentManager.system.getStandaloneConfigPath();
70
- if (!AgentManager.system.isUserOwnedInstall() && (0, node_fs_1.existsSync)(orphan)) {
85
+ if (orphanedConfig) {
71
86
  output_1.default.error((0, texts_1.TXT_AGENT_APP_INSTALL_ORPHANED)(orphan));
72
87
  }
73
88
  output_1.default.exitNormal();
74
89
  };
75
- commandAgentStatus.action(async () => {
90
+ commandAgentStatus.option('--format <text|json>', texts_1.OPTION_FORMAT);
91
+ commandAgentStatus.action((options) => {
76
92
  let version = '';
77
93
  const id = commandAgentStatus.agentId;
78
94
  const running = !!commandAgentStatus.agentStatus;
@@ -89,6 +105,6 @@ commandAgentStatus.action(async () => {
89
105
  tunneling = commandAgentStatus.agentStatus.tunneling;
90
106
  proxy = commandAgentStatus.agentStatus.proxy;
91
107
  }
92
- outputStatus(!!commandAgentStatus.agentInstalled, !!commandAgentStatus.agentStandalone, version, id, running, enabled, target, tunneling, proxy, status);
108
+ outputStatus(!!commandAgentStatus.agentInstalled, !!commandAgentStatus.agentStandalone, version, id, running, enabled, target, tunneling, proxy, status, options.format === 'json');
93
109
  });
94
110
  exports.default = commandAgentStatus;
@@ -6,8 +6,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const utils_1 = require("../../../utils");
7
7
  const texts_1 = require("../../../texts");
8
8
  const output_1 = __importDefault(require("../../../output"));
9
- const commandAgentTagsGet = (0, utils_1.newCommand)('get', texts_1.DESC_COMMAND_AGENT_TAGS_GET);
10
- commandAgentTagsGet.action(async () => {
9
+ const commandAgentTagsGet = (0, utils_1.newCommand)('get', texts_1.DESC_COMMAND_AGENT_TAGS_GET, { examples: texts_1.EXAMPLE_AGENT_TAGS_GET });
10
+ commandAgentTagsGet.option('--format <text|json>', texts_1.OPTION_FORMAT);
11
+ commandAgentTagsGet.action(async (options) => {
11
12
  const ApiAgent = require('../../../tunnel/api/agent').default;
12
13
  if (!commandAgentTagsGet.agentInstalled) {
13
14
  output_1.default.exitError(texts_1.ERR_AGENT_NOT_INSTALLED);
@@ -18,6 +19,12 @@ commandAgentTagsGet.action(async () => {
18
19
  try {
19
20
  const api = new ApiAgent(commandAgentTagsGet.agentPort || 0);
20
21
  const data = await api.fetchAgentTags();
22
+ if (options.format === 'json') {
23
+ output_1.default.json(data);
24
+ if (!data.tags?.length)
25
+ output_1.default.exitError(texts_1.TXT_AGENT_NO_TAGS);
26
+ output_1.default.exitNormal();
27
+ }
21
28
  if (data.tags && data.tags.length > 0) {
22
29
  output_1.default.exitNormal(data.tags.join(' '));
23
30
  }
@@ -6,8 +6,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const utils_1 = require("../../../utils");
7
7
  const texts_1 = require("../../../texts");
8
8
  const output_1 = __importDefault(require("../../../output"));
9
- const commandAgentTargetStatus = (0, utils_1.newCommand)('status', texts_1.DESC_COMMAND_AGENT_TARGET_STATUS);
10
- commandAgentTargetStatus.action(async () => {
9
+ const commandAgentTargetStatus = (0, utils_1.newCommand)('status', texts_1.DESC_COMMAND_AGENT_TARGET_STATUS, { examples: texts_1.EXAMPLE_AGENT_TARGET_STATUS });
10
+ commandAgentTargetStatus.option('--format <text|json>', texts_1.OPTION_FORMAT);
11
+ commandAgentTargetStatus.action(async (options) => {
11
12
  const ApiAgent = require('../../../tunnel/api/agent').default;
12
13
  if (!commandAgentTargetStatus.agentInstalled) {
13
14
  output_1.default.exitError(texts_1.ERR_AGENT_NOT_INSTALLED);
@@ -18,6 +19,12 @@ commandAgentTargetStatus.action(async () => {
18
19
  try {
19
20
  const api = new ApiAgent(commandAgentTargetStatus.agentPort || 0);
20
21
  const data = await api.fetchAgentTarget();
22
+ if (options.format === 'json') {
23
+ output_1.default.json(data);
24
+ if (!data.target)
25
+ output_1.default.exitError(texts_1.TXT_AGENT_TARGET_DISABLED);
26
+ output_1.default.exitNormal();
27
+ }
21
28
  if (data.target) {
22
29
  output_1.default.exitSuccess(texts_1.TXT_AGENT_TARGET_ENABLED);
23
30
  }
@@ -8,7 +8,8 @@ const texts_1 = require("../../../texts");
8
8
  const utils_1 = require("../../../utils");
9
9
  const commandAgentTunnelList = (0, utils_1.newCommand)('list', texts_1.DESC_COMMAND_AGENT_TUNNEL_LIST, { examples: texts_1.EXAMPLE_AGENT_TUNNEL_LIST });
10
10
  commandAgentTunnelList.alias('ls');
11
- commandAgentTunnelList.action(async () => {
11
+ commandAgentTunnelList.option('--format <text|json>', texts_1.OPTION_FORMAT);
12
+ commandAgentTunnelList.action(async (options) => {
12
13
  const ApiAgent = require('../../../tunnel/api/agent').default;
13
14
  if (!commandAgentTunnelList.agentInstalled) {
14
15
  output_1.default.exitError(texts_1.ERR_AGENT_NOT_INSTALLED);
@@ -19,6 +20,10 @@ commandAgentTunnelList.action(async () => {
19
20
  try {
20
21
  const api = new ApiAgent(commandAgentTunnelList.agentPort || 0);
21
22
  const data = await api.fetchTunnels();
23
+ if (options.format === 'json') {
24
+ output_1.default.json(data);
25
+ output_1.default.exitNormal();
26
+ }
22
27
  output_1.default.agentTunnels(data.tunnels);
23
28
  }
24
29
  catch (err) {
@@ -8,7 +8,8 @@ const output_1 = __importDefault(require("../../../output"));
8
8
  const utils_1 = require("../../../utils");
9
9
  const commandAgentTunnelStatus = (0, utils_1.newCommand)('status', texts_1.DESC_COMMAND_AGENT_TUNNEL_STATUS, { examples: texts_1.EXAMPLE_AGENT_TUNNEL_STATUS });
10
10
  commandAgentTunnelStatus.argument('[id]', texts_1.OPTION_ID);
11
- commandAgentTunnelStatus.action(async (id) => {
11
+ commandAgentTunnelStatus.option('--format <text|json>', texts_1.OPTION_FORMAT);
12
+ commandAgentTunnelStatus.action(async (id, options) => {
12
13
  const ApiAgent = require('../../../tunnel/api/agent').default;
13
14
  const AgentSocketTunnel = require('../../../agent/socket/tunnel').default;
14
15
  if (!commandAgentTunnelStatus.agentInstalled) {
@@ -21,6 +22,12 @@ commandAgentTunnelStatus.action(async (id) => {
21
22
  const api = new ApiAgent(commandAgentTunnelStatus.agentPort || 0);
22
23
  if (!id) {
23
24
  const data = await api.fetchAgentTunneling();
25
+ if (options.format === 'json') {
26
+ output_1.default.json(data);
27
+ if (!data.tunneling)
28
+ output_1.default.exitError(texts_1.TXT_AGENT_TUNNELING_DISABLED);
29
+ output_1.default.exitNormal();
30
+ }
24
31
  if (data.tunneling) {
25
32
  output_1.default.exitSuccess(texts_1.TXT_AGENT_TUNNELING_ENABLED);
26
33
  }
@@ -32,6 +39,11 @@ commandAgentTunnelStatus.action(async (id) => {
32
39
  const ws = await api.socketTunnel(id);
33
40
  const tunnel = new AgentSocketTunnel(ws, id);
34
41
  await tunnel.waitForReady();
42
+ if (options.format === 'json') {
43
+ output_1.default.json(tunnel.rawStatus);
44
+ tunnel.close();
45
+ output_1.default.exitNormal();
46
+ }
35
47
  output_1.default.tunnel(tunnel);
36
48
  }
37
49
  catch (err) {
@@ -6,14 +6,19 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const output_1 = __importDefault(require("../../output"));
7
7
  const texts_1 = require("../../texts");
8
8
  const utils_1 = require("../../utils");
9
- const commandAgentVersion = (0, utils_1.newCommand)('version', texts_1.DESC_COMMAND_AGENT_VERSION);
10
- commandAgentVersion.action(async () => {
9
+ const commandAgentVersion = (0, utils_1.newCommand)('version', texts_1.DESC_COMMAND_AGENT_VERSION, { examples: texts_1.EXAMPLE_AGENT_VERSION });
10
+ commandAgentVersion.option('--format <text|json>', texts_1.OPTION_FORMAT);
11
+ commandAgentVersion.action(async (options) => {
11
12
  if (!commandAgentVersion.agentInstalled) {
12
13
  output_1.default.exitError(texts_1.ERR_AGENT_NOT_INSTALLED);
13
14
  }
14
15
  if (!commandAgentVersion.agentStatus) {
15
16
  output_1.default.exitError(texts_1.ERR_AGENT_NOT_RUNNING);
16
17
  }
18
+ if (options.format === 'json') {
19
+ output_1.default.json(commandAgentVersion.agentStatus);
20
+ output_1.default.exitNormal();
21
+ }
17
22
  output_1.default.exitNormal(commandAgentVersion.agentStatus.version);
18
23
  });
19
24
  exports.default = commandAgentVersion;
@@ -9,6 +9,8 @@ const input_1 = __importDefault(require("../../input"));
9
9
  const output_1 = __importDefault(require("../../output"));
10
10
  /** `members` keeps declaration order, which numbers the listing; `values` does not. */
11
11
  const orderedMembers = (body) => {
12
+ if (body.kind === 'array')
13
+ return orderedMembers(body.items);
12
14
  if (body.kind !== 'union')
13
15
  return [];
14
16
  const members = body.discriminator?.members;
@@ -217,15 +219,19 @@ const chosen = (asked, names) => {
217
219
  const position = Number(asked);
218
220
  return Number.isInteger(position) && position >= 1 ? names[position - 1] : undefined;
219
221
  };
220
- const commandApiInfo = (0, utils_1.newCommand)('info', texts_1.DESC_COMMAND_API_INFO);
222
+ const commandApiInfo = (0, utils_1.newCommand)('info', texts_1.DESC_COMMAND_API_INFO, {
223
+ helpText: texts_1.HELP_API_INFO_JSON,
224
+ examples: texts_1.EXAMPLE_API_INFO,
225
+ });
221
226
  commandApiInfo.option('-w, --workspace <domain>', texts_1.OPTION_REST_API_WORKSPACE);
222
227
  commandApiInfo.option('-p, --project <name>', texts_1.OPTION_REST_API_PROJECT);
223
228
  commandApiInfo.option('--request-schema <schema>', texts_1.OPT_COMMAND_API_INFO_SCHEMA);
224
229
  commandApiInfo.option('--response-schema <schema>', texts_1.OPT_COMMAND_API_INFO_SCHEMA);
225
230
  commandApiInfo.argument('<method>', texts_1.OPT_COMMAND_API_INFO_METHOD);
226
231
  commandApiInfo.argument('<url>', texts_1.OPT_COMMAND_API_INFO_URL);
232
+ commandApiInfo.option('--format <text|json>', texts_1.OPTION_FORMAT);
227
233
  commandApiInfo.action(async (fm, filterUrl, options) => {
228
- const { apiEndpoints } = require('../../openapi');
234
+ const { apiEndpoints, apiSpecification } = require('../../openapi');
229
235
  const workspace = input_1.default.restApiWorkspace(options.workspace);
230
236
  const project = input_1.default.restApiProject(options.project, true);
231
237
  const method = (input_1.default.restApiMethod(fm, false) ?? '').toLowerCase();
@@ -246,6 +252,48 @@ commandApiInfo.action(async (fm, filterUrl, options) => {
246
252
  const namesOf = (bodies) => bodies.flatMap((body) => [...orderedMembers(body)]);
247
253
  const request = chosen(options.requestSchema, namesOf((listed.request?.contents ?? []).map((content) => content.body)));
248
254
  const response = chosen(options.responseSchema, namesOf(listed.responses.flatMap((one) => one.contents.map((content) => content.body))));
255
+ if (options.format === 'json') {
256
+ const raw = await apiSpecification(client.baseUrl);
257
+ const schema = (asked, selected, bodies) => {
258
+ const refs = [];
259
+ for (let body of bodies) {
260
+ while (body.kind === 'array')
261
+ body = body.items;
262
+ if (body.kind === 'union' && selected) {
263
+ const name = memberDef(body, selected);
264
+ if (name && raw.components?.schemas?.[name])
265
+ return raw.components.schemas[name];
266
+ }
267
+ if ('ref' in body && body.ref)
268
+ refs.push(body.ref.name);
269
+ }
270
+ // Union positions use the same member list as text output. Only bodies
271
+ // without unions use reference positions across response/content order.
272
+ const name = refs.includes(asked)
273
+ ? asked
274
+ : namesOf(bodies).length === 0
275
+ ? chosen(asked, refs)
276
+ : undefined;
277
+ if (name && raw.components?.schemas?.[name])
278
+ return raw.components.schemas[name];
279
+ output_1.default.exitError((0, texts_1.ERR_API_SCHEMA_NOT_FOUND)(asked));
280
+ };
281
+ const requestSchema = options.requestSchema
282
+ ? schema(options.requestSchema, request, (listed.request?.contents ?? []).map((content) => content.body))
283
+ : undefined;
284
+ const responseSchema = options.responseSchema
285
+ ? schema(options.responseSchema, response, listed.responses.flatMap((one) => one.contents.map((content) => content.body)))
286
+ : undefined;
287
+ output_1.default.json(requestSchema && responseSchema
288
+ ? { request: requestSchema, response: responseSchema }
289
+ : (requestSchema ??
290
+ responseSchema ?? {
291
+ method: wanted.method.toUpperCase(),
292
+ path: (0, utils_1.openApiPrepareUrl)(wanted.path, workspace, project),
293
+ operation: raw.paths[wanted.path][method],
294
+ }));
295
+ output_1.default.exitNormal();
296
+ }
249
297
  const member = request || response
250
298
  ? { ...(request ? { request } : {}), ...(response ? { response } : {}) }
251
299
  : undefined;
@@ -25,12 +25,15 @@ const outputUrl = (operation, workspace, project) => {
25
25
  if (operation.summary)
26
26
  output_1.default.gray(operation.summary);
27
27
  };
28
- const commandApiList = (0, utils_1.newCommand)('list', texts_1.DESC_COMMAND_API_LIST);
28
+ const commandApiList = (0, utils_1.newCommand)('list', texts_1.DESC_COMMAND_API_LIST, {
29
+ examples: texts_1.EXAMPLE_API_LIST,
30
+ });
29
31
  commandApiList.alias('ls');
30
32
  commandApiList.option('-w, --workspace <domain>', texts_1.OPTION_REST_API_WORKSPACE);
31
33
  commandApiList.option('-p, --project <name>', texts_1.OPTION_REST_API_PROJECT);
32
34
  commandApiList.option('-m, --method <method>', texts_1.OPT_COMMAND_API_LIST_METHOD);
33
35
  commandApiList.option('-s, --search <phrase>', texts_1.OPT_COMMAND_API_LIST_SEARCH);
36
+ commandApiList.option('--format <text|json>', texts_1.OPTION_FORMAT);
34
37
  commandApiList.action(async (options) => {
35
38
  const { apiEndpoints } = require('../../openapi');
36
39
  const workspace = input_1.default.restApiWorkspace(options.workspace);
@@ -39,12 +42,25 @@ commandApiList.action(async (options) => {
39
42
  const filter = options.search;
40
43
  const client = input_1.default.restApiTokenClient(true);
41
44
  const api = await apiEndpoints(client.baseUrl);
45
+ const operations = [];
42
46
  for (const operation of await api.operations()) {
43
47
  if (filter && !operation.path.includes(filter))
44
48
  continue;
45
49
  if (filterMethod && filterMethod !== operation.method.toLowerCase())
46
50
  continue;
47
- outputUrl(operation, workspace, project);
51
+ if (options.format === 'json') {
52
+ operations.push({
53
+ method: operation.method.toUpperCase(),
54
+ path: (0, utils_1.openApiPrepareUrl)(operation.path, workspace, project),
55
+ summary: operation.summary,
56
+ });
57
+ }
58
+ else {
59
+ outputUrl(operation, workspace, project);
60
+ }
61
+ }
62
+ if (options.format === 'json') {
63
+ output_1.default.json(operations);
48
64
  }
49
65
  output_1.default.exitNormal();
50
66
  });
@@ -23,6 +23,7 @@ commandArtifactCreate.option('-a, --auth <user:pass>', texts_1.OPTION_HTTP_AUTH)
23
23
  commandArtifactCreate.option('--note <note>', texts_1.OPT_COMMAND_NOTE);
24
24
  commandArtifactCreate.option('--agent-note <note>', texts_1.OPT_COMMAND_AGENT_NOTE);
25
25
  commandArtifactCreate.argument('[name]', texts_1.OPT_COMMAND_ARTIFACT_NAME);
26
+ commandArtifactCreate.option('--format <text|json>', texts_1.OPTION_FORMAT);
26
27
  commandArtifactCreate.action(async (name, options) => {
27
28
  const humanId = require('human-id').default;
28
29
  const workspace = input_1.default.restApiWorkspace(options.workspace);
@@ -71,6 +72,10 @@ commandArtifactCreate.action(async (name, options) => {
71
72
  };
72
73
  }
73
74
  const result = await client.createArtifact(workspace, data);
75
+ if (options.format === 'json') {
76
+ output_1.default.json(result);
77
+ output_1.default.exitNormal();
78
+ }
74
79
  output_1.default.exitSuccess((0, texts_1.TXT_ARTIFACT_CREATED)(client.baseUrl, workspace, result));
75
80
  });
76
81
  exports.default = commandArtifactCreate;