codex-copilot-dx 0.8.8 → 0.8.10

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
@@ -217,7 +217,7 @@ If a configured npm mirror has not synchronized the current release yet, use the
217
217
 
218
218
  After starting `ccdx`, open [http://127.0.0.1:2026/](http://127.0.0.1:2026/) in a browser. If `ADAPTER_PORT` is customized, use that port instead. The dashboard shares the existing adapter listener; CCDX does not open a browser automatically or start another service. The page and its assets require both a loopback connection and a loopback Host header, even when LAN binding is enabled.
219
219
 
220
- The dashboard loads local status, a live GitHub Copilot model directory, and a summary of local usage metadata once when opened. Each section has an explicit Refresh control; nothing polls in the background. The live model table uses the same fresh account validation and selectable GPT filtering as `ccdx models`, never labels cached fallback data as online, and makes no inference request. A catalog listing does not guarantee inference access. The Usage table combines the current and rotated local usage logs, shows totals and up to 100 model rows, and contains no prompt, response body, or credential fields. The animation selector previews the existing themes and saves through the same settings writer as `ccdx animation`; changes take effect on the next adapter start, and `CCDX_TERMINAL_ANIMATION=off` still disables terminal motion. Dashboard data requests require a custom header that cross-site simple requests cannot send; animation writes also require a matching same-origin `Origin`. The dashboard does not initialize the optional image handler. The overview's known-model count is the adapter's current in-memory catalog, not the live table. A missing image-generation status does not prove the feature is disabled: use `ccdx image-status` to check setup. Recent upstream error prose may contain sensitive details, so review the page before sharing screenshots.
220
+ The dashboard loads local status, a live GitHub Copilot model directory, and a summary of local usage metadata once when opened. Each section has an explicit Refresh control; nothing polls in the background. The appearance button switches between the default dark terminal theme (sun icon) and a light theme (moon icon); the choice is stored only in that browser. The live model table uses the same fresh account validation and selectable GPT filtering as `ccdx models`, never labels cached fallback data as online, and makes no inference request. A catalog listing does not guarantee inference access. The Usage table combines the current and rotated local usage logs, shows totals and up to 100 model rows, and contains no prompt, response body, or credential fields. The animation selector previews the existing themes and saves through the same settings writer as `ccdx animation`; changes take effect on the next adapter start, and `CCDX_TERMINAL_ANIMATION=off` still disables terminal motion. Dashboard data requests require a custom header that cross-site simple requests cannot send; animation writes also require a matching same-origin `Origin`. The dashboard does not initialize the optional image handler. The overview's known-model count is the adapter's current in-memory catalog, not the live table. A missing image-generation status does not prove the feature is disabled: use `ccdx image-status` to check setup. Recent upstream error prose may contain sensitive details, so review the page before sharing screenshots.
221
221
 
222
222
  Inspect only the local Codex configuration:
223
223
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-copilot-dx",
3
- "version": "0.8.8",
3
+ "version": "0.8.10",
4
4
  "description": "Use Codex App with GitHub Copilot",
5
5
  "author": "Dale Xiao",
6
6
  "type": "module",
package/src/adapter.mjs CHANGED
@@ -64,7 +64,7 @@ export {
64
64
  } from "./responses-request.mjs";
65
65
 
66
66
  const ADAPTER_RUNTIME_CONFIG = loadRuntimeConfig();
67
- const DASHBOARD_PATHS = new Set(["/", "/ui.css", "/ui.js"]);
67
+ const DASHBOARD_PATHS = new Set(["/", "/ui.css", "/theme.js", "/ui.js"]);
68
68
  const DASHBOARD_API_PATHS = new Set(["/_ccdx/ui/animation", "/_ccdx/ui/models/live", "/_ccdx/ui/usage"]);
69
69
 
70
70
  export function requestPath(reqUrl) {
@@ -3,8 +3,9 @@
3
3
  <head>
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
- <meta name="color-scheme" content="dark">
6
+ <meta name="color-scheme" content="dark light">
7
7
  <link rel="icon" href="data:,">
8
+ <script src="/theme.js"></script>
8
9
  <link rel="stylesheet" href="/ui.css">
9
10
  <script src="/ui.js" defer></script>
10
11
  <title>CCDX · Local dashboard</title>
@@ -13,7 +14,7 @@
13
14
  <div class="shell">
14
15
  <header class="topbar">
15
16
  <div class="brand">CCDX <span class="brand-detail">/ LOCAL STATUS</span></div>
16
- <div class="topbar-actions"><span id="connection" class="badge neutral" role="status">READING</span><button id="refresh" type="button">Refresh</button></div>
17
+ <div class="topbar-actions"><span id="connection" class="badge neutral" role="status">READING</span><button id="theme-toggle" class="icon-button" type="button" aria-label="Switch to light mode" title="Switch to light mode"><svg class="icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="4"/><path d="M12 2v2m0 16v2M4.93 4.93l1.42 1.42m11.3 11.3 1.42 1.42M2 12h2m16 0h2M4.93 19.07l1.42-1.42m11.3-11.3 1.42-1.42"/></svg><svg class="icon-moon" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" focusable="false"><path d="M12 3A9 9 0 1 0 21 12 7 7 0 0 1 12 3Z"/></svg></button><button id="refresh" type="button">Refresh</button></div>
17
18
  </header>
18
19
 
19
20
  <main>
@@ -0,0 +1,24 @@
1
+ (() => {
2
+ const key = "ccdx.dashboard.theme";
3
+ let theme = "dark";
4
+ try {
5
+ if (localStorage.getItem(key) === "light") theme = "light";
6
+ } catch { /* Private browsing may disable storage. */ }
7
+ document.documentElement.dataset.theme = theme;
8
+
9
+ document.addEventListener("DOMContentLoaded", () => {
10
+ const button = document.getElementById("theme-toggle");
11
+ const updateLabel = () => {
12
+ const label = `Switch to ${theme === "dark" ? "light" : "dark"} mode`;
13
+ button.setAttribute("aria-label", label);
14
+ button.title = label;
15
+ };
16
+ updateLabel();
17
+ button.addEventListener("click", () => {
18
+ theme = theme === "dark" ? "light" : "dark";
19
+ document.documentElement.dataset.theme = theme;
20
+ try { localStorage.setItem(key, theme); } catch { /* Keep the current selection. */ }
21
+ updateLabel();
22
+ });
23
+ });
24
+ })();
@@ -1,82 +1,88 @@
1
- :root { color-scheme: dark; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; }
1
+ :root { color-scheme: dark; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; --page: #0b110d; --surface: #111a13; --text: #e0e9df; --strong: #f0f6ec; --muted: #9aada0; --badge: #a9b9aa; --subtle: #78917d; --quiet: #6d8371; --footer: #617966; --border: #2b3b2f; --top-border: #2a3a2e; --accent: #a6f5a8; --accent-soft: #84e99e; --heading: #83b98b; --bad: #ff947e; --failed: #ff9f91; --incomplete: #e3c77c; --button-border: #45634b; --button-bg: #152219; --button-fg: #b2f1bd; --button-hover: #203526; --selected-bg: #192a1d; --option-hover: #1a2a1e; --table-text: #d8e2d8; --preview: #657d69; --ansi-white: #f5fff5; --ansi-159: #9af5e9; --ansi-123: #75e4d9; --ansi-87: #5ad0d4; --ansi-51: #42b9d8; --ansi-45: #3ca2d0; --ansi-39: #4389c2; --ansi-33: #4775b2; }
2
+ :root[data-theme="light"] { color-scheme: light; --page: #f5f8f3; --surface: #ffffff; --text: #1b2b20; --strong: #122219; --muted: #475d4e; --badge: #475d4e; --subtle: #4b6652; --quiet: #55715c; --footer: #59735f; --border: #c4d2c5; --top-border: #b8c9ba; --accent: #176b3a; --accent-soft: #176b3a; --heading: #235f37; --bad: #a33625; --failed: #a33625; --incomplete: #835600; --button-border: #79a085; --button-bg: #ebf4ec; --button-fg: #185b32; --button-hover: #dbeedd; --selected-bg: #e4f3e7; --option-hover: #edf6ee; --table-text: #233b2b; --preview: #596f60; --ansi-white: #234231; --ansi-159: #116b64; --ansi-123: #0d7771; --ansi-87: #116b75; --ansi-51: #14697e; --ansi-45: #1a6183; --ansi-39: #265f8d; --ansi-33: #365a8d; }
2
3
  * { box-sizing: border-box; }
3
- body { margin: 0; color: #e0e9df; background: #0b110d; font-size: 13px; }
4
+ body { margin: 0; color: var(--text); background: var(--page); font-size: 13px; }
4
5
  .shell { max-width: 1220px; margin: 0 auto; padding: 0 24px; }
5
- .topbar { min-height: 68px; display: flex; justify-content: space-between; align-items: center; gap: 20px; border-bottom: 1px solid #2a3a2e; }
6
- .brand { color: #a6f5a8; font-size: 16px; font-weight: 750; letter-spacing: .07em; white-space: nowrap; }
7
- .brand-detail { color: #78917d; font-size: 11px; font-weight: 500; letter-spacing: .03em; }
6
+ .topbar { min-height: 68px; display: flex; justify-content: space-between; align-items: center; gap: 20px; border-bottom: 1px solid var(--top-border); }
7
+ .brand { color: var(--accent); font-size: 16px; font-weight: 750; letter-spacing: .07em; white-space: nowrap; }
8
+ .brand-detail { color: var(--subtle); font-size: 11px; font-weight: 500; letter-spacing: .03em; }
8
9
  .topbar-actions { display: flex; align-items: center; gap: 20px; }
9
- .badge { color: #a9b9aa; white-space: nowrap; font-size: 11px; font-weight: 650; text-transform: uppercase; }
10
+ .badge { color: var(--badge); white-space: nowrap; font-size: 11px; font-weight: 650; text-transform: uppercase; }
10
11
  .badge::before { content: "●"; margin-right: 8px; }
11
- .badge.good { color: #84e99e; }
12
- .badge.bad { color: #ff947e; }
13
- button { border: 1px solid #45634b; border-radius: 2px; background: #152219; color: #b2f1bd; padding: 8px 13px; font: inherit; font-size: 11px; font-weight: 700; text-transform: uppercase; cursor: pointer; }
14
- button:hover { background: #203526; }
15
- button:focus-visible { outline: 2px solid #a6f5a8; outline-offset: 3px; }
12
+ .badge.good { color: var(--accent-soft); }
13
+ .badge.bad { color: var(--bad); }
14
+ button { border: 1px solid var(--button-border); border-radius: 2px; background: var(--button-bg); color: var(--button-fg); padding: 8px 13px; font: inherit; font-size: 11px; font-weight: 700; text-transform: uppercase; cursor: pointer; }
15
+ button:hover { background: var(--button-hover); }
16
+ button:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
16
17
  button:disabled { opacity: .55; cursor: wait; }
18
+ .icon-button { display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; padding: 6px; border: 0; background: transparent; }
19
+ .icon-button:hover { background: transparent; color: var(--accent); }
20
+ .icon-button svg { width: 18px; height: 18px; }
21
+ .icon-moon, :root[data-theme="light"] .icon-sun { display: none; }
22
+ :root[data-theme="light"] .icon-moon { display: block; }
17
23
  .intro { display: flex; justify-content: space-between; align-items: baseline; gap: 20px; padding: 38px 0 25px; }
18
24
  h1 { margin: 0; font-size: clamp(20px, 3vw, 27px); font-weight: 650; letter-spacing: -.035em; }
19
- h1 span { color: #84e99e; }
20
- .intro p { margin: 0; color: #78917d; font-size: 11px; }
25
+ h1 span { color: var(--accent-soft); }
26
+ .intro p { margin: 0; color: var(--subtle); font-size: 11px; }
21
27
  .overview { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 11px; }
22
- .card, .panel, .lower { min-width: 0; background: #111a13; border: 1px solid #2b3b2f; border-radius: 2px; }
28
+ .card, .panel, .lower { min-width: 0; background: var(--surface); border: 1px solid var(--border); border-radius: 2px; }
23
29
  .card { min-height: 139px; padding: 18px; }
24
- .card h2, .panel h2, .lower h2 { color: #83b98b; font-size: 11px; font-weight: 700; letter-spacing: .08em; margin: 0; }
25
- .card strong, .panel > strong { display: block; margin-top: 21px; color: #f0f6ec; font-size: 23px; font-weight: 650; line-height: 1.25; letter-spacing: -.035em; overflow-wrap: anywhere; }
26
- .card p { margin: 9px 0 0; color: #9aada0; font-size: 11px; line-height: 1.45; overflow-wrap: anywhere; }
30
+ .card h2, .panel h2, .lower h2 { color: var(--heading); font-size: 11px; font-weight: 700; letter-spacing: .08em; margin: 0; }
31
+ .card strong, .panel > strong { display: block; margin-top: 21px; color: var(--strong); font-size: 23px; font-weight: 650; line-height: 1.25; letter-spacing: -.035em; overflow-wrap: anywhere; }
32
+ .card p { margin: 9px 0 0; color: var(--muted); font-size: 11px; line-height: 1.45; overflow-wrap: anywhere; }
27
33
  .details { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 11px; margin-top: 11px; }
28
34
  .panel { min-height: 205px; padding: 18px; }
29
35
  .panel-heading { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; }
30
- .panel-heading span { color: #6d8371; font-size: 10px; white-space: nowrap; }
36
+ .panel-heading span { color: var(--quiet); font-size: 10px; white-space: nowrap; }
31
37
  .stats { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 17px; margin: 26px 0 0; }
32
- .stats div { display: flex; justify-content: space-between; border-bottom: 1px solid #2b3b2f; padding-bottom: 8px; gap: 8px; }
33
- .stats dt { color: #9aada0; font-size: 11px; }
38
+ .stats div { display: flex; justify-content: space-between; border-bottom: 1px solid var(--border); padding-bottom: 8px; gap: 8px; }
39
+ .stats dt { color: var(--muted); font-size: 11px; }
34
40
  .stats dd { margin: 0; font-size: 14px; font-weight: 650; }
35
- #incomplete { color: #e3c77c; }
36
- #failed { color: #ff9f91; }
37
- .hint { color: #78917d; font-size: 11px; line-height: 1.5; margin: 15px 0 0; overflow-wrap: anywhere; }
38
- .meter { display: block; width: 100%; height: 6px; border: 0; border-radius: 0; background: #2b3b2f; margin-top: 20px; appearance: none; }
39
- .meter::-webkit-progress-bar { background: #2b3b2f; }
40
- .meter::-webkit-progress-value { background: #84e99e; }
41
- .meter::-moz-progress-bar { background: #84e99e; }
41
+ #incomplete { color: var(--incomplete); }
42
+ #failed { color: var(--failed); }
43
+ .hint { color: var(--subtle); font-size: 11px; line-height: 1.5; margin: 15px 0 0; overflow-wrap: anywhere; }
44
+ .meter { display: block; width: 100%; height: 6px; border: 0; border-radius: 0; background: var(--border); margin-top: 20px; appearance: none; }
45
+ .meter::-webkit-progress-bar { background: var(--border); }
46
+ .meter::-webkit-progress-value { background: var(--accent-soft); }
47
+ .meter::-moz-progress-bar { background: var(--accent-soft); }
42
48
  .dashboard-section { min-height: 0; margin-top: 11px; }
43
49
  .heading-actions { display: flex; align-items: center; gap: 16px; }
44
50
  .heading-actions button { padding: 5px 9px; }
45
51
  .animation-options { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; margin-top: 18px; }
46
- .theme-option { display: grid; grid-template-columns: 18px 76px minmax(0, 1fr); align-items: center; gap: 8px; min-width: 0; width: 100%; border: 1px solid #2b3b2f; padding: 10px; background: transparent; color: #e0e9df; text-align: left; text-transform: none; font-size: 11px; cursor: pointer; }
47
- .theme-option::before { content: "○"; color: #78917d; font-size: 17px; line-height: 1; }
48
- .theme-option[aria-pressed="true"] { border-color: #84e99e; background: #192a1d; }
49
- .theme-option[aria-pressed="true"]::before { content: "●"; color: #84e99e; }
50
- .theme-option:hover { background: #1a2a1e; }
51
- .theme-name { color: #e0e9df; font-size: 11px; white-space: nowrap; }
52
- .theme-name small { display: block; color: #78917d; font-size: 9px; margin-top: 2px; }
53
- .theme-preview { color: #657d69; white-space: pre; overflow: hidden; font-size: 11px; letter-spacing: 0; }
54
- .ansi-white { color: #f5fff5; }
55
- .ansi-159 { color: #9af5e9; }
56
- .ansi-123 { color: #75e4d9; }
57
- .ansi-87 { color: #5ad0d4; }
58
- .ansi-51 { color: #42b9d8; }
59
- .ansi-45 { color: #3ca2d0; }
60
- .ansi-39 { color: #4389c2; }
61
- .ansi-33 { color: #4775b2; }
52
+ .theme-option { display: grid; grid-template-columns: 18px 76px minmax(0, 1fr); align-items: center; gap: 8px; min-width: 0; width: 100%; border: 1px solid var(--border); padding: 10px; background: transparent; color: var(--text); text-align: left; text-transform: none; font-size: 11px; cursor: pointer; }
53
+ .theme-option::before { content: "○"; color: var(--subtle); font-size: 17px; line-height: 1; }
54
+ .theme-option[aria-pressed="true"] { border-color: var(--accent-soft); background: var(--selected-bg); }
55
+ .theme-option[aria-pressed="true"]::before { content: "●"; color: var(--accent-soft); }
56
+ .theme-option:hover { background: var(--option-hover); }
57
+ .theme-name { color: var(--text); font-size: 11px; white-space: nowrap; }
58
+ .theme-name small { display: block; color: var(--subtle); font-size: 9px; margin-top: 2px; }
59
+ .theme-preview { color: var(--preview); white-space: pre; overflow: hidden; font-size: 11px; letter-spacing: 0; }
60
+ .ansi-white { color: var(--ansi-white); }
61
+ .ansi-159 { color: var(--ansi-159); }
62
+ .ansi-123 { color: var(--ansi-123); }
63
+ .ansi-87 { color: var(--ansi-87); }
64
+ .ansi-51 { color: var(--ansi-51); }
65
+ .ansi-45 { color: var(--ansi-45); }
66
+ .ansi-39 { color: var(--ansi-39); }
67
+ .ansi-33 { color: var(--ansi-33); }
62
68
  .section-actions { display: flex; align-items: center; gap: 15px; margin-top: 16px; }
63
- .section-actions span { color: #9aada0; font-size: 11px; }
64
- .table-note { margin: 15px 0 8px; color: #9aada0; font-size: 11px; }
69
+ .section-actions span { color: var(--muted); font-size: 11px; }
70
+ .table-note { margin: 15px 0 8px; color: var(--muted); font-size: 11px; }
65
71
  .table-wrap { width: 100%; overflow-x: auto; }
66
72
  table { width: 100%; border-collapse: collapse; font-size: 11px; }
67
- th { color: #83b98b; text-align: left; font-weight: 700; letter-spacing: .05em; }
68
- th, td { padding: 9px 12px 9px 0; border-bottom: 1px solid #2b3b2f; white-space: nowrap; }
73
+ th { color: var(--heading); text-align: left; font-weight: 700; letter-spacing: .05em; }
74
+ th, td { padding: 9px 12px 9px 0; border-bottom: 1px solid var(--border); white-space: nowrap; }
69
75
  th:last-child, td:last-child { padding-right: 0; }
70
- td { color: #d8e2d8; }
76
+ td { color: var(--table-text); }
71
77
  .usage-table th:not(:first-child), .usage-table td:not(:first-child) { text-align: right; }
72
- .usage-table tr.total-row td { color: #a6f5a8; font-weight: 700; }
78
+ .usage-table tr.total-row td { color: var(--accent); font-weight: 700; }
73
79
  .lower { margin-top: 11px; padding: 18px; }
74
80
  .lower ol { list-style: none; margin: 16px 0 0; padding: 0; }
75
- .lower li { display: grid; grid-template-columns: minmax(150px, 1fr) minmax(0, 2fr); gap: 14px; padding: 9px 0; border-top: 1px solid #2b3b2f; font-size: 11px; }
81
+ .lower li { display: grid; grid-template-columns: minmax(150px, 1fr) minmax(0, 2fr); gap: 14px; padding: 9px 0; border-top: 1px solid var(--border); font-size: 11px; }
76
82
  .lower li strong, .lower li span { overflow-wrap: anywhere; }
77
- .lower li strong { color: #e0e9df; font-weight: 650; }
78
- .lower li span, .lower li.empty { color: #9aada0; line-height: 1.5; }
79
- footer { color: #617966; font-size: 10px; letter-spacing: .05em; padding: 22px 0 30px; }
83
+ .lower li strong { color: var(--text); font-weight: 650; }
84
+ .lower li span, .lower li.empty { color: var(--muted); line-height: 1.5; }
85
+ footer { color: var(--footer); font-size: 10px; letter-spacing: .05em; padding: 22px 0 30px; }
80
86
  @media (max-width: 1000px) { .animation-options { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
81
87
  @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; } }
82
88
  @media (max-width: 680px) { .animation-options { grid-template-columns: 1fr; } }
package/src/dashboard.mjs CHANGED
@@ -4,6 +4,7 @@ import { isLoopbackAddress, isLoopbackHostHeader } from "./observability.mjs";
4
4
  const ASSETS = new Map([
5
5
  ["/", ["./dashboard/index.html", "text/html; charset=utf-8"]],
6
6
  ["/ui.css", ["./dashboard/ui.css", "text/css; charset=utf-8"]],
7
+ ["/theme.js", ["./dashboard/theme.js", "text/javascript; charset=utf-8"]],
7
8
  ["/ui.js", ["./dashboard/ui.js", "text/javascript; charset=utf-8"]],
8
9
  ]);
9
10