@devicecloud.dev/dcd 5.0.0-beta.0 → 5.0.0-beta.1

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 (101) hide show
  1. package/README.md +35 -0
  2. package/dist/commands/artifacts.d.ts +28 -28
  3. package/dist/commands/artifacts.js +20 -23
  4. package/dist/commands/cloud.d.ts +57 -57
  5. package/dist/commands/cloud.js +173 -186
  6. package/dist/commands/list.d.ts +22 -22
  7. package/dist/commands/list.js +36 -38
  8. package/dist/commands/live.js +134 -127
  9. package/dist/commands/login.d.ts +11 -11
  10. package/dist/commands/login.js +46 -44
  11. package/dist/commands/logout.js +16 -18
  12. package/dist/commands/status.d.ts +11 -11
  13. package/dist/commands/status.js +45 -43
  14. package/dist/commands/switch-org.d.ts +7 -7
  15. package/dist/commands/switch-org.js +19 -21
  16. package/dist/commands/upgrade.js +29 -31
  17. package/dist/commands/upload.d.ts +10 -10
  18. package/dist/commands/upload.js +42 -43
  19. package/dist/commands/whoami.js +17 -20
  20. package/dist/config/environments.js +6 -12
  21. package/dist/config/flags/api.flags.js +1 -4
  22. package/dist/config/flags/binary.flags.js +1 -4
  23. package/dist/config/flags/device.flags.js +6 -9
  24. package/dist/config/flags/environment.flags.js +1 -4
  25. package/dist/config/flags/execution.flags.js +1 -4
  26. package/dist/config/flags/github.flags.js +1 -4
  27. package/dist/config/flags/output.flags.js +1 -4
  28. package/dist/constants.js +15 -18
  29. package/dist/gateways/api-gateway.d.ts +31 -6
  30. package/dist/gateways/api-gateway.js +70 -16
  31. package/dist/gateways/cli-auth-gateway.d.ts +1 -1
  32. package/dist/gateways/cli-auth-gateway.js +3 -6
  33. package/dist/gateways/realtime-gateway.d.ts +32 -0
  34. package/dist/gateways/realtime-gateway.js +103 -0
  35. package/dist/gateways/supabase-gateway.d.ts +1 -1
  36. package/dist/gateways/supabase-gateway.js +10 -14
  37. package/dist/index.js +41 -38
  38. package/dist/mcp/context.d.ts +33 -0
  39. package/dist/mcp/context.js +33 -0
  40. package/dist/mcp/helpers.d.ts +16 -0
  41. package/dist/mcp/helpers.js +34 -0
  42. package/dist/mcp/index.d.ts +2 -0
  43. package/dist/mcp/index.js +24 -0
  44. package/dist/mcp/server.d.ts +7 -0
  45. package/dist/mcp/server.js +27 -0
  46. package/dist/mcp/tools/download-artifacts.d.ts +11 -0
  47. package/dist/mcp/tools/download-artifacts.js +84 -0
  48. package/dist/mcp/tools/get-status.d.ts +7 -0
  49. package/dist/mcp/tools/get-status.js +39 -0
  50. package/dist/mcp/tools/list-devices.d.ts +7 -0
  51. package/dist/mcp/tools/list-devices.js +27 -0
  52. package/dist/mcp/tools/list-runs.d.ts +3 -0
  53. package/dist/mcp/tools/list-runs.js +60 -0
  54. package/dist/mcp/tools/run-cloud-test.d.ts +14 -0
  55. package/dist/mcp/tools/run-cloud-test.js +233 -0
  56. package/dist/methods.d.ts +32 -1
  57. package/dist/methods.js +125 -66
  58. package/dist/services/device-validation.service.d.ts +1 -1
  59. package/dist/services/device-validation.service.js +1 -5
  60. package/dist/services/execution-plan.service.js +14 -17
  61. package/dist/services/execution-plan.utils.js +15 -23
  62. package/dist/services/flow-paths.d.ts +17 -0
  63. package/dist/services/flow-paths.js +52 -0
  64. package/dist/services/metadata-extractor.service.js +22 -25
  65. package/dist/services/moropo.service.js +18 -20
  66. package/dist/services/report-download.service.d.ts +1 -1
  67. package/dist/services/report-download.service.js +5 -9
  68. package/dist/services/results-polling.service.d.ts +18 -3
  69. package/dist/services/results-polling.service.js +195 -108
  70. package/dist/services/telemetry.service.d.ts +10 -1
  71. package/dist/services/telemetry.service.js +40 -18
  72. package/dist/services/test-submission.service.d.ts +21 -4
  73. package/dist/services/test-submission.service.js +51 -34
  74. package/dist/services/version.service.d.ts +1 -1
  75. package/dist/services/version.service.js +1 -5
  76. package/dist/types/domain/auth.types.d.ts +8 -0
  77. package/dist/types/domain/auth.types.js +1 -2
  78. package/dist/types/domain/device.types.js +8 -11
  79. package/dist/types/domain/live.types.js +1 -2
  80. package/dist/types/generated/schema.types.js +1 -2
  81. package/dist/types/index.d.ts +2 -2
  82. package/dist/types/index.js +2 -18
  83. package/dist/types.js +1 -2
  84. package/dist/utils/auth.d.ts +1 -1
  85. package/dist/utils/auth.js +27 -28
  86. package/dist/utils/ci.d.ts +12 -0
  87. package/dist/utils/ci.js +39 -0
  88. package/dist/utils/cli.js +18 -27
  89. package/dist/utils/compatibility.d.ts +1 -1
  90. package/dist/utils/compatibility.js +5 -7
  91. package/dist/utils/config-store.js +33 -43
  92. package/dist/utils/connectivity.js +1 -4
  93. package/dist/utils/expo.js +15 -21
  94. package/dist/utils/orgs.js +8 -12
  95. package/dist/utils/paths.js +2 -5
  96. package/dist/utils/progress.js +2 -5
  97. package/dist/utils/styling.d.ts +35 -37
  98. package/dist/utils/styling.js +52 -86
  99. package/dist/utils/ui.d.ts +41 -0
  100. package/dist/utils/ui.js +95 -0
  101. package/package.json +27 -24
