agent-device 0.4.1 → 0.5.0

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 (87) hide show
  1. package/README.md +19 -20
  2. package/dist/src/797.js +1 -1
  3. package/dist/src/bin.js +32 -32
  4. package/dist/src/daemon.js +17 -17
  5. package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests.swift +8 -2
  6. package/package.json +7 -9
  7. package/skills/agent-device/SKILL.md +22 -19
  8. package/skills/agent-device/references/permissions.md +10 -17
  9. package/skills/agent-device/references/session-management.md +3 -1
  10. package/skills/agent-device/references/snapshot-refs.md +1 -4
  11. package/dist/bin/axsnapshot +0 -0
  12. package/ios-runner/AXSnapshot/Package.swift +0 -18
  13. package/ios-runner/AXSnapshot/Sources/AXSnapshot/main.swift +0 -444
  14. package/src/__tests__/cli-help.test.ts +0 -102
  15. package/src/bin.ts +0 -3
  16. package/src/cli.ts +0 -289
  17. package/src/core/__tests__/capabilities.test.ts +0 -74
  18. package/src/core/__tests__/open-target.test.ts +0 -16
  19. package/src/core/capabilities.ts +0 -57
  20. package/src/core/dispatch.ts +0 -360
  21. package/src/core/open-target.ts +0 -13
  22. package/src/daemon/__tests__/app-state.test.ts +0 -138
  23. package/src/daemon/__tests__/is-predicates.test.ts +0 -68
  24. package/src/daemon/__tests__/selectors.test.ts +0 -261
  25. package/src/daemon/__tests__/session-routing.test.ts +0 -108
  26. package/src/daemon/__tests__/session-selector.test.ts +0 -64
  27. package/src/daemon/__tests__/session-store.test.ts +0 -142
  28. package/src/daemon/__tests__/snapshot-processing.test.ts +0 -47
  29. package/src/daemon/action-utils.ts +0 -29
  30. package/src/daemon/app-state.ts +0 -65
  31. package/src/daemon/context.ts +0 -48
  32. package/src/daemon/device-ready.ts +0 -13
  33. package/src/daemon/handlers/__tests__/find.test.ts +0 -99
  34. package/src/daemon/handlers/__tests__/interaction.test.ts +0 -22
  35. package/src/daemon/handlers/__tests__/replay-heal.test.ts +0 -509
  36. package/src/daemon/handlers/__tests__/session-reinstall.test.ts +0 -219
  37. package/src/daemon/handlers/__tests__/session.test.ts +0 -343
  38. package/src/daemon/handlers/__tests__/snapshot-handler.test.ts +0 -92
  39. package/src/daemon/handlers/__tests__/snapshot.test.ts +0 -128
  40. package/src/daemon/handlers/find.ts +0 -324
  41. package/src/daemon/handlers/interaction.ts +0 -550
  42. package/src/daemon/handlers/parse-utils.ts +0 -8
  43. package/src/daemon/handlers/record-trace.ts +0 -154
  44. package/src/daemon/handlers/session.ts +0 -1032
  45. package/src/daemon/handlers/snapshot.ts +0 -439
  46. package/src/daemon/is-predicates.ts +0 -46
  47. package/src/daemon/selectors.ts +0 -540
  48. package/src/daemon/session-routing.ts +0 -22
  49. package/src/daemon/session-selector.ts +0 -39
  50. package/src/daemon/session-store.ts +0 -298
  51. package/src/daemon/snapshot-processing.ts +0 -131
  52. package/src/daemon/types.ts +0 -56
  53. package/src/daemon-client.ts +0 -172
  54. package/src/daemon.ts +0 -295
  55. package/src/platforms/__tests__/boot-diagnostics.test.ts +0 -59
  56. package/src/platforms/android/__tests__/index.test.ts +0 -157
  57. package/src/platforms/android/devices.ts +0 -196
  58. package/src/platforms/android/index.ts +0 -754
  59. package/src/platforms/android/ui-hierarchy.ts +0 -312
  60. package/src/platforms/boot-diagnostics.ts +0 -128
  61. package/src/platforms/ios/__tests__/index.test.ts +0 -24
  62. package/src/platforms/ios/__tests__/runner-client.test.ts +0 -113
  63. package/src/platforms/ios/ax-snapshot.ts +0 -207
  64. package/src/platforms/ios/devices.ts +0 -87
  65. package/src/platforms/ios/index.ts +0 -455
  66. package/src/platforms/ios/runner-client.ts +0 -938
  67. package/src/utils/__tests__/args.test.ts +0 -221
  68. package/src/utils/__tests__/daemon-client.test.ts +0 -78
  69. package/src/utils/__tests__/exec.test.ts +0 -16
  70. package/src/utils/__tests__/finders.test.ts +0 -34
  71. package/src/utils/__tests__/keyed-lock.test.ts +0 -55
  72. package/src/utils/__tests__/process-identity.test.ts +0 -33
  73. package/src/utils/__tests__/retry.test.ts +0 -44
  74. package/src/utils/args.ts +0 -234
  75. package/src/utils/command-schema.ts +0 -629
  76. package/src/utils/device.ts +0 -84
  77. package/src/utils/errors.ts +0 -35
  78. package/src/utils/exec.ts +0 -339
  79. package/src/utils/finders.ts +0 -101
  80. package/src/utils/interactive.ts +0 -4
  81. package/src/utils/interactors.ts +0 -173
  82. package/src/utils/keyed-lock.ts +0 -14
  83. package/src/utils/output.ts +0 -204
  84. package/src/utils/process-identity.ts +0 -100
  85. package/src/utils/retry.ts +0 -180
  86. package/src/utils/snapshot.ts +0 -64
  87. package/src/utils/version.ts +0 -26
