codex-copilot-dx 0.9.6 → 0.9.7
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 +2 -0
- package/package.json +1 -1
- package/src/adapter.mjs +1 -1
- package/src/dashboard/index.html +1 -1
- package/src/dashboard/ui.css +3 -0
- package/src/dashboard/ui.js +24 -1
- package/src/dashboard-api.mjs +25 -0
package/README.md
CHANGED
|
@@ -236,6 +236,8 @@ 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
|
+
|
|
239
241
|
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
242
|
|
|
241
243
|
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
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;
|
package/src/dashboard/index.html
CHANGED
|
@@ -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">
|
package/src/dashboard/ui.css
CHANGED
|
@@ -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; } }
|
package/src/dashboard/ui.js
CHANGED
|
@@ -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 = [];
|
package/src/dashboard-api.mjs
CHANGED
|
@@ -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;
|