copilot-hub 0.1.21 → 0.1.24
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 +21 -13
- package/apps/agent-engine/dist/config.js +35 -18
- package/apps/control-plane/dist/channels/telegram-channel.js +2 -1
- package/apps/control-plane/dist/config.js +35 -18
- package/apps/control-plane/dist/copilot-hub.js +2 -2
- package/package.json +1 -2
- package/packages/core/dist/config-paths.d.ts +11 -0
- package/packages/core/dist/config-paths.js +42 -0
- package/packages/core/dist/config-paths.js.map +1 -0
- package/packages/core/dist/index.d.ts +1 -0
- package/packages/core/dist/index.js +1 -0
- package/packages/core/dist/index.js.map +1 -1
- package/packages/core/dist/telegram-channel.js +2 -1
- package/packages/core/dist/telegram-channel.js.map +1 -1
- package/packages/core/package.json +4 -0
- package/scripts/dist/cli.mjs +8 -89
- package/scripts/dist/configure.mjs +8 -9
- package/scripts/dist/daemon.mjs +41 -10
- package/scripts/dist/install-layout.mjs +140 -0
- package/scripts/dist/service.mjs +78 -11
- package/scripts/dist/supervisor.mjs +35 -8
- package/scripts/dist/windows-hidden-launcher.mjs +51 -0
- package/scripts/src/cli.mts +10 -115
- package/scripts/src/configure.mts +9 -10
- package/scripts/src/daemon.mts +48 -10
- package/scripts/src/install-layout.mts +207 -0
- package/scripts/src/service.mts +91 -11
- package/scripts/src/supervisor.mts +36 -8
- package/scripts/src/windows-hidden-launcher.mts +81 -0
- package/scripts/test/install-layout.test.mjs +82 -0
- package/scripts/test/windows-hidden-launcher.test.mjs +66 -0
package/README.md
CHANGED
|
@@ -82,7 +82,7 @@ copilot-hub start
|
|
|
82
82
|
|
|
83
83
|
`start` runs guided setup automatically if required values are missing.
|
|
84
84
|
On interactive terminals, `start` can also offer OS-native service installation when it is not yet configured.
|
|
85
|
-
`start`
|
|
85
|
+
`start` also verifies the supported Codex CLI range and can install the validated version automatically when needed.
|
|
86
86
|
|
|
87
87
|
## Quick start from source
|
|
88
88
|
|
|
@@ -161,16 +161,29 @@ Default values are already applied, and actions start from that agent workspace
|
|
|
161
161
|
|
|
162
162
|
- Never commit real bot tokens.
|
|
163
163
|
- If a token is leaked, regenerate it in `@BotFather` using `/revoke`.
|
|
164
|
-
- Keep local runtime files
|
|
164
|
+
- Keep local runtime files private.
|
|
165
165
|
|
|
166
166
|
## Startup troubleshooting
|
|
167
167
|
|
|
168
168
|
- If `npm run start` fails, first read the error and follow the suggested action.
|
|
169
169
|
- `npm run start` now checks that Codex CLI is inside the supported range and can install the validated version automatically if missing or outside that range.
|
|
170
170
|
- For Codex login issues, run `codex login` (or the configured `CODEX_BIN`) and retry `npm run start`.
|
|
171
|
-
- If auto-install is skipped or unavailable, install Codex CLI with `npm install -g @openai/codex@0.113.0` or set `CODEX_BIN` in
|
|
171
|
+
- If auto-install is skipped or unavailable, install Codex CLI with `npm install -g @openai/codex@0.113.0` or set `CODEX_BIN` in your Copilot Hub config to a binary in the supported `0.113.x` range.
|
|
172
172
|
- If you are still stuck, ask your favorite LLM with the exact error output.
|
|
173
173
|
|
|
174
|
+
## Upgrades
|
|
175
|
+
|
|
176
|
+
Use the standard npm flow:
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
npm install -g copilot-hub@latest
|
|
180
|
+
copilot-hub restart
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
If Copilot Hub is not already running, use `copilot-hub start` after the install instead of `copilot-hub restart`.
|
|
184
|
+
|
|
185
|
+
Tokens, registry data, logs and runtime state are now stored in a persistent per-user Copilot Hub directory, so they survive package upgrades. The runtime also no longer keeps the installed package directory as its working directory, so `npm install -g copilot-hub@latest` can replace the package cleanly on Windows while the service is running.
|
|
186
|
+
|
|
174
187
|
## Commands
|
|
175
188
|
|
|
176
189
|
```bash
|
|
@@ -180,19 +193,12 @@ npm run restart
|
|
|
180
193
|
npm run status
|
|
181
194
|
npm run logs
|
|
182
195
|
npm run configure
|
|
183
|
-
npm run update
|
|
184
196
|
npm run test
|
|
185
197
|
npm run lint
|
|
186
198
|
npm run format:check
|
|
187
199
|
npm run check:apps
|
|
188
200
|
```
|
|
189
201
|
|
|
190
|
-
Global update command:
|
|
191
|
-
|
|
192
|
-
```bash
|
|
193
|
-
copilot-hub update
|
|
194
|
-
```
|
|
195
|
-
|
|
196
202
|
Service mode (optional, OS-native):
|
|
197
203
|
|
|
198
204
|
```bash
|
|
@@ -238,11 +244,13 @@ Authentication options:
|
|
|
238
244
|
|
|
239
245
|
## Runtime files
|
|
240
246
|
|
|
241
|
-
-
|
|
242
|
-
-
|
|
247
|
+
- Config, tokens, registry, logs and runtime state live in the per-user Copilot Hub home directory.
|
|
248
|
+
- Windows default: `%APPDATA%\\copilot-hub`
|
|
249
|
+
- macOS default: `~/Library/Application Support/copilot-hub`
|
|
250
|
+
- Linux default: `${XDG_CONFIG_HOME:-~/.config}/copilot-hub`
|
|
243
251
|
|
|
244
252
|
## Security
|
|
245
253
|
|
|
246
254
|
- Never commit real tokens.
|
|
247
|
-
- Keep
|
|
255
|
+
- Keep your Copilot Hub home directory private.
|
|
248
256
|
- Rotate leaked tokens immediately.
|
|
@@ -4,16 +4,17 @@ import { spawnSync } from "node:child_process";
|
|
|
4
4
|
import dotenv from "dotenv";
|
|
5
5
|
import { createWorkspaceBoundaryPolicy, assertWorkspaceAllowed, parseWorkspaceAllowedRoots, } from "@copilot-hub/core/workspace-policy";
|
|
6
6
|
import { parseTurnActivityTimeoutSetting } from "@copilot-hub/core/codex-app-utils";
|
|
7
|
+
import { resolveConfigBaseDir, resolveOptionalPathFromBase, resolvePathFromBase, } from "@copilot-hub/core/config-paths";
|
|
7
8
|
import { getDefaultExternalWorkspaceBasePath, getKernelRootPath, } from "@copilot-hub/core/workspace-paths";
|
|
8
|
-
|
|
9
|
+
const envBaseDir = loadEnvironment();
|
|
9
10
|
const kernelRootPath = getKernelRootPath();
|
|
10
11
|
const configuredDefaultWorkspaceRoot = String(process.env.DEFAULT_WORKSPACE_ROOT ?? "").trim();
|
|
11
12
|
const defaultWorkspaceRoot = resolveWorkspaceRoot(configuredDefaultWorkspaceRoot || getDefaultExternalWorkspaceBasePath(kernelRootPath));
|
|
12
13
|
const configuredProjectsBaseDir = String(process.env.PROJECTS_BASE_DIR ?? "").trim();
|
|
13
|
-
const projectsBaseDir =
|
|
14
|
+
const projectsBaseDir = resolvePathFromBase(configuredProjectsBaseDir || defaultWorkspaceRoot, envBaseDir);
|
|
14
15
|
const workspaceStrictMode = parseBoolean(process.env.WORKSPACE_STRICT_MODE ?? "true");
|
|
15
16
|
const workspaceAllowedRoots = parseWorkspaceAllowedRoots(process.env.WORKSPACE_ALLOWED_ROOTS ?? "", {
|
|
16
|
-
cwd:
|
|
17
|
+
cwd: envBaseDir,
|
|
17
18
|
});
|
|
18
19
|
const workspacePolicy = createWorkspaceBoundaryPolicy({
|
|
19
20
|
kernelRootPath,
|
|
@@ -27,15 +28,19 @@ assertWorkspaceAllowed({
|
|
|
27
28
|
policy: workspacePolicy,
|
|
28
29
|
label: "DEFAULT_WORKSPACE_ROOT",
|
|
29
30
|
});
|
|
30
|
-
const dataDir =
|
|
31
|
-
const botRegistryFilePath =
|
|
32
|
-
const secretStoreFilePath =
|
|
31
|
+
const dataDir = resolvePathFromBase(process.env.BOT_DATA_DIR ?? path.join(envBaseDir, "data"), envBaseDir);
|
|
32
|
+
const botRegistryFilePath = resolvePathFromBase(process.env.BOT_REGISTRY_FILE ?? path.join(dataDir, "bot-registry.json"), envBaseDir);
|
|
33
|
+
const secretStoreFilePath = resolvePathFromBase(process.env.SECRET_STORE_FILE ?? path.join(dataDir, "secrets.json"), envBaseDir);
|
|
33
34
|
const instanceLockEnabled = parseBoolean(process.env.INSTANCE_LOCK_ENABLED ?? "true");
|
|
34
|
-
const instanceLockFilePath =
|
|
35
|
+
const instanceLockFilePath = resolvePathFromBase(process.env.INSTANCE_LOCK_FILE ?? path.join(dataDir, "runtime.lock"), envBaseDir);
|
|
35
36
|
const bootstrapTelegramToken = String(process.env.TELEGRAM_BOT_TOKEN ?? "").trim();
|
|
36
37
|
const defaultProviderKind = normalizeProviderKind(process.env.DEFAULT_PROVIDER_KIND ?? "codex");
|
|
37
38
|
const codexBin = resolveCodexBin(process.env.CODEX_BIN);
|
|
38
|
-
const codexHomeDir =
|
|
39
|
+
const codexHomeDir = resolveOptionalPathFromBase(process.env.CODEX_HOME_DIR, envBaseDir);
|
|
40
|
+
if (codexHomeDir) {
|
|
41
|
+
process.env.CODEX_HOME_DIR = codexHomeDir;
|
|
42
|
+
process.env.CODEX_HOME = codexHomeDir;
|
|
43
|
+
}
|
|
39
44
|
const codexSandbox = normalizeCodexSandbox(process.env.CODEX_SANDBOX ?? "danger-full-access");
|
|
40
45
|
const codexApprovalPolicy = normalizeApprovalPolicy(process.env.CODEX_APPROVAL_POLICY ?? "never");
|
|
41
46
|
const turnActivityTimeoutMs = parseTurnActivityTimeoutSetting(process.env.TURN_ACTIVITY_TIMEOUT_MS ?? "0", 0);
|
|
@@ -79,6 +84,7 @@ const defaultAllowedChatIds = new Set((process.env.TELEGRAM_ALLOWED_CHAT_IDS ??
|
|
|
79
84
|
.filter(Boolean));
|
|
80
85
|
fs.mkdirSync(dataDir, { recursive: true });
|
|
81
86
|
export const config = {
|
|
87
|
+
envBaseDir,
|
|
82
88
|
defaultProviderKind,
|
|
83
89
|
providerDefaults: {
|
|
84
90
|
defaultKind: defaultProviderKind,
|
|
@@ -118,6 +124,24 @@ export const config = {
|
|
|
118
124
|
defaultSharedThreadId,
|
|
119
125
|
defaultAllowedChatIds,
|
|
120
126
|
};
|
|
127
|
+
function loadEnvironment() {
|
|
128
|
+
const configuredEnvPath = String(process.env.COPILOT_HUB_ENV_PATH ?? "").trim();
|
|
129
|
+
const resolvedEnvPath = configuredEnvPath ? path.resolve(configuredEnvPath) : "";
|
|
130
|
+
const baseDir = resolveConfigBaseDir({
|
|
131
|
+
configuredBaseDir: process.env.COPILOT_HUB_ENV_BASE_DIR,
|
|
132
|
+
configuredEnvPath: resolvedEnvPath,
|
|
133
|
+
cwd: process.cwd(),
|
|
134
|
+
});
|
|
135
|
+
if (configuredEnvPath) {
|
|
136
|
+
process.env.COPILOT_HUB_ENV_PATH = resolvedEnvPath;
|
|
137
|
+
dotenv.config({ path: resolvedEnvPath });
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
dotenv.config();
|
|
141
|
+
}
|
|
142
|
+
process.env.COPILOT_HUB_ENV_BASE_DIR = baseDir;
|
|
143
|
+
return baseDir;
|
|
144
|
+
}
|
|
121
145
|
function resolveCodexBin(rawValue) {
|
|
122
146
|
const value = String(rawValue ?? "").trim();
|
|
123
147
|
const normalized = value.toLowerCase();
|
|
@@ -200,14 +224,14 @@ function spawnNpm(args) {
|
|
|
200
224
|
const comspec = process.env.ComSpec || "cmd.exe";
|
|
201
225
|
const commandLine = ["npm", ...args].join(" ");
|
|
202
226
|
return spawnSync(comspec, ["/d", "/s", "/c", commandLine], {
|
|
203
|
-
cwd:
|
|
227
|
+
cwd: envBaseDir,
|
|
204
228
|
stdio: ["ignore", "pipe", "pipe"],
|
|
205
229
|
shell: false,
|
|
206
230
|
encoding: "utf8",
|
|
207
231
|
});
|
|
208
232
|
}
|
|
209
233
|
return spawnSync("npm", args, {
|
|
210
|
-
cwd:
|
|
234
|
+
cwd: envBaseDir,
|
|
211
235
|
stdio: ["ignore", "pipe", "pipe"],
|
|
212
236
|
shell: false,
|
|
213
237
|
encoding: "utf8",
|
|
@@ -234,13 +258,6 @@ function parseBoolean(value) {
|
|
|
234
258
|
}
|
|
235
259
|
throw new Error("Invalid boolean value in environment.");
|
|
236
260
|
}
|
|
237
|
-
function resolveOptionalPath(value) {
|
|
238
|
-
const raw = String(value ?? "").trim();
|
|
239
|
-
if (!raw) {
|
|
240
|
-
return null;
|
|
241
|
-
}
|
|
242
|
-
return path.resolve(raw);
|
|
243
|
-
}
|
|
244
261
|
function normalizeCodexSandbox(value) {
|
|
245
262
|
const mode = String(value ?? "")
|
|
246
263
|
.trim()
|
|
@@ -276,5 +293,5 @@ function resolveWorkspaceRoot(value) {
|
|
|
276
293
|
if (!raw) {
|
|
277
294
|
throw new Error("DEFAULT_WORKSPACE_ROOT must not be empty.");
|
|
278
295
|
}
|
|
279
|
-
return
|
|
296
|
+
return resolvePathFromBase(raw, envBaseDir);
|
|
280
297
|
}
|
|
@@ -2,6 +2,7 @@ import fs from "node:fs/promises";
|
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { Bot } from "grammy";
|
|
5
|
+
import { resolvePathFromBase, resolveProcessConfigBaseDir } from "@copilot-hub/core/config-paths";
|
|
5
6
|
import { formatCodexQuotaLine } from "@copilot-hub/core/codex-quota-display";
|
|
6
7
|
import { maybeHandleHubOpsCallback, maybeHandleHubOpsCommand, maybeHandleHubOpsFollowUp, } from "./hub-ops-commands.js";
|
|
7
8
|
import { readCachedCodexQuotaSnapshot, writeCachedCodexQuotaSnapshot, } from "./codex-quota-cache.js";
|
|
@@ -991,7 +992,7 @@ async function readCodexAuthTokens() {
|
|
|
991
992
|
function resolveCodexHomeDir() {
|
|
992
993
|
const fromEnv = String(process.env.CODEX_HOME_DIR ?? process.env.CODEX_HOME ?? "").trim();
|
|
993
994
|
if (fromEnv) {
|
|
994
|
-
return
|
|
995
|
+
return resolvePathFromBase(fromEnv, resolveProcessConfigBaseDir());
|
|
995
996
|
}
|
|
996
997
|
return path.join(os.homedir(), ".codex");
|
|
997
998
|
}
|
|
@@ -3,18 +3,19 @@ import path from "node:path";
|
|
|
3
3
|
import { spawnSync } from "node:child_process";
|
|
4
4
|
import dotenv from "dotenv";
|
|
5
5
|
import { parseTurnActivityTimeoutSetting } from "@copilot-hub/core/codex-app-utils";
|
|
6
|
+
import { resolveConfigBaseDir, resolveOptionalPathFromBase, resolvePathFromBase, } from "@copilot-hub/core/config-paths";
|
|
6
7
|
import { createWorkspaceBoundaryPolicy, assertWorkspaceAllowed, parseWorkspaceAllowedRoots, } from "@copilot-hub/core/workspace-policy";
|
|
7
8
|
import { normalizeAdminBotId, normalizeAdminTokenEnv } from "./kernel/admin-contract.js";
|
|
8
9
|
import { getDefaultExternalWorkspaceBasePath, getKernelRootPath, } from "@copilot-hub/core/workspace-paths";
|
|
9
|
-
|
|
10
|
+
const envBaseDir = loadEnvironment();
|
|
10
11
|
const kernelRootPath = getKernelRootPath();
|
|
11
12
|
const configuredDefaultWorkspaceRoot = String(process.env.DEFAULT_WORKSPACE_ROOT ?? "").trim();
|
|
12
13
|
const defaultWorkspaceRoot = resolveWorkspaceRoot(configuredDefaultWorkspaceRoot || getDefaultExternalWorkspaceBasePath(kernelRootPath));
|
|
13
14
|
const configuredProjectsBaseDir = String(process.env.PROJECTS_BASE_DIR ?? "").trim();
|
|
14
|
-
const projectsBaseDir =
|
|
15
|
+
const projectsBaseDir = resolvePathFromBase(configuredProjectsBaseDir || defaultWorkspaceRoot, envBaseDir);
|
|
15
16
|
const workspaceStrictMode = parseBoolean(process.env.WORKSPACE_STRICT_MODE ?? "true");
|
|
16
17
|
const workspaceAllowedRoots = parseWorkspaceAllowedRoots(process.env.WORKSPACE_ALLOWED_ROOTS ?? "", {
|
|
17
|
-
cwd:
|
|
18
|
+
cwd: envBaseDir,
|
|
18
19
|
});
|
|
19
20
|
const workspacePolicy = createWorkspaceBoundaryPolicy({
|
|
20
21
|
kernelRootPath,
|
|
@@ -28,18 +29,22 @@ assertWorkspaceAllowed({
|
|
|
28
29
|
policy: workspacePolicy,
|
|
29
30
|
label: "DEFAULT_WORKSPACE_ROOT",
|
|
30
31
|
});
|
|
31
|
-
const dataDir =
|
|
32
|
-
const botRegistryFilePath =
|
|
33
|
-
const secretStoreFilePath =
|
|
32
|
+
const dataDir = resolvePathFromBase(process.env.BOT_DATA_DIR ?? path.join(envBaseDir, "data"), envBaseDir);
|
|
33
|
+
const botRegistryFilePath = resolvePathFromBase(process.env.BOT_REGISTRY_FILE ?? path.join(dataDir, "bot-registry.json"), envBaseDir);
|
|
34
|
+
const secretStoreFilePath = resolvePathFromBase(process.env.SECRET_STORE_FILE ?? path.join(dataDir, "secrets.json"), envBaseDir);
|
|
34
35
|
const adminBotId = normalizeAdminBotId(process.env.ADMIN_BOT_ID ?? "admin_agent");
|
|
35
36
|
const adminTelegramTokenEnvName = normalizeAdminTokenEnv(process.env.ADMIN_TELEGRAM_TOKEN_ENV ?? "TELEGRAM_TOKEN_ADMIN");
|
|
36
37
|
const adminTelegramToken = String(process.env[adminTelegramTokenEnvName] ?? "").trim();
|
|
37
38
|
const instanceLockEnabled = parseBoolean(process.env.INSTANCE_LOCK_ENABLED ?? "true");
|
|
38
|
-
const instanceLockFilePath =
|
|
39
|
+
const instanceLockFilePath = resolvePathFromBase(process.env.INSTANCE_LOCK_FILE ?? path.join(dataDir, "runtime.lock"), envBaseDir);
|
|
39
40
|
const bootstrapTelegramToken = String(process.env.TELEGRAM_BOT_TOKEN ?? "").trim();
|
|
40
41
|
const defaultProviderKind = normalizeProviderKind(process.env.DEFAULT_PROVIDER_KIND ?? "codex");
|
|
41
42
|
const codexBin = resolveCodexBin(process.env.CODEX_BIN);
|
|
42
|
-
const codexHomeDir =
|
|
43
|
+
const codexHomeDir = resolveOptionalPathFromBase(process.env.CODEX_HOME_DIR, envBaseDir);
|
|
44
|
+
if (codexHomeDir) {
|
|
45
|
+
process.env.CODEX_HOME_DIR = codexHomeDir;
|
|
46
|
+
process.env.CODEX_HOME = codexHomeDir;
|
|
47
|
+
}
|
|
43
48
|
const codexSandbox = normalizeCodexSandbox(process.env.CODEX_SANDBOX ?? "danger-full-access");
|
|
44
49
|
const codexApprovalPolicy = normalizeApprovalPolicy(process.env.CODEX_APPROVAL_POLICY ?? "never");
|
|
45
50
|
const turnActivityTimeoutMs = parseTurnActivityTimeoutSetting(process.env.TURN_ACTIVITY_TIMEOUT_MS ?? "0", 0);
|
|
@@ -83,6 +88,7 @@ const defaultAllowedChatIds = new Set((process.env.TELEGRAM_ALLOWED_CHAT_IDS ??
|
|
|
83
88
|
.filter(Boolean));
|
|
84
89
|
fs.mkdirSync(dataDir, { recursive: true });
|
|
85
90
|
export const config = {
|
|
91
|
+
envBaseDir,
|
|
86
92
|
defaultProviderKind,
|
|
87
93
|
providerDefaults: {
|
|
88
94
|
defaultKind: defaultProviderKind,
|
|
@@ -125,6 +131,24 @@ export const config = {
|
|
|
125
131
|
defaultSharedThreadId,
|
|
126
132
|
defaultAllowedChatIds,
|
|
127
133
|
};
|
|
134
|
+
function loadEnvironment() {
|
|
135
|
+
const configuredEnvPath = String(process.env.COPILOT_HUB_ENV_PATH ?? "").trim();
|
|
136
|
+
const resolvedEnvPath = configuredEnvPath ? path.resolve(configuredEnvPath) : "";
|
|
137
|
+
const baseDir = resolveConfigBaseDir({
|
|
138
|
+
configuredBaseDir: process.env.COPILOT_HUB_ENV_BASE_DIR,
|
|
139
|
+
configuredEnvPath: resolvedEnvPath,
|
|
140
|
+
cwd: process.cwd(),
|
|
141
|
+
});
|
|
142
|
+
if (configuredEnvPath) {
|
|
143
|
+
process.env.COPILOT_HUB_ENV_PATH = resolvedEnvPath;
|
|
144
|
+
dotenv.config({ path: resolvedEnvPath });
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
dotenv.config();
|
|
148
|
+
}
|
|
149
|
+
process.env.COPILOT_HUB_ENV_BASE_DIR = baseDir;
|
|
150
|
+
return baseDir;
|
|
151
|
+
}
|
|
128
152
|
function resolveCodexBin(rawValue) {
|
|
129
153
|
const value = String(rawValue ?? "").trim();
|
|
130
154
|
const normalized = value.toLowerCase();
|
|
@@ -207,14 +231,14 @@ function spawnNpm(args) {
|
|
|
207
231
|
const comspec = process.env.ComSpec || "cmd.exe";
|
|
208
232
|
const commandLine = ["npm", ...args].join(" ");
|
|
209
233
|
return spawnSync(comspec, ["/d", "/s", "/c", commandLine], {
|
|
210
|
-
cwd:
|
|
234
|
+
cwd: envBaseDir,
|
|
211
235
|
stdio: ["ignore", "pipe", "pipe"],
|
|
212
236
|
shell: false,
|
|
213
237
|
encoding: "utf8",
|
|
214
238
|
});
|
|
215
239
|
}
|
|
216
240
|
return spawnSync("npm", args, {
|
|
217
|
-
cwd:
|
|
241
|
+
cwd: envBaseDir,
|
|
218
242
|
stdio: ["ignore", "pipe", "pipe"],
|
|
219
243
|
shell: false,
|
|
220
244
|
encoding: "utf8",
|
|
@@ -241,13 +265,6 @@ function parseBoolean(value) {
|
|
|
241
265
|
}
|
|
242
266
|
throw new Error("Invalid boolean value in environment.");
|
|
243
267
|
}
|
|
244
|
-
function resolveOptionalPath(value) {
|
|
245
|
-
const raw = String(value ?? "").trim();
|
|
246
|
-
if (!raw) {
|
|
247
|
-
return null;
|
|
248
|
-
}
|
|
249
|
-
return path.resolve(raw);
|
|
250
|
-
}
|
|
251
268
|
function normalizeCodexSandbox(value) {
|
|
252
269
|
const mode = String(value ?? "")
|
|
253
270
|
.trim()
|
|
@@ -283,5 +300,5 @@ function resolveWorkspaceRoot(value) {
|
|
|
283
300
|
if (!raw) {
|
|
284
301
|
throw new Error("DEFAULT_WORKSPACE_ROOT must not be empty.");
|
|
285
302
|
}
|
|
286
|
-
return
|
|
303
|
+
return resolvePathFromBase(raw, envBaseDir);
|
|
287
304
|
}
|
|
@@ -21,13 +21,13 @@ if (!TELEGRAM_TOKEN_PATTERN.test(hubToken) || hubToken.toLowerCase().includes("r
|
|
|
21
21
|
const hubId = String(process.env.HUB_ID ?? "copilot_hub").trim() || "copilot_hub";
|
|
22
22
|
const hubName = String(process.env.HUB_NAME ?? "Copilot Hub").trim() || "Copilot Hub";
|
|
23
23
|
const configuredHubWorkspaceRoot = String(process.env.HUB_WORKSPACE_ROOT ?? "").trim();
|
|
24
|
-
const hubWorkspaceRootRaw = path.resolve(configuredHubWorkspaceRoot || config.defaultWorkspaceRoot);
|
|
24
|
+
const hubWorkspaceRootRaw = path.resolve(config.envBaseDir, configuredHubWorkspaceRoot || config.defaultWorkspaceRoot);
|
|
25
25
|
const hubWorkspaceRoot = assertWorkspaceAllowed({
|
|
26
26
|
workspaceRoot: hubWorkspaceRootRaw,
|
|
27
27
|
policy: config.workspacePolicy,
|
|
28
28
|
label: "HUB_WORKSPACE_ROOT",
|
|
29
29
|
});
|
|
30
|
-
const hubDataDir = path.resolve(String(process.env.HUB_DATA_DIR ?? path.join(config.dataDir, "copilot_hub")));
|
|
30
|
+
const hubDataDir = path.resolve(config.envBaseDir, String(process.env.HUB_DATA_DIR ?? path.join(config.dataDir, "copilot_hub")));
|
|
31
31
|
const hubThreadMode = normalizeThreadMode(process.env.HUB_THREAD_MODE ?? "per_chat");
|
|
32
32
|
const hubSharedThreadId = String(process.env.HUB_SHARED_THREAD_ID ?? "shared-copilot-hub").trim() || "shared-copilot-hub";
|
|
33
33
|
const allowedChatIds = parseCsvSet(process.env.HUB_ALLOWED_CHAT_IDS ?? "");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "copilot-hub",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.24",
|
|
4
4
|
"description": "Copilot Hub CLI and runtime bundle",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -58,7 +58,6 @@
|
|
|
58
58
|
"status": "npm run build:scripts --silent && node scripts/dist/cli.mjs status",
|
|
59
59
|
"logs": "npm run build:scripts --silent && node scripts/dist/cli.mjs logs",
|
|
60
60
|
"configure": "npm run build:scripts --silent && node scripts/dist/cli.mjs configure",
|
|
61
|
-
"update": "npm run build:scripts --silent && node scripts/dist/cli.mjs update",
|
|
62
61
|
"test:scripts": "npm run build:scripts --silent && node --test scripts/test/*.test.mjs",
|
|
63
62
|
"test": "npm run test:scripts && npm run test --workspaces --if-present",
|
|
64
63
|
"lint": "eslint .",
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare function resolveConfigBaseDir({ configuredBaseDir, configuredEnvPath, cwd, }?: {
|
|
2
|
+
configuredBaseDir?: string | null | undefined;
|
|
3
|
+
configuredEnvPath?: string | null | undefined;
|
|
4
|
+
cwd?: string;
|
|
5
|
+
}): string;
|
|
6
|
+
export declare function resolveProcessConfigBaseDir({ env, cwd, }?: {
|
|
7
|
+
env?: NodeJS.ProcessEnv;
|
|
8
|
+
cwd?: string;
|
|
9
|
+
}): string;
|
|
10
|
+
export declare function resolvePathFromBase(value: unknown, baseDir: string): string;
|
|
11
|
+
export declare function resolveOptionalPathFromBase(value: unknown, baseDir: string): string | null;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
export function resolveConfigBaseDir({ configuredBaseDir, configuredEnvPath, cwd = process.cwd(), } = {}) {
|
|
4
|
+
const explicitBaseDir = normalizePath(configuredBaseDir);
|
|
5
|
+
if (explicitBaseDir) {
|
|
6
|
+
return explicitBaseDir;
|
|
7
|
+
}
|
|
8
|
+
const envPath = normalizePath(configuredEnvPath);
|
|
9
|
+
if (envPath) {
|
|
10
|
+
return path.dirname(envPath);
|
|
11
|
+
}
|
|
12
|
+
return path.resolve(cwd);
|
|
13
|
+
}
|
|
14
|
+
export function resolveProcessConfigBaseDir({ env = process.env, cwd = process.cwd(), } = {}) {
|
|
15
|
+
return resolveConfigBaseDir({
|
|
16
|
+
configuredBaseDir: env.COPILOT_HUB_ENV_BASE_DIR,
|
|
17
|
+
configuredEnvPath: env.COPILOT_HUB_ENV_PATH,
|
|
18
|
+
cwd,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
export function resolvePathFromBase(value, baseDir) {
|
|
22
|
+
const raw = String(value ?? "").trim();
|
|
23
|
+
if (!raw) {
|
|
24
|
+
throw new Error("Path value must not be empty.");
|
|
25
|
+
}
|
|
26
|
+
return path.resolve(baseDir, raw);
|
|
27
|
+
}
|
|
28
|
+
export function resolveOptionalPathFromBase(value, baseDir) {
|
|
29
|
+
const raw = String(value ?? "").trim();
|
|
30
|
+
if (!raw) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
return resolvePathFromBase(raw, baseDir);
|
|
34
|
+
}
|
|
35
|
+
function normalizePath(value) {
|
|
36
|
+
const raw = String(value ?? "").trim();
|
|
37
|
+
if (!raw) {
|
|
38
|
+
return "";
|
|
39
|
+
}
|
|
40
|
+
return path.resolve(raw);
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=config-paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-paths.js","sourceRoot":"","sources":["../src/config-paths.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,OAAO,MAAM,cAAc,CAAC;AAEnC,MAAM,UAAU,oBAAoB,CAAC,EACnC,iBAAiB,EACjB,iBAAiB,EACjB,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,MAKjB,EAAE;IACJ,MAAM,eAAe,GAAG,aAAa,CAAC,iBAAiB,CAAC,CAAC;IACzD,IAAI,eAAe,EAAE,CAAC;QACpB,OAAO,eAAe,CAAC;IACzB,CAAC;IAED,MAAM,OAAO,GAAG,aAAa,CAAC,iBAAiB,CAAC,CAAC;IACjD,IAAI,OAAO,EAAE,CAAC;QACZ,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/B,CAAC;IAED,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,2BAA2B,CAAC,EAC1C,GAAG,GAAG,OAAO,CAAC,GAAG,EACjB,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,MAIjB,EAAE;IACJ,OAAO,oBAAoB,CAAC;QAC1B,iBAAiB,EAAE,GAAG,CAAC,wBAAwB;QAC/C,iBAAiB,EAAE,GAAG,CAAC,oBAAoB;QAC3C,GAAG;KACJ,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,KAAc,EAAE,OAAe;IACjE,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACvC,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;IACnD,CAAC;IACD,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;AACpC,CAAC;AAED,MAAM,UAAU,2BAA2B,CAAC,KAAc,EAAE,OAAe;IACzE,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACvC,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,mBAAmB,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AAC3C,CAAC;AAED,SAAS,aAAa,CAAC,KAAc;IACnC,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACvC,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;AAC3B,CAAC"}
|
|
@@ -2,6 +2,7 @@ export { createWorkspaceBoundaryPolicy, assertWorkspaceAllowed, isPathInside, no
|
|
|
2
2
|
export { normalizeThreadId } from "./thread-id.js";
|
|
3
3
|
export { createProjectFingerprint } from "./project-fingerprint.js";
|
|
4
4
|
export { DEFAULT_EXTERNAL_WORKSPACES_DIRNAME, getKernelRootPath, getDefaultExternalWorkspaceBasePath, resolveDefaultWorkspaceForBot, isPathInside as isWorkspacePathInside, } from "./workspace-paths.js";
|
|
5
|
+
export { resolveConfigBaseDir, resolveProcessConfigBaseDir, resolvePathFromBase, resolveOptionalPathFromBase, } from "./config-paths.js";
|
|
5
6
|
export { InstanceLock } from "./instance-lock.js";
|
|
6
7
|
export { JsonStateStore } from "./state-store.js";
|
|
7
8
|
export { assertControlPermission } from "./control-permission.js";
|
|
@@ -2,6 +2,7 @@ export { createWorkspaceBoundaryPolicy, assertWorkspaceAllowed, isPathInside, no
|
|
|
2
2
|
export { normalizeThreadId } from "./thread-id.js";
|
|
3
3
|
export { createProjectFingerprint } from "./project-fingerprint.js";
|
|
4
4
|
export { DEFAULT_EXTERNAL_WORKSPACES_DIRNAME, getKernelRootPath, getDefaultExternalWorkspaceBasePath, resolveDefaultWorkspaceForBot, isPathInside as isWorkspacePathInside, } from "./workspace-paths.js";
|
|
5
|
+
export { resolveConfigBaseDir, resolveProcessConfigBaseDir, resolvePathFromBase, resolveOptionalPathFromBase, } from "./config-paths.js";
|
|
5
6
|
export { InstanceLock } from "./instance-lock.js";
|
|
6
7
|
export { JsonStateStore } from "./state-store.js";
|
|
7
8
|
export { assertControlPermission } from "./control-permission.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,6BAA6B,EAC7B,sBAAsB,EACtB,YAAY,EACZ,qBAAqB,EACrB,0BAA0B,GAC3B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,EACL,mCAAmC,EACnC,iBAAiB,EACjB,mCAAmC,EACnC,6BAA6B,EAC7B,YAAY,IAAI,qBAAqB,GACtC,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,6BAA6B,GAC9B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC9E,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EACL,eAAe,EACf,sBAAsB,EACtB,eAAe,EACf,oBAAoB,GACrB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,0BAA0B,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAC3F,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,6BAA6B,EAC7B,sBAAsB,EACtB,YAAY,EACZ,qBAAqB,EACrB,0BAA0B,GAC3B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,EACL,mCAAmC,EACnC,iBAAiB,EACjB,mCAAmC,EACnC,6BAA6B,EAC7B,YAAY,IAAI,qBAAqB,GACtC,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,oBAAoB,EACpB,2BAA2B,EAC3B,mBAAmB,EACnB,2BAA2B,GAC5B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,6BAA6B,GAC9B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC9E,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EACL,eAAe,EACf,sBAAsB,EACtB,eAAe,EACf,oBAAoB,GACrB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,0BAA0B,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAC3F,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC"}
|
|
@@ -2,6 +2,7 @@ import fs from "node:fs/promises";
|
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { Bot } from "grammy";
|
|
5
|
+
import { resolvePathFromBase, resolveProcessConfigBaseDir } from "./config-paths.js";
|
|
5
6
|
import { formatCodexQuotaLine, hasCodexQuotaWindows } from "./codex-quota-display.js";
|
|
6
7
|
const CODEX_USAGE_CACHE_TTL_MS = 60_000;
|
|
7
8
|
let cachedCodexUsage = null;
|
|
@@ -1194,7 +1195,7 @@ async function readCodexAuthTokens() {
|
|
|
1194
1195
|
function resolveCodexHomeDir() {
|
|
1195
1196
|
const fromEnv = String(process.env.CODEX_HOME_DIR ?? process.env.CODEX_HOME ?? "").trim();
|
|
1196
1197
|
if (fromEnv) {
|
|
1197
|
-
return
|
|
1198
|
+
return resolvePathFromBase(fromEnv, resolveProcessConfigBaseDir());
|
|
1198
1199
|
}
|
|
1199
1200
|
return path.join(os.homedir(), ".codex");
|
|
1200
1201
|
}
|