codex-copilot-dx 0.9.6 → 0.9.8

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/README.md CHANGED
@@ -236,6 +236,10 @@ The dashboard loads local status, a live GitHub Copilot model directory, and a s
236
236
 
237
237
  The transparent, single-color double-peak favicon follows the dashboard's saved light/dark choice without a separate network request.
238
238
 
239
+ The saved GitHub Copilot account appears immediately to the left of the Snapshot time. It uses the same local credentials and fingerprint-bound identity metadata as `ccdx auth status`, with username (or numeric ID fallback) and a `SAVED` label; this is not an online entitlement check. Missing, invalid, unidentified, or unreadable credentials are shown explicitly. The account read runs once on page load and on the top Refresh action, independently of adapter status, with no polling or upstream authentication calls. Its loopback-only, custom-header-protected API exposes no token, fingerprint, credential path, or raw metadata. The general status endpoint retains its existing identity-free contract.
240
+
241
+ Account names containing underscores are supported, including `dingxiao_microsoft`. A numeric ID is a fallback, not a replacement for an available, safely displayable username; the existing length bounds and credential-shaped text suppression still apply.
242
+
239
243
  Usage Analytics is collapsed by default. Opening it adds daily input/output token bars (7/30/90 days) and a 365-day calendar of recorded usage calls or tokens, grouped in the browser's time zone. Cache-read tokens are already part of input and are not added again. Model and range filters use the current snapshot without additional requests; selecting a day shows its filtered model totals in the Usage table, and All history restores the original table. Analytics shares the existing current/rotated-log scan and exposes only bounded metadata (365 days, up to 100 model filters). Days before retained history are hatched, not presented as zero; the oldest retained day may be partial. Counts describe retained usage records, not messages, session time, or all HTTP requests. Refresh explicitly to update the snapshot; there is no background collection or polling.
240
244
 
241
245
  Terminal Animation is collapsed at the bottom of the page, with an Animation shortcut in the header. Preview frames load only when opened; closing it, hiding the page, or requesting reduced motion stops the preview timer. Unsaved choices survive folding and reopening. Recent upstream failures expand to existing diagnostic fields (time, model, code, message, response/request IDs, and retry policy) with a Copy diagnostic control. These are the last 10 retained `response.failed` events, not complete session histories; a retry attempt does not prove recovery. Diagnostic prose is redacted by the existing recorder but can still contain sensitive context: review before copying or sharing.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-copilot-dx",
3
- "version": "0.9.6",
3
+ "version": "0.9.8",
4
4
  "description": "Use Codex App with GitHub Copilot",
5
5
  "author": "Dale Xiao",
6
6
  "type": "module",
