@marcusrbrown/infra 0.2.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.
@@ -0,0 +1,274 @@
1
+ import type {goke} from 'goke'
2
+
3
+ import {z} from 'zod'
4
+
5
+ const DEFAULT_CLIPROXY_URL = 'https://cliproxy.fro.bot'
6
+ const HTTP_TIMEOUT_MS = 10_000
7
+
8
+ type CheckLevel = 'ok' | 'warning' | 'error'
9
+
10
+ interface CheckResult {
11
+ title: string
12
+ level: CheckLevel
13
+ summary: string
14
+ details?: string[]
15
+ }
16
+
17
+ export function levelLabel(level: CheckLevel): string {
18
+ if (level === 'ok') {
19
+ return 'OK'
20
+ }
21
+
22
+ if (level === 'warning') {
23
+ return 'WARN'
24
+ }
25
+
26
+ return 'ERROR'
27
+ }
28
+
29
+ export function formatDurationMs(durationMs: number): string {
30
+ return `${Math.max(0, Math.round(durationMs))}ms`
31
+ }
32
+
33
+ export function stripTrailingSlash(value: string): string {
34
+ return value.endsWith('/') ? value.slice(0, -1) : value
35
+ }
36
+
37
+ function managementHeaders(key: string): Headers {
38
+ const headers = new Headers()
39
+ headers.set('authorization', `Bearer ${key}`)
40
+ headers.set('x-management-key', key)
41
+ return headers
42
+ }
43
+
44
+ async function parseJsonResponse(response: Response): Promise<unknown> {
45
+ try {
46
+ return await response.json()
47
+ } catch {
48
+ return null
49
+ }
50
+ }
51
+
52
+ export function toNumber(value: unknown): number | null {
53
+ return typeof value === 'number' && Number.isFinite(value) ? value : null
54
+ }
55
+
56
+ export async function checkHttpReachability(url: string, verbose: boolean): Promise<CheckResult> {
57
+ const startedAt = performance.now()
58
+
59
+ try {
60
+ const response = await fetch(url, {
61
+ signal: AbortSignal.timeout(HTTP_TIMEOUT_MS),
62
+ })
63
+ const elapsedMs = performance.now() - startedAt
64
+ const details: string[] = []
65
+
66
+ if (verbose) {
67
+ details.push(`URL: ${url}`)
68
+ details.push(`Status text: ${response.statusText || '(none)'}`)
69
+ if (response.headers.get('content-type')) {
70
+ details.push(`Content-Type: ${response.headers.get('content-type')}`)
71
+ }
72
+ }
73
+
74
+ return {
75
+ title: 'HTTP reachability',
76
+ level: response.ok ? 'ok' : 'error',
77
+ summary: `GET ${url} → ${response.status} (${formatDurationMs(elapsedMs)})`,
78
+ details,
79
+ }
80
+ } catch (error) {
81
+ const elapsedMs = performance.now() - startedAt
82
+ const message = error instanceof Error ? error.message : String(error)
83
+
84
+ return {
85
+ title: 'HTTP reachability',
86
+ level: 'error',
87
+ summary: `Request failed after ${formatDurationMs(elapsedMs)}: ${message}`,
88
+ details: verbose ? [`URL: ${url}`, `Timeout: ${HTTP_TIMEOUT_MS}ms`] : undefined,
89
+ }
90
+ }
91
+ }
92
+
93
+ export async function checkUsageStats(baseUrl: string, key: string): Promise<CheckResult> {
94
+ const endpoint = `${baseUrl}/v0/management/usage`
95
+
96
+ try {
97
+ const response = await fetch(endpoint, {
98
+ headers: managementHeaders(key),
99
+ signal: AbortSignal.timeout(HTTP_TIMEOUT_MS),
100
+ })
101
+
102
+ if (response.status === 429) {
103
+ return {
104
+ title: 'Usage stats',
105
+ level: 'warning',
106
+ summary: 'Rate limited by management API (HTTP 429). Retry in a few moments.',
107
+ }
108
+ }
109
+
110
+ if (!response.ok) {
111
+ return {
112
+ title: 'Usage stats',
113
+ level: 'error',
114
+ summary: `GET /v0/management/usage failed with HTTP ${response.status}`,
115
+ }
116
+ }
117
+
118
+ const payload = await parseJsonResponse(response)
119
+ const record = payload && typeof payload === 'object' ? payload : {}
120
+ const totalRequests = toNumber((record as Record<string, unknown>).total_requests)
121
+ const failureCount = toNumber((record as Record<string, unknown>).failure_count)
122
+
123
+ if (totalRequests === null || failureCount === null) {
124
+ return {
125
+ title: 'Usage stats',
126
+ level: 'warning',
127
+ summary: 'Management usage payload is missing expected numeric fields.',
128
+ }
129
+ }
130
+
131
+ return {
132
+ title: 'Usage stats',
133
+ level: failureCount > 0 ? 'warning' : 'ok',
134
+ summary:
135
+ failureCount > 0
136
+ ? `total_requests=${totalRequests}, failure_count=${failureCount} (token refresh likely needed)`
137
+ : `total_requests=${totalRequests}, failure_count=${failureCount}`,
138
+ }
139
+ } catch (error) {
140
+ const message = error instanceof Error ? error.message : String(error)
141
+ return {
142
+ title: 'Usage stats',
143
+ level: 'error',
144
+ summary: `Unable to read usage stats: ${message}`,
145
+ }
146
+ }
147
+ }
148
+
149
+ export async function checkVersion(baseUrl: string, key: string): Promise<CheckResult> {
150
+ const endpoint = `${baseUrl}/v0/management/latest-version`
151
+
152
+ try {
153
+ const response = await fetch(endpoint, {
154
+ headers: managementHeaders(key),
155
+ signal: AbortSignal.timeout(HTTP_TIMEOUT_MS),
156
+ })
157
+
158
+ if (response.status === 429) {
159
+ return {
160
+ title: 'Current version',
161
+ level: 'warning',
162
+ summary: 'Rate limited by management API (HTTP 429). Retry in a few moments.',
163
+ }
164
+ }
165
+
166
+ if (!response.ok) {
167
+ return {
168
+ title: 'Current version',
169
+ level: 'error',
170
+ summary: `GET /v0/management/latest-version failed with HTTP ${response.status}`,
171
+ }
172
+ }
173
+
174
+ const payload = await parseJsonResponse(response)
175
+
176
+ if (typeof payload === 'string' && payload.length > 0) {
177
+ return {
178
+ title: 'Current version',
179
+ level: 'ok',
180
+ summary: payload,
181
+ }
182
+ }
183
+
184
+ if (payload && typeof payload === 'object') {
185
+ const version = (payload as Record<string, unknown>).version
186
+ if (typeof version === 'string' && version.length > 0) {
187
+ return {
188
+ title: 'Current version',
189
+ level: 'ok',
190
+ summary: version,
191
+ }
192
+ }
193
+ }
194
+
195
+ return {
196
+ title: 'Current version',
197
+ level: 'warning',
198
+ summary: 'Management version payload did not include a usable version string.',
199
+ }
200
+ } catch (error) {
201
+ const message = error instanceof Error ? error.message : String(error)
202
+ return {
203
+ title: 'Current version',
204
+ level: 'error',
205
+ summary: `Unable to read current version: ${message}`,
206
+ }
207
+ }
208
+ }
209
+
210
+ function printCheckResult(result: CheckResult): void {
211
+ console.log(`[${levelLabel(result.level)}] ${result.title}`)
212
+ console.log(` ${result.summary}`)
213
+
214
+ if (result.details && result.details.length > 0) {
215
+ for (const detail of result.details) {
216
+ console.log(` - ${detail}`)
217
+ }
218
+ }
219
+ }
220
+
221
+ export function registerCliproxyStatus(cli: ReturnType<typeof goke>): void {
222
+ cli
223
+ .command('cliproxy status', 'Show operational health of CLIProxyAPI and its management endpoints.')
224
+ .option(
225
+ '--url [url]',
226
+ z
227
+ .string()
228
+ .describe('Base URL for CLIProxyAPI health checks. Falls back to CLIPROXY_URL or https://cliproxy.fro.bot.'),
229
+ )
230
+ .option(
231
+ '--key [key]',
232
+ z.string().describe('Management API bearer token. Falls back to CLIPROXY_MANAGEMENT_KEY when omitted.'),
233
+ )
234
+ .action(async options => {
235
+ const verbose = options.verbose === true
236
+ const baseUrl = stripTrailingSlash(options.url ?? process.env.CLIPROXY_URL ?? DEFAULT_CLIPROXY_URL)
237
+ const managementKey = options.key ?? process.env.CLIPROXY_MANAGEMENT_KEY
238
+
239
+ console.log('CLIProxyAPI status')
240
+ console.log('')
241
+
242
+ const results: CheckResult[] = [await checkHttpReachability(baseUrl, verbose)]
243
+
244
+ if (managementKey) {
245
+ const [usageResult, versionResult] = await Promise.all([
246
+ checkUsageStats(baseUrl, managementKey),
247
+ checkVersion(baseUrl, managementKey),
248
+ ])
249
+
250
+ results.push(usageResult, versionResult)
251
+ } else {
252
+ results.push({
253
+ title: 'Management checks',
254
+ level: 'warning',
255
+ summary:
256
+ 'CLIPROXY_MANAGEMENT_KEY is not set. Skipping usage stats and version checks. Provide --key or set env var.',
257
+ })
258
+ }
259
+
260
+ for (const result of results) {
261
+ printCheckResult(result)
262
+ console.log('')
263
+ }
264
+
265
+ const errorCount = results.filter(result => result.level === 'error').length
266
+ const warningCount = results.filter(result => result.level === 'warning').length
267
+
268
+ console.log(`Summary: ${results.length} checks, ${errorCount} errors, ${warningCount} warnings`)
269
+
270
+ if (errorCount > 0) {
271
+ process.exitCode = 1
272
+ }
273
+ })
274
+ }