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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +52 -0
  3. package/dist/commands/artifacts.d.ts +28 -28
  4. package/dist/commands/artifacts.js +20 -23
  5. package/dist/commands/cloud.d.ts +57 -57
  6. package/dist/commands/cloud.js +224 -192
  7. package/dist/commands/list.d.ts +22 -22
  8. package/dist/commands/list.js +43 -40
  9. package/dist/commands/live.js +134 -127
  10. package/dist/commands/login.d.ts +11 -11
  11. package/dist/commands/login.js +46 -44
  12. package/dist/commands/logout.js +16 -18
  13. package/dist/commands/status.d.ts +11 -11
  14. package/dist/commands/status.js +53 -44
  15. package/dist/commands/switch-org.d.ts +7 -7
  16. package/dist/commands/switch-org.js +19 -21
  17. package/dist/commands/upgrade.js +41 -33
  18. package/dist/commands/upload.d.ts +10 -10
  19. package/dist/commands/upload.js +42 -43
  20. package/dist/commands/whoami.js +17 -20
  21. package/dist/config/environments.js +6 -12
  22. package/dist/config/flags/api.flags.js +1 -4
  23. package/dist/config/flags/binary.flags.js +1 -4
  24. package/dist/config/flags/device.flags.js +6 -9
  25. package/dist/config/flags/environment.flags.js +1 -4
  26. package/dist/config/flags/execution.flags.js +1 -4
  27. package/dist/config/flags/github.flags.js +1 -4
  28. package/dist/config/flags/output.flags.js +1 -4
  29. package/dist/constants.js +15 -18
  30. package/dist/gateways/api-gateway.d.ts +31 -6
  31. package/dist/gateways/api-gateway.js +70 -16
  32. package/dist/gateways/cli-auth-gateway.d.ts +1 -1
  33. package/dist/gateways/cli-auth-gateway.js +3 -6
  34. package/dist/gateways/realtime-gateway.d.ts +32 -0
  35. package/dist/gateways/realtime-gateway.js +103 -0
  36. package/dist/gateways/supabase-gateway.d.ts +1 -1
  37. package/dist/gateways/supabase-gateway.js +10 -14
  38. package/dist/index.js +41 -38
  39. package/dist/mcp/context.d.ts +33 -0
  40. package/dist/mcp/context.js +33 -0
  41. package/dist/mcp/helpers.d.ts +16 -0
  42. package/dist/mcp/helpers.js +34 -0
  43. package/dist/mcp/index.d.ts +2 -0
  44. package/dist/mcp/index.js +24 -0
  45. package/dist/mcp/server.d.ts +7 -0
  46. package/dist/mcp/server.js +27 -0
  47. package/dist/mcp/tools/download-artifacts.d.ts +11 -0
  48. package/dist/mcp/tools/download-artifacts.js +84 -0
  49. package/dist/mcp/tools/get-status.d.ts +7 -0
  50. package/dist/mcp/tools/get-status.js +39 -0
  51. package/dist/mcp/tools/list-devices.d.ts +7 -0
  52. package/dist/mcp/tools/list-devices.js +27 -0
  53. package/dist/mcp/tools/list-runs.d.ts +3 -0
  54. package/dist/mcp/tools/list-runs.js +60 -0
  55. package/dist/mcp/tools/run-cloud-test.d.ts +14 -0
  56. package/dist/mcp/tools/run-cloud-test.js +233 -0
  57. package/dist/methods.d.ts +32 -1
  58. package/dist/methods.js +133 -79
  59. package/dist/services/device-validation.service.d.ts +1 -1
  60. package/dist/services/device-validation.service.js +1 -5
  61. package/dist/services/execution-plan.service.js +14 -17
  62. package/dist/services/execution-plan.utils.js +15 -23
  63. package/dist/services/flow-paths.d.ts +17 -0
  64. package/dist/services/flow-paths.js +52 -0
  65. package/dist/services/metadata-extractor.service.js +22 -25
  66. package/dist/services/moropo.service.js +18 -20
  67. package/dist/services/report-download.service.d.ts +1 -1
  68. package/dist/services/report-download.service.js +5 -9
  69. package/dist/services/results-polling.service.d.ts +18 -3
  70. package/dist/services/results-polling.service.js +211 -108
  71. package/dist/services/telemetry.service.d.ts +10 -1
  72. package/dist/services/telemetry.service.js +40 -18
  73. package/dist/services/test-submission.service.d.ts +21 -4
  74. package/dist/services/test-submission.service.js +51 -34
  75. package/dist/services/version.service.d.ts +30 -7
  76. package/dist/services/version.service.js +88 -32
  77. package/dist/types/domain/auth.types.d.ts +8 -0
  78. package/dist/types/domain/auth.types.js +1 -2
  79. package/dist/types/domain/device.types.js +8 -11
  80. package/dist/types/domain/live.types.js +1 -2
  81. package/dist/types/generated/schema.types.js +1 -2
  82. package/dist/types/index.d.ts +2 -2
  83. package/dist/types/index.js +2 -18
  84. package/dist/types.js +1 -2
  85. package/dist/utils/auth.d.ts +1 -1
  86. package/dist/utils/auth.js +27 -28
  87. package/dist/utils/ci.d.ts +12 -0
  88. package/dist/utils/ci.js +39 -0
  89. package/dist/utils/cli.d.ts +16 -2
  90. package/dist/utils/cli.js +57 -29
  91. package/dist/utils/compatibility.d.ts +1 -1
  92. package/dist/utils/compatibility.js +5 -7
  93. package/dist/utils/config-store.js +33 -43
  94. package/dist/utils/connectivity.js +1 -4
  95. package/dist/utils/expo.js +15 -21
  96. package/dist/utils/orgs.js +8 -12
  97. package/dist/utils/paths.js +2 -5
  98. package/dist/utils/progress.d.ts +3 -0
  99. package/dist/utils/progress.js +47 -8
  100. package/dist/utils/styling.d.ts +35 -37
  101. package/dist/utils/styling.js +52 -86
  102. package/dist/utils/ui.d.ts +41 -0
  103. package/dist/utils/ui.js +95 -0
  104. package/package.json +27 -24
