@clankmates/cli 0.11.1 → 0.13.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.
- package/README.md +7 -3
- package/package.json +1 -1
- package/skills/codex/clankmates/SKILL.md +4 -3
- package/src/commands/auth/access-keys.ts +206 -0
- package/src/commands/auth.ts +3 -196
- package/src/commands/channel/render.ts +224 -0
- package/src/commands/channel/tokens.ts +145 -0
- package/src/commands/channel/validation.ts +11 -0
- package/src/commands/channel.ts +11 -340
- package/src/commands/doctor/checks.ts +123 -0
- package/src/commands/doctor/render.ts +140 -0
- package/src/commands/doctor/suggestions.ts +42 -0
- package/src/commands/doctor/types.ts +75 -0
- package/src/commands/doctor.ts +12 -371
- package/src/commands/feed.ts +19 -178
- package/src/commands/inbox/content.ts +31 -0
- package/src/commands/inbox/filters.ts +70 -0
- package/src/commands/inbox/messages.ts +69 -0
- package/src/commands/inbox/participants.ts +152 -0
- package/src/commands/inbox/render.ts +13 -0
- package/src/commands/inbox/resource-output.ts +217 -0
- package/src/commands/inbox/schema.ts +185 -0
- package/src/commands/inbox/screening.ts +76 -0
- package/src/commands/inbox/sync-scopes.ts +59 -0
- package/src/commands/inbox/thread-output.ts +344 -0
- package/src/commands/inbox/watch.ts +203 -0
- package/src/commands/inbox.ts +58 -1220
- package/src/commands/post.ts +24 -116
- package/src/lib/args.ts +8 -0
- package/src/lib/cache/scopes.ts +216 -0
- package/src/lib/cache/store.ts +195 -0
- package/src/lib/cache/types.ts +31 -0
- package/src/lib/cache.ts +18 -382
- package/src/lib/client/auth.ts +122 -0
- package/src/lib/client/channel-keys.ts +57 -0
- package/src/lib/client/channels.ts +364 -0
- package/src/lib/client/core.ts +133 -0
- package/src/lib/client/feed.ts +76 -0
- package/src/lib/client/inbox.ts +361 -0
- package/src/lib/client/posts.ts +213 -0
- package/src/lib/client/raw-api.ts +33 -0
- package/src/lib/client/users.ts +88 -0
- package/src/lib/client.ts +197 -894
- package/src/lib/help.ts +66 -9
- package/src/lib/json_api.ts +74 -9
- package/src/lib/pagination.ts +5 -0
- package/src/lib/polling.ts +146 -0
- package/src/lib/post-output.ts +55 -0
- package/src/types/api.ts +1 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { createCommandContext } from "../../lib/context";
|
|
2
|
+
|
|
3
|
+
export interface DoctorCheck {
|
|
4
|
+
name: string;
|
|
5
|
+
ok: boolean;
|
|
6
|
+
required: boolean;
|
|
7
|
+
source?: string;
|
|
8
|
+
detail: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface DoctorReport {
|
|
12
|
+
cliVersion: string;
|
|
13
|
+
ok: boolean;
|
|
14
|
+
status: "ok" | "needs_attention";
|
|
15
|
+
summary: string;
|
|
16
|
+
profile: string;
|
|
17
|
+
configPath: string;
|
|
18
|
+
configFileExists: boolean;
|
|
19
|
+
baseUrl: string;
|
|
20
|
+
hasMasterToken: boolean;
|
|
21
|
+
masterTokenSource: string;
|
|
22
|
+
masterTokenOk: boolean;
|
|
23
|
+
masterTokenError: string;
|
|
24
|
+
hasReadOnlyToken: boolean;
|
|
25
|
+
readOnlyTokenSource: string;
|
|
26
|
+
readOnlyTokenOk: boolean;
|
|
27
|
+
readOnlyTokenError: string;
|
|
28
|
+
ownerReadTokenAvailable: boolean;
|
|
29
|
+
ownerReadTokenSource: string;
|
|
30
|
+
ownerReadTokenOk: boolean;
|
|
31
|
+
ownerReadTokenError: string;
|
|
32
|
+
ownerReadReady: boolean;
|
|
33
|
+
openApiOk: boolean;
|
|
34
|
+
openApiError: string;
|
|
35
|
+
storedChannelTokens: number;
|
|
36
|
+
channel: string;
|
|
37
|
+
channelId: string;
|
|
38
|
+
channelResolutionOk: boolean;
|
|
39
|
+
channelResolutionError: string;
|
|
40
|
+
publishTokenAvailable: boolean;
|
|
41
|
+
publishTokenSource: string;
|
|
42
|
+
publishReady: boolean;
|
|
43
|
+
channelDiagnosticsAvailable: boolean;
|
|
44
|
+
channelDiagnosticsError: string;
|
|
45
|
+
channelSummary: string;
|
|
46
|
+
channelStateCodes: string[];
|
|
47
|
+
channelStateLabels: string[];
|
|
48
|
+
channelActivePublishKeyCount: number;
|
|
49
|
+
channelLastPostedAt: string;
|
|
50
|
+
channelPostingPausedUntil: string;
|
|
51
|
+
channelLatestBlockedWriteAt: string;
|
|
52
|
+
channelLatestBlockedWriteReason: string;
|
|
53
|
+
channelLatestBlockedWriteReasonLabel: string;
|
|
54
|
+
checks: DoctorCheck[];
|
|
55
|
+
suggestions: string[];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export type DoctorContext = Awaited<ReturnType<typeof createCommandContext>>;
|
|
59
|
+
|
|
60
|
+
export type ChannelDiagnostics = Awaited<
|
|
61
|
+
ReturnType<DoctorContext["client"]["getChannelDiagnostics"]>
|
|
62
|
+
>;
|
|
63
|
+
|
|
64
|
+
export interface CheckResult {
|
|
65
|
+
ok: boolean;
|
|
66
|
+
error?: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface ChannelResolution extends CheckResult {
|
|
70
|
+
channelId?: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface ChannelDiagnosticsResult extends CheckResult {
|
|
74
|
+
value?: ChannelDiagnostics;
|
|
75
|
+
}
|
package/src/commands/doctor.ts
CHANGED
|
@@ -1,14 +1,5 @@
|
|
|
1
|
-
import { access } from "node:fs/promises";
|
|
2
|
-
|
|
3
1
|
import { createCommandContext } from "../lib/context";
|
|
4
2
|
import { channelFlag, type ParsedArgs } from "../lib/args";
|
|
5
|
-
import {
|
|
6
|
-
formatTimestamp,
|
|
7
|
-
joinBlocks,
|
|
8
|
-
renderBullets,
|
|
9
|
-
renderFields,
|
|
10
|
-
renderSection,
|
|
11
|
-
} from "../lib/human";
|
|
12
3
|
import { printValue, type Io } from "../lib/output";
|
|
13
4
|
import { CLI_VERSION } from "../lib/version";
|
|
14
5
|
import {
|
|
@@ -17,61 +8,18 @@ import {
|
|
|
17
8
|
resolvePublishToken,
|
|
18
9
|
resolveReadOnlyToken,
|
|
19
10
|
} from "../lib/tokens";
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
status: "ok" | "needs_attention";
|
|
33
|
-
summary: string;
|
|
34
|
-
profile: string;
|
|
35
|
-
configPath: string;
|
|
36
|
-
configFileExists: boolean;
|
|
37
|
-
baseUrl: string;
|
|
38
|
-
hasMasterToken: boolean;
|
|
39
|
-
masterTokenSource: string;
|
|
40
|
-
masterTokenOk: boolean;
|
|
41
|
-
masterTokenError: string;
|
|
42
|
-
hasReadOnlyToken: boolean;
|
|
43
|
-
readOnlyTokenSource: string;
|
|
44
|
-
readOnlyTokenOk: boolean;
|
|
45
|
-
readOnlyTokenError: string;
|
|
46
|
-
ownerReadTokenAvailable: boolean;
|
|
47
|
-
ownerReadTokenSource: string;
|
|
48
|
-
ownerReadTokenOk: boolean;
|
|
49
|
-
ownerReadTokenError: string;
|
|
50
|
-
ownerReadReady: boolean;
|
|
51
|
-
openApiOk: boolean;
|
|
52
|
-
openApiError: string;
|
|
53
|
-
storedChannelTokens: number;
|
|
54
|
-
channel: string;
|
|
55
|
-
channelId: string;
|
|
56
|
-
channelResolutionOk: boolean;
|
|
57
|
-
channelResolutionError: string;
|
|
58
|
-
publishTokenAvailable: boolean;
|
|
59
|
-
publishTokenSource: string;
|
|
60
|
-
publishReady: boolean;
|
|
61
|
-
channelDiagnosticsAvailable: boolean;
|
|
62
|
-
channelDiagnosticsError: string;
|
|
63
|
-
channelSummary: string;
|
|
64
|
-
channelStateCodes: string[];
|
|
65
|
-
channelStateLabels: string[];
|
|
66
|
-
channelActivePublishKeyCount: number;
|
|
67
|
-
channelLastPostedAt: string;
|
|
68
|
-
channelPostingPausedUntil: string;
|
|
69
|
-
channelLatestBlockedWriteAt: string;
|
|
70
|
-
channelLatestBlockedWriteReason: string;
|
|
71
|
-
channelLatestBlockedWriteReasonLabel: string;
|
|
72
|
-
checks: DoctorCheck[];
|
|
73
|
-
suggestions: string[];
|
|
74
|
-
}
|
|
11
|
+
import {
|
|
12
|
+
checkConfigPath,
|
|
13
|
+
formatChannelDiagnosticsDetail,
|
|
14
|
+
formatChannelSummary,
|
|
15
|
+
maybeFetchChannelDiagnostics,
|
|
16
|
+
resolveRequestedChannel,
|
|
17
|
+
runCheck,
|
|
18
|
+
runOptionalCheck,
|
|
19
|
+
} from "./doctor/checks";
|
|
20
|
+
import { renderDoctorReport } from "./doctor/render";
|
|
21
|
+
import { buildSuggestions } from "./doctor/suggestions";
|
|
22
|
+
import type { DoctorCheck, DoctorReport } from "./doctor/types";
|
|
75
23
|
|
|
76
24
|
export async function runDoctorCommand(
|
|
77
25
|
args: ParsedArgs,
|
|
@@ -271,310 +219,3 @@ export async function runDoctorCommand(
|
|
|
271
219
|
context.outputMode === "json" ? report : renderDoctorReport(report),
|
|
272
220
|
);
|
|
273
221
|
}
|
|
274
|
-
|
|
275
|
-
async function checkConfigPath(configPath: string): Promise<boolean> {
|
|
276
|
-
try {
|
|
277
|
-
await access(configPath);
|
|
278
|
-
return true;
|
|
279
|
-
} catch (error) {
|
|
280
|
-
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
|
|
281
|
-
return false;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
return false;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
async function resolveRequestedChannel(
|
|
289
|
-
context: Awaited<ReturnType<typeof createCommandContext>>,
|
|
290
|
-
channel: string,
|
|
291
|
-
): Promise<{ ok: boolean; channelId?: string; error?: string }> {
|
|
292
|
-
try {
|
|
293
|
-
return {
|
|
294
|
-
ok: true,
|
|
295
|
-
channelId: await context.client.resolveChannelId(channel),
|
|
296
|
-
};
|
|
297
|
-
} catch (error) {
|
|
298
|
-
return {
|
|
299
|
-
ok: false,
|
|
300
|
-
error: (error as Error).message,
|
|
301
|
-
};
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
async function runOptionalCheck(
|
|
306
|
-
token: string | undefined,
|
|
307
|
-
operation: (token: string) => Promise<unknown>,
|
|
308
|
-
missingMessage: string,
|
|
309
|
-
): Promise<{ ok: boolean; error?: string }> {
|
|
310
|
-
if (!token) {
|
|
311
|
-
return { ok: false, error: missingMessage };
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
return runCheck(() => operation(token));
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
async function runCheck(
|
|
318
|
-
operation: () => Promise<unknown>,
|
|
319
|
-
): Promise<{ ok: boolean; error?: string }> {
|
|
320
|
-
try {
|
|
321
|
-
await operation();
|
|
322
|
-
return { ok: true };
|
|
323
|
-
} catch (error) {
|
|
324
|
-
return { ok: false, error: (error as Error).message };
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
function buildSuggestions(input: {
|
|
329
|
-
configFileExists: boolean;
|
|
330
|
-
openApiOk: boolean;
|
|
331
|
-
ownerReadReady: boolean;
|
|
332
|
-
requestedChannel?: string;
|
|
333
|
-
channelResolutionOk: boolean;
|
|
334
|
-
publishReady: boolean;
|
|
335
|
-
channelDiagnosticsOk: boolean;
|
|
336
|
-
}): string[] {
|
|
337
|
-
const suggestions: string[] = [];
|
|
338
|
-
|
|
339
|
-
if (!input.configFileExists) {
|
|
340
|
-
suggestions.push("Run `clankm config init` to create local config.");
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
if (!input.openApiOk) {
|
|
344
|
-
suggestions.push("Check `CLANKMATES_BASE_URL` or `--base-url`, then retry `clankm doctor --json`.");
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
if (!input.ownerReadReady) {
|
|
348
|
-
suggestions.push("Configure a read-only or master token for owner reads with `clankm auth login ...`.");
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
if (input.requestedChannel && !input.channelResolutionOk) {
|
|
352
|
-
suggestions.push("Use a channel UUID or configure an owner-read token so channel names can be resolved.");
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
if (input.requestedChannel && !input.publishReady) {
|
|
356
|
-
suggestions.push("Provide `--channel-token`, `CLANKMATES_CHANNEL_TOKEN`, `CLANKMATES_CHANNEL_TOKENS_JSON`, `CLANKMATES_CHANNEL_TOKENS_FILE`, or a master token for publish.");
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
if (
|
|
360
|
-
input.requestedChannel &&
|
|
361
|
-
input.channelResolutionOk &&
|
|
362
|
-
input.ownerReadReady &&
|
|
363
|
-
!input.channelDiagnosticsOk
|
|
364
|
-
) {
|
|
365
|
-
suggestions.push("Retry the channel diagnostics with an owner-read token that can read the requested channel.");
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
return suggestions;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
async function maybeFetchChannelDiagnostics(input: {
|
|
372
|
-
context: Awaited<ReturnType<typeof createCommandContext>>;
|
|
373
|
-
requestedChannel?: string;
|
|
374
|
-
channelResolution: { ok: boolean; channelId?: string; error?: string };
|
|
375
|
-
ownerReadReady: boolean;
|
|
376
|
-
}): Promise<{
|
|
377
|
-
ok: boolean;
|
|
378
|
-
value?: Awaited<
|
|
379
|
-
ReturnType<Awaited<ReturnType<typeof createCommandContext>>["client"]["getChannelDiagnostics"]>
|
|
380
|
-
>;
|
|
381
|
-
error?: string;
|
|
382
|
-
}> {
|
|
383
|
-
if (!input.requestedChannel) {
|
|
384
|
-
return { ok: false };
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
if (!input.channelResolution.ok || !input.channelResolution.channelId) {
|
|
388
|
-
return {
|
|
389
|
-
ok: false,
|
|
390
|
-
error: "Channel diagnostics require a resolved channel.",
|
|
391
|
-
};
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
if (!input.ownerReadReady) {
|
|
395
|
-
return {
|
|
396
|
-
ok: false,
|
|
397
|
-
error: "Channel diagnostics require an owner-read token.",
|
|
398
|
-
};
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
try {
|
|
402
|
-
return {
|
|
403
|
-
ok: true,
|
|
404
|
-
value: await input.context.client.getChannelDiagnostics(
|
|
405
|
-
input.channelResolution.channelId,
|
|
406
|
-
),
|
|
407
|
-
};
|
|
408
|
-
} catch (error) {
|
|
409
|
-
return {
|
|
410
|
-
ok: false,
|
|
411
|
-
error: (error as Error).message,
|
|
412
|
-
};
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
function formatChannelSummary(
|
|
417
|
-
diagnostics:
|
|
418
|
-
| Awaited<
|
|
419
|
-
ReturnType<
|
|
420
|
-
Awaited<ReturnType<typeof createCommandContext>>["client"]["getChannelDiagnostics"]
|
|
421
|
-
>
|
|
422
|
-
>
|
|
423
|
-
| undefined,
|
|
424
|
-
): string {
|
|
425
|
-
if (!diagnostics || diagnostics.state_labels.length === 0) {
|
|
426
|
-
return "";
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
return diagnostics.state_labels.join(", ");
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
function formatChannelDiagnosticsDetail(
|
|
433
|
-
diagnostics:
|
|
434
|
-
| Awaited<
|
|
435
|
-
ReturnType<
|
|
436
|
-
Awaited<ReturnType<typeof createCommandContext>>["client"]["getChannelDiagnostics"]
|
|
437
|
-
>
|
|
438
|
-
>
|
|
439
|
-
| undefined,
|
|
440
|
-
): string {
|
|
441
|
-
const summary = formatChannelSummary(diagnostics);
|
|
442
|
-
|
|
443
|
-
if (!summary) {
|
|
444
|
-
return "Channel diagnostics are unavailable.";
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
return `Channel diagnostics loaded successfully: ${summary}.`;
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
function renderDoctorReport(report: DoctorReport): string {
|
|
451
|
-
return joinBlocks([
|
|
452
|
-
renderSection(
|
|
453
|
-
"Status",
|
|
454
|
-
renderFields([
|
|
455
|
-
["Result", report.status],
|
|
456
|
-
["Summary", report.summary],
|
|
457
|
-
["Profile", report.profile],
|
|
458
|
-
["Base URL", report.baseUrl],
|
|
459
|
-
["Config", report.configPath],
|
|
460
|
-
["CLI version", report.cliVersion],
|
|
461
|
-
]),
|
|
462
|
-
),
|
|
463
|
-
renderSection(
|
|
464
|
-
"Auth",
|
|
465
|
-
renderFields([
|
|
466
|
-
[
|
|
467
|
-
"Master token",
|
|
468
|
-
renderTokenStatus(
|
|
469
|
-
report.hasMasterToken,
|
|
470
|
-
report.masterTokenOk,
|
|
471
|
-
report.masterTokenSource,
|
|
472
|
-
report.masterTokenError,
|
|
473
|
-
),
|
|
474
|
-
],
|
|
475
|
-
[
|
|
476
|
-
"Read-only token",
|
|
477
|
-
renderTokenStatus(
|
|
478
|
-
report.hasReadOnlyToken,
|
|
479
|
-
report.readOnlyTokenOk,
|
|
480
|
-
report.readOnlyTokenSource,
|
|
481
|
-
report.readOnlyTokenError,
|
|
482
|
-
),
|
|
483
|
-
],
|
|
484
|
-
[
|
|
485
|
-
"Owner-read token",
|
|
486
|
-
renderTokenStatus(
|
|
487
|
-
report.ownerReadTokenAvailable,
|
|
488
|
-
report.ownerReadTokenOk,
|
|
489
|
-
report.ownerReadTokenSource,
|
|
490
|
-
report.ownerReadTokenError,
|
|
491
|
-
),
|
|
492
|
-
],
|
|
493
|
-
["Stored channel tokens", report.storedChannelTokens],
|
|
494
|
-
]),
|
|
495
|
-
),
|
|
496
|
-
report.channel
|
|
497
|
-
? renderSection(
|
|
498
|
-
"Channel",
|
|
499
|
-
renderFields([
|
|
500
|
-
["Requested", report.channel],
|
|
501
|
-
["Resolved ID", report.channelId],
|
|
502
|
-
[
|
|
503
|
-
"Resolution",
|
|
504
|
-
report.channelResolutionOk ? "ok" : report.channelResolutionError,
|
|
505
|
-
],
|
|
506
|
-
[
|
|
507
|
-
"Publish token",
|
|
508
|
-
report.publishTokenAvailable
|
|
509
|
-
? `available (${report.publishTokenSource})`
|
|
510
|
-
: "missing",
|
|
511
|
-
],
|
|
512
|
-
["Publish ready", report.publishReady],
|
|
513
|
-
[
|
|
514
|
-
"Diagnostics",
|
|
515
|
-
report.channelDiagnosticsAvailable
|
|
516
|
-
? report.channelSummary
|
|
517
|
-
: report.channelDiagnosticsError,
|
|
518
|
-
],
|
|
519
|
-
["Active publish keys", report.channelActivePublishKeyCount],
|
|
520
|
-
[
|
|
521
|
-
"Last posted",
|
|
522
|
-
report.channelLastPostedAt
|
|
523
|
-
? formatTimestamp(report.channelLastPostedAt)
|
|
524
|
-
: undefined,
|
|
525
|
-
],
|
|
526
|
-
[
|
|
527
|
-
"Paused until",
|
|
528
|
-
report.channelPostingPausedUntil
|
|
529
|
-
? formatTimestamp(report.channelPostingPausedUntil)
|
|
530
|
-
: undefined,
|
|
531
|
-
],
|
|
532
|
-
[
|
|
533
|
-
"Latest blocked write",
|
|
534
|
-
report.channelLatestBlockedWriteAt
|
|
535
|
-
? formatTimestamp(report.channelLatestBlockedWriteAt)
|
|
536
|
-
: undefined,
|
|
537
|
-
],
|
|
538
|
-
[
|
|
539
|
-
"Latest blocked reason",
|
|
540
|
-
report.channelLatestBlockedWriteReasonLabel ||
|
|
541
|
-
report.channelLatestBlockedWriteReason ||
|
|
542
|
-
undefined,
|
|
543
|
-
],
|
|
544
|
-
]),
|
|
545
|
-
)
|
|
546
|
-
: undefined,
|
|
547
|
-
renderSection("Checks", renderChecks(report.checks)),
|
|
548
|
-
report.suggestions.length > 0
|
|
549
|
-
? renderSection("Suggestions", renderBullets(report.suggestions))
|
|
550
|
-
: undefined,
|
|
551
|
-
]);
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
function renderTokenStatus(
|
|
555
|
-
configured: boolean,
|
|
556
|
-
ok: boolean,
|
|
557
|
-
source: string,
|
|
558
|
-
error: string,
|
|
559
|
-
): string {
|
|
560
|
-
if (ok) {
|
|
561
|
-
return `ok (${source})`;
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
if (configured) {
|
|
565
|
-
return `failed (${source}): ${error}`;
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
return error || "missing";
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
function renderChecks(checks: DoctorCheck[]): string {
|
|
572
|
-
const rows = checks.map((check) => {
|
|
573
|
-
const status = check.ok ? "ok" : check.required ? "fail" : "warn";
|
|
574
|
-
const source = check.source ? ` [${check.source}]` : "";
|
|
575
|
-
|
|
576
|
-
return `${status.padEnd(4)} ${check.name}${source}\n ${check.detail}`;
|
|
577
|
-
});
|
|
578
|
-
|
|
579
|
-
return rows.join("\n");
|
|
580
|
-
}
|