package/src/adapter.mjs CHANGED
@@ -65,7 +65,7 @@ export {
65
65
 
66
66
  const ADAPTER_RUNTIME_CONFIG = loadRuntimeConfig();
67
67
  const DASHBOARD_PATHS = new Set(["/", "/ui.css", "/theme.js", "/ui.js"]);
68
- const DASHBOARD_API_PATHS = new Set(["/_ccdx/ui/animation", "/_ccdx/ui/models/live", "/_ccdx/ui/usage"]);
68
+ const DASHBOARD_API_PATHS = new Set(["/_ccdx/ui/animation", "/_ccdx/ui/models/live", "/_ccdx/ui/usage", "/_ccdx/ui/auth"]);
69
69
 
70
70
  export function requestPath(reqUrl) {
71
71
  return new URL(reqUrl || "/", "http://localhost").pathname;
@@ -20,7 +20,7 @@
20
20
  <main>
21
21
  <section class="intro">
22
22
  <h1><span aria-hidden="true">$</span> ccdx status</h1>
23
- <p id="updated">Reading status…</p>
23
+ <div class="snapshot-meta"><p id="auth-account" role="status" title="Saved GitHub Copilot account / local credentials only; not an online entitlement check.">GitHub / READING</p><p id="updated">Reading status…</p></div>
24
24
  </section>
25
25
 
26
26
  <section class="overview" aria-label="Overview">
@@ -25,6 +25,8 @@ button:disabled { opacity: .55; cursor: wait; }
25
25
  h1 { margin: 0; font-size: clamp(20px, 3vw, 27px); font-weight: 650; letter-spacing: -.035em; }
26
26
  h1 span { color: var(--accent-soft); }
27
27
  .intro p { margin: 0; color: var(--subtle); font-size: 11px; }
28
+ .snapshot-meta { display: flex; flex-wrap: wrap; justify-content: flex-end; align-items: baseline; gap: 8px 18px; min-width: 0; }
29
+ .intro .snapshot-meta p { margin: 0; overflow-wrap: anywhere; }
28
30
  .overview { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 11px; }
29
31
  .card, .panel, .lower { min-width: 0; background: var(--surface); border: 1px solid var(--border); border-radius: 2px; }
30
32
  .card { min-height: 139px; padding: 18px; }
@@ -126,5 +128,6 @@ footer { color: var(--footer); font-size: 10px; letter-spacing: .05em; padding:
126
128
  @media (max-width: 1000px) { .animation-options { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
127
129
  @media (max-width: 900px) { .overview { grid-template-columns: repeat(2, minmax(0, 1fr)); } .details { grid-template-columns: repeat(2, minmax(0, 1fr)); } .details .panel:last-child { grid-column: 1 / -1; } }
128
130
  @media (max-width: 680px) { .animation-options { grid-template-columns: 1fr; } }
131
+ @media (max-width: 620px) { .snapshot-meta { justify-content: flex-start; margin-top: 9px; } }
129
132
  @media (max-width: 620px) { .shell { padding: 0 16px; } .topbar { min-height: 60px; } .brand-detail { display: none; } .topbar-actions { gap: 11px; } .intro { display: block; padding: 29px 0 21px; } .intro p { margin-top: 9px; } .overview, .details { grid-template-columns: 1fr; } .details .panel:last-child { grid-column: auto; } .card { min-height: 122px; } .card strong { margin-top: 14px; } .lower li { display: block; } .lower li span { display: block; margin-top: 5px; } }
130
133
  @media (max-width: 460px) { .topbar { flex-wrap: wrap; padding: 12px 0; gap: 10px; } .topbar-actions { gap: 9px; } .panel-heading { flex-wrap: wrap; } .heading-actions { gap: 10px; } .failure-detail { grid-template-columns: 1fr; gap: 3px; } .failure-detail dd { margin-bottom: 7px; } select { max-width: 210px; } }
@@ -3,6 +3,28 @@ const text = (id, value) => { element(id).textContent = value; };
3
3
  const finite = (value) => value !== null && value !== undefined && Number.isFinite(Number(value));
4
4
  const number = (value) => finite(value) ? Math.max(0, Math.round(Number(value))).toLocaleString() : "—";
5
5
  const mib = (value) => finite(value) ? `${(Number(value) / 1048576).toFixed(1)} MiB` : "—";
6
+ let authLoading = false;
7
+
8
+ function renderAuth(data) {
9
+ const account = data.login ? `@${data.login}` : data.id ? `ID ${data.id}` : "account unknown";
10
+ const label = data.reason === "credential_read_failed" ? "UNAVAILABLE"
11
+ : !data.configured ? "NOT CONFIGURED" : !data.valid ? "INVALID" : `${account} / SAVED`;
12
+ text("auth-account", `GitHub / ${label}`);
13
+ }
14
+
15
+ async function loadAuth() {
16
+ if (authLoading) return;
17
+ authLoading = true;
18
+ text("auth-account", "GitHub / READING");
19
+ try {
20
+ const response = await fetch("/_ccdx/ui/auth", { cache: "no-store", headers: { "X-CCDX-Dashboard": "1" }, signal: AbortSignal.timeout(5000) });
21
+ const data = await response.json();
22
+ if (!response.ok || data.source !== "local_auth_status") throw new Error("Auth status unavailable");
23
+ renderAuth(data);
24
+ } catch {
25
+ text("auth-account", "GitHub / UNAVAILABLE");
26
+ } finally { authLoading = false; }
27
+ }
6
28
 
7
29
  function duration(value) {
8
30
  if (!finite(value)) return "—";
@@ -127,8 +149,9 @@ async function refresh() {
127
149
  }
128
150
  }
129
151
 
130
- element("refresh").addEventListener("click", refresh);
152
+ element("refresh").addEventListener("click", () => { refresh(); loadAuth(); });
131
153
  refresh();
154
+ loadAuth();
132
155
 
133
156
  const animationRows = new Map();
134
157
  let animationTimelines = [];
@@ -1,5 +1,7 @@
1
1
  import os from "node:os";
2
2
  import { fetchLiveCopilotModels } from "./cli-models.mjs";
3
+ import { authStatus } from "./cli-auth.mjs";
4
+ import { redactDiagnosticText } from "./diagnostic-text.mjs";
3
5
  import { createRequestAbort, readJsonBody } from "./http-transport.mjs";
4
6
  import { isLoopbackAddress, isLoopbackHostHeader } from "./observability.mjs";
5
7
  import {
@@ -16,6 +18,7 @@ import { readUserSettings, terminalAnimationPreference, writeTerminalAnimationTh
16
18
  const ANIMATION_PATH = "/_ccdx/ui/animation";
17
19
  const MODELS_PATH = "/_ccdx/ui/models/live";
18
20
  const USAGE_PATH = "/_ccdx/ui/usage";
21
+ const AUTH_PATH = "/_ccdx/ui/auth";
19
22
  const DISABLED_ANIMATION = /^(0|false|no|off)$/i;
20
23
 
21
24
  function sendJson(res, statusCode, value) {
@@ -109,6 +112,7 @@ export async function handleDashboardApi(req, res, pathname, {
109
112
  home = os.homedir(),
110
113
  liveModelsFn = () => fetchLiveCopilotModels({ home }),
111
114
  usageSummaryFn = (options) => summarizeUsageLogs(undefined, options),
115
+ authStatusFn = () => authStatus({ home }),
112
116
  } = {}) {
113
117
  if (!isLoopbackAddress(req.socket?.remoteAddress)
114
118
  || req.headers?.host === undefined
@@ -121,6 +125,27 @@ export async function handleDashboardApi(req, res, pathname, {
121
125
  return;
122
126
  }
123
127
 
128
+ if (pathname === AUTH_PATH && req.method === "GET") {
129
+ try {
130
+ const profile = authStatusFn()?.profiles?.codex;
131
+ if (!profile || typeof profile.configured !== "boolean" || typeof profile.valid !== "boolean") throw new Error("Invalid auth status");
132
+ const login = typeof profile.login === "string" && /^[a-z\d_-]{1,39}$/i.test(profile.login)
133
+ && redactDiagnosticText(profile.login) === profile.login ? profile.login : "";
134
+ const id = /^\d{1,20}$/.test(String(profile.id || "")) ? String(profile.id) : "";
135
+ sendJson(res, 200, {
136
+ source: "local_auth_status",
137
+ configured: profile.configured,
138
+ valid: profile.valid,
139
+ login: profile.valid ? login : "",
140
+ id: profile.valid ? id : "",
141
+ reason: ["unconfigured", "empty_token", "credential_read_failed"].includes(profile.reason) ? profile.reason : "",
142
+ });
143
+ } catch {
144
+ sendJson(res, 500, { error: "Could not read local auth status" });
145
+ }
146
+ return;
147
+ }
148
+
124
149
  if (pathname === ANIMATION_PATH && req.method === "GET") {
125
150
  try {
126
151
  const query = new URL(req.url, "http://localhost").searchParams;