@@ -1,25 +1,49 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ux = void 0;
4
1
  /**
5
2
  * Progress adapter that wraps @clack/prompts spinner with a
6
3
  * drop-in API for existing services that used oclif's `ux.action` / `ux.info`.
7
4
  *
8
5
  * Keeps call sites unchanged while migrating away from @oclif/core.
6
+ *
7
+ * TTY-awareness: @clack/prompts' spinner animates on a timer and, when stdout
8
+ * isn't a TTY (CI, pipes, redirects), it can't rewrite a line in place — every
9
+ * frame becomes a fresh line, flooding logs with hundreds of duplicates. In
10
+ * non-interactive environments we skip the spinner entirely and instead print a
11
+ * plain line once per *distinct* status, so CI logs show real progress without
12
+ * the flood.
9
13
  */
10
- const p = require("@clack/prompts");
14
+ import * as p from '@clack/prompts';
15
+ import { isCI } from './ci.js';
11
16
  class Action {
12
17
  current = null;
13
18
  _status = '';
19
+ // Last line emitted in non-interactive mode, for de-duplication.
20
+ _lastPrinted = '';
21
+ interactive() {
22
+ return process.stdout.isTTY === true && !isCI();
23
+ }
14
24
  start(title, initialStatus, _opts) {
25
+ this._status = initialStatus ?? '';
26
+ const line = initialStatus ? `${title} — ${initialStatus}` : title;
27
+ if (!this.interactive()) {
28
+ this.current = null;
29
+ this.print(line);
30
+ return;
31
+ }
15
32
  if (this.current) {
16
33
  this.current.stop();
17
34
  }
18
35
  this.current = p.spinner();
19
- this._status = initialStatus ?? '';
20
- this.current.start(initialStatus ? `${title} — ${initialStatus}` : title);
36
+ this.current.start(line);
21
37
  }
22
38
  stop(message) {
39
+ if (!this.interactive()) {
40
+ if (message)
41
+ this.print(message);
42
+ this.current = null;
43
+ this._status = '';
44
+ this._lastPrinted = '';
45
+ return;
46
+ }
23
47
  if (!this.current) {
24
48
  if (message) {
25
49
  // eslint-disable-next-line no-console
@@ -33,15 +57,30 @@ class Action {
33
57
  }
34
58
  set status(value) {
35
59
  this._status = value;
36
- if (this.current && value) {
60
+ if (!value)
61
+ return;
62
+ if (!this.interactive()) {
63
+ this.print(value);
64
+ return;
65
+ }
66
+ if (this.current) {
37
67
  this.current.message(value);
38
68
  }
39
69
  }
40
70
  get status() {
41
71
  return this._status;
42
72
  }
73
+ // Emit a line only when it differs from the previous one, so repeated polls
74
+ // with no state change stay quiet.
75
+ print(line) {
76
+ if (line === this._lastPrinted)
77
+ return;
78
+ this._lastPrinted = line;
79
+ // eslint-disable-next-line no-console
80
+ console.log(line);
81
+ }
43
82
  }
44
- exports.ux = {
83
+ export const ux = {
45
84
  action: new Action(),
46
85
  info(message) {
47
86
  // eslint-disable-next-line no-console
@@ -1,4 +1,9 @@
1
- import chalk = require('chalk');
1
+ /**
2
+ * Centralized styling utilities for CLI output
3
+ * Provides consistent, developer-friendly visual formatting
4
+ */
5
+ /** Strip ANSI color escape sequences for visible-width calculations. */
6
+ export declare const stripAnsi: (s: string) => string;
2
7
  /**
3
8
  * Status symbols with associated colors
4
9
  */
@@ -17,50 +22,49 @@ export declare const symbols: {
17
22
  * Color utility functions for semantic styling
18
23
  */
19
24
  export declare const colors: {
20
- readonly bold: chalk.Chalk;
21
- readonly dim: chalk.Chalk;
22
- readonly error: chalk.Chalk;
23
- readonly highlight: chalk.Chalk;
24
- readonly info: chalk.Chalk;
25
- readonly success: chalk.Chalk;
26
- readonly url: chalk.Chalk;
27
- readonly warning: chalk.Chalk;
25
+ readonly bold: import("chalk").ChalkInstance;
26
+ readonly dim: import("chalk").ChalkInstance;
27
+ readonly error: import("chalk").ChalkInstance;
28
+ readonly highlight: import("chalk").ChalkInstance;
29
+ readonly info: import("chalk").ChalkInstance;
30
+ readonly success: import("chalk").ChalkInstance;
31
+ readonly url: import("chalk").ChalkInstance;
32
+ readonly warning: import("chalk").ChalkInstance;
33
+ };
34
+ /**
35
+ * Structural glyphs that give the CLI its tree-shaped layout. `section` marks a
36
+ * top-level heading; `branch` opens the group of detail rows beneath it.
37
+ * See STYLE_GUIDE.md.
38
+ */
39
+ export declare const glyphs: {
40
+ readonly branch: "⎿";
41
+ readonly section: "⏺";
28
42
  };
29
43
  /**
30
- * Dividers for visual separation
44
+ * The single source of truth mapping a run/test status to its colour and
45
+ * (already-coloured) symbol. Both {@link formatStatus} and the `ui` status
46
+ * helpers build on this, so every status reads identically everywhere.
47
+ * @param status - The status string (case-insensitive)
31
48
  */
32
- export declare const dividers: {
33
- readonly heavy: string;
34
- readonly light: string;
35
- readonly short: string;
49
+ export declare function statusPalette(status: string): {
50
+ color: (s: string) => string;
51
+ symbol: string;
36
52
  };
37
53
  /**
38
- * Format a status with appropriate symbol and color
54
+ * Format a status as a coloured symbol followed by the lowercased status word,
55
+ * e.g. `✓ passed`.
39
56
  * @param status - The status string to format
40
57
  * @returns Formatted status string with color and symbol
41
58
  */
42
59
  export declare function formatStatus(status: string): string;
43
60
  /**
44
- * Format a section header
61
+ * Format a top-level section header in the tree style: a `⏺` marker followed by
62
+ * the bold title, preceded by a blank line for separation. Detail rows belong
63
+ * underneath in a branch group (see the `ui` helpers).
45
64
  * @param title - The title of the section
46
65
  * @returns Formatted section header
47
66
  */
48
67
  export declare function sectionHeader(title: string): string;
49
- /**
50
- * Format a key-value pair with optional icon
51
- * @param icon - Icon to display before the key
52
- * @param key - The key name
53
- * @param value - The value to display
54
- * @returns Formatted key-value string
55
- */
56
- export declare function keyValue(icon: string, key: string, value: string): string;
57
- /**
58
- * Format a list item
59
- * @param text - The text of the list item
60
- * @param prefix - The prefix character (default: '•')
61
- * @returns Formatted list item
62
- */
63
- export declare function listItem(text: string, prefix?: string): string;
64
68
  /**
65
69
  * Format a URL
66
70
  * @param url - The URL to format
@@ -87,12 +91,6 @@ export declare function formatTestSummary(summary: {
87
91
  running: number;
88
92
  total: number;
89
93
  }): string;
90
- /**
91
- * Format a box with content
92
- * @param content - The content to display in the box
93
- * @returns Formatted box with borders
94
- */
95
- export declare function box(content: string): string;
96
94
  /**
97
95
  * Minimal column table renderer.
98
96
  * Columns are defined with a `get(row) => string` accessor and optional header label.
@@ -1,29 +1,16 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.dividers = exports.colors = exports.symbols = void 0;
4
- exports.formatStatus = formatStatus;
5
- exports.sectionHeader = sectionHeader;
6
- exports.keyValue = keyValue;
7
- exports.listItem = listItem;
8
- exports.formatUrl = formatUrl;
9
- exports.formatId = formatId;
10
- exports.formatTestSummary = formatTestSummary;
11
- exports.box = box;
12
- exports.table = table;
13
- exports.getConsoleUrl = getConsoleUrl;
14
- const chalk = require("chalk");
15
- const environments_1 = require("../config/environments");
1
+ import chalk from 'chalk';
2
+ import { findEnvByApiUrl } from '../config/environments.js';
16
3
  /**
17
4
  * Centralized styling utilities for CLI output
18
5
  * Provides consistent, developer-friendly visual formatting
19
6
  */
20
7
  /** Strip ANSI color escape sequences for visible-width calculations. */
21
8
  // eslint-disable-next-line no-control-regex -- matches ANSI escape sequences
22
- const stripAnsi = (s) => s.replace(/\u001B\[[0-9;]*m/g, '');
9
+ export const stripAnsi = (s) => s.replace(/\u001B\[[0-9;]*m/g, '');
23
10
  /**
24
11
  * Status symbols with associated colors
25
12
  */
26
- exports.symbols = {
13
+ export const symbols = {
27
14
  cancelled: chalk.gray('⊘'),
28
15
  error: chalk.red('✗'),
29
16
  info: chalk.blue('ℹ'),
@@ -37,7 +24,7 @@ exports.symbols = {
37
24
  /**
38
25
  * Color utility functions for semantic styling
39
26
  */
40
- exports.colors = {
27
+ export const colors = {
41
28
  bold: chalk.bold,
42
29
  dim: chalk.gray,
43
30
  error: chalk.red,
@@ -48,125 +35,104 @@ exports.colors = {
48
35
  warning: chalk.yellow,
49
36
  };
50
37
  /**
51
- * Dividers for visual separation
38
+ * Structural glyphs that give the CLI its tree-shaped layout. `section` marks a
39
+ * top-level heading; `branch` opens the group of detail rows beneath it.
40
+ * See STYLE_GUIDE.md.
52
41
  */
53
- exports.dividers = {
54
- heavy: chalk.gray(''.repeat(80)),
55
- light: chalk.gray(''.repeat(80)),
56
- short: chalk.gray('─'.repeat(40)),
42
+ export const glyphs = {
43
+ branch: '',
44
+ section: '',
57
45
  };
58
46
  /**
59
- * Format a status with appropriate symbol and color
60
- * @param status - The status string to format
61
- * @returns Formatted status string with color and symbol
47
+ * The single source of truth mapping a run/test status to its colour and
48
+ * (already-coloured) symbol. Both {@link formatStatus} and the `ui` status
49
+ * helpers build on this, so every status reads identically everywhere.
50
+ * @param status - The status string (case-insensitive)
62
51
  */
63
- function formatStatus(status) {
64
- const statusUpper = status.toUpperCase();
65
- switch (statusUpper) {
52
+ export function statusPalette(status) {
53
+ switch (status.toUpperCase()) {
66
54
  case 'PASSED': {
67
- return `${exports.symbols.success} ${exports.colors.success(status)}`;
55
+ return { color: colors.success, symbol: symbols.success };
68
56
  }
57
+ case 'ERROR':
69
58
  case 'FAILED': {
70
- return `${exports.symbols.error} ${exports.colors.error(status)}`;
59
+ return { color: colors.error, symbol: symbols.error };
71
60
  }
72
61
  case 'RUNNING': {
73
- return `${exports.symbols.running} ${exports.colors.info(status)}`;
62
+ return { color: colors.info, symbol: symbols.running };
74
63
  }
75
64
  case 'PENDING': {
76
- return `${exports.symbols.pending} ${exports.colors.warning(status)}`;
65
+ return { color: colors.warning, symbol: symbols.pending };
77
66
  }
78
67
  case 'QUEUED': {
79
- return `${exports.symbols.queued} ${exports.colors.dim(status)}`;
68
+ return { color: colors.dim, symbol: symbols.queued };
80
69
  }
81
70
  case 'CANCELLED': {
82
- return `${exports.symbols.cancelled} ${exports.colors.dim(status)}`;
71
+ return { color: colors.dim, symbol: symbols.cancelled };
83
72
  }
84
73
  default: {
85
- return `${exports.symbols.unknown} ${exports.colors.dim(status)}`;
74
+ return { color: colors.dim, symbol: symbols.unknown };
86
75
  }
87
76
  }
88
77
  }
89
78
  /**
90
- * Format a section header
91
- * @param title - The title of the section
92
- * @returns Formatted section header
93
- */
94
- function sectionHeader(title) {
95
- return `\n${exports.colors.bold(title)}\n${exports.dividers.light}`;
96
- }
97
- /**
98
- * Format a key-value pair with optional icon
99
- * @param icon - Icon to display before the key
100
- * @param key - The key name
101
- * @param value - The value to display
102
- * @returns Formatted key-value string
79
+ * Format a status as a coloured symbol followed by the lowercased status word,
80
+ * e.g. `✓ passed`.
81
+ * @param status - The status string to format
82
+ * @returns Formatted status string with color and symbol
103
83
  */
104
- function keyValue(icon, key, value) {
105
- return `${icon} ${exports.colors.dim(key + ':')} ${exports.colors.highlight(value)}`;
84
+ export function formatStatus(status) {
85
+ const { color, symbol } = statusPalette(status);
86
+ return `${symbol} ${color(status.toLowerCase())}`;
106
87
  }
107
88
  /**
108
- * Format a list item
109
- * @param text - The text of the list item
110
- * @param prefix - The prefix character (default: '•')
111
- * @returns Formatted list item
89
+ * Format a top-level section header in the tree style: a `⏺` marker followed by
90
+ * the bold title, preceded by a blank line for separation. Detail rows belong
91
+ * underneath in a branch group (see the `ui` helpers).
92
+ * @param title - The title of the section
93
+ * @returns Formatted section header
112
94
  */
113
- function listItem(text, prefix = '•') {
114
- return `${exports.colors.dim(prefix)} ${text}`;
95
+ export function sectionHeader(title) {
96
+ return `\n${colors.dim(glyphs.section)} ${colors.bold(title)}`;
115
97
  }
116
98
  /**
117
99
  * Format a URL
118
100
  * @param url - The URL to format
119
101
  * @returns Formatted URL with styling
120
102
  */
121
- function formatUrl(url) {
122
- return exports.colors.url(url);
103
+ export function formatUrl(url) {
104
+ return colors.url(url);
123
105
  }
124
106
  /**
125
107
  * Format an ID or identifier
126
108
  * @param id - The ID to format
127
109
  * @returns Formatted ID with highlighting
128
110
  */
129
- function formatId(id) {
130
- return exports.colors.highlight(id);
111
+ export function formatId(id) {
112
+ return colors.highlight(id);
131
113
  }
132
114
  /**
133
115
  * Format a test summary line
134
116
  * @param summary - Object containing test counts
135
117
  * @returns Formatted summary string
136
118
  */
137
- function formatTestSummary(summary) {
119
+ export function formatTestSummary(summary) {
138
120
  const parts = [
139
121
  chalk.bold(`${summary.completed}/${summary.total}`),
140
- exports.colors.success(`✓ ${summary.passed}`),
141
- exports.colors.error(`✗ ${summary.failed}`),
142
- exports.colors.info(`▶ ${summary.running}`),
143
- exports.colors.warning(`⏸ ${summary.pending}`),
144
- exports.colors.dim(`⏳ ${summary.queued}`),
122
+ colors.success(`✓ ${summary.passed}`),
123
+ colors.error(`✗ ${summary.failed}`),
124
+ colors.info(`▶ ${summary.running}`),
125
+ colors.warning(`⏸ ${summary.pending}`),
126
+ colors.dim(`⏳ ${summary.queued}`),
145
127
  ];
146
128
  return parts.join(' │ ');
147
129
  }
148
- /**
149
- * Format a box with content
150
- * @param content - The content to display in the box
151
- * @returns Formatted box with borders
152
- */
153
- function box(content) {
154
- const lines = content.split('\n');
155
- const visibleLen = (s) => stripAnsi(s).length;
156
- const maxLength = Math.max(...lines.map((l) => visibleLen(l)));
157
- const top = chalk.gray('┌' + '─'.repeat(maxLength + 2) + '┐');
158
- const bottom = chalk.gray('└' + '─'.repeat(maxLength + 2) + '┘');
159
- const middle = lines
160
- .map((line) => chalk.gray('│ ') + line + ' '.repeat(Math.max(0, maxLength - visibleLen(line))) + chalk.gray(' │'))
161
- .join('\n');
162
- return `${top}\n${middle}\n${bottom}`;
163
- }
164
130
  /**
165
131
  * Minimal column table renderer.
166
132
  * Columns are defined with a `get(row) => string` accessor and optional header label.
167
133
  * Matches the subset of oclif's ux.table used by this CLI.
168
134
  */
169
- function table(rows, columns, options = {}) {
135
+ export function table(rows, columns, options = {}) {
170
136
  const printLine = options.printLine ?? ((line) => {
171
137
  // eslint-disable-next-line no-console
172
138
  console.log(line);
@@ -194,8 +160,8 @@ function table(rows, columns, options = {}) {
194
160
  * @param resultId - The result ID
195
161
  * @returns The appropriate console URL
196
162
  */
197
- function getConsoleUrl(apiUrl, uploadId, resultId) {
198
- const env = (0, environments_1.findEnvByApiUrl)(apiUrl);
163
+ export function getConsoleUrl(apiUrl, uploadId, resultId) {
164
+ const env = findEnvByApiUrl(apiUrl);
199
165
  const base = env?.frontendUrl ?? 'https://dev.console.devicecloud.dev';
200
166
  return `${base}/results?upload=${uploadId}&result=${resultId}`;
201
167
  }
@@ -0,0 +1,41 @@
1
+ /** A `[label, value]` detail row; the value is passed through already styled. */
2
+ export type Field = [label: string, value: string];
3
+ export declare const ui: {
4
+ /**
5
+ * Render detail rows as a branch group beneath the most recent section:
6
+ *
7
+ * ⎿ row one
8
+ * row two
9
+ *
10
+ * Embedded newlines in a row are kept aligned. Returns `''` for no rows.
11
+ */
12
+ readonly branch: (rows: string[]) => string;
13
+ /**
14
+ * Align `[label, value]` pairs into `label value` rows for use inside a
15
+ * {@link branch} group. Plain labels are dimmed; labels the caller already
16
+ * styled (e.g. `colors.bold(name)`) are left as-is. Padding is measured on
17
+ * visible width so ANSI colours never throw the columns off.
18
+ */
19
+ readonly fields: (pairs: Field[]) => string[];
20
+ /** `ℹ message` — neutral, standalone information. */
21
+ readonly info: (message: string) => string;
22
+ /** Dimmed secondary text — hints, tips, "you can close this terminal", etc. */
23
+ readonly note: (message: string) => string;
24
+ /** `▶ message` — an action currently in progress. */
25
+ readonly running: (message: string) => string;
26
+ /**
27
+ * A top-level section header — `⏺ Title`, preceded by a blank line. Detail
28
+ * rows belong underneath via {@link branch}.
29
+ */
30
+ readonly section: (title: string) => string;
31
+ /** A coloured status word + symbol, e.g. `✓ passed`. Delegates to the shared palette. */
32
+ readonly status: (status: string) => string;
33
+ /** The coloured status symbol alone, e.g. green `✓`. */
34
+ readonly statusSymbol: (status: string) => string;
35
+ /** The lowercased status word in its status colour, e.g. green `passed`. */
36
+ readonly statusWord: (status: string) => string;
37
+ /** `✓ message` — a completed action; the message is emphasised. */
38
+ readonly success: (message: string) => string;
39
+ /** `⚠ message` — a non-fatal warning. */
40
+ readonly warn: (message: string) => string;
41
+ };
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Unified rendering layer for all CLI command output.
3
+ *
4
+ * Every command composes its output from these helpers so the look stays
5
+ * consistent — a tree of `⏺` section headings with `⎿` branch groups of detail
6
+ * rows beneath them, inspired by Claude Code. See STYLE_GUIDE.md for the rules
7
+ * and worked examples.
8
+ *
9
+ * Functions RETURN strings (never write to stdout themselves) so callers route
10
+ * them through their own logger / `--json` gate and they stay unit-testable.
11
+ */
12
+ import { colors, formatStatus, glyphs, sectionHeader, statusPalette, stripAnsi, symbols, } from './styling.js';
13
+ /**
14
+ * Indentation under a section. The branch glyph sits two columns in and its
15
+ * content begins at column four; continuation rows align to the same column.
16
+ *
17
+ * ⏺ Title
18
+ * ⎿ first detail row
19
+ * continuation row
20
+ */
21
+ const BRANCH_PREFIX = ` ${colors.dim(glyphs.branch)} `;
22
+ const CONTINUATION_PREFIX = ' ';
23
+ export const ui = {
24
+ /**
25
+ * Render detail rows as a branch group beneath the most recent section:
26
+ *
27
+ * ⎿ row one
28
+ * row two
29
+ *
30
+ * Embedded newlines in a row are kept aligned. Returns `''` for no rows.
31
+ */
32
+ branch(rows) {
33
+ const lines = rows.flatMap((row) => row.split('\n'));
34
+ if (lines.length === 0) {
35
+ return '';
36
+ }
37
+ return lines
38
+ .map((line, index) => index === 0 ? `${BRANCH_PREFIX}${line}` : `${CONTINUATION_PREFIX}${line}`)
39
+ .join('\n');
40
+ },
41
+ /**
42
+ * Align `[label, value]` pairs into `label value` rows for use inside a
43
+ * {@link branch} group. Plain labels are dimmed; labels the caller already
44
+ * styled (e.g. `colors.bold(name)`) are left as-is. Padding is measured on
45
+ * visible width so ANSI colours never throw the columns off.
46
+ */
47
+ fields(pairs) {
48
+ const width = Math.max(0, ...pairs.map(([label]) => stripAnsi(label).length));
49
+ return pairs.map(([label, value]) => {
50
+ const styled = stripAnsi(label) === label ? colors.dim(label) : label;
51
+ const padding = ' '.repeat(Math.max(0, width - stripAnsi(label).length));
52
+ return `${styled}${padding} ${value}`;
53
+ });
54
+ },
55
+ /** `ℹ message` — neutral, standalone information. */
56
+ info(message) {
57
+ return `${symbols.info} ${message}`;
58
+ },
59
+ /** Dimmed secondary text — hints, tips, "you can close this terminal", etc. */
60
+ note(message) {
61
+ return colors.dim(message);
62
+ },
63
+ /** `▶ message` — an action currently in progress. */
64
+ running(message) {
65
+ return `${symbols.running} ${message}`;
66
+ },
67
+ /**
68
+ * A top-level section header — `⏺ Title`, preceded by a blank line. Detail
69
+ * rows belong underneath via {@link branch}.
70
+ */
71
+ section(title) {
72
+ return sectionHeader(title);
73
+ },
74
+ /** A coloured status word + symbol, e.g. `✓ passed`. Delegates to the shared palette. */
75
+ status(status) {
76
+ return formatStatus(status);
77
+ },
78
+ /** The coloured status symbol alone, e.g. green `✓`. */
79
+ statusSymbol(status) {
80
+ return statusPalette(status).symbol;
81
+ },
82
+ /** The lowercased status word in its status colour, e.g. green `passed`. */
83
+ statusWord(status) {
84
+ const { color } = statusPalette(status);
85
+ return color(status.toLowerCase());
86
+ },
87
+ /** `✓ message` — a completed action; the message is emphasised. */
88
+ success(message) {
89
+ return `${symbols.success} ${colors.bold(message)}`;
90
+ },
91
+ /** `⚠ message` — a non-fatal warning. */
92
+ warn(message) {
93
+ return `${symbols.warning} ${message}`;
94
+ },
95
+ };
package/package.json CHANGED
@@ -1,43 +1,45 @@
1
1
  {
2
2
  "author": "devicecloud.dev",
3
3
  "bin": {
4
- "dcd": "dist/index.js"
4
+ "dcd": "dist/index.js",
5
+ "dcd-mcp": "dist/mcp/index.js"
5
6
  },
6
7
  "dependencies": {
7
- "@clack/prompts": "^1.2.0",
8
- "@supabase/supabase-js": "^2.99.1",
8
+ "@clack/prompts": "^1.6.0",
9
+ "@modelcontextprotocol/sdk": "^1.29.0",
10
+ "@supabase/supabase-js": "^2.108.2",
9
11
  "bplist-parser": "^0.3.2",
10
- "chalk": "4.1.2",
11
- "citty": "^0.1.6",
12
- "js-yaml": "^4.1.1",
12
+ "chalk": "^5.6.2",
13
+ "citty": "^0.2.2",
14
+ "js-yaml": "^5.0.0",
13
15
  "node-apk": "^1.2.1",
14
16
  "node-stream-zip": "^1.15.0",
15
- "plist": "^3.1.0",
16
- "tar": "^7.5.11",
17
+ "plist": "^5.0.0",
18
+ "tar": "^7.5.16",
17
19
  "tus-js-client": "^4.3.1",
18
- "yazl": "^3.3.1"
20
+ "yazl": "^3.3.1",
21
+ "zod": "^4.4.3"
19
22
  },
20
- "description": "Better cloud maestro testing",
23
+ "description": "Run Maestro mobile UI tests in the cloud upload an iOS/Android build, execute flows across real devices, and stream results. Ships the dcd CLI and a dcd-mcp MCP server.",
21
24
  "devDependencies": {
22
- "@eslint/js": "^9.39.4",
23
- "@types/chai": "^4.3.20",
25
+ "@eslint/js": "^10.0.1",
26
+ "@types/chai": "^5.2.3",
24
27
  "@types/js-yaml": "^4.0.9",
25
28
  "@types/mocha": "^10.0.10",
26
- "@types/node": "^25.4.0",
27
- "@types/plist": "^3.0.5",
29
+ "@types/node": "^26.0.0",
28
30
  "@types/yazl": "^3.3.1",
29
- "chai": "^4.5.0",
30
- "eslint": "^9.16.0",
31
+ "chai": "^6.2.2",
32
+ "eslint": "^10.5.0",
31
33
  "eslint-config-prettier": "^10.1.8",
32
34
  "eslint-plugin-import": "^2.32.0",
33
- "eslint-plugin-unicorn": "^64.0.0",
35
+ "eslint-plugin-unicorn": "^68.0.0",
34
36
  "husky": "^9.1.7",
35
- "mocha": "^11.7.5",
36
- "prettier": "^3.3.3",
37
+ "mocha": "^11.7.6",
38
+ "prettier": "^3.8.4",
37
39
  "shx": "^0.4.0",
38
- "tsx": "^4.19.2",
39
- "typescript": "^5.9.3",
40
- "typescript-eslint": "^8.59.0"
40
+ "tsx": "^4.22.4",
41
+ "typescript": "^6.0.3",
42
+ "typescript-eslint": "^8.61.1"
41
43
  },
42
44
  "engines": {
43
45
  "node": ">=22.0.0"
@@ -50,6 +52,7 @@
50
52
  "main": "dist/index.js",
51
53
  "name": "@devicecloud.dev/dcd",
52
54
  "private": false,
55
+ "type": "module",
53
56
  "publishConfig": {
54
57
  "access": "public"
55
58
  },
@@ -57,7 +60,7 @@
57
60
  "type": "git",
58
61
  "url": "https://devicecloud.dev"
59
62
  },
60
- "version": "5.0.0-beta.0",
63
+ "version": "5.0.0-beta.2",
61
64
  "bugs": {
62
65
  "url": "https://discord.gg/gm3mJwcNw8"
63
66
  },
@@ -72,7 +75,7 @@
72
75
  "types": "dist/index.d.ts",
73
76
  "scripts": {
74
77
  "dcd": "tsx src/index.ts",
75
- "build": "shx rm -rf dist && tsc -b && shx chmod +x dist/index.js",
78
+ "build": "shx rm -rf dist && tsc -b && shx chmod +x dist/index.js dist/mcp/index.js",
76
79
  "build:binaries": "node scripts/build-binaries.mjs",
77
80
  "lint": "eslint src test --ext .ts",
78
81
  "test": "node scripts/test-runner.mjs",