@clankid/cli 0.17.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 (81) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/LICENSE +21 -0
  3. package/README.md +275 -0
  4. package/bin/clank +6 -0
  5. package/docs/usage/clankid-migration.md +59 -0
  6. package/package.json +47 -0
  7. package/skills/codex/clankid/SKILL.md +256 -0
  8. package/skills/codex/clankid/references/safety.md +28 -0
  9. package/skills/codex/clankid/references/setup.md +65 -0
  10. package/src/README.md +8 -0
  11. package/src/cli.ts +154 -0
  12. package/src/commands/.gitkeep +1 -0
  13. package/src/commands/account.ts +337 -0
  14. package/src/commands/api.ts +43 -0
  15. package/src/commands/auth/access-keys.ts +206 -0
  16. package/src/commands/auth.ts +240 -0
  17. package/src/commands/cache.ts +124 -0
  18. package/src/commands/config.ts +93 -0
  19. package/src/commands/doctor/checks.ts +123 -0
  20. package/src/commands/doctor/render.ts +140 -0
  21. package/src/commands/doctor/suggestions.ts +42 -0
  22. package/src/commands/doctor/types.ts +75 -0
  23. package/src/commands/doctor.ts +221 -0
  24. package/src/commands/feed.ts +185 -0
  25. package/src/commands/identity/keys.ts +145 -0
  26. package/src/commands/identity/render.ts +224 -0
  27. package/src/commands/identity/validation.ts +11 -0
  28. package/src/commands/identity.ts +295 -0
  29. package/src/commands/inbox/content.ts +13 -0
  30. package/src/commands/inbox/filters.ts +70 -0
  31. package/src/commands/inbox/messages.ts +71 -0
  32. package/src/commands/inbox/participants.ts +152 -0
  33. package/src/commands/inbox/render.ts +13 -0
  34. package/src/commands/inbox/resource-output.ts +217 -0
  35. package/src/commands/inbox/schema.ts +185 -0
  36. package/src/commands/inbox/screening.ts +76 -0
  37. package/src/commands/inbox/sync-scopes.ts +62 -0
  38. package/src/commands/inbox/thread-output.ts +345 -0
  39. package/src/commands/inbox/watch.ts +207 -0
  40. package/src/commands/inbox.ts +374 -0
  41. package/src/commands/post.ts +406 -0
  42. package/src/commands/setup.ts +228 -0
  43. package/src/commands/skill.ts +41 -0
  44. package/src/commands/user.ts +33 -0
  45. package/src/lib/.gitkeep +1 -0
  46. package/src/lib/args.ts +231 -0
  47. package/src/lib/body-input.ts +55 -0
  48. package/src/lib/cache/scopes.ts +272 -0
  49. package/src/lib/cache/store.ts +195 -0
  50. package/src/lib/cache/types.ts +31 -0
  51. package/src/lib/cache.ts +135 -0
  52. package/src/lib/client/auth.ts +163 -0
  53. package/src/lib/client/core.ts +133 -0
  54. package/src/lib/client/feed.ts +93 -0
  55. package/src/lib/client/identities.ts +364 -0
  56. package/src/lib/client/identity-keys.ts +57 -0
  57. package/src/lib/client/inbox.ts +385 -0
  58. package/src/lib/client/posts.ts +236 -0
  59. package/src/lib/client/raw-api.ts +33 -0
  60. package/src/lib/client/users.ts +88 -0
  61. package/src/lib/client.ts +469 -0
  62. package/src/lib/config.ts +239 -0
  63. package/src/lib/content.ts +149 -0
  64. package/src/lib/context.ts +43 -0
  65. package/src/lib/errors.ts +17 -0
  66. package/src/lib/help.ts +1587 -0
  67. package/src/lib/http.ts +138 -0
  68. package/src/lib/human.ts +143 -0
  69. package/src/lib/json-input.ts +73 -0
  70. package/src/lib/json_api.ts +120 -0
  71. package/src/lib/output.ts +203 -0
  72. package/src/lib/pagination.ts +116 -0
  73. package/src/lib/paths.ts +44 -0
  74. package/src/lib/polling.ts +146 -0
  75. package/src/lib/post-output.ts +60 -0
  76. package/src/lib/skills.ts +137 -0
  77. package/src/lib/tokens.ts +284 -0
  78. package/src/lib/version.ts +19 -0
  79. package/src/types/.gitkeep +1 -0
  80. package/src/types/api.ts +320 -0
  81. package/src/types/placeholder.d.ts +1 -0
