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.
Files changed (2) hide show
  1. package/bin/codex-usage.js +95 -23
  2. package/package.json +1 -1
@@ -290,20 +290,20 @@ async function runPairCommand(args) {
290
290
  }),
291
291
  })
292
292
 
293
- const payload = await parseJsonResponse(response)
293
+ const payload = await parseResponseBody(response)
294
294
  if (!response.ok) {
295
- throw new Error(payload.error ?? 'Pairing failed.')
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 parseJsonResponse(response)
370
+ const payload = await parseResponseBody(response)
371
371
  if (!response.ok) {
372
- throw new Error(payload.error ?? 'Unable to connect this machine.')
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 parseJsonResponse(response)
506
+ const payload = await parseResponseBody(response)
505
507
  if (!response.ok) {
506
- throw new Error(payload.error ?? 'Sync failed.')
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 parseJsonResponse(response)
524
+ const payload = await parseResponseBody(response)
523
525
  if (!response.ok) {
524
- throw new Error(payload.error ?? 'Unable to open the dashboard.')
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 parseJsonResponse(response) {
829
- const payload = await response.json().catch(() => null)
830
- if (!payload || typeof payload !== 'object') {
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 payload
906
+ return `${text.slice(0, maxLength - 1)}…`
835
907
  }
836
908
 
837
909
  function waitForTermination(cleanup) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "codex-usage-dashboard",
3
3
  "private": false,
4
- "version": "0.1.1",
4
+ "version": "0.1.2",
5
5
  "files": [
6
6
  "README.md",
7
7
  "bin"