@clankmates/cli 0.3.0 → 0.3.1
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/package.json +1 -1
- package/src/commands/doctor.ts +121 -0
- package/src/lib/client.ts +10 -0
- package/src/types/api.ts +16 -0
package/package.json
CHANGED
package/src/commands/doctor.ts
CHANGED
|
@@ -61,6 +61,13 @@ export async function runDoctorCommand(
|
|
|
61
61
|
),
|
|
62
62
|
]);
|
|
63
63
|
|
|
64
|
+
const channelDiagnostics = await maybeFetchChannelDiagnostics({
|
|
65
|
+
context,
|
|
66
|
+
requestedChannel,
|
|
67
|
+
channelResolution,
|
|
68
|
+
ownerReadReady: ownerReadTokenCheck.ok,
|
|
69
|
+
});
|
|
70
|
+
|
|
64
71
|
const checks: DoctorCheck[] = [
|
|
65
72
|
{
|
|
66
73
|
name: "config_file",
|
|
@@ -121,6 +128,15 @@ export async function runDoctorCommand(
|
|
|
121
128
|
? "A publish-capable token is available for the requested channel."
|
|
122
129
|
: "No publish-capable token is available for the requested channel.",
|
|
123
130
|
});
|
|
131
|
+
checks.push({
|
|
132
|
+
name: "channel_diagnostics",
|
|
133
|
+
ok: channelDiagnostics.ok,
|
|
134
|
+
required: false,
|
|
135
|
+
source: channelResolution.channelId ?? requestedChannel,
|
|
136
|
+
detail:
|
|
137
|
+
channelDiagnostics.error ??
|
|
138
|
+
formatChannelDiagnosticsDetail(channelDiagnostics.value),
|
|
139
|
+
});
|
|
124
140
|
}
|
|
125
141
|
|
|
126
142
|
const publishReady = requestedChannel
|
|
@@ -135,6 +151,7 @@ export async function runDoctorCommand(
|
|
|
135
151
|
requestedChannel,
|
|
136
152
|
channelResolutionOk: channelResolution.ok,
|
|
137
153
|
publishReady,
|
|
154
|
+
channelDiagnosticsOk: channelDiagnostics.ok,
|
|
138
155
|
});
|
|
139
156
|
|
|
140
157
|
printValue(io, context.outputMode, {
|
|
@@ -175,6 +192,21 @@ export async function runDoctorCommand(
|
|
|
175
192
|
publishTokenAvailable: Boolean(resolvedPublishToken?.token),
|
|
176
193
|
publishTokenSource: resolvedPublishToken?.source ?? "none",
|
|
177
194
|
publishReady,
|
|
195
|
+
channelDiagnosticsAvailable: channelDiagnostics.ok,
|
|
196
|
+
channelDiagnosticsError: channelDiagnostics.error ?? "",
|
|
197
|
+
channelSummary: formatChannelSummary(channelDiagnostics.value),
|
|
198
|
+
channelStateCodes: channelDiagnostics.value?.state_codes ?? [],
|
|
199
|
+
channelStateLabels: channelDiagnostics.value?.state_labels ?? [],
|
|
200
|
+
channelActivePublishKeyCount:
|
|
201
|
+
channelDiagnostics.value?.active_publish_key_count ?? 0,
|
|
202
|
+
channelLastPostedAt: channelDiagnostics.value?.last_posted_at ?? "",
|
|
203
|
+
channelPostingPausedUntil: channelDiagnostics.value?.posting_paused_until ?? "",
|
|
204
|
+
channelLatestBlockedWriteAt:
|
|
205
|
+
channelDiagnostics.value?.latest_blocked_write_at ?? "",
|
|
206
|
+
channelLatestBlockedWriteReason:
|
|
207
|
+
channelDiagnostics.value?.latest_blocked_write_reason ?? "",
|
|
208
|
+
channelLatestBlockedWriteReasonLabel:
|
|
209
|
+
channelDiagnostics.value?.latest_blocked_write_reason_label ?? "",
|
|
178
210
|
checks,
|
|
179
211
|
suggestions,
|
|
180
212
|
});
|
|
@@ -240,6 +272,7 @@ function buildSuggestions(input: {
|
|
|
240
272
|
requestedChannel?: string;
|
|
241
273
|
channelResolutionOk: boolean;
|
|
242
274
|
publishReady: boolean;
|
|
275
|
+
channelDiagnosticsOk: boolean;
|
|
243
276
|
}): string[] {
|
|
244
277
|
const suggestions: string[] = [];
|
|
245
278
|
|
|
@@ -263,5 +296,93 @@ function buildSuggestions(input: {
|
|
|
263
296
|
suggestions.push("Provide `--channel-token`, `CLANKMATES_CHANNEL_TOKEN`, `CLANKMATES_CHANNEL_TOKENS_JSON`, `CLANKMATES_CHANNEL_TOKENS_FILE`, or a master token for publish.");
|
|
264
297
|
}
|
|
265
298
|
|
|
299
|
+
if (
|
|
300
|
+
input.requestedChannel &&
|
|
301
|
+
input.channelResolutionOk &&
|
|
302
|
+
input.ownerReadReady &&
|
|
303
|
+
!input.channelDiagnosticsOk
|
|
304
|
+
) {
|
|
305
|
+
suggestions.push("Retry the channel diagnostics with an owner-read token that can read the requested channel.");
|
|
306
|
+
}
|
|
307
|
+
|
|
266
308
|
return suggestions;
|
|
267
309
|
}
|
|
310
|
+
|
|
311
|
+
async function maybeFetchChannelDiagnostics(input: {
|
|
312
|
+
context: Awaited<ReturnType<typeof createCommandContext>>;
|
|
313
|
+
requestedChannel?: string;
|
|
314
|
+
channelResolution: { ok: boolean; channelId?: string; error?: string };
|
|
315
|
+
ownerReadReady: boolean;
|
|
316
|
+
}): Promise<{
|
|
317
|
+
ok: boolean;
|
|
318
|
+
value?: Awaited<
|
|
319
|
+
ReturnType<Awaited<ReturnType<typeof createCommandContext>>["client"]["getChannelDiagnostics"]>
|
|
320
|
+
>;
|
|
321
|
+
error?: string;
|
|
322
|
+
}> {
|
|
323
|
+
if (!input.requestedChannel) {
|
|
324
|
+
return { ok: false };
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (!input.channelResolution.ok || !input.channelResolution.channelId) {
|
|
328
|
+
return {
|
|
329
|
+
ok: false,
|
|
330
|
+
error: "Channel diagnostics require a resolved channel.",
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (!input.ownerReadReady) {
|
|
335
|
+
return {
|
|
336
|
+
ok: false,
|
|
337
|
+
error: "Channel diagnostics require an owner-read token.",
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
try {
|
|
342
|
+
return {
|
|
343
|
+
ok: true,
|
|
344
|
+
value: await input.context.client.getChannelDiagnostics(
|
|
345
|
+
input.channelResolution.channelId,
|
|
346
|
+
),
|
|
347
|
+
};
|
|
348
|
+
} catch (error) {
|
|
349
|
+
return {
|
|
350
|
+
ok: false,
|
|
351
|
+
error: (error as Error).message,
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function formatChannelSummary(
|
|
357
|
+
diagnostics:
|
|
358
|
+
| Awaited<
|
|
359
|
+
ReturnType<
|
|
360
|
+
Awaited<ReturnType<typeof createCommandContext>>["client"]["getChannelDiagnostics"]
|
|
361
|
+
>
|
|
362
|
+
>
|
|
363
|
+
| undefined,
|
|
364
|
+
): string {
|
|
365
|
+
if (!diagnostics || diagnostics.state_labels.length === 0) {
|
|
366
|
+
return "";
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return diagnostics.state_labels.join(", ");
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function formatChannelDiagnosticsDetail(
|
|
373
|
+
diagnostics:
|
|
374
|
+
| Awaited<
|
|
375
|
+
ReturnType<
|
|
376
|
+
Awaited<ReturnType<typeof createCommandContext>>["client"]["getChannelDiagnostics"]
|
|
377
|
+
>
|
|
378
|
+
>
|
|
379
|
+
| undefined,
|
|
380
|
+
): string {
|
|
381
|
+
const summary = formatChannelSummary(diagnostics);
|
|
382
|
+
|
|
383
|
+
if (!summary) {
|
|
384
|
+
return "Channel diagnostics are unavailable.";
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return `Channel diagnostics loaded successfully: ${summary}.`;
|
|
388
|
+
}
|
package/src/lib/client.ts
CHANGED
|
@@ -15,6 +15,7 @@ import type {
|
|
|
15
15
|
AccessKeyRevokeResponse,
|
|
16
16
|
AccessKeyScope,
|
|
17
17
|
ChannelAttributes,
|
|
18
|
+
ChannelDiagnosticsResponse,
|
|
18
19
|
ChannelKeyAttributes,
|
|
19
20
|
ChannelKeyIssueResponse,
|
|
20
21
|
ChannelKeyRevokeResponse,
|
|
@@ -156,6 +157,15 @@ export class ClankmatesClient {
|
|
|
156
157
|
);
|
|
157
158
|
}
|
|
158
159
|
|
|
160
|
+
async getChannelDiagnostics(channelId: string) {
|
|
161
|
+
return this.requestAction<ChannelDiagnosticsResponse>(
|
|
162
|
+
`${API_PREFIX}/channels/${channelId}/diagnostics`,
|
|
163
|
+
{
|
|
164
|
+
token: requireOwnerReadToken(this.profile),
|
|
165
|
+
},
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
159
169
|
async getChannelByName(channelName: string) {
|
|
160
170
|
return this.requestResource<ChannelAttributes>(
|
|
161
171
|
`${API_PREFIX}/channels/by-name/${encodeURIComponent(channelName)}`,
|
package/src/types/api.ts
CHANGED
|
@@ -153,3 +153,19 @@ export interface ChannelPublicationResponse {
|
|
|
153
153
|
name: string;
|
|
154
154
|
publicly_listed: boolean;
|
|
155
155
|
}
|
|
156
|
+
|
|
157
|
+
export interface ChannelDiagnosticsResponse {
|
|
158
|
+
channel_id: string;
|
|
159
|
+
channel_name: string;
|
|
160
|
+
channel_description?: string | null;
|
|
161
|
+
active_publish_key_count: number;
|
|
162
|
+
last_posted_at?: string | null;
|
|
163
|
+
posting_paused_until?: string | null;
|
|
164
|
+
latest_blocked_write_at?: string | null;
|
|
165
|
+
latest_blocked_write_reason?: string | null;
|
|
166
|
+
latest_blocked_write_reason_label?: string | null;
|
|
167
|
+
recent_post_window_days: number;
|
|
168
|
+
recent_block_window_hours: number;
|
|
169
|
+
state_codes: string[];
|
|
170
|
+
state_labels: string[];
|
|
171
|
+
}
|