copillm 0.2.3 → 0.2.5
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 +1 -0
- package/dist/agentconfig/apply.js +4 -3
- package/dist/agentconfig/load.js +34 -1
- package/dist/agentconfig/schema.js +22 -0
- package/dist/agents/registry.js +80 -6
- package/dist/cli/auth/ensure.js +30 -0
- package/dist/cli/auth/runAuth.js +38 -0
- package/dist/cli/commands/agents/claude.js +47 -0
- package/dist/cli/commands/agents/codex.js +48 -0
- package/dist/cli/commands/agents/copilot.js +49 -0
- package/dist/cli/commands/agents/pi.js +47 -0
- package/dist/cli/commands/agents/shared.js +28 -0
- package/dist/cli/commands/auth.js +99 -0
- package/dist/cli/commands/daemon.js +358 -0
- package/dist/cli/commands/env.js +135 -0
- package/dist/cli/commands/models.js +80 -0
- package/dist/cli/configCommands.js +10 -0
- package/dist/cli/copillmFlags.js +111 -0
- package/dist/cli/daemon/ensureRunning.js +65 -0
- package/dist/cli/daemon/lifecycle.js +61 -0
- package/dist/cli/daemon/probes.js +68 -0
- package/dist/cli/daemon/runDaemon.js +102 -0
- package/dist/cli/daemon/selfSpawn.js +15 -0
- package/dist/cli/daemon/spawnEnv.js +12 -0
- package/dist/cli/index.js +41 -0
- package/dist/cli/integrations/banner.js +51 -0
- package/dist/cli/integrations/claudeExport.js +14 -0
- package/dist/cli/integrations/refreshCodex.js +19 -0
- package/dist/cli/integrations/refreshPi.js +17 -0
- package/dist/cli/packageInfo.js +29 -0
- package/dist/cli/shared/backends.js +31 -0
- package/dist/cli/shared/debug.js +44 -0
- package/dist/cli/shared/deprecation.js +7 -0
- package/dist/cli/shared/exitCodes.js +9 -0
- package/dist/cli/shared/output.js +14 -0
- package/dist/cli/shared/parseAgent.js +6 -0
- package/dist/cli/updateNotifier.js +223 -0
- package/dist/cli.js +1 -1355
- package/dist/server/errors.js +195 -0
- package/dist/server/proxy.js +50 -885
- package/dist/server/routes/debug.js +65 -0
- package/dist/server/routes/health.js +32 -0
- package/dist/server/routes/models.js +41 -0
- package/dist/server/routes/proxyForward.js +108 -0
- package/dist/server/routes/shared.js +161 -0
- package/dist/server/upstream/copilotClient.js +137 -0
- package/dist/server/upstream/streaming.js +146 -0
- package/package.json +7 -2
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { debugLogPath } from "../../config/home.js";
|
|
2
|
+
// The root Command and root logger are constructed in cli/index.ts and
|
|
3
|
+
// registered here so any command module can resolve "is debug globally on?"
|
|
4
|
+
// without taking a Command parameter through every call. This preserves the
|
|
5
|
+
// previous behavior where helpers reached for the module-level `program` and
|
|
6
|
+
// `logger` directly.
|
|
7
|
+
let rootProgram = null;
|
|
8
|
+
let rootLogger = null;
|
|
9
|
+
export function setRootProgram(program) {
|
|
10
|
+
rootProgram = program;
|
|
11
|
+
}
|
|
12
|
+
export function setRootLogger(logger) {
|
|
13
|
+
rootLogger = logger;
|
|
14
|
+
}
|
|
15
|
+
export function getRootLogger() {
|
|
16
|
+
if (!rootLogger) {
|
|
17
|
+
throw new Error("Root logger not initialized — cli/index.ts must call setRootLogger() first.");
|
|
18
|
+
}
|
|
19
|
+
return rootLogger;
|
|
20
|
+
}
|
|
21
|
+
function getGlobalDebug() {
|
|
22
|
+
if (!rootProgram) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
return Boolean(rootProgram.opts().debug);
|
|
26
|
+
}
|
|
27
|
+
export function resolveCopillmDebug(commandDebug) {
|
|
28
|
+
return Boolean(commandDebug) || getGlobalDebug();
|
|
29
|
+
}
|
|
30
|
+
export function enableRuntimeDebug(debug) {
|
|
31
|
+
if (!debug) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
process.env.COPILLM_LOG_LEVEL = "debug";
|
|
35
|
+
if (rootLogger) {
|
|
36
|
+
rootLogger.level = "debug";
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export function currentDebugLogPath(debug) {
|
|
40
|
+
if (!debug) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
return process.env.COPILLM_LOG_FILE ?? debugLogPath();
|
|
44
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export function emitDeprecation(_opts, oldCmd, newCmd) {
|
|
2
|
+
// JSON consumers still get the deprecation on stderr so stdout stays pure;
|
|
3
|
+
// human consumers also get it on stderr. The branches are intentionally
|
|
4
|
+
// identical (preserving the original code shape) so future divergence is
|
|
5
|
+
// explicit.
|
|
6
|
+
process.stderr.write(`note: \`copillm ${oldCmd}\` is deprecated; use \`copillm ${newCmd}\`\n`);
|
|
7
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Named exit codes used by the CLI. Behavior must match the previous inline
|
|
2
|
+
// `process.exit(...)` literals exactly — these constants are documentation,
|
|
3
|
+
// not policy changes.
|
|
4
|
+
export const EXIT_OK = 0;
|
|
5
|
+
export const EXIT_ERROR = 1;
|
|
6
|
+
// Used for "not running" and "not logged in" — terminal states where the
|
|
7
|
+
// command completed successfully but the asked-about resource is absent.
|
|
8
|
+
export const EXIT_NOT_RUNNING = 2;
|
|
9
|
+
export const EXIT_NOT_LOGGED_IN = 2;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function writeCommandOutput(opts, humanLine, payload) {
|
|
2
|
+
if (opts.json) {
|
|
3
|
+
process.stdout.write(JSON.stringify(payload, null, 2) + "\n");
|
|
4
|
+
return;
|
|
5
|
+
}
|
|
6
|
+
process.stdout.write(`${humanLine}\n`);
|
|
7
|
+
}
|
|
8
|
+
export function writeHealthOutput(opts, payload) {
|
|
9
|
+
if (opts.json) {
|
|
10
|
+
process.stdout.write(JSON.stringify(payload, null, 2) + "\n");
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
process.stdout.write(`${JSON.stringify(payload)}\n`);
|
|
14
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { writeFileSecureAtomic } from "../config/fsSecurity.js";
|
|
5
|
+
import { getCopillmHome } from "../config/home.js";
|
|
6
|
+
const DEFAULT_REGISTRY_URL = "https://registry.npmjs.org";
|
|
7
|
+
const UPDATE_CHECK_TIMEOUT_MS = 3_000;
|
|
8
|
+
export async function maybeNotifyAboutUpdate(options) {
|
|
9
|
+
const argv = options.argv ?? process.argv;
|
|
10
|
+
const env = options.env ?? process.env;
|
|
11
|
+
const stderr = options.stderr ?? process.stderr;
|
|
12
|
+
const now = options.now ?? Date.now;
|
|
13
|
+
const packageInfo = options.packageInfo;
|
|
14
|
+
const cacheFile = options.cacheFilePath ?? updateCachePath();
|
|
15
|
+
if (!shouldRunUpdateCheck({ argv, env, moduleUrl: options.moduleUrl ?? import.meta.url, packageInfo, stderr })) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const cache = readUpdateCache(cacheFile, packageInfo.name);
|
|
19
|
+
const checkedAt = now();
|
|
20
|
+
const latestVersion = await fetchLatestNpmVersion(packageInfo.name, {
|
|
21
|
+
fetchImpl: options.fetchImpl,
|
|
22
|
+
registryUrl: env.COPILLM_UPDATE_REGISTRY_URL,
|
|
23
|
+
timeoutMs: UPDATE_CHECK_TIMEOUT_MS
|
|
24
|
+
});
|
|
25
|
+
if (latestVersion) {
|
|
26
|
+
writeUpdateCache(cacheFile, {
|
|
27
|
+
version: 1,
|
|
28
|
+
packageName: packageInfo.name,
|
|
29
|
+
latestVersion,
|
|
30
|
+
checkedAt
|
|
31
|
+
});
|
|
32
|
+
notifyIfNewer(stderr, packageInfo, latestVersion);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
writeUpdateCache(cacheFile, {
|
|
36
|
+
version: 1,
|
|
37
|
+
packageName: packageInfo.name,
|
|
38
|
+
latestVersion: cache?.latestVersion ?? null,
|
|
39
|
+
checkedAt
|
|
40
|
+
});
|
|
41
|
+
notifyIfNewer(stderr, packageInfo, cache?.latestVersion ?? null);
|
|
42
|
+
}
|
|
43
|
+
export async function fetchLatestNpmVersion(packageName, options = {}) {
|
|
44
|
+
const registryUrl = options.registryUrl && options.registryUrl.trim().length > 0
|
|
45
|
+
? options.registryUrl.trim()
|
|
46
|
+
: DEFAULT_REGISTRY_URL;
|
|
47
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
48
|
+
const timeoutMs = options.timeoutMs ?? UPDATE_CHECK_TIMEOUT_MS;
|
|
49
|
+
const signal = AbortSignal.timeout(timeoutMs);
|
|
50
|
+
try {
|
|
51
|
+
const response = await fetchImpl(distTagsUrl(packageName, registryUrl), {
|
|
52
|
+
headers: { accept: "application/json" },
|
|
53
|
+
signal
|
|
54
|
+
});
|
|
55
|
+
if (!response.ok) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
return latestFromDistTags(await response.json());
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export function distTagsUrl(packageName, registryUrl = DEFAULT_REGISTRY_URL) {
|
|
65
|
+
return `${registryUrl.replace(/\/+$/, "")}/-/package/${encodeURIComponent(packageName)}/dist-tags`;
|
|
66
|
+
}
|
|
67
|
+
export function isNewerVersion(candidate, current) {
|
|
68
|
+
return compareSemver(candidate, current) > 0;
|
|
69
|
+
}
|
|
70
|
+
function shouldRunUpdateCheck(opts) {
|
|
71
|
+
if (opts.stderr.isTTY !== true)
|
|
72
|
+
return false;
|
|
73
|
+
if (isTruthyCi(opts.env.CI) || isTruthyCi(opts.env.CONTINUOUS_INTEGRATION))
|
|
74
|
+
return false;
|
|
75
|
+
if (opts.env.NODE_ENV === "test")
|
|
76
|
+
return false;
|
|
77
|
+
if ("NO_UPDATE_NOTIFIER" in opts.env)
|
|
78
|
+
return false;
|
|
79
|
+
if (hasArg(opts.argv, "--no-update-notifier"))
|
|
80
|
+
return false;
|
|
81
|
+
if (hasArg(opts.argv, "--version") || hasArg(opts.argv, "-V") || hasArg(opts.argv, "--help") || hasArg(opts.argv, "-h"))
|
|
82
|
+
return false;
|
|
83
|
+
if (hasArg(opts.argv, "--json"))
|
|
84
|
+
return false;
|
|
85
|
+
if (opts.argv.slice(2).includes("daemon"))
|
|
86
|
+
return false;
|
|
87
|
+
const override = parseBooleanOverride(opts.env.COPILLM_UPDATE_CHECK);
|
|
88
|
+
if (override !== null) {
|
|
89
|
+
return override;
|
|
90
|
+
}
|
|
91
|
+
return isNpmInstalledRuntime(opts.moduleUrl, opts.packageInfo.name);
|
|
92
|
+
}
|
|
93
|
+
function isNpmInstalledRuntime(moduleUrl, packageName) {
|
|
94
|
+
let modulePath;
|
|
95
|
+
try {
|
|
96
|
+
modulePath = fileURLToPath(moduleUrl);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
const normalized = modulePath.split(path.sep).join("/").toLowerCase();
|
|
102
|
+
const marker = `/node_modules/${packageName.toLowerCase()}/`;
|
|
103
|
+
return normalized.includes(marker);
|
|
104
|
+
}
|
|
105
|
+
function updateCachePath() {
|
|
106
|
+
return path.join(getCopillmHome(), "update-check.json");
|
|
107
|
+
}
|
|
108
|
+
function readUpdateCache(filePath, packageName) {
|
|
109
|
+
if (!fs.existsSync(filePath)) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
114
|
+
return parseUpdateCache(parsed, packageName);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function writeUpdateCache(filePath, cache) {
|
|
121
|
+
try {
|
|
122
|
+
writeFileSecureAtomic(filePath, `${JSON.stringify(cache, null, 2)}\n`, 0o600);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
// Update checks are advisory and must never prevent the CLI from starting.
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function parseUpdateCache(value, packageName) {
|
|
129
|
+
if (!value || typeof value !== "object") {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
const candidate = value;
|
|
133
|
+
if (candidate.version !== 1 || candidate.packageName !== packageName) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
if (candidate.latestVersion !== null && typeof candidate.latestVersion !== "string") {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
if (typeof candidate.checkedAt !== "number" || !Number.isFinite(candidate.checkedAt)) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
version: 1,
|
|
144
|
+
packageName,
|
|
145
|
+
latestVersion: candidate.latestVersion,
|
|
146
|
+
checkedAt: candidate.checkedAt
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function latestFromDistTags(value) {
|
|
150
|
+
if (!value || typeof value !== "object") {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
const latest = value.latest;
|
|
154
|
+
return typeof latest === "string" && latest.trim().length > 0 ? latest.trim() : null;
|
|
155
|
+
}
|
|
156
|
+
function notifyIfNewer(stderr, packageInfo, latestVersion) {
|
|
157
|
+
if (!latestVersion || !isNewerVersion(latestVersion, packageInfo.version)) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
stderr.write([
|
|
161
|
+
"",
|
|
162
|
+
`copillm ${latestVersion} is available (current ${packageInfo.version}).`,
|
|
163
|
+
"Update with: npm install -g copillm",
|
|
164
|
+
"Release notes: https://github.com/jcjc-dev/copillm/releases/latest",
|
|
165
|
+
""
|
|
166
|
+
].join("\n"));
|
|
167
|
+
}
|
|
168
|
+
function hasArg(argv, arg) {
|
|
169
|
+
return argv.slice(2).includes(arg);
|
|
170
|
+
}
|
|
171
|
+
function parseBooleanOverride(value) {
|
|
172
|
+
if (value === undefined) {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
const normalized = value.trim().toLowerCase();
|
|
176
|
+
if (["1", "true", "yes", "on"].includes(normalized)) {
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
if (["0", "false", "no", "off"].includes(normalized)) {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
function isTruthyCi(value) {
|
|
185
|
+
if (value === undefined) {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
const normalized = value.trim().toLowerCase();
|
|
189
|
+
return normalized !== "" && normalized !== "0" && normalized !== "false";
|
|
190
|
+
}
|
|
191
|
+
function compareSemver(left, right) {
|
|
192
|
+
const parsedLeft = parseSemver(left);
|
|
193
|
+
const parsedRight = parseSemver(right);
|
|
194
|
+
if (!parsedLeft || !parsedRight) {
|
|
195
|
+
return 0;
|
|
196
|
+
}
|
|
197
|
+
for (let i = 0; i < 3; i += 1) {
|
|
198
|
+
const delta = parsedLeft.core[i] - parsedRight.core[i];
|
|
199
|
+
if (delta !== 0) {
|
|
200
|
+
return delta;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (parsedLeft.prerelease === parsedRight.prerelease) {
|
|
204
|
+
return 0;
|
|
205
|
+
}
|
|
206
|
+
if (parsedLeft.prerelease === null) {
|
|
207
|
+
return 1;
|
|
208
|
+
}
|
|
209
|
+
if (parsedRight.prerelease === null) {
|
|
210
|
+
return -1;
|
|
211
|
+
}
|
|
212
|
+
return parsedLeft.prerelease.localeCompare(parsedRight.prerelease);
|
|
213
|
+
}
|
|
214
|
+
function parseSemver(value) {
|
|
215
|
+
const match = value.trim().match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/);
|
|
216
|
+
if (!match) {
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
core: [Number(match[1]), Number(match[2]), Number(match[3])],
|
|
221
|
+
prerelease: match[4] ?? null
|
|
222
|
+
};
|
|
223
|
+
}
|