firecrawl-cli 1.18.6 → 1.19.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.
@@ -0,0 +1,680 @@
1
+ "use strict";
2
+ /**
3
+ * Doctor command — diagnostics for the Firecrawl CLI.
4
+ *
5
+ * Two modes:
6
+ * 1. `firecrawl doctor` runs a pass/warn/fail checklist over the local
7
+ * environment (CLI version, auth, credits, MCP install, etc.) and exits
8
+ * non-zero if any check fails. Suitable as a CI / onboarding gate.
9
+ * 2. `firecrawl doctor <job-id>` forwards to `/v2/support/ask` and renders
10
+ * the answer for a specific failed run.
11
+ */
12
+ var __importDefault = (this && this.__importDefault) || function (mod) {
13
+ return (mod && mod.__esModule) ? mod : { "default": mod };
14
+ };
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.runChecks = runChecks;
17
+ exports.runSupportAsk = runSupportAsk;
18
+ exports.handleDoctorCommand = handleDoctorCommand;
19
+ const fs_1 = require("fs");
20
+ const path_1 = __importDefault(require("path"));
21
+ const package_json_1 = __importDefault(require("../../package.json"));
22
+ const config_1 = require("../utils/config");
23
+ const agents_1 = require("../utils/agents");
24
+ const npm_registry_1 = require("../utils/npm-registry");
25
+ const status_1 = require("./status");
26
+ const DEFAULT_API_URL = 'https://api.firecrawl.dev';
27
+ const REACHABILITY_WARN_MS = 2000;
28
+ const MIN_NODE_MAJOR = 18;
29
+ function shouldUseColor() {
30
+ if (process.env.FORCE_COLOR !== undefined) {
31
+ return process.env.FORCE_COLOR !== '0';
32
+ }
33
+ if (process.env.NO_COLOR !== undefined)
34
+ return false;
35
+ if (process.env.TERM === 'dumb')
36
+ return false;
37
+ return Boolean(process.stdout.isTTY);
38
+ }
39
+ const colorEnabled = shouldUseColor();
40
+ const color = (code) => (colorEnabled ? code : '');
41
+ const orange = color('\x1b[38;5;208m');
42
+ const reset = color('\x1b[0m');
43
+ const dim = color('\x1b[2m');
44
+ const bold = color('\x1b[1m');
45
+ const green = color('\x1b[32m');
46
+ const red = color('\x1b[31m');
47
+ const yellow = color('\x1b[33m');
48
+ function statusIcon(status) {
49
+ switch (status) {
50
+ case 'pass':
51
+ return `${green}●${reset}`;
52
+ case 'warn':
53
+ return `${yellow}!${reset}`;
54
+ case 'fail':
55
+ return `${red}✗${reset}`;
56
+ }
57
+ }
58
+ function maskApiKey(key) {
59
+ if (!key)
60
+ return '';
61
+ const tail = key.slice(-4);
62
+ return `fc-...${tail}`;
63
+ }
64
+ function authSourceLabel(source) {
65
+ switch (source) {
66
+ case 'flag':
67
+ return 'flag';
68
+ case 'env':
69
+ return 'env';
70
+ case 'stored':
71
+ return 'stored';
72
+ case 'none':
73
+ return 'none';
74
+ }
75
+ }
76
+ function trimApiUrl(url) {
77
+ return url.replace(/\/$/, '');
78
+ }
79
+ function dimParentheticals(message) {
80
+ return message.replace(/\(([^)]*)\)/g, `${dim}($1)${reset}`);
81
+ }
82
+ async function pingCreditUsage(apiKey, apiUrl) {
83
+ const url = `${trimApiUrl(apiUrl)}/v2/team/credit-usage`;
84
+ const start = Date.now();
85
+ try {
86
+ const response = await fetch(url, {
87
+ method: 'GET',
88
+ headers: {
89
+ Authorization: `Bearer ${apiKey}`,
90
+ 'Content-Type': 'application/json',
91
+ },
92
+ });
93
+ const durationMs = Date.now() - start;
94
+ let body;
95
+ try {
96
+ body = await response.json();
97
+ }
98
+ catch {
99
+ body = undefined;
100
+ }
101
+ return { status: response.status, durationMs, body };
102
+ }
103
+ catch (error) {
104
+ return {
105
+ status: 0,
106
+ durationMs: Date.now() - start,
107
+ error: error instanceof Error ? error.message : 'Unknown error',
108
+ };
109
+ }
110
+ }
111
+ async function fetchQueueStatus(apiKey, apiUrl) {
112
+ const url = `${trimApiUrl(apiUrl)}/v2/team/queue-status`;
113
+ try {
114
+ const response = await fetch(url, {
115
+ method: 'GET',
116
+ headers: {
117
+ Authorization: `Bearer ${apiKey}`,
118
+ 'Content-Type': 'application/json',
119
+ },
120
+ });
121
+ if (!response.ok) {
122
+ return { ok: false, error: `HTTP ${response.status}` };
123
+ }
124
+ const data = (await response.json());
125
+ if (!data.success || data.maxConcurrency === undefined) {
126
+ return { ok: false, error: 'Invalid response' };
127
+ }
128
+ return {
129
+ ok: true,
130
+ active: data.activeJobsInQueue ?? 0,
131
+ max: data.maxConcurrency,
132
+ };
133
+ }
134
+ catch (error) {
135
+ return {
136
+ ok: false,
137
+ error: error instanceof Error ? error.message : 'Unknown error',
138
+ };
139
+ }
140
+ }
141
+ function extractCredits(body) {
142
+ if (!body || typeof body !== 'object')
143
+ return {};
144
+ const data = body;
145
+ const root = data.data && typeof data.data === 'object'
146
+ ? data.data
147
+ : data;
148
+ const remaining = typeof root.remainingCredits === 'number'
149
+ ? root.remainingCredits
150
+ : undefined;
151
+ const plan = typeof root.planCredits === 'number' ? root.planCredits : undefined;
152
+ return { remaining, plan };
153
+ }
154
+ function formatNumber(n) {
155
+ return n.toLocaleString('en-US');
156
+ }
157
+ async function readLocalEnv(cwd) {
158
+ const envPath = path_1.default.join(cwd, '.env');
159
+ let envFileExists = false;
160
+ let envKey;
161
+ try {
162
+ const content = await fs_1.promises.readFile(envPath, 'utf8');
163
+ envFileExists = true;
164
+ for (const line of content.split(/\r?\n/)) {
165
+ const trimmed = line.trim();
166
+ if (!trimmed || trimmed.startsWith('#'))
167
+ continue;
168
+ const match = trimmed.match(/^(?:export\s+)?FIRECRAWL_API_KEY\s*=\s*(.+)$/);
169
+ if (match) {
170
+ envKey = match[1].trim().replace(/^['"]/, '').replace(/['"]$/, '');
171
+ break;
172
+ }
173
+ }
174
+ }
175
+ catch {
176
+ envFileExists = false;
177
+ }
178
+ const gitignorePath = path_1.default.join(cwd, '.gitignore');
179
+ let gitignoreExists = false;
180
+ let gitignoreHasFirecrawl = false;
181
+ try {
182
+ const content = await fs_1.promises.readFile(gitignorePath, 'utf8');
183
+ gitignoreExists = true;
184
+ gitignoreHasFirecrawl = content.split(/\r?\n/).some((line) => {
185
+ const trimmed = line.trim();
186
+ if (!trimmed || trimmed.startsWith('#'))
187
+ return false;
188
+ return /^\/?\.firecrawl(?:\/|$)/.test(trimmed);
189
+ });
190
+ }
191
+ catch {
192
+ gitignoreExists = false;
193
+ }
194
+ let firecrawlDirExists = false;
195
+ try {
196
+ const stat = await fs_1.promises.stat(path_1.default.join(cwd, '.firecrawl'));
197
+ firecrawlDirExists = stat.isDirectory();
198
+ }
199
+ catch {
200
+ firecrawlDirExists = false;
201
+ }
202
+ return {
203
+ envFileExists,
204
+ envKey,
205
+ gitignoreExists,
206
+ gitignoreHasFirecrawl,
207
+ firecrawlDirExists,
208
+ };
209
+ }
210
+ function checkCliVersion(latest) {
211
+ const current = package_json_1.default.version;
212
+ if (latest.unreachable || !latest.version) {
213
+ return {
214
+ name: 'CLI Version',
215
+ status: 'warn',
216
+ message: `v${current} (registry unreachable)`,
217
+ };
218
+ }
219
+ const cmp = (0, npm_registry_1.compareVersions)(current, latest.version);
220
+ if (cmp >= 0) {
221
+ return {
222
+ name: 'CLI Version',
223
+ status: 'pass',
224
+ message: `v${current} (latest)`,
225
+ };
226
+ }
227
+ return {
228
+ name: 'CLI Version',
229
+ status: 'warn',
230
+ message: `v${current} (v${latest.version} available)`,
231
+ fix: 'npm install -g firecrawl-cli',
232
+ };
233
+ }
234
+ function checkNodeRuntime() {
235
+ const version = process.versions.node;
236
+ const major = parseInt(version.split('.')[0], 10);
237
+ if (!Number.isFinite(major) || major < MIN_NODE_MAJOR) {
238
+ return {
239
+ name: 'Node Runtime',
240
+ status: 'fail',
241
+ message: `v${version} (requires >=${MIN_NODE_MAJOR})`,
242
+ fix: `Upgrade Node to >=${MIN_NODE_MAJOR}`,
243
+ };
244
+ }
245
+ return {
246
+ name: 'Node Runtime',
247
+ status: 'pass',
248
+ message: `v${version}`,
249
+ };
250
+ }
251
+ function checkApiKey(apiKey, source) {
252
+ if (!apiKey) {
253
+ return {
254
+ name: 'API Key',
255
+ status: 'fail',
256
+ message: `not found`,
257
+ fix: 'firecrawl login',
258
+ };
259
+ }
260
+ return {
261
+ name: 'API Key',
262
+ status: 'pass',
263
+ message: `${maskApiKey(apiKey)} (${authSourceLabel(source)})`,
264
+ };
265
+ }
266
+ function checkApiReachability(apiUrl, ping, isCustomUrl) {
267
+ if (!ping) {
268
+ return {
269
+ name: 'API Reachability',
270
+ status: 'fail',
271
+ message: `not checked (no API key)`,
272
+ };
273
+ }
274
+ if (ping.status === 0) {
275
+ return {
276
+ name: 'API Reachability',
277
+ status: 'fail',
278
+ message: `${apiUrl} (${ping.error || 'unreachable'})`,
279
+ fix: 'Check network/DNS or firewall',
280
+ };
281
+ }
282
+ if (isCustomUrl) {
283
+ return {
284
+ name: 'API Reachability',
285
+ status: 'warn',
286
+ message: `${apiUrl} (custom URL, ${ping.durationMs}ms)`,
287
+ };
288
+ }
289
+ if (ping.durationMs > REACHABILITY_WARN_MS) {
290
+ return {
291
+ name: 'API Reachability',
292
+ status: 'warn',
293
+ message: `${ping.durationMs}ms (slow)`,
294
+ };
295
+ }
296
+ return {
297
+ name: 'API Reachability',
298
+ status: 'pass',
299
+ message: `${ping.durationMs}ms`,
300
+ };
301
+ }
302
+ function checkApiKeyValidity(ping) {
303
+ if (!ping) {
304
+ return {
305
+ name: 'API Key Validity',
306
+ status: 'fail',
307
+ message: 'not checked',
308
+ };
309
+ }
310
+ if (ping.status === 401 || ping.status === 403) {
311
+ return {
312
+ name: 'API Key Validity',
313
+ status: 'fail',
314
+ message: `HTTP ${ping.status} (invalid or revoked)`,
315
+ fix: 'firecrawl login',
316
+ };
317
+ }
318
+ if (ping.status === 0) {
319
+ return {
320
+ name: 'API Key Validity',
321
+ status: 'fail',
322
+ message: `could not reach API`,
323
+ };
324
+ }
325
+ if (ping.status >= 200 && ping.status < 300) {
326
+ return {
327
+ name: 'API Key Validity',
328
+ status: 'pass',
329
+ message: 'accepted',
330
+ };
331
+ }
332
+ return {
333
+ name: 'API Key Validity',
334
+ status: 'warn',
335
+ message: `HTTP ${ping.status}`,
336
+ };
337
+ }
338
+ function checkCredits(ping) {
339
+ if (!ping || ping.status === 0 || ping.status >= 400) {
340
+ return {
341
+ name: 'Credits',
342
+ status: 'warn',
343
+ message: 'unavailable',
344
+ };
345
+ }
346
+ const { remaining, plan } = extractCredits(ping.body);
347
+ if (remaining === undefined) {
348
+ return {
349
+ name: 'Credits',
350
+ status: 'warn',
351
+ message: 'no credit info in response',
352
+ };
353
+ }
354
+ if (remaining === 0) {
355
+ return {
356
+ name: 'Credits',
357
+ status: 'fail',
358
+ message: '0 remaining',
359
+ fix: 'Upgrade plan or buy credits at firecrawl.dev/pricing',
360
+ };
361
+ }
362
+ if (plan && plan > 0) {
363
+ const pct = (remaining / plan) * 100;
364
+ const pctStr = pct > 100 ? 'above plan' : `${pct.toFixed(0)}% left`;
365
+ const label = `${formatNumber(remaining)} / ${formatNumber(plan)} (${pctStr})`;
366
+ if (pct < 10) {
367
+ return { name: 'Credits', status: 'warn', message: label };
368
+ }
369
+ return { name: 'Credits', status: 'pass', message: label };
370
+ }
371
+ // Pay-as-you-go: warn under 100.
372
+ const label = `${formatNumber(remaining)} (pay-as-you-go)`;
373
+ if (remaining < 100) {
374
+ return { name: 'Credits', status: 'warn', message: label };
375
+ }
376
+ return { name: 'Credits', status: 'pass', message: label };
377
+ }
378
+ function checkConcurrency(queue) {
379
+ if (!queue.ok || queue.max === undefined || queue.active === undefined) {
380
+ return {
381
+ name: 'Concurrency',
382
+ status: 'fail',
383
+ message: `queue endpoint error (${queue.error || 'unknown'})`,
384
+ };
385
+ }
386
+ const label = `${queue.active}/${queue.max} jobs`;
387
+ if (queue.active >= queue.max) {
388
+ return {
389
+ name: 'Concurrency',
390
+ status: 'warn',
391
+ message: `${label} (queueing)`,
392
+ };
393
+ }
394
+ return {
395
+ name: 'Concurrency',
396
+ status: 'pass',
397
+ message: label,
398
+ };
399
+ }
400
+ function checkLocalEnv(local, configuredKey) {
401
+ if (!local.envFileExists) {
402
+ return {
403
+ name: 'Local .env',
404
+ status: 'pass',
405
+ message: `not present`,
406
+ };
407
+ }
408
+ if (!local.envKey) {
409
+ return {
410
+ name: 'Local .env',
411
+ status: 'pass',
412
+ message: `present, FIRECRAWL_API_KEY not set`,
413
+ };
414
+ }
415
+ if (configuredKey && configuredKey !== local.envKey) {
416
+ return {
417
+ name: 'Local .env',
418
+ status: 'warn',
419
+ message: `mismatches stored key`,
420
+ fix: 'firecrawl env --overwrite',
421
+ };
422
+ }
423
+ return {
424
+ name: 'Local .env',
425
+ status: 'pass',
426
+ message: `FIRECRAWL_API_KEY set`,
427
+ };
428
+ }
429
+ function checkGitignore(local) {
430
+ if (!local.firecrawlDirExists) {
431
+ return {
432
+ name: '.gitignore',
433
+ status: 'pass',
434
+ message: `no .firecrawl/ to ignore`,
435
+ };
436
+ }
437
+ if (!local.gitignoreExists) {
438
+ return {
439
+ name: '.gitignore',
440
+ status: 'warn',
441
+ message: `missing — .firecrawl/ present`,
442
+ fix: 'echo .firecrawl/ >> .gitignore',
443
+ };
444
+ }
445
+ if (!local.gitignoreHasFirecrawl) {
446
+ return {
447
+ name: '.gitignore',
448
+ status: 'warn',
449
+ message: `.firecrawl/ not ignored`,
450
+ fix: 'echo .firecrawl/ >> .gitignore',
451
+ };
452
+ }
453
+ return {
454
+ name: '.gitignore',
455
+ status: 'pass',
456
+ message: '.firecrawl/ ignored',
457
+ };
458
+ }
459
+ function checkAgents(agents) {
460
+ const detected = agents.filter((a) => a.installed);
461
+ if (detected.length === 0) {
462
+ return {
463
+ name: 'AI Agents',
464
+ status: 'warn',
465
+ message: 'none detected',
466
+ };
467
+ }
468
+ return {
469
+ name: 'AI Agents',
470
+ status: 'pass',
471
+ message: detected.map((a) => a.name).join(', '),
472
+ };
473
+ }
474
+ function checkMcp(agents) {
475
+ const detected = agents.filter((a) => a.installed);
476
+ if (detected.length === 0) {
477
+ return {
478
+ name: 'MCP Server',
479
+ status: 'warn',
480
+ message: `no agents detected`,
481
+ };
482
+ }
483
+ const registered = detected.filter((a) => a.mcpRegistered);
484
+ if (registered.length === 0) {
485
+ return {
486
+ name: 'MCP Server',
487
+ status: 'warn',
488
+ message: `not registered in ${detected.map((a) => a.name).join(', ')}`,
489
+ fix: 'firecrawl setup mcp',
490
+ };
491
+ }
492
+ return {
493
+ name: 'MCP Server',
494
+ status: 'pass',
495
+ message: `registered in ${registered.map((a) => a.name).join(', ')}`,
496
+ };
497
+ }
498
+ /**
499
+ * Run every health check. Exported for tests.
500
+ */
501
+ async function runChecks(options = {}) {
502
+ const config = (0, config_1.getConfig)();
503
+ const apiKey = options.apiKey || config.apiKey;
504
+ const apiUrl = options.apiUrl || config.apiUrl || DEFAULT_API_URL;
505
+ const isCustomUrl = trimApiUrl(apiUrl) !== DEFAULT_API_URL;
506
+ const authSource = options.apiKey
507
+ ? 'flag'
508
+ : (0, status_1.getAuthSource)();
509
+ const [latest, local, agents] = await Promise.all([
510
+ (0, npm_registry_1.getLatestVersion)('firecrawl-cli'),
511
+ readLocalEnv(process.cwd()),
512
+ (0, agents_1.detectAgents)(),
513
+ ]);
514
+ let ping = null;
515
+ let queue = {
516
+ ok: false,
517
+ error: 'no API key',
518
+ };
519
+ if (apiKey) {
520
+ [ping, queue] = await Promise.all([
521
+ pingCreditUsage(apiKey, apiUrl),
522
+ fetchQueueStatus(apiKey, apiUrl),
523
+ ]);
524
+ }
525
+ const checks = [
526
+ checkCliVersion(latest),
527
+ checkNodeRuntime(),
528
+ checkApiKey(apiKey, authSource),
529
+ checkApiReachability(apiUrl, ping, isCustomUrl),
530
+ checkApiKeyValidity(ping),
531
+ checkCredits(ping),
532
+ apiKey
533
+ ? checkConcurrency(queue)
534
+ : {
535
+ name: 'Concurrency',
536
+ status: 'fail',
537
+ message: 'not checked',
538
+ },
539
+ checkLocalEnv(local, apiKey),
540
+ checkGitignore(local),
541
+ checkAgents(agents),
542
+ checkMcp(agents),
543
+ ];
544
+ return { checks, apiUrl };
545
+ }
546
+ function padName(name, width) {
547
+ if (name.length >= width)
548
+ return name;
549
+ return name + ' '.repeat(width - name.length);
550
+ }
551
+ function renderChecks(checks) {
552
+ const width = Math.max(...checks.map((c) => c.name.length)) + 2;
553
+ console.log('');
554
+ console.log(` ${orange}🔥 ${bold}firecrawl${reset} ${dim}doctor v${package_json_1.default.version}${reset}`);
555
+ console.log('');
556
+ for (const check of checks) {
557
+ console.log(` ${statusIcon(check.status)} ${padName(check.name, width)}${dimParentheticals(check.message)}`);
558
+ if (check.fix) {
559
+ console.log(` ${dim}→ ${check.fix}${reset}`);
560
+ }
561
+ }
562
+ const counts = { pass: 0, warn: 0, fail: 0 };
563
+ for (const c of checks)
564
+ counts[c.status] += 1;
565
+ console.log('');
566
+ console.log(` ${dim}${counts.pass} passed, ${counts.warn} warning${counts.warn === 1 ? '' : 's'}, ${counts.fail} failed${reset}`);
567
+ console.log('');
568
+ }
569
+ /**
570
+ * Mode 2: diagnose a specific run via /v2/support/ask.
571
+ */
572
+ async function runSupportAsk(options) {
573
+ const config = (0, config_1.getConfig)();
574
+ const apiKey = options.apiKey || config.apiKey;
575
+ const apiUrl = options.apiUrl || config.apiUrl || DEFAULT_API_URL;
576
+ const jobId = options.jobId;
577
+ const question = options.query || 'why did this run fail?';
578
+ if (!apiKey) {
579
+ console.error(`${red}Error:${reset} API key required for run diagnostics. Run \`firecrawl login\`.`);
580
+ return 1;
581
+ }
582
+ const url = `${trimApiUrl(apiUrl)}/v2/support/ask`;
583
+ let response;
584
+ try {
585
+ response = await fetch(url, {
586
+ method: 'POST',
587
+ headers: {
588
+ Authorization: `Bearer ${apiKey}`,
589
+ 'Content-Type': 'application/json',
590
+ },
591
+ body: JSON.stringify({ question, jobId }),
592
+ });
593
+ }
594
+ catch (error) {
595
+ console.error(`${red}Error:${reset} could not reach support endpoint — ${error instanceof Error ? error.message : 'unknown error'}`);
596
+ return 1;
597
+ }
598
+ let body;
599
+ try {
600
+ body = await response.json();
601
+ }
602
+ catch {
603
+ body = undefined;
604
+ }
605
+ if (!response.ok) {
606
+ const detail = body && typeof body === 'object' && 'error' in body
607
+ ? String(body.error)
608
+ : `HTTP ${response.status}`;
609
+ console.error(`${red}Error:${reset} ${detail}`);
610
+ return 1;
611
+ }
612
+ if (options.json) {
613
+ console.log(JSON.stringify(body, null, 2));
614
+ return 0;
615
+ }
616
+ renderSupportAnswer(jobId, body);
617
+ return 0;
618
+ }
619
+ function renderSupportAnswer(jobId, body) {
620
+ console.log('');
621
+ console.log(` ${orange}🔥 ${bold}firecrawl${reset} ${dim}doctor ${jobId}${reset}`);
622
+ console.log('');
623
+ if (!body || typeof body !== 'object') {
624
+ console.log(' (empty response)');
625
+ console.log('');
626
+ return;
627
+ }
628
+ const obj = body;
629
+ const answer = typeof obj.answer === 'string'
630
+ ? obj.answer
631
+ : typeof obj.message === 'string'
632
+ ? obj.message
633
+ : typeof obj.text === 'string'
634
+ ? obj.text
635
+ : null;
636
+ if (answer) {
637
+ for (const line of answer.split(/\r?\n/)) {
638
+ console.log(` ${line}`);
639
+ }
640
+ }
641
+ else {
642
+ console.log(` ${JSON.stringify(body, null, 2)}`);
643
+ }
644
+ const sources = obj.sources ?? obj.docs ?? obj.links;
645
+ if (Array.isArray(sources) && sources.length > 0) {
646
+ console.log('');
647
+ console.log(` ${dim}Sources:${reset}`);
648
+ for (const s of sources) {
649
+ if (typeof s === 'string') {
650
+ console.log(` ${dim}- ${s}${reset}`);
651
+ }
652
+ else if (s && typeof s === 'object' && 'url' in s) {
653
+ console.log(` ${dim}- ${String(s.url)}${reset}`);
654
+ }
655
+ }
656
+ }
657
+ console.log('');
658
+ }
659
+ /**
660
+ * Top-level command entrypoint.
661
+ */
662
+ async function handleDoctorCommand(options = {}) {
663
+ if (options.jobId) {
664
+ const exitCode = await runSupportAsk(options);
665
+ if (exitCode !== 0)
666
+ process.exit(exitCode);
667
+ return;
668
+ }
669
+ const { checks } = await runChecks(options);
670
+ if (options.json) {
671
+ console.log(JSON.stringify({ checks }, null, 2));
672
+ }
673
+ else {
674
+ renderChecks(checks);
675
+ }
676
+ if (checks.some((c) => c.status === 'fail')) {
677
+ process.exit(1);
678
+ }
679
+ }
680
+ //# sourceMappingURL=doctor.js.map