@@ -1,6 +1,3 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.loginCommand = void 0;
4
1
  /**
5
2
  * `dcd login` — delegates auth to the web frontend and persists the resulting
6
3
  * Supabase session locally.
@@ -28,20 +25,21 @@ exports.loginCommand = void 0;
28
25
  * rendezvous endpoint makes both cases trivial. A leaked URL is inert: it
29
26
  * carries the challenge, not the verifier.
30
27
  */
31
- const p = require("@clack/prompts");
32
- const citty_1 = require("citty");
33
- const node_child_process_1 = require("node:child_process");
34
- const node_crypto_1 = require("node:crypto");
35
- const environments_1 = require("../config/environments");
36
- const cli_1 = require("../utils/cli");
37
- const config_store_1 = require("../utils/config-store");
38
- const orgs_1 = require("../utils/orgs");
39
- const styling_1 = require("../utils/styling");
28
+ import * as p from '@clack/prompts';
29
+ import { defineCommand } from 'citty';
30
+ import { spawn } from 'node:child_process';
31
+ import { createHash, randomBytes } from 'node:crypto';
32
+ import { ENVIRONMENTS, inferEnvFromApiUrl, resolveFrontendUrl } from '../config/environments.js';
33
+ import { CliError, logger } from '../utils/cli.js';
34
+ import { readConfig, writeConfig } from '../utils/config-store.js';
35
+ import { fetchOrgs, pickOrg } from '../utils/orgs.js';
36
+ import { colors } from '../utils/styling.js';
37
+ import { ui } from '../utils/ui.js';
40
38
  const LOGIN_TIMEOUT_MS = 10 * 60 * 1000;
41
39
  // 1s feels snappy after the user clicks "Authorize" in the browser. The api
42
40
  // rate-limits /cli-login/claim at 60/min which is exactly this cadence.
43
41
  const POLL_INTERVAL_MS = 1000;