@@ -0,0 +1,123 @@
1
+ import { access } from "node:fs/promises";
2
+
3
+ import type {
4
+ IdentityDiagnostics,
5
+ IdentityDiagnosticsResult,
6
+ IdentityResolution,
7
+ CheckResult,
8
+ DoctorContext,
9
+ } from "./types";
10
+
11
+ export async function checkConfigPath(configPath: string): Promise<boolean> {
12
+ try {
13
+ await access(configPath);
14
+ return true;
15
+ } catch (error) {
16
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") {
17
+ return false;
18
+ }
19
+
20
+ return false;
21
+ }
22
+ }
23
+
24
+ export async function resolveRequestedIdentity(
25
+ context: DoctorContext,
26
+ identity: string,
27
+ ): Promise<IdentityResolution> {
28
+ try {
29
+ return {
30
+ ok: true,
31
+ identityId: await context.client.resolveIdentityId(identity),
32
+ };
33
+ } catch (error) {
34
+ return {
35
+ ok: false,
36
+ error: (error as Error).message,
37
+ };
38
+ }
39
+ }
40
+
41
+ export async function runOptionalCheck(
42
+ token: string | undefined,
43
+ operation: (token: string) => Promise<unknown>,
44
+ missingMessage: string,
45
+ ): Promise<CheckResult> {
46
+ if (!token) {
47
+ return { ok: false, error: missingMessage };
48
+ }
49
+
50
+ return runCheck(() => operation(token));
51
+ }
52
+
53
+ export async function runCheck(
54
+ operation: () => Promise<unknown>,
55
+ ): Promise<CheckResult> {
56
+ try {
57
+ await operation();
58
+ return { ok: true };
59
+ } catch (error) {
60
+ return { ok: false, error: (error as Error).message };
61
+ }
62
+ }
63
+
64
+ export async function maybeFetchIdentityDiagnostics(input: {
65
+ context: DoctorContext;
66
+ requestedIdentity?: string;
67
+ identityResolution: IdentityResolution;
68
+ ownerReadReady: boolean;
69
+ }): Promise<IdentityDiagnosticsResult> {
70
+ if (!input.requestedIdentity) {
71
+ return { ok: false };
72
+ }
73
+
74
+ if (!input.identityResolution.ok || !input.identityResolution.identityId) {
75
+ return {
76
+ ok: false,
77
+ error: "Identity diagnostics require a resolved identity.",
78
+ };
79
+ }
80
+
81
+ if (!input.ownerReadReady) {
82
+ return {
83
+ ok: false,
84
+ error: "Identity diagnostics require an owner-read token.",
85
+ };
86
+ }
87
+
88
+ try {
89
+ return {
90
+ ok: true,
91
+ value: await input.context.client.getIdentityDiagnostics(
92
+ input.identityResolution.identityId,
93
+ ),
94
+ };
95
+ } catch (error) {
96
+ return {
97
+ ok: false,
98
+ error: (error as Error).message,
99
+ };
100
+ }
101
+ }
102
+
103
+ export function formatIdentitySummary(
104
+ diagnostics: IdentityDiagnostics | undefined,
105
+ ): string {
106
+ if (!diagnostics || diagnostics.state_labels.length === 0) {
107
+ return "";
108
+ }
109
+
110
+ return diagnostics.state_labels.join(", ");
111
+ }
112
+
113
+ export function formatIdentityDiagnosticsDetail(
114
+ diagnostics: IdentityDiagnostics | undefined,
115
+ ): string {
116
+ const summary = formatIdentitySummary(diagnostics);
117
+
118
+ if (!summary) {
119
+ return "Identity diagnostics are unavailable.";
120
+ }
121
+
122
+ return `Identity diagnostics loaded successfully: ${summary}.`;
123
+ }
@@ -0,0 +1,140 @@
1
+ import {
2
+ formatTimestamp,
3
+ joinBlocks,
4
+ renderBullets,
5
+ renderFields,
6
+ renderSection,
7
+ } from "../../lib/human";
8
+ import type { DoctorCheck, DoctorReport } from "./types";
9
+
10
+ export function renderDoctorReport(report: DoctorReport): string {
11
+ return joinBlocks([
12
+ renderSection(
13
+ "Status",
14
+ renderFields([
15
+ ["Result", report.status],
16
+ ["Summary", report.summary],
17
+ ["Profile", report.profile],
18
+ ["Base URL", report.baseUrl],
19
+ ["Config", report.configPath],
20
+ ["CLI version", report.cliVersion],
21
+ ]),
22
+ ),
23
+ renderSection(
24
+ "Auth",
25
+ renderFields([
26
+ [
27
+ "Master token",
28
+ renderTokenStatus(
29
+ report.hasMasterToken,
30
+ report.masterTokenOk,
31
+ report.masterTokenSource,
32
+ report.masterTokenError,
33
+ ),
34
+ ],
35
+ [
36
+ "Read-only token",
37
+ renderTokenStatus(
38
+ report.hasReadOnlyToken,
39
+ report.readOnlyTokenOk,
40
+ report.readOnlyTokenSource,
41
+ report.readOnlyTokenError,
42
+ ),
43
+ ],
44
+ [
45
+ "Owner-read token",
46
+ renderTokenStatus(
47
+ report.ownerReadTokenAvailable,
48
+ report.ownerReadTokenOk,
49
+ report.ownerReadTokenSource,
50
+ report.ownerReadTokenError,
51
+ ),
52
+ ],
53
+ ["Stored identity keys", report.storedIdentityKeys],
54
+ ]),
55
+ ),
56
+ report.identity
57
+ ? renderSection(
58
+ "Identity",
59
+ renderFields([
60
+ ["Requested", report.identity],
61
+ ["Resolved ID", report.identityId],
62
+ [
63
+ "Resolution",
64
+ report.identityResolutionOk ? "ok" : report.identityResolutionError,
65
+ ],
66
+ [
67
+ "Identity credential",
68
+ report.identityCredentialAvailable
69
+ ? `available (${report.identityCredentialSource})`
70
+ : "missing",
71
+ ],
72
+ ["Publish ready", report.publishReady],
73
+ [
74
+ "Diagnostics",
75
+ report.identityDiagnosticsAvailable
76
+ ? report.identitySummary
77
+ : report.identityDiagnosticsError,
78
+ ],
79
+ ["Active identity keys", report.identityActiveKeyCount],
80
+ [
81
+ "Last posted",
82
+ report.identityLastPostedAt
83
+ ? formatTimestamp(report.identityLastPostedAt)
84
+ : undefined,
85
+ ],
86
+ [
87
+ "Paused until",
88
+ report.identityPostingPausedUntil
89
+ ? formatTimestamp(report.identityPostingPausedUntil)
90
+ : undefined,
91
+ ],
92
+ [
93
+ "Latest blocked write",
94
+ report.identityLatestBlockedWriteAt
95
+ ? formatTimestamp(report.identityLatestBlockedWriteAt)
96
+ : undefined,
97
+ ],
98
+ [
99
+ "Latest blocked reason",
100
+ report.identityLatestBlockedWriteReasonLabel ||
101
+ report.identityLatestBlockedWriteReason ||
102
+ undefined,
103
+ ],
104
+ ]),
105
+ )
106
+ : undefined,
107
+ renderSection("Checks", renderChecks(report.checks)),
108
+ report.suggestions.length > 0
109
+ ? renderSection("Suggestions", renderBullets(report.suggestions))
110
+ : undefined,
111
+ ]);
112
+ }
113
+
114
+ function renderTokenStatus(
115
+ configured: boolean,
116
+ ok: boolean,
117
+ source: string,
118
+ error: string,
119
+ ): string {
120
+ if (ok) {
121
+ return `ok (${source})`;
122
+ }
123
+
124
+ if (configured) {
125
+ return `failed (${source}): ${error}`;
126
+ }
127
+
128
+ return error || "missing";
129
+ }
130
+
131
+ function renderChecks(checks: DoctorCheck[]): string {
132
+ const rows = checks.map((check) => {
133
+ const status = check.ok ? "ok" : check.required ? "fail" : "warn";
134
+ const source = check.source ? ` [${check.source}]` : "";
135
+
136
+ return `${status.padEnd(4)} ${check.name}${source}\n ${check.detail}`;
137
+ });
138
+
139
+ return rows.join("\n");
140
+ }
@@ -0,0 +1,42 @@
1
+ export function buildSuggestions(input: {
2
+ configFileExists: boolean;
3
+ openApiOk: boolean;
4
+ ownerReadReady: boolean;
5
+ requestedIdentity?: string;
6
+ identityResolutionOk: boolean;
7
+ publishReady: boolean;
8
+ identityDiagnosticsOk: boolean;
9
+ }): string[] {
10
+ const suggestions: string[] = [];
11
+
12
+ if (!input.configFileExists) {
13
+ suggestions.push("Run `clank config init` to create local config.");
14
+ }
15
+
16
+ if (!input.openApiOk) {
17
+ suggestions.push("Check `CLANKID_BASE_URL` or `--base-url`, then retry `clank doctor --json`.");
18
+ }
19
+
20
+ if (!input.ownerReadReady) {
21
+ suggestions.push("Configure a read-only or master token for owner reads with `clank auth login ...`.");
22
+ }
23
+
24
+ if (input.requestedIdentity && !input.identityResolutionOk) {
25
+ suggestions.push("Use an identity UUID or configure an owner-read token so identity names can be resolved.");
26
+ }
27
+
28
+ if (input.requestedIdentity && !input.publishReady) {
29
+ suggestions.push("Provide `--identity-key`, `CLANKID_IDENTITY_KEY`, `CLANKID_IDENTITY_KEYS_JSON`, `CLANKID_IDENTITY_KEYS_FILE`, or a master token for publish.");
30
+ }
31
+
32
+ if (
33
+ input.requestedIdentity &&
34
+ input.identityResolutionOk &&
35
+ input.ownerReadReady &&
36
+ !input.identityDiagnosticsOk
37
+ ) {
38
+ suggestions.push("Retry the identity diagnostics with an owner-read token that can read the requested identity.");
39
+ }
40
+
41
+ return suggestions;
42
+ }
@@ -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
+ storedIdentityKeys: number;
36
+ identity: string;
37
+ identityId: string;
38
+ identityResolutionOk: boolean;
39
+ identityResolutionError: string;
40
+ identityCredentialAvailable: boolean;
41
+ identityCredentialSource: string;
42
+ publishReady: boolean;
43
+ identityDiagnosticsAvailable: boolean;
44
+ identityDiagnosticsError: string;
45
+ identitySummary: string;
46
+ identityStateCodes: string[];
47
+ identityStateLabels: string[];
48
+ identityActiveKeyCount: number;
49
+ identityLastPostedAt: string;
50
+ identityPostingPausedUntil: string;
51
+ identityLatestBlockedWriteAt: string;
52
+ identityLatestBlockedWriteReason: string;
53
+ identityLatestBlockedWriteReasonLabel: string;
54
+ checks: DoctorCheck[];
55
+ suggestions: string[];
56
+ }
57
+
58
+ export type DoctorContext = Awaited<ReturnType<typeof createCommandContext>>;
59
+
60
+ export type IdentityDiagnostics = Awaited<
61
+ ReturnType<DoctorContext["client"]["getIdentityDiagnostics"]>
62
+ >;
63
+
64
+ export interface CheckResult {
65
+ ok: boolean;
66
+ error?: string;
67
+ }
68
+
69
+ export interface IdentityResolution extends CheckResult {
70
+ identityId?: string;
71
+ }
72
+
73
+ export interface IdentityDiagnosticsResult extends CheckResult {
74
+ value?: IdentityDiagnostics;
75
+ }
@@ -0,0 +1,221 @@
1
+ import { createCommandContext } from "../lib/context";
2
+ import { identityFlag, type ParsedArgs } from "../lib/args";
3
+ import { printValue, type Io } from "../lib/output";
4
+ import { CLI_VERSION } from "../lib/version";
5
+ import {
6
+ resolveMasterToken,
7
+ resolveOwnerReadToken,
8
+ resolveIdentityCredential,
9
+ resolveReadOnlyToken,
10
+ } from "../lib/tokens";
11
+ import {
12
+ checkConfigPath,
13
+ formatIdentityDiagnosticsDetail,
14
+ formatIdentitySummary,
15
+ maybeFetchIdentityDiagnostics,
16
+ resolveRequestedIdentity,
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";
23
+
24
+ export async function runDoctorCommand(
25
+ args: ParsedArgs,
26
+ io: Io,
27
+ ): Promise<void> {
28
+ const context = await createCommandContext(args, io);
29
+ const requestedIdentity = identityFlag(args.flags);
30
+ const resolvedMasterToken = resolveMasterToken(context.profile);
31
+ const resolvedReadOnlyToken = resolveReadOnlyToken(context.profile);
32
+ const resolvedOwnerReadToken = resolveOwnerReadToken(context.profile);
33
+ const configFileExists = await checkConfigPath(context.configPath);
34
+
35
+ const identityResolution = requestedIdentity
36
+ ? await resolveRequestedIdentity(context, requestedIdentity)
37
+ : { ok: true, identityId: undefined as string | undefined, error: undefined as string | undefined };
38
+ const resolvedIdentityCredential = identityResolution.identityId
39
+ ? resolveIdentityCredential(context.profile, identityResolution.identityId)
40
+ : undefined;
41
+
42
+ const [
43
+ openApiCheck,
44
+ masterTokenCheck,
45
+ readOnlyTokenCheck,
46
+ ownerReadTokenCheck,
47
+ ] = await Promise.all([
48
+ runCheck(() => context.client.fetchOpenApi()),
49
+ runOptionalCheck(
50
+ resolvedMasterToken.token,
51
+ (token) => context.client.validateMasterToken(token),
52
+ "No master token configured.",
53
+ ),
54
+ runOptionalCheck(
55
+ resolvedReadOnlyToken.token,
56
+ (token) => context.client.validateReadOnlyToken(token),
57
+ "No read-only token configured.",
58
+ ),
59
+ runOptionalCheck(
60
+ resolvedOwnerReadToken.token,
61
+ (token) => context.client.whoami(token),
62
+ "No owner-read token configured.",
63
+ ),
64
+ ]);
65
+
66
+ const identityDiagnostics = await maybeFetchIdentityDiagnostics({
67
+ context,
68
+ requestedIdentity,
69
+ identityResolution,
70
+ ownerReadReady: ownerReadTokenCheck.ok,
71
+ });
72
+
73
+ const checks: DoctorCheck[] = [
74
+ {
75
+ name: "config_file",
76
+ ok: configFileExists,
77
+ required: false,
78
+ source: context.configPath,
79
+ detail: configFileExists
80
+ ? "Config file exists."
81
+ : "Config file does not exist yet. Run `clank config init` to create one.",
82
+ },
83
+ {
84
+ name: "open_api",
85
+ ok: openApiCheck.ok,
86
+ required: true,
87
+ detail: openApiCheck.error ?? "OpenAPI endpoint responded successfully.",
88
+ },
89
+ {
90
+ name: "master_token",
91
+ ok: masterTokenCheck.ok,
92
+ required: false,
93
+ source: resolvedMasterToken.source,
94
+ detail: masterTokenCheck.error ?? "Master token validated successfully.",
95
+ },
96
+ {
97
+ name: "read_only_token",
98
+ ok: readOnlyTokenCheck.ok,
99
+ required: false,
100
+ source: resolvedReadOnlyToken.source,
101
+ detail: readOnlyTokenCheck.error ?? "Read-only token validated successfully.",
102
+ },
103
+ {
104
+ name: "owner_read_token",
105
+ ok: ownerReadTokenCheck.ok,
106
+ required: false,
107
+ source: resolvedOwnerReadToken.source,
108
+ detail:
109
+ ownerReadTokenCheck.error ??
110
+ "Owner-read token resolved and validated successfully.",
111
+ },
112
+ ];
113
+
114
+ if (requestedIdentity) {
115
+ checks.push({
116
+ name: "identity_resolution",
117
+ ok: identityResolution.ok,
118
+ required: false,
119
+ source: requestedIdentity,
120
+ detail:
121
+ identityResolution.error ??
122
+ `Resolved requested identity to ${identityResolution.identityId}.`,
123
+ });
124
+ checks.push({
125
+ name: "publish_token",
126
+ ok: Boolean(resolvedIdentityCredential?.token),
127
+ required: false,
128
+ source: resolvedIdentityCredential?.source ?? "none",
129
+ detail: resolvedIdentityCredential?.token
130
+ ? "A publish-capable token is available for the requested identity."
131
+ : "No publish-capable token is available for the requested identity.",
132
+ });
133
+ checks.push({
134
+ name: "identity_diagnostics",
135
+ ok: identityDiagnostics.ok,
136
+ required: false,
137
+ source: identityResolution.identityId ?? requestedIdentity,
138
+ detail:
139
+ identityDiagnostics.error ??
140
+ formatIdentityDiagnosticsDetail(identityDiagnostics.value),
141
+ });
142
+ }
143
+
144
+ const publishReady = requestedIdentity
145
+ ? identityResolution.ok && Boolean(resolvedIdentityCredential?.token)
146
+ : false;
147
+ const ownerReadReady = ownerReadTokenCheck.ok;
148
+ const ok = openApiCheck.ok && ownerReadReady && (!requestedIdentity || publishReady);
149
+ const suggestions = buildSuggestions({
150
+ configFileExists,
151
+ openApiOk: openApiCheck.ok,
152
+ ownerReadReady,
153
+ requestedIdentity,
154
+ identityResolutionOk: identityResolution.ok,
155
+ publishReady,
156
+ identityDiagnosticsOk: identityDiagnostics.ok,
157
+ });
158
+
159
+ const report: DoctorReport = {
160
+ cliVersion: CLI_VERSION,
161
+ ok,
162
+ status: ok ? "ok" : "needs_attention",
163
+ summary: ok
164
+ ? requestedIdentity
165
+ ? "CLI can reach the API and publish to the requested identity."
166
+ : "CLI can reach the API."
167
+ : requestedIdentity
168
+ ? "CLI setup needs attention before publish workflows are reliable."
169
+ : "CLI setup needs attention before agent workflows are reliable.",
170
+ profile: context.profileName,
171
+ configPath: context.configPath,
172
+ configFileExists,
173
+ baseUrl: context.profile.baseUrl,
174
+ hasMasterToken: Boolean(resolvedMasterToken.token),
175
+ masterTokenSource: resolvedMasterToken.source,
176
+ masterTokenOk: masterTokenCheck.ok,
177
+ masterTokenError: masterTokenCheck.error ?? "",
178
+ hasReadOnlyToken: Boolean(resolvedReadOnlyToken.token),
179
+ readOnlyTokenSource: resolvedReadOnlyToken.source,
180
+ readOnlyTokenOk: readOnlyTokenCheck.ok,
181
+ readOnlyTokenError: readOnlyTokenCheck.error ?? "",
182
+ ownerReadTokenAvailable: Boolean(resolvedOwnerReadToken.token),
183
+ ownerReadTokenSource: resolvedOwnerReadToken.source,
184
+ ownerReadTokenOk: ownerReadTokenCheck.ok,
185
+ ownerReadTokenError: ownerReadTokenCheck.error ?? "",
186
+ ownerReadReady,
187
+ openApiOk: openApiCheck.ok,
188
+ openApiError: openApiCheck.error ?? "",
189
+ storedIdentityKeys: Object.keys(context.profile.identityKeys).length,
190
+ identity: requestedIdentity ?? "",
191
+ identityId: identityResolution.identityId ?? "",
192
+ identityResolutionOk: identityResolution.ok,
193
+ identityResolutionError: identityResolution.error ?? "",
194
+ identityCredentialAvailable: Boolean(resolvedIdentityCredential?.token),
195
+ identityCredentialSource: resolvedIdentityCredential?.source ?? "none",
196
+ publishReady,
197
+ identityDiagnosticsAvailable: identityDiagnostics.ok,
198
+ identityDiagnosticsError: identityDiagnostics.error ?? "",
199
+ identitySummary: formatIdentitySummary(identityDiagnostics.value),
200
+ identityStateCodes: identityDiagnostics.value?.state_codes ?? [],
201
+ identityStateLabels: identityDiagnostics.value?.state_labels ?? [],
202
+ identityActiveKeyCount:
203
+ identityDiagnostics.value?.active_identity_key_count ?? 0,
204
+ identityLastPostedAt: identityDiagnostics.value?.last_posted_at ?? "",
205
+ identityPostingPausedUntil: identityDiagnostics.value?.posting_paused_until ?? "",
206
+ identityLatestBlockedWriteAt:
207
+ identityDiagnostics.value?.latest_blocked_write_at ?? "",
208
+ identityLatestBlockedWriteReason:
209
+ identityDiagnostics.value?.latest_blocked_write_reason ?? "",
210
+ identityLatestBlockedWriteReasonLabel:
211
+ identityDiagnostics.value?.latest_blocked_write_reason_label ?? "",
212
+ checks,
213
+ suggestions,
214
+ };
215
+
216
+ printValue(
217
+ io,
218
+ context.outputMode,
219
+ context.outputMode === "json" ? report : renderDoctorReport(report),
220
+ );
221
+ }