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 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` and `update` also verify the supported Codex CLI range and can install the validated version automatically when needed.
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 (`data/`, `logs/`) private.
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 `.env` to a binary in the supported `0.113.x` range.
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
- - PIDs: `.copilot-hub/pids/`
242
- - Logs: `logs/`
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 `.env` and runtime data local.
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
- dotenv.config();
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 = path.resolve(configuredProjectsBaseDir || defaultWorkspaceRoot);
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: process.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 = path.resolve(process.env.BOT_DATA_DIR ?? path.join(process.cwd(), "data"));
31
- const botRegistryFilePath = path.resolve(process.env.BOT_REGISTRY_FILE ?? path.join(dataDir, "bot-registry.json"));
32
- const secretStoreFilePath = path.resolve(process.env.SECRET_STORE_FILE ?? path.join(dataDir, "secrets.json"));
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 = path.resolve(process.env.INSTANCE_LOCK_FILE ?? path.join(dataDir, "runtime.lock"));
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 = resolveOptionalPath(process.env.CODEX_HOME_DIR);
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: process.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: process.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 path.resolve(raw);
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 path.resolve(fromEnv);
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
- dotenv.config();
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 = path.resolve(configuredProjectsBaseDir || defaultWorkspaceRoot);
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: process.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 = path.resolve(process.env.BOT_DATA_DIR ?? path.join(process.cwd(), "data"));
32
- const botRegistryFilePath = path.resolve(process.env.BOT_REGISTRY_FILE ?? path.join(dataDir, "bot-registry.json"));
33
- const secretStoreFilePath = path.resolve(process.env.SECRET_STORE_FILE ?? path.join(dataDir, "secrets.json"));
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 = path.resolve(process.env.INSTANCE_LOCK_FILE ?? path.join(dataDir, "runtime.lock"));
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 = resolveOptionalPath(process.env.CODEX_HOME_DIR);
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: process.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: process.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 path.resolve(raw);
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.21",
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 path.resolve(fromEnv);
1198
+ return resolvePathFromBase(fromEnv, resolveProcessConfigBaseDir());
1198
1199
  }
1199
1200
  return path.join(os.homedir(), ".codex");
1200
1201
  }