visor-ai 0.2.5 → 0.2.6

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.
package/dist/cli.js CHANGED
@@ -1,8 +1,9 @@
1
- import { DEFAULT_SERVER_URL, getAdapter } from './adapters.js';
2
- import { DEFAULT_STARTUP_TIMEOUT_SECONDS, isAppiumReachable, startManagedAppium, statusManagedAppium, stopManagedAppium } from './appiumLifecycle.js';
1
+ import { DEFAULT_SERVER_URL } from './adapters.js';
2
+ import { DaemonRequestTimeoutError, DaemonUnavailableError, runDaemonAction, runDaemonScenario, startVisorDaemon, statusVisorDaemon, stopVisorDaemon } from './daemon.js';
3
+ import { DeviceSelectionError, resolveRunningDevice } from './devices.js';
3
4
  import { makeError } from './errors.js';
4
5
  import { writeReports } from './report.js';
5
- import { determinismCheck, runScenario } from './runner.js';
6
+ import { determinismCheck } from './runner.js';
6
7
  import { errorMessage, makeId, utcNowIso } from './utils.js';
7
8
  import { parseAndValidate } from './validator.js';
8
9
  const ACTION_COMMANDS = new Set([
@@ -25,7 +26,6 @@ const ALL_COMMANDS = new Set([
25
26
  'stop'
26
27
  ]);
27
28
  const GLOBAL_SPEC = {
28
- platform: 'string',
29
29
  device: 'string',
30
30
  timeout: 'number',
31
31
  output: 'string',
@@ -33,11 +33,7 @@ const GLOBAL_SPEC = {
33
33
  seed: 'number',
34
34
  'server-url': 'string',
35
35
  'app-id': 'string',
36
- 'appium-cmd': 'string',
37
- 'startup-timeout': 'number',
38
- 'no-auto-start-appium': 'boolean',
39
36
  attach: 'boolean',
40
- mock: 'boolean',
41
37
  verbose: 'boolean'
42
38
  };
43
39
  const ACTION_SPEC = {
@@ -58,40 +54,29 @@ const ACTION_SPEC = {
58
54
  const COMMAND_SPECS = {
59
55
  validate: { format: 'string' },
60
56
  run: {
61
- platform: 'string',
62
57
  device: 'string',
63
58
  timeout: 'number',
64
59
  output: 'string',
65
60
  format: 'string',
66
61
  'server-url': 'string',
67
62
  'app-id': 'string',
68
- 'appium-cmd': 'string',
69
- 'startup-timeout': 'number',
70
- 'no-auto-start-appium': 'boolean',
71
- attach: 'boolean',
72
- mock: 'boolean'
63
+ attach: 'boolean'
73
64
  },
74
65
  benchmark: {
75
66
  runs: 'number',
76
67
  threshold: 'number',
77
- platform: 'string',
78
68
  device: 'string',
79
69
  timeout: 'number',
80
70
  output: 'string',
81
71
  format: 'string',
82
72
  'server-url': 'string',
83
73
  'app-id': 'string',
84
- 'appium-cmd': 'string',
85
- 'startup-timeout': 'number',
86
- 'no-auto-start-appium': 'boolean',
87
- attach: 'boolean',
88
- mock: 'boolean'
74
+ attach: 'boolean'
89
75
  },
90
76
  report: { format: 'string' },
91
77
  start: {
92
78
  'server-url': 'string',
93
79
  'appium-cmd': 'string',
94
- 'startup-timeout': 'number',
95
80
  format: 'string'
96
81
  },
97
82
  status: {
@@ -121,18 +106,19 @@ function helpText() {
121
106
  '',
122
107
  'Commands:',
123
108
  ' validate <scenario>',
124
- ' run <scenario> [--mock] [--output <dir>]',
109
+ ' run <scenario> [--output <dir>]',
125
110
  ' benchmark <scenario> [--runs <n>] [--threshold <percent>]',
126
111
  ' report [path]',
127
- ' start [--server-url <url>]',
112
+ ' start [--server-url <url>] [--appium-cmd <cmd>]',
128
113
  ' status [--server-url <url>]',
129
114
  ' stop [--server-url <url>] [--force]',
130
115
  ' tap|navigate|act|scroll|screenshot|wait|source',
131
116
  '',
132
117
  'Examples:',
133
118
  ' visor validate scenarios/checkout-smoke.json',
134
- ' visor run scenarios/checkout-smoke.json --mock --output artifacts-test',
135
- ' visor scroll --platform android --mock --direction down',
119
+ ' visor start --server-url http://127.0.0.1:4723',
120
+ ' visor run scenarios/checkout-smoke.json --output artifacts-test',
121
+ ' visor scroll --device emulator-5554 --direction down',
136
122
  ' node dist/main.js status'
137
123
  ].join('\n');
138
124
  }
@@ -208,69 +194,31 @@ function parseCommand(argv) {
208
194
  function warningIssues(issues) {
209
195
  return issues.filter((issue) => issue.severity === 'warning');
210
196
  }
211
- function resolvedRuntime(options, scenario) {
212
- const platform = String(options.platform ?? scenario.meta.platform);
213
- const defaultDevice = platform === 'android' ? 'emulator-5554' : 'iPhone 17 Pro';
214
- scenario.meta.platform = platform;
197
+ async function resolvedRuntime(options) {
198
+ const selectedDevice = await resolveRunningDevice(typeof options.device === 'string' ? options.device : undefined);
215
199
  return {
216
- platform,
217
- device: String(options.device ?? defaultDevice),
200
+ platform: selectedDevice.platform,
201
+ device: selectedDevice.id,
218
202
  timeout: typeof options.timeout === 'number'
219
203
  ? options.timeout
220
- : typeof scenario.config.timeoutMs === 'number'
221
- ? scenario.config.timeoutMs
222
- : 2500,
223
- output_dir: String(options.output ?? scenario.config.artifactsDir ?? 'artifacts'),
204
+ : 2500,
205
+ output_dir: String(options.output ?? 'artifacts'),
224
206
  server_url: String(options['server-url'] ?? DEFAULT_SERVER_URL),
225
- use_mock: Boolean(options.mock),
226
207
  app_id: typeof options['app-id'] === 'string' ? options['app-id'] : undefined,
227
- attach_to_running: Boolean(options.attach),
228
- auto_start_appium: !Boolean(options['no-auto-start-appium']),
229
- appium_cmd: typeof options['appium-cmd'] === 'string' ? options['appium-cmd'] : undefined,
230
- startup_timeout: typeof options['startup-timeout'] === 'number'
231
- ? options['startup-timeout']
232
- : DEFAULT_STARTUP_TIMEOUT_SECONDS
208
+ attach_to_running: Boolean(options.attach)
233
209
  };
234
210
  }
235
- async function ensureNonMockRuntime(options) {
236
- if (await isAppiumReachable(options.server_url, 2000)) {
237
- return {
238
- serverUrl: options.server_url,
239
- started: false
240
- };
241
- }
242
- if (!options.auto_start_appium) {
243
- throw new Error(`Cannot reach Appium server at ${options.server_url}. Start Appium and ensure ${options.platform} target '${options.device}' is booted.`);
244
- }
245
- return startManagedAppium(options.server_url, options.appium_cmd, options.startup_timeout);
246
- }
247
- async function stopAutoStartedAppium(runtimeState) {
248
- if (!runtimeState || !runtimeState.started || typeof runtimeState.serverUrl !== 'string') {
249
- return;
250
- }
251
- try {
252
- await stopManagedAppium(runtimeState.serverUrl, false);
253
- }
254
- catch {
255
- await stopManagedAppium(runtimeState.serverUrl, true);
256
- }
257
- }
258
211
  function actionArgs(command, options) {
259
212
  const commonIgnored = new Set([
260
- 'platform',
261
213
  'device',
262
214
  'format',
263
215
  'output',
264
216
  'timeout',
265
217
  'verbose',
266
218
  'server-url',
267
- 'mock',
268
219
  'seed',
269
220
  'app-id',
270
- 'attach',
271
- 'appium-cmd',
272
- 'startup-timeout',
273
- 'no-auto-start-appium'
221
+ 'attach'
274
222
  ]);
275
223
  const args = Object.entries(options).reduce((acc, [key, value]) => {
276
224
  if (!commonIgnored.has(key) && value !== undefined) {
@@ -283,6 +231,25 @@ function actionArgs(command, options) {
283
231
  }
284
232
  return args;
285
233
  }
234
+ function isTargetInitializationError(error) {
235
+ const message = errorMessage(error);
236
+ return (message.includes('Failed to create WebdriverIO Appium session') ||
237
+ message.includes('CoreSimulatorService') ||
238
+ message.includes('Could not find a driver for automationName'));
239
+ }
240
+ function targetInitializationNextStep(error) {
241
+ const message = errorMessage(error);
242
+ if (message.includes('CoreSimulatorService') || message.includes('simctl')) {
243
+ return 'Run `xcrun simctl list` from the same shell. If it fails, restart Simulator/CoreSimulator and ensure Appium is not running from a sandboxed process.';
244
+ }
245
+ if (message.includes('Could not find a driver for automationName')) {
246
+ return 'Install the matching Appium driver, verify it with `appium driver list --installed`, then restart `visor start`.';
247
+ }
248
+ if (message.includes('bundle identifier') && message.includes('unknown')) {
249
+ return 'Verify the iOS bundle id is installed on the selected simulator/device, launch it first when using `--attach`, then retry.';
250
+ }
251
+ return 'Verify Appium driver setup, target device state, and app id, then retry.';
252
+ }
286
253
  function cmdHelp() {
287
254
  const commandId = makeId('cmd');
288
255
  const startedAt = utcNowIso();
@@ -292,8 +259,9 @@ function cmdHelp() {
292
259
  commands: Array.from(ALL_COMMANDS),
293
260
  examples: [
294
261
  'visor validate scenarios/checkout-smoke.json',
295
- 'visor run scenarios/checkout-smoke.json --mock --output artifacts-test',
296
- 'visor scroll --platform android --mock --direction down',
262
+ 'visor start --server-url http://127.0.0.1:4723',
263
+ 'visor run scenarios/checkout-smoke.json --output artifacts-test',
264
+ 'visor scroll --device emulator-5554 --direction down',
297
265
  'node dist/main.js status'
298
266
  ]
299
267
  };
@@ -333,17 +301,36 @@ export async function cmdRun(parsed) {
333
301
  response.data = { issues };
334
302
  return { code: 1, response };
335
303
  }
336
- const runtime = resolvedRuntime(parsed.options, scenario);
337
- let runtimeState;
338
- let cleanupError;
304
+ let runtime;
339
305
  try {
340
- if (!runtime.use_mock) {
341
- runtimeState = await ensureNonMockRuntime(runtime);
306
+ runtime = await resolvedRuntime({
307
+ ...parsed.options,
308
+ timeout: typeof parsed.options.timeout === 'number'
309
+ ? parsed.options.timeout
310
+ : typeof scenario.config.timeoutMs === 'number'
311
+ ? scenario.config.timeoutMs
312
+ : undefined,
313
+ output: parsed.options.output ??
314
+ (typeof scenario.config.artifactsDir === 'string' ? scenario.config.artifactsDir : undefined)
315
+ });
316
+ }
317
+ catch (error) {
318
+ if (!(error instanceof DeviceSelectionError)) {
319
+ throw error;
342
320
  }
343
- const adapter = await getAdapter(runtime.platform, runtime.server_url, runtime.device, runtime.use_mock, runtime.app_id, runtime.attach_to_running);
344
- const result = await runScenario(scenario, adapter, runtime.device, runtime.timeout, runtime.output_dir);
321
+ const response = envelopeFail(commandId, startedAt, 'TARGET_ERROR', 'Device selection failed', error.message, 'Start one target device or rerun with --device <device-id>');
322
+ response.data = { devices: error.devices };
323
+ return { code: 1, response };
324
+ }
325
+ try {
326
+ const result = await runDaemonScenario({
327
+ platform: runtime.platform,
328
+ server_url: runtime.server_url,
329
+ device: runtime.device,
330
+ app_id: runtime.app_id,
331
+ attach_to_running: runtime.attach_to_running
332
+ }, scenario, runtime.device, runtime.timeout, runtime.output_dir);
345
333
  const outputs = writeReports(result, runtime.output_dir);
346
- await stopAutoStartedAppium(runtimeState);
347
334
  if (result.status === 'fail' && result.error) {
348
335
  const response = envelopeFail(commandId, startedAt, result.error.code, result.error.message, result.error.likely_cause, result.error.next_step);
349
336
  response.artifacts = Object.values(outputs);
@@ -361,17 +348,19 @@ export async function cmdRun(parsed) {
361
348
  return { code: 0, response };
362
349
  }
363
350
  catch (error) {
364
- const response = envelopeFail(commandId, startedAt, 'TARGET_ERROR', 'Failed to initialize platform target', errorMessage(error), 'For local non-mock runs: install Node deps, run `visor start` (or remove --no-auto-start-appium), boot target emulator/simulator, and retry.');
351
+ const isDaemonUnavailable = error instanceof DaemonUnavailableError;
352
+ const isDaemonTimeout = error instanceof DaemonRequestTimeoutError;
353
+ const response = envelopeFail(commandId, startedAt, 'TARGET_ERROR', isDaemonUnavailable
354
+ ? 'Run requires visor start'
355
+ : isDaemonTimeout
356
+ ? 'Timed out waiting for Visor daemon'
357
+ : 'Failed to initialize platform target', errorMessage(error), isDaemonUnavailable
358
+ ? 'Run `visor start`, verify the target emulator/simulator is booted, and retry.'
359
+ : isDaemonTimeout
360
+ ? 'Inspect .visor/daemon/daemon.log and .visor/appium/*.log, then retry or run `visor stop --force` before restarting.'
361
+ : 'Verify the target emulator/simulator and Appium driver setup, then retry.');
365
362
  return { code: 1, response };
366
363
  }
367
- finally {
368
- try {
369
- await stopAutoStartedAppium(runtimeState);
370
- }
371
- catch (error) {
372
- cleanupError = error;
373
- }
374
- }
375
364
  }
376
365
  export async function cmdBenchmark(parsed) {
377
366
  const commandId = makeId('cmd');
@@ -383,62 +372,62 @@ export async function cmdBenchmark(parsed) {
383
372
  response.data = { issues };
384
373
  return { code: 1, response };
385
374
  }
386
- const runtime = resolvedRuntime(parsed.options, scenario);
375
+ let runtime;
376
+ try {
377
+ runtime = await resolvedRuntime({
378
+ ...parsed.options,
379
+ timeout: typeof parsed.options.timeout === 'number'
380
+ ? parsed.options.timeout
381
+ : typeof scenario.config.timeoutMs === 'number'
382
+ ? scenario.config.timeoutMs
383
+ : undefined,
384
+ output: parsed.options.output ??
385
+ (typeof scenario.config.artifactsDir === 'string' ? scenario.config.artifactsDir : undefined)
386
+ });
387
+ }
388
+ catch (error) {
389
+ if (!(error instanceof DeviceSelectionError)) {
390
+ throw error;
391
+ }
392
+ const response = envelopeFail(commandId, startedAt, 'TARGET_ERROR', 'Device selection failed', error.message, 'Start one target device or rerun with --device <device-id>');
393
+ response.data = { devices: error.devices };
394
+ return { code: 1, response };
395
+ }
387
396
  const runs = typeof parsed.options.runs === 'number' ? parsed.options.runs : 20;
388
397
  const threshold = typeof parsed.options.threshold === 'number' ? parsed.options.threshold : 95;
389
398
  const signatures = [];
390
399
  const runIds = [];
391
400
  let failures = 0;
392
- let runtimeState;
393
- let cleanupError;
394
- if (!runtime.use_mock) {
401
+ for (let index = 0; index < runs; index += 1) {
395
402
  try {
396
- runtimeState = await ensureNonMockRuntime(runtime);
397
- }
398
- catch (error) {
399
- const response = envelopeFail(commandId, startedAt, 'TARGET_ERROR', 'Failed benchmark preflight for non-mock runtime', errorMessage(error), 'Start Appium (or allow auto-start), verify local device target, then rerun benchmark');
400
- return { code: 1, response };
401
- }
402
- }
403
- try {
404
- for (let index = 0; index < runs; index += 1) {
405
- try {
406
- const adapter = await getAdapter(runtime.platform, runtime.server_url, runtime.device, runtime.use_mock, runtime.app_id, runtime.attach_to_running);
407
- const result = await runScenario(scenario, adapter, runtime.device, runtime.timeout, runtime.output_dir);
408
- writeReports(result, runtime.output_dir);
409
- signatures.push(result.determinism_signature);
410
- runIds.push(result.run_id);
411
- if (result.status !== 'ok') {
412
- failures += 1;
413
- }
414
- }
415
- catch {
403
+ const result = await runDaemonScenario({
404
+ platform: runtime.platform,
405
+ server_url: runtime.server_url,
406
+ device: runtime.device,
407
+ app_id: runtime.app_id,
408
+ attach_to_running: runtime.attach_to_running
409
+ }, scenario, runtime.device, runtime.timeout, runtime.output_dir);
410
+ writeReports(result, runtime.output_dir);
411
+ signatures.push(result.determinism_signature);
412
+ runIds.push(result.run_id);
413
+ if (result.status !== 'ok') {
416
414
  failures += 1;
417
415
  }
418
416
  }
419
- }
420
- finally {
421
- try {
422
- await stopAutoStartedAppium(runtimeState);
423
- }
424
417
  catch (error) {
425
- cleanupError = error;
418
+ if (error instanceof DaemonUnavailableError) {
419
+ const response = envelopeFail(commandId, startedAt, 'TARGET_ERROR', 'Benchmark requires visor start', errorMessage(error), 'Run `visor start`, verify the target emulator/simulator is booted, and retry.');
420
+ return { code: 1, response };
421
+ }
422
+ if (error instanceof DaemonRequestTimeoutError) {
423
+ const response = envelopeFail(commandId, startedAt, 'TARGET_ERROR', 'Timed out waiting for Visor daemon', errorMessage(error), 'Inspect .visor/daemon/daemon.log and .visor/appium/*.log, then retry or run `visor stop --force` before restarting.');
424
+ return { code: 1, response };
425
+ }
426
+ failures += 1;
426
427
  }
427
428
  }
428
429
  const score = determinismCheck(signatures);
429
430
  const passGate = score >= threshold && failures === 0;
430
- if (cleanupError) {
431
- const response = envelopeFail(commandId, startedAt, 'TARGET_ERROR', 'Benchmark completed but failed to stop auto-started Appium', errorMessage(cleanupError), 'Inspect .visor/appium logs and stop Appium manually');
432
- response.data = {
433
- runs,
434
- threshold,
435
- determinismScore: score,
436
- pass: false,
437
- failures,
438
- runIds
439
- };
440
- return { code: 1, response };
441
- }
442
431
  const response = envelopeOk(commandId, startedAt, [], 'report');
443
432
  response.data = {
444
433
  runs,
@@ -466,43 +455,29 @@ export async function cmdReport(parsed) {
466
455
  export async function cmdAction(command, parsed) {
467
456
  const commandId = makeId('cmd');
468
457
  const startedAt = utcNowIso();
469
- const options = {
470
- platform: String(parsed.options.platform ?? 'android'),
471
- device: typeof parsed.options.device === 'string' ? parsed.options.device : undefined,
472
- server_url: String(parsed.options['server-url'] ?? DEFAULT_SERVER_URL),
473
- use_mock: Boolean(parsed.options.mock),
474
- app_id: typeof parsed.options['app-id'] === 'string' ? parsed.options['app-id'] : undefined,
475
- attach_to_running: Boolean(parsed.options.attach),
476
- auto_start_appium: !Boolean(parsed.options['no-auto-start-appium']),
477
- appium_cmd: typeof parsed.options['appium-cmd'] === 'string' ? parsed.options['appium-cmd'] : undefined,
478
- startup_timeout: typeof parsed.options['startup-timeout'] === 'number'
479
- ? parsed.options['startup-timeout']
480
- : DEFAULT_STARTUP_TIMEOUT_SECONDS
481
- };
482
- let runtimeState;
483
- let cleanupError;
458
+ let options;
459
+ try {
460
+ options = await resolvedRuntime(parsed.options);
461
+ }
462
+ catch (error) {
463
+ if (!(error instanceof DeviceSelectionError)) {
464
+ throw error;
465
+ }
466
+ const response = envelopeFail(commandId, startedAt, 'TARGET_ERROR', 'Device selection failed', error.message, 'Start one target device or rerun with --device <device-id>');
467
+ response.data = { devices: error.devices };
468
+ return { code: 1, response };
469
+ }
484
470
  let payload = {};
485
471
  let artifacts = [];
486
472
  let actionError;
487
- let adapter;
488
473
  try {
489
- if (!options.use_mock) {
490
- runtimeState = await ensureNonMockRuntime({
491
- platform: options.platform,
492
- device: options.device ?? (options.platform === 'android' ? 'emulator-5554' : 'iPhone 17 Pro'),
493
- timeout: undefined,
494
- output_dir: 'artifacts',
495
- server_url: options.server_url,
496
- use_mock: options.use_mock,
497
- app_id: options.app_id,
498
- attach_to_running: options.attach_to_running,
499
- auto_start_appium: options.auto_start_appium,
500
- appium_cmd: options.appium_cmd,
501
- startup_timeout: options.startup_timeout
502
- });
503
- }
504
- adapter = await getAdapter(options.platform, options.server_url, options.device, options.use_mock, options.app_id, options.attach_to_running);
505
- payload = await adapter[command](actionArgs(command, parsed.options));
474
+ payload = await runDaemonAction({
475
+ platform: options.platform,
476
+ server_url: options.server_url,
477
+ device: options.device,
478
+ app_id: options.app_id,
479
+ attach_to_running: options.attach_to_running
480
+ }, command, actionArgs(command, parsed.options));
506
481
  const actionPayload = payload.args;
507
482
  if (actionPayload && typeof actionPayload === 'object' && !Array.isArray(actionPayload)) {
508
483
  const maybePath = actionPayload.path;
@@ -514,27 +489,23 @@ export async function cmdAction(command, parsed) {
514
489
  catch (error) {
515
490
  actionError = error;
516
491
  }
517
- finally {
518
- if (adapter) {
519
- await adapter.close();
520
- }
521
- try {
522
- await stopAutoStartedAppium(runtimeState);
523
- }
524
- catch (error) {
525
- cleanupError = error;
526
- }
527
- }
528
492
  if (actionError) {
529
- const cause = cleanupError
530
- ? `${errorMessage(actionError)}; additionally failed to stop auto-started Appium: ${errorMessage(cleanupError)}`
531
- : errorMessage(actionError);
532
- const response = envelopeFail(commandId, startedAt, 'ACTION_ERROR', `${command} failed`, cause, 'Check command args and retry');
533
- response.data = payload;
534
- return { code: 1, response };
535
- }
536
- if (cleanupError) {
537
- const response = envelopeFail(commandId, startedAt, 'TARGET_ERROR', `${command} completed but failed to stop auto-started Appium`, errorMessage(cleanupError), 'Inspect .visor/appium logs and stop Appium manually');
493
+ const isDaemonUnavailable = actionError instanceof DaemonUnavailableError;
494
+ const isDaemonTimeout = actionError instanceof DaemonRequestTimeoutError;
495
+ const isTargetInitialization = isTargetInitializationError(actionError);
496
+ const response = envelopeFail(commandId, startedAt, isDaemonUnavailable || isDaemonTimeout || isTargetInitialization ? 'TARGET_ERROR' : 'ACTION_ERROR', isDaemonUnavailable
497
+ ? `${command} requires visor start`
498
+ : isDaemonTimeout
499
+ ? `${command} timed out waiting for Visor daemon`
500
+ : isTargetInitialization
501
+ ? `${command} failed to initialize platform target`
502
+ : `${command} failed`, errorMessage(actionError), isDaemonUnavailable
503
+ ? 'Run `visor start`, verify the target emulator/simulator is booted, and retry.'
504
+ : isDaemonTimeout
505
+ ? 'Inspect .visor/daemon/daemon.log and .visor/appium/*.log, then retry or run `visor stop --force` before restarting.'
506
+ : isTargetInitialization
507
+ ? targetInitializationNextStep(actionError)
508
+ : 'Check command args and retry');
538
509
  response.data = payload;
539
510
  return { code: 1, response };
540
511
  }
@@ -546,23 +517,22 @@ export async function cmdStart(parsed) {
546
517
  const commandId = makeId('cmd');
547
518
  const startedAt = utcNowIso();
548
519
  try {
549
- const status = await startManagedAppium(String(parsed.options['server-url'] ?? DEFAULT_SERVER_URL), typeof parsed.options['appium-cmd'] === 'string' ? parsed.options['appium-cmd'] : undefined, typeof parsed.options['startup-timeout'] === 'number'
550
- ? parsed.options['startup-timeout']
551
- : DEFAULT_STARTUP_TIMEOUT_SECONDS);
520
+ const status = await startVisorDaemon(String(parsed.options['server-url'] ?? DEFAULT_SERVER_URL), typeof parsed.options['appium-cmd'] === 'string' ? parsed.options['appium-cmd'] : undefined);
552
521
  const response = envelopeOk(commandId, startedAt, [], 'run');
553
522
  response.data = status;
554
523
  return { code: 0, response };
555
524
  }
556
525
  catch (error) {
557
- const response = envelopeFail(commandId, startedAt, 'TARGET_ERROR', 'Failed to start Appium', errorMessage(error), 'Install Node deps, check --appium-cmd, and inspect .visor/appium/*.log');
526
+ const response = envelopeFail(commandId, startedAt, 'TARGET_ERROR', 'Failed to start Visor daemon', errorMessage(error), 'Install Node deps, check --appium-cmd, and inspect .visor/daemon/*.log');
558
527
  return { code: 1, response };
559
528
  }
560
529
  }
561
530
  export async function cmdStatus(parsed) {
562
531
  const commandId = makeId('cmd');
563
532
  const startedAt = utcNowIso();
564
- const status = await statusManagedAppium(String(parsed.options['server-url'] ?? DEFAULT_SERVER_URL));
565
- const response = envelopeOk(commandId, startedAt, [], Boolean(status.reachable) ? 'run' : 'start');
533
+ const status = await statusVisorDaemon(String(parsed.options['server-url'] ?? DEFAULT_SERVER_URL));
534
+ const daemon = status.daemon;
535
+ const response = envelopeOk(commandId, startedAt, [], daemon?.running ? 'run' : daemon?.unresponsive ? 'stop' : 'start');
566
536
  response.data = status;
567
537
  return { code: 0, response };
568
538
  }
@@ -570,13 +540,13 @@ export async function cmdStop(parsed) {
570
540
  const commandId = makeId('cmd');
571
541
  const startedAt = utcNowIso();
572
542
  try {
573
- const result = await stopManagedAppium(String(parsed.options['server-url'] ?? DEFAULT_SERVER_URL), Boolean(parsed.options.force));
543
+ const result = await stopVisorDaemon(String(parsed.options['server-url'] ?? DEFAULT_SERVER_URL), Boolean(parsed.options.force));
574
544
  const response = envelopeOk(commandId, startedAt, [], 'none');
575
545
  response.data = result;
576
546
  return { code: 0, response };
577
547
  }
578
548
  catch (error) {
579
- const response = envelopeFail(commandId, startedAt, 'TARGET_ERROR', 'Failed to stop managed Appium', errorMessage(error), 'Retry with --force or check process state manually');
549
+ const response = envelopeFail(commandId, startedAt, 'TARGET_ERROR', 'Failed to stop Visor daemon', errorMessage(error), 'Retry with --force or check process state manually');
580
550
  return { code: 1, response };
581
551
  }
582
552
  }