codex-usage-dashboard 0.1.1 → 0.1.2
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/bin/codex-usage.js +95 -23
- package/package.json +1 -1
package/bin/codex-usage.js
CHANGED
|
@@ -290,20 +290,20 @@ async function runPairCommand(args) {
|
|
|
290
290
|
}),
|
|
291
291
|
})
|
|
292
292
|
|
|
293
|
-
const payload = await
|
|
293
|
+
const payload = await parseResponseBody(response)
|
|
294
294
|
if (!response.ok) {
|
|
295
|
-
throw new Error(payload
|
|
295
|
+
throw new Error(buildHttpErrorMessage(response, payload, 'Pairing failed.'))
|
|
296
296
|
}
|
|
297
297
|
|
|
298
298
|
const config = {
|
|
299
299
|
authMode: 'website-paired',
|
|
300
300
|
codexHome,
|
|
301
|
-
deviceId: payload.deviceId,
|
|
302
|
-
deviceToken: payload.deviceToken,
|
|
301
|
+
deviceId: payload.data.deviceId,
|
|
302
|
+
deviceToken: payload.data.deviceToken,
|
|
303
303
|
dashboardOrigin: new URL(pairUrl).origin,
|
|
304
304
|
label: device.label,
|
|
305
|
-
pollMs: payload.pollMs ?? DEFAULT_POLL_MS,
|
|
306
|
-
syncUrl: payload.syncUrl,
|
|
305
|
+
pollMs: payload.data.pollMs ?? DEFAULT_POLL_MS,
|
|
306
|
+
syncUrl: payload.data.syncUrl,
|
|
307
307
|
}
|
|
308
308
|
|
|
309
309
|
await writeConfig(codexHome, config)
|
|
@@ -367,24 +367,26 @@ async function runConnectCommand(args) {
|
|
|
367
367
|
}),
|
|
368
368
|
})
|
|
369
369
|
|
|
370
|
-
const payload = await
|
|
370
|
+
const payload = await parseResponseBody(response)
|
|
371
371
|
if (!response.ok) {
|
|
372
|
-
throw new Error(
|
|
372
|
+
throw new Error(
|
|
373
|
+
buildHttpErrorMessage(response, payload, 'Unable to connect this machine.'),
|
|
374
|
+
)
|
|
373
375
|
}
|
|
374
376
|
|
|
375
377
|
const config = {
|
|
376
378
|
authMode: 'guest-link',
|
|
377
379
|
codexHome,
|
|
378
380
|
dashboardOrigin: siteOrigin,
|
|
379
|
-
deviceId: payload.deviceId,
|
|
380
|
-
deviceToken: payload.deviceToken,
|
|
381
|
+
deviceId: payload.data.deviceId,
|
|
382
|
+
deviceToken: payload.data.deviceToken,
|
|
381
383
|
label: device.label,
|
|
382
|
-
pollMs: payload.pollMs ?? DEFAULT_POLL_MS,
|
|
383
|
-
syncUrl: payload.syncUrl,
|
|
384
|
+
pollMs: payload.data.pollMs ?? DEFAULT_POLL_MS,
|
|
385
|
+
syncUrl: payload.data.syncUrl,
|
|
384
386
|
}
|
|
385
387
|
|
|
386
388
|
await writeConfig(codexHome, config)
|
|
387
|
-
await openDashboard(payload.dashboardUrl)
|
|
389
|
+
await openDashboard(payload.data.dashboardUrl)
|
|
388
390
|
console.log('Dashboard opened.')
|
|
389
391
|
console.log(`Config saved to ${resolveConfigPath(codexHome)}`)
|
|
390
392
|
console.log(
|
|
@@ -501,9 +503,9 @@ async function syncOnce(client, config, args) {
|
|
|
501
503
|
}),
|
|
502
504
|
})
|
|
503
505
|
|
|
504
|
-
const payload = await
|
|
506
|
+
const payload = await parseResponseBody(response)
|
|
505
507
|
if (!response.ok) {
|
|
506
|
-
throw new Error(payload
|
|
508
|
+
throw new Error(buildHttpErrorMessage(response, payload, 'Sync failed.'))
|
|
507
509
|
}
|
|
508
510
|
}
|
|
509
511
|
|
|
@@ -519,12 +521,14 @@ async function resolveExistingDashboardUrl(config, args) {
|
|
|
519
521
|
}),
|
|
520
522
|
})
|
|
521
523
|
|
|
522
|
-
const payload = await
|
|
524
|
+
const payload = await parseResponseBody(response)
|
|
523
525
|
if (!response.ok) {
|
|
524
|
-
throw new Error(
|
|
526
|
+
throw new Error(
|
|
527
|
+
buildHttpErrorMessage(response, payload, 'Unable to open the dashboard.'),
|
|
528
|
+
)
|
|
525
529
|
}
|
|
526
530
|
|
|
527
|
-
return payload.dashboardUrl ?? null
|
|
531
|
+
return payload.data.dashboardUrl ?? null
|
|
528
532
|
}
|
|
529
533
|
|
|
530
534
|
const siteOrigin =
|
|
@@ -825,13 +829,81 @@ async function readConfig(codexHome) {
|
|
|
825
829
|
return JSON.parse(await readFile(configPath, 'utf8'))
|
|
826
830
|
}
|
|
827
831
|
|
|
828
|
-
async function
|
|
829
|
-
const
|
|
830
|
-
if (!
|
|
831
|
-
return {}
|
|
832
|
+
async function parseResponseBody(response) {
|
|
833
|
+
const text = (await response.text().catch(() => '')).trim()
|
|
834
|
+
if (!text) {
|
|
835
|
+
return { data: {}, text: '' }
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
try {
|
|
839
|
+
const parsed = JSON.parse(text)
|
|
840
|
+
if (parsed && typeof parsed === 'object') {
|
|
841
|
+
return { data: parsed, text }
|
|
842
|
+
}
|
|
843
|
+
} catch {
|
|
844
|
+
// Fall back to the raw text when the response body is not JSON.
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
return { data: {}, text }
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
function buildHttpErrorMessage(response, payload, fallbackMessage) {
|
|
851
|
+
const bodyError =
|
|
852
|
+
typeof payload.data?.error === 'string' && payload.data.error.trim()
|
|
853
|
+
? payload.data.error.trim()
|
|
854
|
+
: null
|
|
855
|
+
const plainText =
|
|
856
|
+
payload.text && !looksLikeHtml(payload.text)
|
|
857
|
+
? payload.text.replace(/\s+/g, ' ').trim()
|
|
858
|
+
: ''
|
|
859
|
+
const statusLabel = `${response.status}${response.statusText ? ` ${response.statusText}` : ''}`
|
|
860
|
+
const vercelError = response.headers.get('x-vercel-error')
|
|
861
|
+
const vercelId = response.headers.get('x-vercel-id')
|
|
862
|
+
|
|
863
|
+
const detail = bodyError ?? truncateText(plainText, 240)
|
|
864
|
+
let message = detail
|
|
865
|
+
? `${fallbackMessage} ${detail}`
|
|
866
|
+
: `${fallbackMessage} HTTP ${statusLabel}.`
|
|
867
|
+
|
|
868
|
+
if (!detail) {
|
|
869
|
+
return appendHttpContext(message, vercelError, vercelId)
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
if (!bodyError) {
|
|
873
|
+
message = `${message} (HTTP ${statusLabel})`
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
return appendHttpContext(message, vercelError, vercelId)
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
function appendHttpContext(message, vercelError, vercelId) {
|
|
880
|
+
const context = []
|
|
881
|
+
|
|
882
|
+
if (vercelError) {
|
|
883
|
+
context.push(`Vercel error: ${vercelError}`)
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
if (vercelId) {
|
|
887
|
+
context.push(`request id: ${vercelId}`)
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
if (!context.length) {
|
|
891
|
+
return message
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
return `${message} [${context.join('; ')}]`
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
function looksLikeHtml(text) {
|
|
898
|
+
return /^<!doctype html>|^<html[\s>]/i.test(text)
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
function truncateText(text, maxLength) {
|
|
902
|
+
if (text.length <= maxLength) {
|
|
903
|
+
return text
|
|
832
904
|
}
|
|
833
905
|
|
|
834
|
-
return
|
|
906
|
+
return `${text.slice(0, maxLength - 1)}…`
|
|
835
907
|
}
|
|
836
908
|
|
|
837
909
|
function waitForTermination(cleanup) {
|