codex-copilot-dx 0.9.17 → 0.9.18

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-copilot-dx",
3
- "version": "0.9.17",
3
+ "version": "0.9.18",
4
4
  "description": "Use Codex App with GitHub Copilot",
5
5
  "author": "Dale Xiao",
6
6
  "type": "module",
package/src/adapter.mjs CHANGED
@@ -6,8 +6,6 @@ import {
6
6
  ADAPTER_STATUS_PATH,
7
7
  classifyAdapterRoute,
8
8
  createRequestMetrics,
9
- isLoopbackAddress,
10
- isLoopbackHostHeader,
11
9
  runtimeStatusPayload,
12
10
  } from "./observability.mjs";
13
11
  import { createRequestId, runWithRequestContext } from "./request-context.mjs";
@@ -26,13 +24,14 @@ import {
26
24
  } from "./cache-control.mjs";
27
25
  import { loadRuntimeConfig, parsePositiveInteger } from "./runtime-config.mjs";
28
26
  import {
29
- createRequestAdmission,
30
27
  createRequestAbort,
31
- httpError,
32
28
  logRequestFailure,
33
29
  readJsonBody,
34
30
  sendJsonError,
35
31
  } from "./http-transport.mjs";
32
+ import { httpError } from "./http-errors.mjs";
33
+ import { createRequestAdmission } from "./request-admission.mjs";
34
+ import { isLoopbackAddress, isLoopbackHostHeader } from "./security.mjs";
36
35
 