package/src/cli.ts DELETED
@@ -1,289 +0,0 @@
1
- import { parseArgs, toDaemonFlags, usage, usageForCommand } from './utils/args.ts';
2
- import { asAppError, AppError } from './utils/errors.ts';
3
- import { formatSnapshotText, printHumanError, printJson } from './utils/output.ts';
4
- import { readVersion } from './utils/version.ts';
5
- import { pathToFileURL } from 'node:url';
6
- import { sendToDaemon } from './daemon-client.ts';
7
- import fs from 'node:fs';
8
- import os from 'node:os';
9
- import path from 'node:path';
10
-
11
- type CliDeps = {
12
- sendToDaemon: typeof sendToDaemon;
13
- };
14
-
15
- const DEFAULT_CLI_DEPS: CliDeps = {
16
- sendToDaemon,
17
- };
18
-
19
- export async function runCli(argv: string[], deps: CliDeps = DEFAULT_CLI_DEPS): Promise<void> {
20
- const parsed = parseArgs(argv);
21
- for (const warning of parsed.warnings) {
22
- process.stderr.write(`Warning: ${warning}\n`);
23
- }
24
-
25
- if (parsed.flags.version) {
26
- process.stdout.write(`${readVersion()}\n`);
27
- process.exit(0);
28
- }
29
-
30
- const isHelpAlias = parsed.command === 'help';
31
- const isHelpFlag = parsed.flags.help;
32
- if (isHelpAlias || isHelpFlag) {
33
- if (isHelpAlias && parsed.positionals.length > 1) {
34
- printHumanError(new AppError('INVALID_ARGS', 'help accepts at most one command.'));
35
- process.exit(1);
36
- }
37
- const helpTarget = isHelpAlias ? parsed.positionals[0] : parsed.command;
38
- if (!helpTarget) {
39
- process.stdout.write(`${usage()}\n`);
40
- process.exit(0);
41
- }
42
- const commandHelp = usageForCommand(helpTarget);
43
- if (commandHelp) {
44
- process.stdout.write(commandHelp);
45
- process.exit(0);
46
- }
47
- printHumanError(new AppError('INVALID_ARGS', `Unknown command: ${helpTarget}`));
48
- process.stdout.write(`${usage()}\n`);
49
- process.exit(1);
50
- }
51
-
52
- if (!parsed.command) {
53
- process.stdout.write(`${usage()}\n`);
54
- process.exit(1);
55
- }
56
-
57
- const { command, positionals, flags } = parsed;
58
- const daemonFlags = toDaemonFlags(flags);
59
- const sessionName = flags.session ?? process.env.AGENT_DEVICE_SESSION ?? 'default';
60
- const logTailStopper = flags.verbose && !flags.json ? startDaemonLogTail() : null;
61
- try {
62
- if (command === 'session') {
63
- const sub = positionals[0] ?? 'list';
64
- if (sub !== 'list') {
65
- throw new AppError('INVALID_ARGS', 'session only supports list');
66
- }
67
- const response = await deps.sendToDaemon({
68
- session: sessionName,
69
- command: 'session_list',
70
- positionals: [],
71
- flags: daemonFlags,
72
- });
73
- if (!response.ok) throw new AppError(response.error.code as any, response.error.message);
74
- if (flags.json) printJson({ success: true, data: response.data ?? {} });
75
- else process.stdout.write(`${JSON.stringify(response.data ?? {}, null, 2)}\n`);
76
- if (logTailStopper) logTailStopper();
77
- return;
78
- }
79
-
80
- const response = await deps.sendToDaemon({
81
- session: sessionName,
82
- command: command!,
83
- positionals,
84
- flags: daemonFlags,
85
- });
86
-
87
- if (response.ok) {
88
- if (flags.json) {
89
- printJson({ success: true, data: response.data ?? {} });
90
- if (logTailStopper) logTailStopper();
91
- return;
92
- }
93
- if (command === 'snapshot') {
94
- process.stdout.write(
95
- formatSnapshotText((response.data ?? {}) as Record<string, unknown>, {
96
- raw: flags.snapshotRaw,
97
- flatten: flags.snapshotInteractiveOnly,
98
- }),
99
- );
100
- if (logTailStopper) logTailStopper();
101
- return;
102
- }
103
- if (command === 'get') {
104
- const sub = positionals[0];
105
- if (sub === 'text') {
106
- const text = (response.data as any)?.text ?? '';
107
- process.stdout.write(`${text}\n`);
108
- if (logTailStopper) logTailStopper();
109
- return;
110
- }
111
- if (sub === 'attrs') {
112
- const node = (response.data as any)?.node ?? {};
113
- process.stdout.write(`${JSON.stringify(node, null, 2)}\n`);
114
- if (logTailStopper) logTailStopper();
115
- return;
116
- }
117
- }
118
- if (command === 'find') {
119
- const data = response.data as any;
120
- if (typeof data?.text === 'string') {
121
- process.stdout.write(`${data.text}\n`);
122
- if (logTailStopper) logTailStopper();
123
- return;
124
- }
125
- if (typeof data?.found === 'boolean') {
126
- process.stdout.write(`Found: ${data.found}\n`);
127
- if (logTailStopper) logTailStopper();
128
- return;
129
- }
130
- if (data?.node) {
131
- process.stdout.write(`${JSON.stringify(data.node, null, 2)}\n`);
132
- if (logTailStopper) logTailStopper();
133
- return;
134
- }
135
- }
136
- if (command === 'is') {
137
- const predicate = (response.data as any)?.predicate ?? 'assertion';
138
- process.stdout.write(`Passed: is ${predicate}\n`);
139
- if (logTailStopper) logTailStopper();
140
- return;
141
- }
142
- if (command === 'boot') {
143
- const platform = (response.data as any)?.platform ?? 'unknown';
144
- const device = (response.data as any)?.device ?? (response.data as any)?.id ?? 'unknown';
145
- process.stdout.write(`Boot ready: ${device} (${platform})\n`);
146
- if (logTailStopper) logTailStopper();
147
- return;
148
- }
149
- if (command === 'click') {
150
- const ref = (response.data as any)?.ref ?? '';
151
- const x = (response.data as any)?.x;
152
- const y = (response.data as any)?.y;
153
- if (ref && typeof x === 'number' && typeof y === 'number') {
154
- process.stdout.write(`Clicked @${ref} (${x}, ${y})\n`);
155
- }
156
- if (logTailStopper) logTailStopper();
157
- return;
158
- }
159
- if (response.data && typeof response.data === 'object') {
160
- const data = response.data as Record<string, unknown>;
161
- if (command === 'devices') {
162
- const devices = Array.isArray((data as any).devices) ? (data as any).devices : [];
163
- const lines = devices.map((d: any) => {
164
- const name = d?.name ?? d?.id ?? 'unknown';
165
- const platform = d?.platform ?? 'unknown';
166
- const kind = d?.kind ? ` ${d.kind}` : '';
167
- const booted = typeof d?.booted === 'boolean' ? ` booted=${d.booted}` : '';
168
- return `${name} (${platform}${kind})${booted}`;
169
- });
170
- process.stdout.write(`${lines.join('\n')}\n`);
171
- if (logTailStopper) logTailStopper();
172
- return;
173
- }
174
- if (command === 'apps') {
175
- const apps = Array.isArray((data as any).apps) ? (data as any).apps : [];
176
- const lines = apps.map((app: any) => {
177
- if (typeof app === 'string') return app;
178
- if (app && typeof app === 'object') {
179
- const bundleId = app.bundleId ?? app.package;
180
- const name = app.name ?? app.label;
181
- if (name && bundleId) return `${name} (${bundleId})`;
182
- if (bundleId && typeof app.launchable === 'boolean') {
183
- return `${bundleId} (launchable=${app.launchable})`;
184
- }
185
- if (bundleId) return String(bundleId);
186
- return JSON.stringify(app);
187
- }
188
- return String(app);
189
- });
190
- process.stdout.write(`${lines.join('\n')}\n`);
191
- if (logTailStopper) logTailStopper();
192
- return;
193
- }
194
- if (command === 'appstate') {
195
- const platform = (data as any)?.platform;
196
- const appBundleId = (data as any)?.appBundleId;
197
- const appName = (data as any)?.appName;
198
- const source = (data as any)?.source;
199
- const pkg = (data as any)?.package;
200
- const activity = (data as any)?.activity;
201
- if (platform === 'ios') {
202
- process.stdout.write(`Foreground app: ${appName ?? appBundleId}\n`);
203
- if (appBundleId) process.stdout.write(`Bundle: ${appBundleId}\n`);
204
- if (source) process.stdout.write(`Source: ${source}\n`);
205
- if (logTailStopper) logTailStopper();
206
- return;
207
- }
208
- if (platform === 'android') {
209
- process.stdout.write(`Foreground app: ${pkg ?? 'unknown'}\n`);
210
- if (activity) process.stdout.write(`Activity: ${activity}\n`);
211
- if (logTailStopper) logTailStopper();
212
- return;
213
- }
214
- }
215
- }
216
- if (logTailStopper) logTailStopper();
217
- return;
218
- }
219
-
220
- throw new AppError(response.error.code as any, response.error.message, response.error.details);
221
- } catch (err) {
222
- const appErr = asAppError(err);
223
- if (flags.json) {
224
- printJson({
225
- success: false,
226
- error: { code: appErr.code, message: appErr.message, details: appErr.details },
227
- });
228
- } else {
229
- printHumanError(appErr);
230
- if (flags.verbose) {
231
- try {
232
- const fs = await import('node:fs');
233
- const os = await import('node:os');
234
- const path = await import('node:path');
235
- const logPath = path.join(os.homedir(), '.agent-device', 'daemon.log');
236
- if (fs.existsSync(logPath)) {
237
- const content = fs.readFileSync(logPath, 'utf8');
238
- const lines = content.split('\n');
239
- const tail = lines.slice(Math.max(0, lines.length - 200)).join('\n');
240
- if (tail.trim().length > 0) {
241
- process.stderr.write(`\n[daemon log]\n${tail}\n`);
242
- }
243
- }
244
- } catch {
245
- // ignore
246
- }
247
- }
248
- }
249
- if (logTailStopper) logTailStopper();
250
- process.exit(1);
251
- }
252
- }
253
-
254
- const isDirectRun = pathToFileURL(process.argv[1] ?? '').href === import.meta.url;
255
- if (isDirectRun) {
256
- runCli(process.argv.slice(2)).catch((err) => {
257
- const appErr = asAppError(err);
258
- printHumanError(appErr);
259
- process.exit(1);
260
- });
261
- }
262
-
263
- function startDaemonLogTail(): (() => void) | null {
264
- try {
265
- const logPath = path.join(os.homedir(), '.agent-device', 'daemon.log');
266
- let offset = 0;
267
- let stopped = false;
268
- const interval = setInterval(() => {
269
- if (stopped) return;
270
- if (!fs.existsSync(logPath)) return;
271
- const stats = fs.statSync(logPath);
272
- if (stats.size <= offset) return;
273
- const fd = fs.openSync(logPath, 'r');
274
- const buffer = Buffer.alloc(stats.size - offset);
275
- fs.readSync(fd, buffer, 0, buffer.length, offset);
276
- fs.closeSync(fd);
277
- offset = stats.size;
278
- if (buffer.length > 0) {
279
- process.stdout.write(buffer.toString('utf8'));
280
- }
281
- }, 200);
282
- return () => {
283
- stopped = true;
284
- clearInterval(interval);
285
- };
286
- } catch {
287
- return null;
288
- }
289
- }
@@ -1,74 +0,0 @@
1
- import test from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { isCommandSupportedOnDevice } from '../capabilities.ts';
4
- import type { DeviceInfo } from '../../utils/device.ts';
5
-
6
- const iosSimulator: DeviceInfo = {
7
- platform: 'ios',
8
- id: 'sim-1',
9
- name: 'iPhone',
10
- kind: 'simulator',
11
- };
12
-
13
- const iosDevice: DeviceInfo = {
14
- platform: 'ios',
15
- id: 'dev-1',
16
- name: 'iPhone',
17
- kind: 'device',
18
- };
19
-
20
- const androidDevice: DeviceInfo = {
21
- platform: 'android',
22
- id: 'and-1',
23
- name: 'Pixel',
24
- kind: 'device',
25
- };
26
-
27
- test('iOS simulator-only commands reject iOS devices and Android', () => {
28
- for (const cmd of ['alert', 'pinch']) {
29
- assert.equal(isCommandSupportedOnDevice(cmd, iosSimulator), true, `${cmd} on iOS sim`);
30
- assert.equal(isCommandSupportedOnDevice(cmd, iosDevice), false, `${cmd} on iOS device`);
31
- assert.equal(isCommandSupportedOnDevice(cmd, androidDevice), false, `${cmd} on Android`);
32
- }
33
- });
34
-
35
- test('simulator-only iOS commands with Android support reject iOS devices', () => {
36
- for (const cmd of ['apps', 'reinstall', 'record', 'settings', 'swipe']) {
37
- assert.equal(isCommandSupportedOnDevice(cmd, iosSimulator), true, `${cmd} on iOS sim`);
38
- assert.equal(isCommandSupportedOnDevice(cmd, iosDevice), false, `${cmd} on iOS device`);
39
- assert.equal(isCommandSupportedOnDevice(cmd, androidDevice), true, `${cmd} on Android`);
40
- }
41
- });
42
-
43
- test('core commands support iOS simulator, iOS device, and Android', () => {
44
- for (const cmd of [
45
- 'app-switcher',
46
- 'back',
47
- 'boot',
48
- 'click',
49
- 'close',
50
- 'fill',
51
- 'find',
52
- 'focus',
53
- 'get',
54
- 'home',
55
- 'long-press',
56
- 'open',
57
- 'press',
58
- 'screenshot',
59
- 'scroll',
60
- 'scrollintoview',
61
- 'snapshot',
62
- 'type',
63
- 'wait',
64
- ]) {
65
- assert.equal(isCommandSupportedOnDevice(cmd, iosSimulator), true, `${cmd} on iOS sim`);
66
- assert.equal(isCommandSupportedOnDevice(cmd, iosDevice), true, `${cmd} on iOS device`);
67
- assert.equal(isCommandSupportedOnDevice(cmd, androidDevice), true, `${cmd} on Android`);
68
- }
69
- });
70
-
71
- test('unknown commands default to supported', () => {
72
- assert.equal(isCommandSupportedOnDevice('some-future-cmd', iosSimulator), true);
73
- assert.equal(isCommandSupportedOnDevice('some-future-cmd', androidDevice), true);
74
- });
@@ -1,16 +0,0 @@
1
- import test from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { isDeepLinkTarget } from '../open-target.ts';
4
-
5
- test('isDeepLinkTarget accepts URL-style deep links', () => {
6
- assert.equal(isDeepLinkTarget('myapp://home'), true);
7
- assert.equal(isDeepLinkTarget('https://example.com'), true);
8
- assert.equal(isDeepLinkTarget('tel:123456789'), true);
9
- assert.equal(isDeepLinkTarget('mailto:test@example.com'), true);
10
- });
11
-
12
- test('isDeepLinkTarget rejects app identifiers and malformed URLs', () => {
13
- assert.equal(isDeepLinkTarget('com.example.app'), false);
14
- assert.equal(isDeepLinkTarget('settings'), false);
15
- assert.equal(isDeepLinkTarget('http:/x'), false);
16
- });
@@ -1,57 +0,0 @@
1
- import type { DeviceInfo } from '../utils/device.ts';
2
-
3
- type KindMatrix = {
4
- simulator?: boolean;
5
- device?: boolean;
6
- emulator?: boolean;
7
- unknown?: boolean;
8
- };
9
-
10
- type CommandCapability = {
11
- ios?: KindMatrix;
12
- android?: KindMatrix;
13
- };
14
-
15
- const COMMAND_CAPABILITY_MATRIX: Record<string, CommandCapability> = {
16
- // iOS simulator-only.
17
- alert: { ios: { simulator: true }, android: {} },
18
- pinch: { ios: { simulator: true }, android: {} },
19
- 'app-switcher': { ios: { simulator: true, device: true }, android: { emulator: true, device: true, unknown: true } },
20
- apps: { ios: { simulator: true }, android: { emulator: true, device: true, unknown: true } },
21
- back: { ios: { simulator: true, device: true }, android: { emulator: true, device: true, unknown: true } },
22
- boot: { ios: { simulator: true, device: true }, android: { emulator: true, device: true, unknown: true } },
23
- click: { ios: { simulator: true, device: true }, android: { emulator: true, device: true, unknown: true } },
24
- close: { ios: { simulator: true, device: true }, android: { emulator: true, device: true, unknown: true } },
25
- fill: { ios: { simulator: true, device: true }, android: { emulator: true, device: true, unknown: true } },
26
- find: { ios: { simulator: true, device: true }, android: { emulator: true, device: true, unknown: true } },
27
- focus: { ios: { simulator: true, device: true }, android: { emulator: true, device: true, unknown: true } },
28
- get: { ios: { simulator: true, device: true }, android: { emulator: true, device: true, unknown: true } },
29
- is: { ios: { simulator: true, device: true }, android: { emulator: true, device: true, unknown: true } },
30
- home: { ios: { simulator: true, device: true }, android: { emulator: true, device: true, unknown: true } },
31
- 'long-press': { ios: { simulator: true, device: true }, android: { emulator: true, device: true, unknown: true } },
32
- open: { ios: { simulator: true, device: true }, android: { emulator: true, device: true, unknown: true } },
33
- reinstall: { ios: { simulator: true }, android: { emulator: true, device: true, unknown: true } },
34
- press: { ios: { simulator: true, device: true }, android: { emulator: true, device: true, unknown: true } },
35
- record: { ios: { simulator: true }, android: { emulator: true, device: true, unknown: true } },
36
- screenshot: { ios: { simulator: true, device: true }, android: { emulator: true, device: true, unknown: true } },
37
- scroll: { ios: { simulator: true, device: true }, android: { emulator: true, device: true, unknown: true } },
38
- scrollintoview: { ios: { simulator: true, device: true }, android: { emulator: true, device: true, unknown: true } },
39
- swipe: { ios: { simulator: true }, android: { emulator: true, device: true, unknown: true } },
40
- settings: { ios: { simulator: true }, android: { emulator: true, device: true, unknown: true } },
41
- snapshot: { ios: { simulator: true, device: true }, android: { emulator: true, device: true, unknown: true } },
42
- type: { ios: { simulator: true, device: true }, android: { emulator: true, device: true, unknown: true } },
43
- wait: { ios: { simulator: true, device: true }, android: { emulator: true, device: true, unknown: true } },
44
- };
45
-
46
- export function isCommandSupportedOnDevice(command: string, device: DeviceInfo): boolean {
47
- const capability = COMMAND_CAPABILITY_MATRIX[command];
48
- if (!capability) return true;
49
- const byPlatform = capability[device.platform];
50
- if (!byPlatform) return false;
51
- const kind = (device.kind ?? 'unknown') as keyof KindMatrix;
52
- return byPlatform[kind] === true;
53
- }
54
-
55
- export function listCapabilityCommands(): string[] {
56
- return Object.keys(COMMAND_CAPABILITY_MATRIX).sort();
57
- }