44
- exports.loginCommand = (0, citty_1.defineCommand)({
42
+ export const loginCommand = defineCommand({
45
43
  meta: {
46
44
  name: 'login',
47
45
  description: 'Authenticate with devicecloud.dev via your browser',
@@ -71,7 +69,7 @@ exports.loginCommand = (0, citty_1.defineCommand)({
71
69
  // If there's an existing stored session, make the user confirm before we
72
70
  // overwrite it. Silent clobber is fine for power users but surprising if
73
71
  // someone runs `dcd login` by mistake while already authenticated.
74
- const existing = (0, config_store_1.readConfig)();
72
+ const existing = readConfig();
75
73
  if (existing?.session) {
76
74
  const currentOrg = existing.current_org_name ?? existing.current_org_id;
77
75
  const ok = await p.confirm({
@@ -81,35 +79,37 @@ exports.loginCommand = (0, citty_1.defineCommand)({
81
79
  initialValue: false,
82
80
  });
83
81
  if (p.isCancel(ok) || !ok) {
84
- cli_1.logger.log(`${styling_1.symbols.info} Keeping existing session.`);
82
+ logger.log(ui.info('Keeping existing session.'));
85
83
  return;
86
84
  }
87
85
  }
88
- const env = (0, environments_1.inferEnvFromApiUrl)(apiUrl);
89
- const SUPABASE_URL = environments_1.ENVIRONMENTS[env].supabase.url;
90
- const frontendUrl = (frontendOverride ?? (0, environments_1.resolveFrontendUrl)(apiUrl)).replace(/\/$/, '');
91
- const state = (0, node_crypto_1.randomBytes)(32).toString('hex');
92
- const codeVerifier = base64url((0, node_crypto_1.randomBytes)(32));
93
- const codeChallenge = base64url((0, node_crypto_1.createHash)('sha256').update(codeVerifier, 'ascii').digest());
86
+ const env = inferEnvFromApiUrl(apiUrl);
87
+ const SUPABASE_URL = ENVIRONMENTS[env].supabase.url;
88
+ const frontendUrl = (frontendOverride ?? resolveFrontendUrl(apiUrl)).replace(/\/$/, '');
89
+ const state = randomBytes(32).toString('hex');
90
+ const codeVerifier = base64url(randomBytes(32));
91
+ const codeChallenge = base64url(createHash('sha256').update(codeVerifier, 'ascii').digest());
94
92
  const loginUrl = `${frontendUrl}/cli-login?state=${state}&code_challenge=${codeChallenge}`;
95
- cli_1.logger.log((0, styling_1.sectionHeader)('Signing in to devicecloud.dev'));
93
+ logger.log(ui.section('Signing in to devicecloud.dev'));
94
+ const rows = [];
96
95
  if (noBrowser) {
97
- cli_1.logger.log(` ${styling_1.colors.dim('Open this URL in a browser to finish login:')}`);
98
- cli_1.logger.log(` ${styling_1.colors.highlight(loginUrl)}`);
96
+ rows.push(ui.note('Open this URL in a browser to finish login:'), colors.highlight(loginUrl));
99
97
  }
100
98
  else {
101
- cli_1.logger.log(` ${styling_1.colors.dim('Opening your browser...')}`);
102
99
  const opened = openBrowser(loginUrl);
100
+ rows.push(ui.note(opened
101
+ ? 'Opening your browser…'
102
+ : 'Could not launch a browser. Open this URL manually:'));
103
103
  if (!opened) {
104
- cli_1.logger.log(` ${styling_1.colors.dim('Could not launch a browser. Open this URL manually:')}`);
105
- cli_1.logger.log(` ${styling_1.colors.highlight(loginUrl)}`);
104
+ rows.push(colors.highlight(loginUrl));
106
105
  }
107
106
  }
108
- cli_1.logger.log(` ${styling_1.colors.dim('Waiting for login to complete...')}\n`);
107
+ rows.push(ui.note('Waiting for login to complete'));
108
+ logger.log(ui.branch(rows));
109
109
  try {
110
110
  const payload = await pollForClaim(apiUrl, state, codeVerifier);
111
111
  if (!payload.access_token || typeof payload.access_token !== 'string') {
112
- throw new cli_1.CliError('Claim response did not include an access_token.');
112
+ throw new CliError('Claim response did not include an access_token.');
113
113
  }
114
114
  // Fetch orgs with the just-minted Bearer token. We don't write the
115
115
  // config until the user has picked an org — half-completed state isn't
@@ -119,7 +119,7 @@ exports.loginCommand = (0, citty_1.defineCommand)({
119
119
  };
120
120
  let orgs;
121
121
  try {
122
- orgs = await (0, orgs_1.fetchOrgs)(apiUrl, bearerHeaders);
122
+ orgs = await fetchOrgs(apiUrl, bearerHeaders);
123
123
  }
124
124
  catch (err) {
125
125
  // The api rejected the JWT. Before blaming the api, hit Supabase's
@@ -127,15 +127,15 @@ exports.loginCommand = (0, citty_1.defineCommand)({
127
127
  // rejects it, the token itself is bad; if Supabase accepts, the
128
128
  // api's JwtService is misconfigured.
129
129
  const iss = decodeJwtIssuer(payload.access_token);
130
- const supabaseUser = await probeSupabaseUser(iss, environments_1.ENVIRONMENTS[env].supabase.anonKey, payload.access_token);
130
+ const supabaseUser = await probeSupabaseUser(iss, ENVIRONMENTS[env].supabase.anonKey, payload.access_token);
131
131
  const parts = [
132
132
  iss ? `token iss: ${iss}` : null,
133
133
  `supabase /auth/v1/user: ${supabaseUser}`,
134
134
  ].filter(Boolean);
135
- throw new cli_1.CliError(`${err.message} [${parts.join(' | ')}]`);
135
+ throw new CliError(`${err.message} [${parts.join(' | ')}]`);
136
136
  }
137
- const chosen = await (0, orgs_1.pickOrg)(orgs);
138
- (0, config_store_1.writeConfig)({
137
+ const chosen = await pickOrg(orgs);
138
+ writeConfig({
139
139
  version: 1,
140
140
  env,
141
141
  api_url: apiUrl,
@@ -150,14 +150,16 @@ exports.loginCommand = (0, citty_1.defineCommand)({
150
150
  current_org_id: chosen.id,
151
151
  current_org_name: chosen.name,
152
152
  });
153
- cli_1.logger.log(`${styling_1.symbols.success} ${styling_1.colors.bold('Logged in')} as ${styling_1.colors.highlight(payload.user_email)}`);
154
- cli_1.logger.log(` ${styling_1.colors.dim('Organization:')} ${styling_1.colors.highlight(chosen.name)}`);
155
- if (orgs.length > 1) {
156
- cli_1.logger.log(` ${styling_1.colors.dim('Switch orgs later with')} ${styling_1.colors.highlight('dcd switch-org')}`);
157
- }
153
+ logger.log(ui.success(`Logged in as ${colors.highlight(payload.user_email)}`));
154
+ logger.log(ui.branch([
155
+ ...ui.fields([['organization', colors.highlight(chosen.name)]]),
156
+ ...(orgs.length > 1
157
+ ? [ui.note(`Switch orgs later with ${colors.highlight('dcd switch-org')}`)]
158
+ : []),
159
+ ]));
158
160
  }
159
161
  catch (error) {
160
- cli_1.logger.error(error, { exit: 1 });
162
+ logger.error(error, { exit: 1 });
161
163
  }
162
164
  },
163
165
  });
@@ -168,7 +170,7 @@ function openBrowser(url) {
168
170
  ? { bin: 'cmd', args: ['/c', 'start', '""', url] }
169
171
  : { bin: 'xdg-open', args: [url] };
170
172
  try {
171
- const child = (0, node_child_process_1.spawn)(cmd.bin, cmd.args, { detached: true, stdio: 'ignore' });
173
+ const child = spawn(cmd.bin, cmd.args, { detached: true, stdio: 'ignore' });
172
174
  child.unref();
173
175
  return true;
174
176
  }
@@ -207,9 +209,9 @@ async function pollForClaim(apiUrl, state, codeVerifier) {
207
209
  continue;
208
210
  }
209
211
  const body = await res.text().catch(() => '');
210
- throw new cli_1.CliError(`Login failed: ${res.status} ${res.statusText}${body ? ` — ${body}` : ''}`);
212
+ throw new CliError(`Login failed: ${res.status} ${res.statusText}${body ? ` — ${body}` : ''}`);
211
213
  }
212
- throw new cli_1.CliError('Login timed out. Please run `dcd login` again.');
214
+ throw new CliError('Login timed out. Please run `dcd login` again.');
213
215
  }
214
216
  function base64url(buf) {
215
217
  return buf.toString('base64').replaceAll('+', '-').replaceAll('/', '_').replace(/=+$/, '');
@@ -247,4 +249,4 @@ function decodeJwtIssuer(token) {
247
249
  function sleep(ms) {
248
250
  return new Promise((resolve) => setTimeout(resolve, ms));
249
251
  }
250
- exports.default = exports.loginCommand;
252
+ export default loginCommand;
@@ -1,32 +1,30 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.logoutCommand = void 0;
4
1
  /**
5
2
  * `dcd logout` — clears the stored Supabase session and best-effort revokes
6
3
  * it on Supabase. Leaves DEVICE_CLOUD_API_KEY (env/flag) untouched.
7
4
  */
8
- const citty_1 = require("citty");
9
- const environments_1 = require("../config/environments");
10
- const cli_auth_gateway_1 = require("../gateways/cli-auth-gateway");
11
- const cli_1 = require("../utils/cli");
12
- const config_store_1 = require("../utils/config-store");
13
- const styling_1 = require("../utils/styling");
14
- exports.logoutCommand = (0, citty_1.defineCommand)({
5
+ import { defineCommand } from 'citty';
6
+ import { ENVIRONMENTS } from '../config/environments.js';
7
+ import { CliAuthGateway } from '../gateways/cli-auth-gateway.js';
8
+ import { logger } from '../utils/cli.js';
9
+ import { clearConfig, getConfigPath, readConfig } from '../utils/config-store.js';
10
+ import { colors } from '../utils/styling.js';
11
+ import { ui } from '../utils/ui.js';
12
+ export const logoutCommand = defineCommand({
15
13
  meta: {
16
14
  name: 'logout',
17
15
  description: 'Clear the stored devicecloud.dev session',
18
16
  },
19
17
  async run() {
20
- const config = (0, config_store_1.readConfig)();
18
+ const config = readConfig();
21
19
  if (!config?.session) {
22
- cli_1.logger.log(`${styling_1.symbols.info} No active session found (${styling_1.colors.dim((0, config_store_1.getConfigPath)())}).`);
23
- (0, config_store_1.clearConfig)();
20
+ logger.log(ui.info(`No active session found (${colors.dim(getConfigPath())}).`));
21
+ clearConfig();
24
22
  return;
25
23
  }
26
- const { anonKey } = environments_1.ENVIRONMENTS[config.env].supabase;
27
- await cli_auth_gateway_1.CliAuthGateway.signOut(config.supabase_url, anonKey, config.session);
28
- (0, config_store_1.clearConfig)();
29
- cli_1.logger.log(`${styling_1.symbols.success} Logged out ${styling_1.colors.dim(`(${config.session.user_email})`)}.`);
24
+ const { anonKey } = ENVIRONMENTS[config.env].supabase;
25
+ await CliAuthGateway.signOut(config.supabase_url, anonKey, config.session);
26
+ clearConfig();
27
+ logger.log(ui.success(`Logged out ${colors.dim(`(${config.session.user_email})`)}`));
30
28
  },
31
29
  });
32
- exports.default = exports.logoutCommand;
30
+ export default logoutCommand;
@@ -1,22 +1,22 @@
1
1
  export declare const statusCommand: import("citty").CommandDef<{
2
- json: {
3
- type: "boolean";
4
- description: string;
2
+ readonly json: {
3
+ readonly type: "boolean";
4
+ readonly description: "output in json format";
5
5
  };
6
- name: {
7
- type: "string";
8
- description: string;
6
+ readonly name: {
7
+ readonly type: "string";
8
+ readonly description: "Name of the upload to check status for";
9
9
  };
10
- 'upload-id': {
11
- type: "string";
12
- description: string;
10
+ readonly 'upload-id': {
11
+ readonly type: "string";
12
+ readonly description: "UUID of the upload to check status for";
13
13
  };
14
- 'api-key': {
14
+ readonly 'api-key': {
15
15
  readonly type: "string";
16
16
  readonly alias: ["apiKey"];
17
17
  readonly description: "API key for devicecloud.dev (find this in the console UI). You can also set the DEVICE_CLOUD_API_KEY environment variable.";
18
18
  };
19
- 'api-url': {
19
+ readonly 'api-url': {
20
20
  readonly type: "string";
21
21
  readonly alias: ["apiURL", "apiUrl"];
22
22
  readonly description: "API base URL (defaults to the URL stored by `dcd login`, else prod)";
@@ -1,15 +1,13 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.statusCommand = void 0;
4
- const citty_1 = require("citty");
5
- const api_flags_1 = require("../config/flags/api.flags");
6
- const api_gateway_1 = require("../gateways/api-gateway");
7
- const methods_1 = require("../methods");
8
- const auth_1 = require("../utils/auth");
9
- const cli_1 = require("../utils/cli");
10
- const config_store_1 = require("../utils/config-store");
11
- const connectivity_1 = require("../utils/connectivity");
12
- const styling_1 = require("../utils/styling");
1
+ import { defineCommand } from 'citty';
2
+ import { apiFlags } from '../config/flags/api.flags.js';
3
+ import { ApiGateway } from '../gateways/api-gateway.js';
4
+ import { formatDurationSeconds } from '../methods.js';
5
+ import { resolveAuth } from '../utils/auth.js';
6
+ import { CliError, logger } from '../utils/cli.js';
7
+ import { resolveApiUrl } from '../utils/config-store.js';
8
+ import { checkInternetConnectivity, } from '../utils/connectivity.js';
9
+ import { colors, formatId, formatUrl } from '../utils/styling.js';
10
+ import { ui } from '../utils/ui.js';
13
11
  /** Errors the API gateway surfaces for 4xx-class failures — retrying is pointless. */
14
12
  function isClientApiError(error) {
15
13
  if (!error)
@@ -30,13 +28,13 @@ function formatDateTime(isoString) {
30
28
  return isoString;
31
29
  }
32
30
  }
33
- exports.statusCommand = (0, citty_1.defineCommand)({
31
+ export const statusCommand = defineCommand({
34
32
  meta: {
35
33
  name: 'status',
36
34
  description: 'Get the status of an upload by name or upload ID',
37
35
  },
38
36
  args: {
39
- ...api_flags_1.apiFlags,
37
+ ...apiFlags,
40
38
  json: {
41
39
  type: 'boolean',
42
40
  description: 'output in json format',
@@ -53,7 +51,7 @@ exports.statusCommand = (0, citty_1.defineCommand)({
53
51
  // eslint-disable-next-line complexity
54
52
  async run({ args }) {
55
53
  const apiKeyFlag = args['api-key'];
56
- const apiUrl = (0, config_store_1.resolveApiUrl)(args['api-url']);
54
+ const apiUrl = resolveApiUrl(args['api-url']);
57
55
  const json = Boolean(args.json);
58
56
  const name = args.name;
59
57
  const uploadId = args['upload-id'];
@@ -61,17 +59,17 @@ exports.statusCommand = (0, citty_1.defineCommand)({
61
59
  await statusMain({ apiKeyFlag, apiUrl, json, name, uploadId });
62
60
  }
63
61
  catch (error) {
64
- cli_1.logger.error(error, { exit: 1, json });
62
+ logger.error(error, { exit: 1, json });
65
63
  }
66
64
  },
67
65
  });
68
66
  async function statusMain({ apiKeyFlag, apiUrl, json, name, uploadId, }) {
69
- const auth = await (0, auth_1.resolveAuth)({ apiKeyFlag });
67
+ const auth = await resolveAuth({ apiKeyFlag });
70
68
  if (name && uploadId) {
71
- throw new cli_1.CliError('Cannot provide both --name and --upload-id. These options are mutually exclusive.');
69
+ throw new CliError('Cannot provide both --name and --upload-id. These options are mutually exclusive.');
72
70
  }
73
71
  if (!name && !uploadId) {
74
- throw new cli_1.CliError('Either --name or --upload-id must be provided');
72
+ throw new CliError('Either --name or --upload-id must be provided');
75
73
  }
76
74
  let lastError = null;
77
75
  let status = null;
@@ -79,7 +77,7 @@ async function statusMain({ apiKeyFlag, apiUrl, json, name, uploadId, }) {
79
77
  for (let attempt = 1; attempt <= 5; attempt++) {
80
78
  try {
81
79
  attemptsMade = attempt;
82
- status = (await api_gateway_1.ApiGateway.getUploadStatus(apiUrl, auth, {
80
+ status = (await ApiGateway.getUploadStatus(apiUrl, auth, {
83
81
  name,
84
82
  uploadId,
85
83
  }));
@@ -93,7 +91,7 @@ async function statusMain({ apiKeyFlag, apiUrl, json, name, uploadId, }) {
93
91
  break;
94
92
  }
95
93
  if (attempt < 5) {
96
- cli_1.logger.log(isNetworkError
94
+ logger.log(isNetworkError
97
95
  ? `Network error on attempt ${attempt}/5. Retrying...`
98
96
  : `Request failed on attempt ${attempt}/5. Retrying...`);
99
97
  await new Promise((resolve) => {
@@ -115,9 +113,9 @@ async function statusMain({ apiKeyFlag, apiUrl, json, name, uploadId, }) {
115
113
  }, null, 2));
116
114
  return;
117
115
  }
118
- throw new cli_1.CliError(errorMessage);
116
+ throw new CliError(errorMessage);
119
117
  }
120
- const connectivityCheck = await (0, connectivity_1.checkInternetConnectivity)();
118
+ const connectivityCheck = await checkInternetConnectivity();
121
119
  let errorMessage;
122
120
  if (connectivityCheck.connected) {
123
121
  errorMessage = `Failed to get status after ${attemptsMade} attempt${attemptsMade > 1 ? 's' : ''}. Internet appears functional but unable to reach API. Last error: ${lastError?.message || 'Unknown error'}`;
@@ -143,7 +141,7 @@ async function statusMain({ apiKeyFlag, apiUrl, json, name, uploadId, }) {
143
141
  }, null, 2));
144
142
  return;
145
143
  }
146
- throw new cli_1.CliError(errorMessage);
144
+ throw new CliError(errorMessage);
147
145
  }
148
146
  try {
149
147
  if (json) {
@@ -152,42 +150,46 @@ async function statusMain({ apiKeyFlag, apiUrl, json, name, uploadId, }) {
152
150
  console.log(JSON.stringify({ ...rest, tests }, null, 2));
153
151
  return;
154
152
  }
155
- cli_1.logger.log((0, styling_1.sectionHeader)('Upload Status'));
156
- cli_1.logger.log(` ${(0, styling_1.formatStatus)(status.status)}`);
153
+ const fields = [];
157
154
  if (status.name) {
158
- cli_1.logger.log(` ${styling_1.colors.dim('Name:')} ${styling_1.colors.bold(status.name)}`);
155
+ fields.push(['name', colors.bold(status.name)]);
159
156
  }
160
157
  if (status.uploadId) {
161
- cli_1.logger.log(` ${styling_1.colors.dim('Upload ID:')} ${(0, styling_1.formatId)(status.uploadId)}`);
158
+ fields.push(['upload id', formatId(status.uploadId)]);
162
159
  }
163
160
  if (status.appBinaryId) {
164
- cli_1.logger.log(` ${styling_1.colors.dim('Binary ID:')} ${(0, styling_1.formatId)(status.appBinaryId)}`);
161
+ fields.push(['binary id', formatId(status.appBinaryId)]);
165
162
  }
166
163
  if (status.createdAt) {
167
- cli_1.logger.log(` ${styling_1.colors.dim('Created:')} ${formatDateTime(status.createdAt)}`);
164
+ fields.push(['created', formatDateTime(status.createdAt)]);
168
165
  }
169
166
  if (status.consoleUrl) {
170
- cli_1.logger.log(` ${styling_1.colors.dim('Console:')} ${(0, styling_1.formatUrl)(status.consoleUrl)}`);
167
+ fields.push(['console', formatUrl(status.consoleUrl)]);
171
168
  }
169
+ logger.log(ui.section('Upload Status'));
170
+ logger.log(ui.branch([ui.status(status.status), ...ui.fields(fields)]));
172
171
  if (status.tests.length > 0) {
173
- cli_1.logger.log((0, styling_1.sectionHeader)('Test Results'));
174
- for (const item of status.tests) {
175
- cli_1.logger.log(` ${(0, styling_1.formatStatus)(item.status)} ${styling_1.colors.bold(item.name)}`);
176
- if (item.status === 'FAILED' && item.failReason) {
177
- cli_1.logger.log(` ${styling_1.colors.error('Fail reason:')} ${item.failReason}`);
178
- }
172
+ logger.log(ui.section('Test Results'));
173
+ logger.log(ui.branch(status.tests.map((item) => {
174
+ const head = `${ui.statusSymbol(item.status)} ${item.name}`;
175
+ const meta = [];
179
176
  if (item.durationSeconds) {
180
- cli_1.logger.log(` ${styling_1.colors.dim('Duration:')} ${(0, methods_1.formatDurationSeconds)(item.durationSeconds)}`);
177
+ meta.push(formatDurationSeconds(item.durationSeconds));
181
178
  }
182
179
  if (item.createdAt) {
183
- cli_1.logger.log(` ${styling_1.colors.dim('Created:')} ${formatDateTime(item.createdAt)}`);
180
+ meta.push(colors.dim(formatDateTime(item.createdAt)));
184
181
  }
185
- cli_1.logger.log('');
186
- }
182
+ if (item.status === 'FAILED' && item.failReason) {
183
+ meta.push(colors.error(item.failReason));
184
+ }
185
+ return meta.length > 0
186
+ ? `${head} ${colors.dim('·')} ${meta.join(colors.dim(' · '))}`
187
+ : head;
188
+ })));
187
189
  }
188
190
  }
189
191
  catch (error) {
190
- throw new cli_1.CliError(`Failed to get status: ${error.message}`);
192
+ throw new CliError(`Failed to get status: ${error.message}`);
191
193
  }
192
194
  }
193
- exports.default = exports.statusCommand;
195
+ export default statusCommand;
@@ -1,12 +1,12 @@
1
1
  export declare const switchOrgCommand: import("citty").CommandDef<{
2
- 'api-url': {
3
- type: "string";
4
- description: string;
2
+ readonly 'api-url': {
3
+ readonly type: "string";
4
+ readonly description: "API base URL (defaults to the URL stored by `dcd login`)";
5
5
  };
6
- org: {
7
- type: "positional";
8
- required: false;
9
- description: string;
6
+ readonly org: {
7
+ readonly type: "positional";
8
+ readonly required: false;
9
+ readonly description: "Org name to switch to (omit for an interactive picker)";
10
10
  };
11
11
  }>;
12
12
  export default switchOrgCommand;
@@ -1,6 +1,3 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.switchOrgCommand = void 0;
4
1
  /**
5
2
  * `dcd switch-org` — changes the active org on the stored session.
6
3
  *
@@ -8,13 +5,14 @@ exports.switchOrgCommand = void 0;
8
5
  * confirming the user is a member via GET /me/orgs.
9
6
  * Without an argument: fetch orgs and prompt the user to pick one.
10
7
  */
11
- const citty_1 = require("citty");
12
- const auth_1 = require("../utils/auth");
13
- const cli_1 = require("../utils/cli");
14
- const config_store_1 = require("../utils/config-store");
15
- const orgs_1 = require("../utils/orgs");
16
- const styling_1 = require("../utils/styling");
17
- exports.switchOrgCommand = (0, citty_1.defineCommand)({
8
+ import { defineCommand } from 'citty';
9
+ import { resolveAuth } from '../utils/auth.js';
10
+ import { CliError, logger } from '../utils/cli.js';
11
+ import { readConfig, resolveApiUrl, writeConfig } from '../utils/config-store.js';
12
+ import { fetchOrgs, pickOrg } from '../utils/orgs.js';
13
+ import { colors } from '../utils/styling.js';
14
+ import { ui } from '../utils/ui.js';
15
+ export const switchOrgCommand = defineCommand({
18
16
  meta: {
19
17
  name: 'switch-org',
20
18
  description: 'Switch the active organization for the logged-in session',
@@ -31,31 +29,31 @@ exports.switchOrgCommand = (0, citty_1.defineCommand)({
31
29
  },
32
30
  },
33
31
  async run({ args }) {
34
- const config = (0, config_store_1.readConfig)();
32
+ const config = readConfig();
35
33
  if (!config?.session) {
36
- throw new cli_1.CliError('Not logged in. Run `dcd login` first.');
34
+ throw new CliError('Not logged in. Run `dcd login` first.');
37
35
  }
38
36
  // Honor the env the user logged into — defaulting to prod here would send
39
37
  // a dev Bearer token to the prod API.
40
- const apiUrl = (0, config_store_1.resolveApiUrl)(args['api-url']);
38
+ const apiUrl = resolveApiUrl(args['api-url']);
41
39
  const target = args.org;
42
40
  // sessionOnly: an exported DEVICE_CLOUD_API_KEY must not shadow the
43
41
  // browser session this command requires.
44
- const auth = await (0, auth_1.resolveAuth)({ apiKeyFlag: undefined, sessionOnly: true });
45
- const orgs = await (0, orgs_1.fetchOrgs)(apiUrl, auth.headers);
42
+ const auth = await resolveAuth({ apiKeyFlag: undefined, sessionOnly: true });
43
+ const orgs = await fetchOrgs(apiUrl, auth.headers);
46
44
  let chosen;
47
45
  if (target) {
48
46
  chosen = matchOrg(orgs, target);
49
47
  }
50
48
  else {
51
- chosen = await (0, orgs_1.pickOrg)(orgs);
49
+ chosen = await pickOrg(orgs);
52
50
  }
53
- (0, config_store_1.writeConfig)({
51
+ writeConfig({
54
52
  ...config,
55
53
  current_org_id: chosen.id,
56
54
  current_org_name: chosen.name,
57
55
  });
58
- cli_1.logger.log(`${styling_1.symbols.success} ${styling_1.colors.bold('Switched')} to ${styling_1.colors.highlight(chosen.name)}`);
56
+ logger.log(ui.success(`Switched to ${colors.highlight(chosen.name)}`));
59
57
  },
60
58
  });
61
59
  /**
@@ -67,12 +65,12 @@ function matchOrg(orgs, target) {
67
65
  const needle = target.toLowerCase();
68
66
  const nameMatches = orgs.filter((o) => o.name.toLowerCase() === needle);
69
67
  if (nameMatches.length > 1) {
70
- throw new cli_1.CliError(`Multiple orgs named "${target}". Run \`dcd switch-org\` without arguments to pick interactively.`);
68
+ throw new CliError(`Multiple orgs named "${target}". Run \`dcd switch-org\` without arguments to pick interactively.`);
71
69
  }
72
70
  const chosen = nameMatches[0] ?? orgs.find((o) => o.slug === target || o.id === target);
73
71
  if (!chosen) {
74
- throw new cli_1.CliError(`No org named "${target}". Available: ${orgs.map((o) => o.name).join(', ')}`);
72
+ throw new CliError(`No org named "${target}". Available: ${orgs.map((o) => o.name).join(', ')}`);
75
73
  }
76
74
  return chosen;
77
75
  }
78
- exports.default = exports.switchOrgCommand;
76
+ export default switchOrgCommand;