codex-copilot-dx 0.6.21 → 0.7.1
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 +142 -323
- package/bin/cli.mjs +26 -130
- package/package.json +2 -8
- package/scripts/benchmark.mjs +0 -114
- package/src/adapter.mjs +22 -107
- package/src/auth-profile.mjs +18 -130
- package/src/cli-animation.mjs +108 -0
- package/src/cli-auth.mjs +44 -398
- package/src/cli-models.mjs +21 -49
- package/src/cli-options.mjs +53 -75
- package/src/cli-status.mjs +6 -23
- package/src/config.mjs +2 -8
- package/src/copilot.mjs +2 -2
- package/src/doctor.mjs +44 -291
- package/src/model-cache.mjs +4 -11
- package/src/models.mjs +0 -184
- package/src/observability.mjs +1 -38
- package/src/profile-model-runtime.mjs +61 -189
- package/src/profile-routing.mjs +2 -102
- package/src/profile-runtime.mjs +15 -68
- package/src/responses-handler.mjs +1 -1
- package/src/responses-proxy.mjs +1 -1
- package/src/running-adapter.mjs +2 -4
- package/src/stream-errors.mjs +3 -6
- package/src/stream-performance.mjs +1 -1
- package/src/terminal-activity.mjs +40 -34
- package/src/terminal-animation.mjs +249 -0
- package/src/usage.mjs +0 -31
- package/src/user-settings.mjs +50 -0
- package/src/anthropic-handler.mjs +0 -195
- package/src/anthropic.mjs +0 -387
- package/src/claude-config.mjs +0 -57
- package/src/claude-desktop-config.mjs +0 -317
- package/src/cli-error.mjs +0 -50
- package/src/pm-studio-asar.mjs +0 -1244
- package/src/pm-studio-bundle-content.mjs +0 -165
- package/src/pm-studio-relay.mjs +0 -563
- package/src/pm-studio-setup-lock.mjs +0 -336
- package/src/pm-studio-setup.mjs +0 -1581
- package/src/pm-studio-status.mjs +0 -369
- package/src/token-counter-service.mjs +0 -211
- package/src/token-counter-worker.mjs +0 -16
|
@@ -1,317 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import os from "node:os";
|
|
4
|
-
import { randomUUID } from "node:crypto";
|
|
5
|
-
import { claudeDesktopModelIds } from "./models.mjs";
|
|
6
|
-
import { status } from "./status.mjs";
|
|
7
|
-
import { atomicWriteFileIfChangedSync, atomicWriteFileSync } from "./atomic-file.mjs";
|
|
8
|
-
|
|
9
|
-
const CONFIG_FILE = "claude_desktop_config.json";
|
|
10
|
-
const CONFIG_LIBRARY_DIR = "configLibrary";
|
|
11
|
-
export const DEFAULT_CLAUDE_DESKTOP_PROFILE_ID = "00000000-0000-4000-8000-000000202600";
|
|
12
|
-
export const DEFAULT_CLAUDE_DESKTOP_PROFILE_NAME = "Codex Copilot DX";
|
|
13
|
-
|
|
14
|
-
function jsonClone(value) {
|
|
15
|
-
return value === undefined ? undefined : JSON.parse(JSON.stringify(value));
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function valueOrDefault(value, fallback) {
|
|
19
|
-
const trimmed = String(value || "").trim();
|
|
20
|
-
return trimmed || fallback;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function isLoopbackHost(host) {
|
|
24
|
-
const normalized = String(host || "").toLowerCase().replace(/^\[(.*)\]$/, "$1");
|
|
25
|
-
return ["127.0.0.1", "localhost", "::1"].includes(normalized);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function localGatewayBaseUrl(host, port) {
|
|
29
|
-
const safeHost = String(host || "127.0.0.1");
|
|
30
|
-
const urlHost = safeHost.includes(":") && !safeHost.startsWith("[") ? `[${safeHost}]` : safeHost;
|
|
31
|
-
return `http://${urlHost}:${port}`;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function claudeDesktopPaths(home = os.homedir(), platform = process.platform, env = process.env) {
|
|
35
|
-
if (platform === "win32") {
|
|
36
|
-
const localAppData = valueOrDefault(env.LOCALAPPDATA, path.join(home, "AppData", "Local"));
|
|
37
|
-
return pathsFromDirs(path.join(localAppData, "Claude"), path.join(localAppData, "Claude-3p"));
|
|
38
|
-
}
|
|
39
|
-
const appSupport = path.join(home, "Library", "Application Support");
|
|
40
|
-
return pathsFromDirs(path.join(appSupport, "Claude"), path.join(appSupport, "Claude-3p"));
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function pathsFromDirs(normalDir, threepDir) {
|
|
44
|
-
const configLibraryPath = path.join(threepDir, CONFIG_LIBRARY_DIR);
|
|
45
|
-
return {
|
|
46
|
-
normalConfigPath: path.join(normalDir, CONFIG_FILE),
|
|
47
|
-
threepConfigPath: path.join(threepDir, CONFIG_FILE),
|
|
48
|
-
configLibraryPath,
|
|
49
|
-
metaPath: path.join(configLibraryPath, "_meta.json"),
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function validateClaudeDesktopBaseUrl(raw) {
|
|
54
|
-
let parsed;
|
|
55
|
-
try {
|
|
56
|
-
parsed = new URL(raw);
|
|
57
|
-
} catch {
|
|
58
|
-
throw new Error(`Claude App gateway base URL is not valid: ${raw}`);
|
|
59
|
-
}
|
|
60
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
61
|
-
throw new Error("Claude App gateway base URL must use http or https");
|
|
62
|
-
}
|
|
63
|
-
if (parsed.protocol === "http:" && !isLoopbackHost(parsed.hostname)) {
|
|
64
|
-
throw new Error("Claude App allows http only for loopback; use https for LAN or remote gateways");
|
|
65
|
-
}
|
|
66
|
-
if (parsed.pathname !== "/" || parsed.search || parsed.hash) {
|
|
67
|
-
throw new Error("Claude App gateway base URL must be the root URL without /v1, query, or hash");
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function isGatewayProfileField(key) {
|
|
72
|
-
return [
|
|
73
|
-
"inferenceProvider",
|
|
74
|
-
"inferenceGatewayBaseUrl",
|
|
75
|
-
"inferenceGatewayApiKey",
|
|
76
|
-
"inferenceGatewayAuthScheme",
|
|
77
|
-
"inferenceModels",
|
|
78
|
-
].includes(key);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export function computeClaudeDesktopProfile(sourceProfile = {}, {
|
|
82
|
-
baseUrl,
|
|
83
|
-
gatewayApiKey,
|
|
84
|
-
modelIds = claudeDesktopModelIds(),
|
|
85
|
-
} = {}) {
|
|
86
|
-
validateClaudeDesktopBaseUrl(baseUrl);
|
|
87
|
-
if (!String(gatewayApiKey || "").trim()) {
|
|
88
|
-
throw new Error("Claude App gateway API key is required");
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const profile = {};
|
|
92
|
-
for (const [key, value] of Object.entries(sourceProfile || {})) {
|
|
93
|
-
if (key === "enterpriseConfig" || isGatewayProfileField(key)) continue;
|
|
94
|
-
profile[key] = jsonClone(value);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
profile.inferenceProvider = "gateway";
|
|
98
|
-
profile.inferenceGatewayBaseUrl = baseUrl;
|
|
99
|
-
profile.inferenceGatewayApiKey = gatewayApiKey;
|
|
100
|
-
profile.inferenceGatewayAuthScheme = "bearer";
|
|
101
|
-
profile.inferenceModels = JSON.stringify(modelIds);
|
|
102
|
-
if (!profile.disableDeploymentModeChooser) profile.disableDeploymentModeChooser = "true";
|
|
103
|
-
return profile;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export function computeClaudeDesktopMeta(meta = {}, profileId = DEFAULT_CLAUDE_DESKTOP_PROFILE_ID, profileName = DEFAULT_CLAUDE_DESKTOP_PROFILE_NAME) {
|
|
107
|
-
const out = { ...(meta || {}) };
|
|
108
|
-
const entries = Array.isArray(out.entries) ? out.entries : [];
|
|
109
|
-
out.entries = [
|
|
110
|
-
...entries.filter((entry) => entry?.id !== profileId),
|
|
111
|
-
{ id: profileId, name: profileName },
|
|
112
|
-
];
|
|
113
|
-
out.appliedId = profileId;
|
|
114
|
-
return out;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
export function computeDeploymentConfig(config = {}) {
|
|
118
|
-
return { ...(config || {}), deploymentMode: "3p" };
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function readJsonFile(filePath, fallback = {}) {
|
|
122
|
-
try {
|
|
123
|
-
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
124
|
-
} catch (e) {
|
|
125
|
-
if (e?.code === "ENOENT") return fallback;
|
|
126
|
-
throw e;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function writeJsonFile(filePath, data) {
|
|
131
|
-
return atomicWriteFileIfChangedSync(filePath, JSON.stringify(data, null, 2) + "\n", { mode: 0o600 });
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function snapshotFiles(files) {
|
|
135
|
-
return files.map((filePath) => {
|
|
136
|
-
try {
|
|
137
|
-
return { filePath, exists: true, data: fs.readFileSync(filePath) };
|
|
138
|
-
} catch (e) {
|
|
139
|
-
if (e?.code === "ENOENT") return { filePath, exists: false };
|
|
140
|
-
throw e;
|
|
141
|
-
}
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function restoreSnapshots(snapshots) {
|
|
146
|
-
for (const snapshot of snapshots) {
|
|
147
|
-
if (!snapshot.exists) {
|
|
148
|
-
fs.rmSync(snapshot.filePath, { force: true });
|
|
149
|
-
continue;
|
|
150
|
-
}
|
|
151
|
-
atomicWriteFileSync(snapshot.filePath, snapshot.data, { mode: 0o600 });
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function readActiveProfile(paths) {
|
|
156
|
-
const meta = readJsonFile(paths.metaPath, {});
|
|
157
|
-
const appliedId = String(meta.appliedId || "").trim();
|
|
158
|
-
if (!appliedId) return {};
|
|
159
|
-
return readJsonFile(path.join(paths.configLibraryPath, `${appliedId}.json`), {});
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function activeManagedProfile({
|
|
163
|
-
port = 2026,
|
|
164
|
-
host = "127.0.0.1",
|
|
165
|
-
home = os.homedir(),
|
|
166
|
-
platform = process.platform,
|
|
167
|
-
env = process.env,
|
|
168
|
-
} = {}) {
|
|
169
|
-
const paths = claudeDesktopPaths(home, platform, env);
|
|
170
|
-
const meta = readJsonFile(paths.metaPath, {});
|
|
171
|
-
if (String(meta.appliedId || "").trim() !== DEFAULT_CLAUDE_DESKTOP_PROFILE_ID) return null;
|
|
172
|
-
|
|
173
|
-
const profilePath = path.join(paths.configLibraryPath, `${DEFAULT_CLAUDE_DESKTOP_PROFILE_ID}.json`);
|
|
174
|
-
const profile = readJsonFile(profilePath, {});
|
|
175
|
-
if (profile.inferenceProvider !== "gateway") return null;
|
|
176
|
-
if (String(profile.inferenceGatewayAuthScheme || "").trim().toLowerCase() !== "bearer") return null;
|
|
177
|
-
|
|
178
|
-
const profileBaseUrl = String(profile.inferenceGatewayBaseUrl || "").trim();
|
|
179
|
-
validateClaudeDesktopBaseUrl(profileBaseUrl);
|
|
180
|
-
if (new URL(profileBaseUrl).href !== new URL(localGatewayBaseUrl(host, port)).href) return null;
|
|
181
|
-
|
|
182
|
-
return { profilePath, profile };
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
function normalizedModelIds(modelIds) {
|
|
186
|
-
const ids = [];
|
|
187
|
-
const seen = new Set();
|
|
188
|
-
for (const value of Array.isArray(modelIds) ? modelIds : []) {
|
|
189
|
-
const id = String(value || "").trim();
|
|
190
|
-
if (!id || seen.has(id)) continue;
|
|
191
|
-
ids.push(id);
|
|
192
|
-
seen.add(id);
|
|
193
|
-
}
|
|
194
|
-
return ids;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
export function loadManagedClaudeDesktopApiKey({
|
|
198
|
-
port = 2026,
|
|
199
|
-
host = "127.0.0.1",
|
|
200
|
-
home = os.homedir(),
|
|
201
|
-
platform = process.platform,
|
|
202
|
-
env = process.env,
|
|
203
|
-
} = {}) {
|
|
204
|
-
try {
|
|
205
|
-
const managed = activeManagedProfile({ port, host, home, platform, env });
|
|
206
|
-
const apiKey = String(managed?.profile.inferenceGatewayApiKey || "").trim();
|
|
207
|
-
return apiKey && apiKey !== "dummy" ? apiKey : "";
|
|
208
|
-
} catch {
|
|
209
|
-
return "";
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
export function syncManagedClaudeDesktopModels({
|
|
214
|
-
modelIds,
|
|
215
|
-
port = 2026,
|
|
216
|
-
host = "127.0.0.1",
|
|
217
|
-
home = os.homedir(),
|
|
218
|
-
platform = process.platform,
|
|
219
|
-
env = process.env,
|
|
220
|
-
} = {}) {
|
|
221
|
-
const normalizedIds = normalizedModelIds(modelIds);
|
|
222
|
-
if (!normalizedIds.length) return { updated: false, reason: "empty-model-list", modelIds: normalizedIds };
|
|
223
|
-
|
|
224
|
-
try {
|
|
225
|
-
const managed = activeManagedProfile({ port, host, home, platform, env });
|
|
226
|
-
if (!managed) return { updated: false, reason: "not-managed", modelIds: normalizedIds };
|
|
227
|
-
|
|
228
|
-
const inferenceModels = JSON.stringify(normalizedIds);
|
|
229
|
-
if (managed.profile.inferenceModels === inferenceModels) {
|
|
230
|
-
return { updated: false, reason: "unchanged", profilePath: managed.profilePath, modelIds: normalizedIds };
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
const updated = writeJsonFile(managed.profilePath, {
|
|
234
|
-
...managed.profile,
|
|
235
|
-
inferenceModels,
|
|
236
|
-
});
|
|
237
|
-
return {
|
|
238
|
-
updated,
|
|
239
|
-
reason: updated ? "updated" : "unchanged",
|
|
240
|
-
profilePath: managed.profilePath,
|
|
241
|
-
modelIds: normalizedIds,
|
|
242
|
-
};
|
|
243
|
-
} catch (error) {
|
|
244
|
-
return { updated: false, reason: "error", error, modelIds: normalizedIds };
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
export function applyClaudeDesktopConfig({
|
|
249
|
-
port = 2026,
|
|
250
|
-
host = "127.0.0.1",
|
|
251
|
-
home = os.homedir(),
|
|
252
|
-
platform = process.platform,
|
|
253
|
-
env = process.env,
|
|
254
|
-
baseUrl = localGatewayBaseUrl(host, port),
|
|
255
|
-
gatewayApiKey = env.CCDX_CLAUDE_DESKTOP_API_KEY || env.CCDX_PROXY_API_KEY || "dummy",
|
|
256
|
-
modelIds = claudeDesktopModelIds(env),
|
|
257
|
-
profileId = DEFAULT_CLAUDE_DESKTOP_PROFILE_ID,
|
|
258
|
-
profileName = DEFAULT_CLAUDE_DESKTOP_PROFILE_NAME,
|
|
259
|
-
} = {}) {
|
|
260
|
-
const paths = claudeDesktopPaths(home, platform, env);
|
|
261
|
-
const profilePath = path.join(paths.configLibraryPath, `${profileId}.json`);
|
|
262
|
-
const snapshots = snapshotFiles([
|
|
263
|
-
paths.normalConfigPath,
|
|
264
|
-
paths.threepConfigPath,
|
|
265
|
-
paths.metaPath,
|
|
266
|
-
profilePath,
|
|
267
|
-
]);
|
|
268
|
-
|
|
269
|
-
try {
|
|
270
|
-
const sourceProfile = readActiveProfile(paths);
|
|
271
|
-
const normalConfig = readJsonFile(paths.normalConfigPath, {});
|
|
272
|
-
const threepConfig = readJsonFile(paths.threepConfigPath, {});
|
|
273
|
-
const meta = readJsonFile(paths.metaPath, {});
|
|
274
|
-
|
|
275
|
-
writeJsonFile(paths.normalConfigPath, computeDeploymentConfig(normalConfig));
|
|
276
|
-
writeJsonFile(paths.threepConfigPath, computeDeploymentConfig(threepConfig));
|
|
277
|
-
writeJsonFile(profilePath, computeClaudeDesktopProfile(sourceProfile, { baseUrl, gatewayApiKey, modelIds }));
|
|
278
|
-
writeJsonFile(paths.metaPath, computeClaudeDesktopMeta(meta, profileId, profileName));
|
|
279
|
-
} catch (e) {
|
|
280
|
-
restoreSnapshots(snapshots);
|
|
281
|
-
throw e;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
return {
|
|
285
|
-
paths,
|
|
286
|
-
profileId,
|
|
287
|
-
profilePath,
|
|
288
|
-
baseUrl,
|
|
289
|
-
modelIds,
|
|
290
|
-
};
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
export function formatClaudeDesktopApplyResult(result) {
|
|
294
|
-
return [
|
|
295
|
-
"Claude App local gateway profile",
|
|
296
|
-
`profile id: ${result.profileId}`,
|
|
297
|
-
`profile path: ${result.profilePath}`,
|
|
298
|
-
`base url: ${result.baseUrl}`,
|
|
299
|
-
`models: ${result.modelIds.join(", ")}`,
|
|
300
|
-
"gateway api key: <redacted>",
|
|
301
|
-
].join("\n");
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
export function ensureClaudeDesktopConfig(port = 2026, host = "127.0.0.1") {
|
|
305
|
-
const result = applyClaudeDesktopConfig({
|
|
306
|
-
port,
|
|
307
|
-
host,
|
|
308
|
-
gatewayApiKey: process.env.CCDX_CLAUDE_DESKTOP_API_KEY || process.env.CCDX_PROXY_API_KEY || generatedClaudeDesktopApiKey(),
|
|
309
|
-
});
|
|
310
|
-
console.log(status("ok", `Configured Claude App gateway profile at ${result.baseUrl}`));
|
|
311
|
-
console.log(formatClaudeDesktopApplyResult(result));
|
|
312
|
-
return result;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
export function generatedClaudeDesktopApiKey() {
|
|
316
|
-
return `ccdx_${randomUUID().replaceAll("-", "")}`;
|
|
317
|
-
}
|
package/src/cli-error.mjs
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { terminalCell } from "./cli-table.mjs";
|
|
2
|
-
import { status } from "./status.mjs";
|
|
3
|
-
|
|
4
|
-
const MAX_MESSAGE_CHARACTERS = 1024;
|
|
5
|
-
const MAX_DETAIL_CHARACTERS = 512;
|
|
6
|
-
const MAX_DETAILS = 16;
|
|
7
|
-
|
|
8
|
-
function redactSensitiveValues(value) {
|
|
9
|
-
return value
|
|
10
|
-
.replace(/\b(authorization\s*[:=]\s*)(?:bearer\s+)?[^\s,;]+/gi, "$1[REDACTED]")
|
|
11
|
-
.replace(/\bbearer\s+[^\s,;]+/gi, "Bearer [REDACTED]")
|
|
12
|
-
.replace(/\b((?:api[-_ ]?key|access[-_ ]?token|refresh[-_ ]?token|password|secret)\s*[:=]\s*)[^\s,;]+/gi, "$1[REDACTED]")
|
|
13
|
-
.replace(/\b(?:github_pat_[A-Za-z0-9_]{8,}|gh[pousr]_[A-Za-z0-9_]{8,}|sk-[A-Za-z0-9_-]{8,})\b/g, "[REDACTED]");
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function boundedTerminalText(value, fallback, maxCharacters) {
|
|
17
|
-
try {
|
|
18
|
-
return redactSensitiveValues(terminalCell(value, { fallback })).slice(0, maxCharacters);
|
|
19
|
-
} catch {
|
|
20
|
-
return fallback;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function formatCliError(error) {
|
|
25
|
-
let rawMessage;
|
|
26
|
-
try {
|
|
27
|
-
rawMessage = error?.message;
|
|
28
|
-
} catch {
|
|
29
|
-
rawMessage = undefined;
|
|
30
|
-
}
|
|
31
|
-
const message = boundedTerminalText(rawMessage, "Command failed", MAX_MESSAGE_CHARACTERS);
|
|
32
|
-
let details;
|
|
33
|
-
try {
|
|
34
|
-
details = error?.details;
|
|
35
|
-
} catch {
|
|
36
|
-
details = undefined;
|
|
37
|
-
}
|
|
38
|
-
const values = details === undefined
|
|
39
|
-
? []
|
|
40
|
-
: Array.isArray(details) ? details : [details];
|
|
41
|
-
const visible = values.slice(0, MAX_DETAILS);
|
|
42
|
-
const lines = [status("err", message)];
|
|
43
|
-
for (const detail of visible) {
|
|
44
|
-
lines.push(status("info", boundedTerminalText(detail, "Detail unavailable", MAX_DETAIL_CHARACTERS)));
|
|
45
|
-
}
|
|
46
|
-
if (values.length > visible.length) {
|
|
47
|
-
lines.push(status("info", `${values.length - visible.length} additional detail(s) omitted`));
|
|
48
|
-
}
|
|
49
|
-
return lines.join("\n");
|
|
50
|
-
}
|