37
36
  export {
38
37
  abortErrorStatusCode,
@@ -0,0 +1,175 @@
1
+ import os from "node:os";
2
+ import { validateGithubToken } from "./auth.mjs";
3
+ import { AUTH_PROFILE_CODEX, readAuthProfileCredentials } from "./auth-profile.mjs";
4
+ import { buildHeaders, FALLBACK_VSCODE_VERSION, parseApiBase } from "./copilot.mjs";
5
+ import { MAX_UPSTREAM_MODEL_CATALOG_BYTES, readBoundedResponseText } from "./http-transport.mjs";
6
+ import { profileRouting } from "./profile-routing.mjs";
7
+
8
+ const DEFAULT_COPILOT_CATALOG_TIMEOUT_MS = 30 * 1000;
9
+
10
+ function responseDetail(value) {
11
+ return String(value || "").replace(/\s+/g, " ").trim().slice(0, 240);
12
+ }
13
+
14
+ function abortError(signal) {
15
+ if (signal?.reason instanceof Error) return signal.reason;
16
+ const error = new Error("The operation was aborted");
17
+ error.name = "AbortError";
18
+ return error;
19
+ }
20
+
21
+ function catalogModels(payload) {
22
+ const models = Array.isArray(payload) ? payload : payload?.data;
23
+ if (!Array.isArray(models)) throw new Error("Copilot model catalog contained no model list");
24
+ return models;
25
+ }
26
+
27
+ async function fetchCopilotCatalog(tokenData, {
28
+ fetchImpl,
29
+ signal,
30
+ vscodeVersion = FALLBACK_VSCODE_VERSION,
31
+ timeoutMs = DEFAULT_COPILOT_CATALOG_TIMEOUT_MS,
32
+ } = {}) {
33
+ const parsedTimeout = Number(timeoutMs);
34
+ const deadlineMs = Number.isFinite(parsedTimeout) && parsedTimeout > 0
35
+ ? parsedTimeout
36
+ : DEFAULT_COPILOT_CATALOG_TIMEOUT_MS;
37
+ const controller = new AbortController();
38
+ const onCallerAbort = () => controller.abort(signal?.reason);
39
+ signal?.addEventListener("abort", onCallerAbort, { once: true });
40
+ if (signal?.aborted) onCallerAbort();
41
+ const timer = setTimeout(() => controller.abort(new Error(`Copilot model catalog timed out after ${deadlineMs}ms`)), deadlineMs);
42
+ try {
43
+ const apiBase = parseApiBase(tokenData);
44
+ const headers = buildHeaders({
45
+ token: tokenData.token,
46
+ version: vscodeVersion,
47
+ initiator: "user",
48
+ vision: false,
49
+ });
50
+ const response = await fetchImpl(`${apiBase}/models`, {
51
+ headers,
52
+ redirect: "error",
53
+ signal: controller.signal,
54
+ });
55
+ const modelText = await readBoundedResponseText(response, {
56
+ maxBytes: MAX_UPSTREAM_MODEL_CATALOG_BYTES,
57
+ label: "Copilot model catalog",
58
+ signal: controller.signal,
59
+ });
60
+ if (!response.ok) {
61
+ const detail = responseDetail(modelText);
62
+ throw new Error(`Copilot model catalog failed with HTTP ${response.status}${detail ? `: ${detail}` : ""}`);
63
+ }
64
+
65
+ let catalog;
66
+ try {
67
+ catalog = JSON.parse(modelText);
68
+ } catch {
69
+ throw new Error("Copilot model catalog returned invalid JSON");
70
+ }
71
+ catalogModels(catalog);
72
+ return { catalog, apiBase };
73
+ } catch (error) {
74
+ if (signal?.aborted) throw abortError(signal);
75
+ if (controller.signal.aborted) throw abortError(controller.signal);
76
+ throw error;
77
+ } finally {
78
+ clearTimeout(timer);
79
+ signal?.removeEventListener("abort", onCallerAbort);
80
+ }
81
+ }
82
+
83
+ function publicProfile(credentials) {
84
+ return {
85
+ configured: credentials.configured,
86
+ valid: credentials.valid,
87
+ reason: credentials.reason,
88
+ login: credentials.identity?.login || "",
89
+ id: credentials.identity?.id || "",
90
+ source: "legacy",
91
+ };
92
+ }
93
+
94
+ function readStatusCredentials(home) {
95
+ try {
96
+ return readAuthProfileCredentials(AUTH_PROFILE_CODEX, { home });
97
+ } catch {
98
+ return {
99
+ profile: AUTH_PROFILE_CODEX,
100
+ configured: true,
101
+ valid: false,
102
+ reason: "credential_read_failed",
103
+ token: "",
104
+ identity: null,
105
+ };
106
+ }
107
+ }
108
+
109
+ export function authStatus({ home = os.homedir() } = {}) {
110
+ const codex = readStatusCredentials(home);
111
+ return {
112
+ profiles: { codex: publicProfile(codex) },
113
+ routing: profileRouting(),
114
+ };
115
+ }
116
+
117
+ async function inspectProfileOnline(credentials, { fetchImpl, timeoutMs } = {}) {
118
+ if (!credentials.valid) {
119
+ return { checked: false, ok: false, reason: credentials.reason || "unconfigured" };
120
+ }
121
+
122
+ const controller = new AbortController();
123
+ const timer = setTimeout(() => controller.abort(new Error(`timed out after ${timeoutMs}ms`)), timeoutMs);
124
+ try {
125
+ const validation = await validateGithubToken(credentials.token, {
126
+ fetchImpl,
127
+ signal: controller.signal,
128
+ });
129
+ if (controller.signal.aborted) throw abortError(controller.signal);
130
+ if (!validation.ok) {
131
+ return {
132
+ checked: true,
133
+ ok: false,
134
+ reason: validation.reason,
135
+ httpStatus: validation.status || null,
136
+ };
137
+ }
138
+ const { catalog, apiBase } = await fetchCopilotCatalog(validation.copilotTokenData, {
139
+ fetchImpl,
140
+ signal: controller.signal,
141
+ });
142
+ let upstreamHost = "GitHub Copilot";
143
+ try { upstreamHost = new URL(apiBase).hostname; } catch {}
144
+ return {
145
+ checked: true,
146
+ ok: true,
147
+ login: validation.login || credentials.identity?.login || "",
148
+ models: catalogModels(catalog).length,
149
+ upstreamHost,
150
+ };
151
+ } catch (error) {
152
+ return {
153
+ checked: true,
154
+ ok: false,
155
+ reason: controller.signal.aborted ? `timed out after ${timeoutMs}ms` : error.message,
156
+ };
157
+ } finally {
158
+ clearTimeout(timer);
159
+ }
160
+ }
161
+
162
+ export async function authStatusOnline({
163
+ home = os.homedir(),
164
+ fetchImpl = fetch,
165
+ timeoutMs = 10000,
166
+ } = {}) {
167
+ const local = authStatus({ home });
168
+ const online = await inspectProfileOnline(readStatusCredentials(home), { fetchImpl, timeoutMs });
169
+ return {
170
+ ...local,
171
+ profiles: {
172
+ codex: { ...local.profiles.codex, online },
173
+ },
174
+ };
175
+ }
package/src/cli-auth.mjs CHANGED
@@ -1,15 +1,4 @@
1
- import os from "node:os";
2
- import { validateGithubToken } from "./auth.mjs";
3
- import {
4
- AUTH_PROFILE_CODEX,
5
- readAuthProfileCredentials,
6
- } from "./auth-profile.mjs";
7
- import { buildHeaders, FALLBACK_VSCODE_VERSION, parseApiBase } from "./copilot.mjs";
8
- import {
9
- MAX_UPSTREAM_MODEL_CATALOG_BYTES,
10
- readBoundedResponseText,
11
- } from "./http-transport.mjs";
12
- import { profileRouting } from "./profile-routing.mjs";
1
+ import { authStatus, authStatusOnline } from "./auth-status.mjs";
13
2
  import { status } from "./status.mjs";
14
3
  import {
15
4
  cliOutputFormat,
@@ -18,174 +7,7 @@ import {
18
7
  terminalCell,
19
8
  } from "./cli-table.mjs";
20
9
 
21
- const DEFAULT_COPILOT_CATALOG_TIMEOUT_MS = 30 * 1000;
22
-
23
- function responseDetail(value) {
24
- return String(value || "").replace(/\s+/g, " ").trim().slice(0, 240);
25
- }
26
-
27
- function abortError(signal) {
28
- if (signal?.reason instanceof Error) return signal.reason;
29
- const error = new Error("The operation was aborted");
30
- error.name = "AbortError";
31
- return error;
32
- }
33
-
34
- function catalogModels(payload) {
35
- const models = Array.isArray(payload) ? payload : payload?.data;
36
- if (!Array.isArray(models)) throw new Error("Copilot model catalog contained no model list");
37
- return models;
38
- }
39
-
40
- async function fetchCopilotCatalog(tokenData, {
41
- fetchImpl,
42
- signal,
43
- vscodeVersion = FALLBACK_VSCODE_VERSION,
44
- timeoutMs = DEFAULT_COPILOT_CATALOG_TIMEOUT_MS,
45
- } = {}) {
46
- const parsedTimeout = Number(timeoutMs);
47
- const deadlineMs = Number.isFinite(parsedTimeout) && parsedTimeout > 0
48
- ? parsedTimeout
49
- : DEFAULT_COPILOT_CATALOG_TIMEOUT_MS;
50
- const controller = new AbortController();
51
- const onCallerAbort = () => controller.abort(signal?.reason);
52
- signal?.addEventListener("abort", onCallerAbort, { once: true });
53
- if (signal?.aborted) onCallerAbort();
54
- const timer = setTimeout(() => controller.abort(new Error(`Copilot model catalog timed out after ${deadlineMs}ms`)), deadlineMs);
55
- try {
56
- const apiBase = parseApiBase(tokenData);
57
- const headers = buildHeaders({
58
- token: tokenData.token,
59
- version: vscodeVersion,
60
- initiator: "user",
61
- vision: false,
62
- });
63
- const response = await fetchImpl(`${apiBase}/models`, {
64
- headers,
65
- redirect: "error",
66
- signal: controller.signal,
67
- });
68
- const modelText = await readBoundedResponseText(response, {
69
- maxBytes: MAX_UPSTREAM_MODEL_CATALOG_BYTES,
70
- label: "Copilot model catalog",
71
- signal: controller.signal,
72
- });
73
- if (!response.ok) {
74
- const detail = responseDetail(modelText);
75
- throw new Error(`Copilot model catalog failed with HTTP ${response.status}${detail ? `: ${detail}` : ""}`);
76
- }
77
-
78
- let catalog;
79
- try {
80
- catalog = JSON.parse(modelText);
81
- } catch {
82
- throw new Error("Copilot model catalog returned invalid JSON");
83
- }
84
- catalogModels(catalog);
85
- return { catalog, apiBase };
86
- } catch (error) {
87
- if (signal?.aborted) throw abortError(signal);
88
- if (controller.signal.aborted) throw abortError(controller.signal);
89
- throw error;
90
- } finally {
91
- clearTimeout(timer);
92
- signal?.removeEventListener("abort", onCallerAbort);
93
- }
94
- }
95
-
96
- function publicProfile(credentials) {
97
- return {
98
- configured: credentials.configured,
99
- valid: credentials.valid,
100
- reason: credentials.reason,
101
- login: credentials.identity?.login || "",
102
- id: credentials.identity?.id || "",
103
- source: "legacy",
104
- };
105
- }
106
-
107
- function readStatusCredentials(home) {
108
- try {
109
- return readAuthProfileCredentials(AUTH_PROFILE_CODEX, { home });
110
- } catch {
111
- return {
112
- profile: AUTH_PROFILE_CODEX,
113
- configured: true,
114
- valid: false,
115
- reason: "credential_read_failed",
116
- token: "",
117
- identity: null,
118
- };
119
- }
120
- }
121
-
122
- export function authStatus({ home = os.homedir() } = {}) {
123
- const codex = readStatusCredentials(home);
124
- return {
125
- profiles: { codex: publicProfile(codex) },
126
- routing: profileRouting(),
127
- };
128
- }
129
-
130
- async function inspectProfileOnline(credentials, { fetchImpl, timeoutMs } = {}) {
131
- if (!credentials.valid) {
132
- return { checked: false, ok: false, reason: credentials.reason || "unconfigured" };
133
- }
134
-
135
- const controller = new AbortController();
136
- const timer = setTimeout(() => controller.abort(new Error(`timed out after ${timeoutMs}ms`)), timeoutMs);
137
- try {
138
- const validation = await validateGithubToken(credentials.token, {
139
- fetchImpl,
140
- signal: controller.signal,
141
- });
142
- if (controller.signal.aborted) throw abortError(controller.signal);
143
- if (!validation.ok) {
144
- return {
145
- checked: true,
146
- ok: false,
147
- reason: validation.reason,
148
- httpStatus: validation.status || null,
149
- };
150
- }
151
- const { catalog, apiBase } = await fetchCopilotCatalog(validation.copilotTokenData, {
152
- fetchImpl,
153
- signal: controller.signal,
154
- });
155
- let upstreamHost = "GitHub Copilot";
156
- try { upstreamHost = new URL(apiBase).hostname; } catch {}
157
- return {
158
- checked: true,
159
- ok: true,
160
- login: validation.login || credentials.identity?.login || "",
161
- models: catalogModels(catalog).length,
162
- upstreamHost,
163
- };
164
- } catch (error) {
165
- return {
166
- checked: true,
167
- ok: false,
168
- reason: controller.signal.aborted ? `timed out after ${timeoutMs}ms` : error.message,
169
- };
170
- } finally {
171
- clearTimeout(timer);
172
- }
173
- }
174
-
175
- export async function authStatusOnline({
176
- home = os.homedir(),
177
- fetchImpl = fetch,
178
- timeoutMs = 10000,
179
- } = {}) {
180
- const local = authStatus({ home });
181
- const online = await inspectProfileOnline(readStatusCredentials(home), { fetchImpl, timeoutMs });
182
- return {
183
- ...local,
184
- profiles: {
185
- codex: { ...local.profiles.codex, online },
186
- },
187
- };
188
- }
10
+ export { authStatus, authStatusOnline } from "./auth-status.mjs";
189
11
 
190
12
  function accountLabel(profile) {
191
13
  if (!profile.configured) return "not configured";
@@ -1,10 +1,3 @@
1
- import os from "node:os";
2
- import { validateGithubToken } from "./auth.mjs";
3
- import {
4
- AUTH_PROFILE_CODEX,
5
- readAuthProfileCredentials,
6
- } from "./auth-profile.mjs";
7
- import { buildHeaders, FALLBACK_VSCODE_VERSION, parseApiBase } from "./copilot.mjs";
8
1
  import { status } from "./status.mjs";
9
2
  import {
10
3
  cliOutputFormat,
@@ -13,11 +6,7 @@ import {
13
6
  terminalCell,
14
7
  } from "./cli-table.mjs";
15
8
 
16
- const DEFAULT_MODELS_TIMEOUT_MS = 10000;
17
- const SUPPORTED_ENDPOINTS = [
18
- ["responses", new Set(["/responses", "/v1/responses"])],
19
- ["chat", new Set(["/chat/completions"])],
20
- ];
9
+ export { fetchLiveCopilotModels, selectableCopilotModels } from "./live-models.mjs";
21
10
 
22
11
  function safeText(value, fallback = "") {
23
12
  const text = String(value ?? "")
@@ -27,125 +16,6 @@ function safeText(value, fallback = "") {
27
16
  return text || fallback;
28
17
  }
29
18
 
30
- function endpointLabels(model) {
31
- const advertised = new Set(Array.isArray(model?.supported_endpoints) ? model.supported_endpoints : []);
32
- return SUPPORTED_ENDPOINTS
33
- .filter(([, values]) => [...values].some((value) => advertised.has(value)))
34
- .map(([label]) => label);
35
- }
36
-
37
- export function selectableCopilotModels(payload) {
38
- const data = Array.isArray(payload) ? payload : payload?.data;
39
- if (!Array.isArray(data)) throw new Error("Copilot models response contained no model list");
40
-
41
- const models = [];
42
- const seen = new Set();
43
- for (const model of data) {
44
- if (model?.model_picker_enabled !== true) continue;
45
- const policyState = safeText(model?.policy?.state).toLowerCase();
46
- if (policyState && policyState !== "enabled") continue;
47
- const capabilityType = safeText(model?.capabilities?.type).toLowerCase();
48
- if (capabilityType && capabilityType !== "chat") continue;
49
-
50
- const id = safeText(model?.id);
51
- const endpoints = endpointLabels(model);
52
- if (!id.startsWith("gpt-") || id === "codex-auto-review" || !endpoints.length || seen.has(id)) continue;
53
- seen.add(id);
54
- models.push({
55
- id,
56
- vendor: safeText(model?.vendor || model?.owned_by, "Unknown"),
57
- endpoints,
58
- preview: model?.preview === true,
59
- });
60
- }
61
-
62
- models.sort((left, right) => left.vendor.localeCompare(right.vendor, "en")
63
- || left.id.localeCompare(right.id, "en"));
64
- return { advertised: data.length, models };
65
- }
66
-
67
- function validationFailure(validation) {
68
- const httpStatus = validation?.status ? ` (HTTP ${validation.status})` : "";
69
- return `GitHub Copilot authentication failed: ${validation?.reason || "unknown error"}${httpStatus}`;
70
- }
71
-
72
- async function responseText(response) {
73
- try {
74
- return await response.text();
75
- } catch {
76
- return "";
77
- }
78
- }
79
-
80
- function readModelCredentials(home) {
81
- let credentials;
82
- try {
83
- credentials = readAuthProfileCredentials(AUTH_PROFILE_CODEX, { home });
84
- } catch (error) {
85
- throw new Error(`Could not read the GitHub token: ${error.message}`);
86
- }
87
- if (credentials.valid) return credentials;
88
- if (!credentials.configured) throw new Error("GitHub token not found. Start ccdx once to log in.");
89
- if (credentials.reason === "empty_token") throw new Error("GitHub token file is empty. Start ccdx again to log in.");
90
- throw new Error(`GitHub token is unavailable (${credentials.reason || "unknown"}). Start ccdx again to log in.`);
91
- }
92
-
93
- export async function fetchLiveCopilotModels({
94
- home = os.homedir(),
95
- fetchImpl = fetch,
96
- timeoutMs = DEFAULT_MODELS_TIMEOUT_MS,
97
- vscodeVersion = FALLBACK_VSCODE_VERSION,
98
- } = {}) {
99
- const credentials = readModelCredentials(home);
100
- const controller = new AbortController();
101
- const timer = setTimeout(() => controller.abort(), timeoutMs);
102
- try {
103
- const validation = await validateGithubToken(credentials.token, {
104
- fetchImpl,
105
- signal: controller.signal,
106
- });
107
- if (controller.signal.aborted) throw new Error(`Live model lookup timed out after ${timeoutMs}ms`);
108
- if (!validation.ok) throw new Error(validationFailure(validation));
109
-
110
- const tokenData = validation.copilotTokenData;
111
- const apiBase = parseApiBase(tokenData);
112
- const headers = buildHeaders({
113
- token: tokenData.token,
114
- version: vscodeVersion,
115
- initiator: "user",
116
- vision: false,
117
- });
118
- const response = await fetchImpl(`${apiBase}/models`, {
119
- headers,
120
- redirect: "error",
121
- signal: controller.signal,
122
- });
123
- const body = await responseText(response);
124
- if (!response.ok) {
125
- const detail = safeText(body).slice(0, 240);
126
- throw new Error(`Copilot models endpoint returned HTTP ${response.status}${detail ? `: ${detail}` : ""}`);
127
- }
128
-
129
- let payload;
130
- try {
131
- payload = JSON.parse(body);
132
- } catch {
133
- throw new Error("Copilot models endpoint returned invalid JSON");
134
- }
135
- const catalog = selectableCopilotModels(payload);
136
- let upstreamHost = "GitHub Copilot";
137
- try { upstreamHost = new URL(apiBase).hostname; } catch {}
138
- return { ...catalog, profile: AUTH_PROFILE_CODEX, upstreamHost: safeText(upstreamHost, "GitHub Copilot") };
139
- } catch (error) {
140
- if (controller.signal.aborted && !/timed out/.test(error?.message || "")) {
141
- throw new Error(`Live model lookup timed out after ${timeoutMs}ms`);
142
- }
143
- throw error;
144
- } finally {
145
- clearTimeout(timer);
146
- }
147
- }
148
-
149
19
  function liveCatalogHeader(catalog, { commandName, models }) {
150
20
  const responses = models.filter((model) => model.endpoints.includes("responses")).length;
151
21
  const chat = models.filter((model) => model.endpoints.includes("chat")).length;
package/src/cli-table.mjs CHANGED
@@ -1,4 +1,6 @@
1
- import { stripVTControlCharacters } from "node:util";
1
+ import { terminalCell } from "./terminal-text.mjs";
2
+
3
+ export { terminalCell } from "./terminal-text.mjs";
2
4
 
3
5
  const DEFAULT_TERMINAL_COLUMNS = 120;
4
6
  const COMBINING_MARK = /\p{Mark}/u;
@@ -50,16 +52,6 @@ function padded(value, width, align) {
50
52
  return align === "right" ? `${spaces}${value}` : `${value}${spaces}`;
51
53
  }
52
54
 
53
- export function terminalCell(value, { fallback = "—" } = {}) {
54
- if (value === undefined || value === null) return fallback;
55
- const text = stripVTControlCharacters(String(value))
56
- .replace(/[\u0000-\u001f\u007f-\u009f]/g, " ")
57
- .replace(/[\u061c\u200b\u200e\u200f\u202a-\u202e\u2060\u2066-\u2069\ufeff]/g, "")
58
- .replace(/\s+/g, " ")
59
- .trim();
60
- return text || fallback;
61
- }
62
-
63
55
  export function cliOutputFormat(format = "auto", output = process.stdout) {
64
56
  if (format === "table" || format === "plain") return format;
65
57
  return output?.isTTY === true ? "table" : "plain";
@@ -5,10 +5,10 @@ import {
5
5
  sanitizeImageNamespaceCollisionRequest,
6
6
  } from "./copilot-responses-policy.mjs";
7
7
  import {
8
- httpError,
9
8
  MAX_UPSTREAM_ERROR_BODY_BYTES,
10
9
  readBoundedResponseText,
11
10
  } from "./http-transport.mjs";
11
+ import { httpError } from "./http-errors.mjs";
12
12
  import {
13
13
  finalizeEncryptedHistoryRebase,
14
14
  sanitizeEncryptedReasoningRequest,
@@ -1,9 +1,9 @@
1
1
  import os from "node:os";
2
- import { fetchLiveCopilotModels } from "./cli-models.mjs";
3
- import { authStatus } from "./cli-auth.mjs";
2
+ import { fetchLiveCopilotModels } from "./live-models.mjs";
3
+ import { authStatus } from "./auth-status.mjs";
4
4
  import { redactDiagnosticText } from "./diagnostic-text.mjs";
5
5
  import { createRequestAbort, readJsonBody } from "./http-transport.mjs";
6
- import { isLoopbackAddress, isLoopbackHostHeader } from "./observability.mjs";
6
+ import { isLoopbackAddress, isLoopbackHostHeader } from "./security.mjs";
7
7
  import {
8
8
  DEFAULT_TERMINAL_ANIMATION_THEME,
9
9
  getTerminalAnimationFrameDelay,
@@ -11,7 +11,7 @@ import {
11
11
  renderTerminalAnimationFrame,
12
12
  TERMINAL_ANIMATION_THEMES,
13
13
  } from "./terminal-animation.mjs";
14
- import { cacheReadTokens, summarizeUsageLogs, usageCacheHitRate } from "./usage.mjs";
14
+ import { cacheReadTokens, summarizeUsageLogs, usageCacheHitRate } from "./usage-store.mjs";
15
15
  import { createUsageAnalytics } from "./usage-analytics.mjs";
16
16
  import { readUserSettings, terminalAnimationPreference, writeTerminalAnimationTheme } from "./user-settings.mjs";
17
17
 
package/src/dashboard.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { readFile } from "node:fs/promises";
2
- import { isLoopbackAddress, isLoopbackHostHeader } from "./observability.mjs";
2
+ import { isLoopbackAddress, isLoopbackHostHeader } from "./security.mjs";
3
3
 
4
4
  const ASSETS = new Map([
5
5
  ["/", ["./dashboard/index.html", "text/html; charset=utf-8"]],
@@ -0,0 +1,5 @@
1
+ export function httpError(message, statusCode) {
2
+ const err = new Error(message);
3
+ err.statusCode = statusCode;
4
+ return err;
5
+ }