copilot-hub 0.1.28 → 0.1.30
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/apps/agent-engine/.env.example +6 -8
- package/apps/agent-engine/dist/config.js +18 -17
- package/apps/control-plane/.env.example +4 -4
- package/apps/control-plane/dist/config.js +19 -17
- package/apps/control-plane/dist/copilot-hub.js +16 -4
- package/apps/control-plane/dist/hub-token-config.js +37 -0
- package/apps/control-plane/dist/test/hub-token-config.test.js +46 -0
- package/package.json +2 -1
- package/packages/core/dist/env-config.d.ts +11 -0
- package/packages/core/dist/env-config.js +70 -0
- package/packages/core/dist/env-config.js.map +1 -0
- package/packages/core/dist/index.d.ts +2 -1
- package/packages/core/dist/index.js +2 -1
- package/packages/core/dist/index.js.map +1 -1
- package/packages/core/dist/workspace-paths.d.ts +12 -1
- package/packages/core/dist/workspace-paths.js +30 -6
- package/packages/core/dist/workspace-paths.js.map +1 -1
- package/packages/core/package.json +4 -0
- package/scripts/dist/cli.mjs +52 -1
- package/scripts/dist/configure.mjs +26 -68
- package/scripts/dist/daemon.mjs +17 -0
- package/scripts/dist/env-file-utils.mjs +83 -0
- package/scripts/dist/install-layout.mjs +130 -1
- package/scripts/dist/supervisor.mjs +5 -0
- package/scripts/src/cli.mts +65 -1
- package/scripts/src/configure.mts +32 -88
- package/scripts/src/daemon.mts +28 -0
- package/scripts/src/env-file-utils.mts +98 -0
- package/scripts/src/install-layout.mts +172 -2
- package/scripts/src/supervisor.mts +5 -0
- package/scripts/test/install-layout.test.mjs +54 -2
|
@@ -4,6 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
import process, { stdin as input, stdout as output } from "node:process";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { createInterface } from "node:readline/promises";
|
|
7
|
+
import { parseEnvMap, readEnvLines, setEnvValue, writeEnvLines } from "./env-file-utils.mjs";
|
|
7
8
|
import { initializeCopilotHubLayout, resolveCopilotHubLayout } from "./install-layout.mjs";
|
|
8
9
|
const __filename = fileURLToPath(import.meta.url);
|
|
9
10
|
const __dirname = path.dirname(__filename);
|
|
@@ -15,15 +16,16 @@ const engineExamplePath = path.join(repoRoot, "apps", "agent-engine", ".env.exam
|
|
|
15
16
|
const controlPlaneEnvPath = layout.controlPlaneEnvPath;
|
|
16
17
|
const controlPlaneExamplePath = path.join(repoRoot, "apps", "control-plane", ".env.example");
|
|
17
18
|
const TELEGRAM_TOKEN_PATTERN = /^\d{5,}:[A-Za-z0-9_-]{20,}$/;
|
|
18
|
-
const DEFAULT_CONTROL_PLANE_TOKEN_ENV = "
|
|
19
|
+
const DEFAULT_CONTROL_PLANE_TOKEN_ENV = "HUB_TELEGRAM_TOKEN_FILE";
|
|
20
|
+
const LEGACY_CONTROL_PLANE_TOKEN_ENV = "HUB_TELEGRAM_TOKEN";
|
|
19
21
|
const args = new Set(process.argv.slice(2));
|
|
20
22
|
const requiredOnly = args.has("--required-only");
|
|
21
23
|
await main();
|
|
22
24
|
async function main() {
|
|
23
25
|
ensureEnvFile(engineEnvPath, engineExamplePath);
|
|
24
26
|
ensureEnvFile(controlPlaneEnvPath, controlPlaneExamplePath);
|
|
25
|
-
const engineLines =
|
|
26
|
-
const controlPlaneLines =
|
|
27
|
+
const engineLines = readEnvLines(engineEnvPath);
|
|
28
|
+
const controlPlaneLines = readEnvLines(controlPlaneEnvPath);
|
|
27
29
|
const rl = createInterface({ input, output });
|
|
28
30
|
try {
|
|
29
31
|
if (requiredOnly) {
|
|
@@ -40,13 +42,11 @@ async function main() {
|
|
|
40
42
|
finally {
|
|
41
43
|
rl.close();
|
|
42
44
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
writeEnvLines(engineEnvPath, engineLines);
|
|
46
|
+
writeEnvLines(controlPlaneEnvPath, controlPlaneLines);
|
|
45
47
|
}
|
|
46
48
|
async function configureRequiredTokens({ rl, controlPlaneLines }) {
|
|
47
|
-
const
|
|
48
|
-
const controlPlaneTokenEnvName = nonEmpty(controlPlaneMap.HUB_TELEGRAM_TOKEN_ENV, DEFAULT_CONTROL_PLANE_TOKEN_ENV);
|
|
49
|
-
setEnvValue(controlPlaneLines, "HUB_TELEGRAM_TOKEN_ENV", controlPlaneTokenEnvName);
|
|
49
|
+
const controlPlaneTokenEnvName = migrateControlPlaneTokenEnv(controlPlaneLines);
|
|
50
50
|
const postControlPlaneMap = parseEnvMap(controlPlaneLines);
|
|
51
51
|
const currentToken = String(postControlPlaneMap[controlPlaneTokenEnvName] ?? "").trim();
|
|
52
52
|
if (isUsableTelegramToken(currentToken)) {
|
|
@@ -62,10 +62,8 @@ async function configureRequiredTokens({ rl, controlPlaneLines }) {
|
|
|
62
62
|
console.log("Required token saved.");
|
|
63
63
|
}
|
|
64
64
|
async function configureAll({ rl, controlPlaneLines }) {
|
|
65
|
-
const controlPlaneMap = parseEnvMap(controlPlaneLines);
|
|
66
65
|
console.log("\nCopilot Hub control-plane configuration\n");
|
|
67
|
-
const controlPlaneTokenEnvDefault =
|
|
68
|
-
setEnvValue(controlPlaneLines, "HUB_TELEGRAM_TOKEN_ENV", controlPlaneTokenEnvDefault);
|
|
66
|
+
const controlPlaneTokenEnvDefault = migrateControlPlaneTokenEnv(controlPlaneLines);
|
|
69
67
|
const currentControlPlaneToken = String(parseEnvMap(controlPlaneLines)[controlPlaneTokenEnvDefault] ?? "").trim();
|
|
70
68
|
const newControlPlaneToken = currentControlPlaneToken
|
|
71
69
|
? await askTelegramToken(rl, "Control-plane Telegram token (press Enter to keep current)", true)
|
|
@@ -77,6 +75,23 @@ async function configureAll({ rl, controlPlaneLines }) {
|
|
|
77
75
|
console.log("- Control-plane token left unchanged.");
|
|
78
76
|
}
|
|
79
77
|
}
|
|
78
|
+
function migrateControlPlaneTokenEnv(lines) {
|
|
79
|
+
const controlPlaneMap = parseEnvMap(lines);
|
|
80
|
+
const configuredTokenEnvName = nonEmpty(controlPlaneMap.HUB_TELEGRAM_TOKEN_ENV, DEFAULT_CONTROL_PLANE_TOKEN_ENV);
|
|
81
|
+
const shouldMigrateLegacyName = configuredTokenEnvName === LEGACY_CONTROL_PLANE_TOKEN_ENV;
|
|
82
|
+
const nextTokenEnvName = shouldMigrateLegacyName
|
|
83
|
+
? DEFAULT_CONTROL_PLANE_TOKEN_ENV
|
|
84
|
+
: configuredTokenEnvName;
|
|
85
|
+
setEnvValue(lines, "HUB_TELEGRAM_TOKEN_ENV", nextTokenEnvName);
|
|
86
|
+
if (shouldMigrateLegacyName) {
|
|
87
|
+
const legacyToken = String(controlPlaneMap[LEGACY_CONTROL_PLANE_TOKEN_ENV] ?? "").trim();
|
|
88
|
+
const dedicatedToken = String(controlPlaneMap[DEFAULT_CONTROL_PLANE_TOKEN_ENV] ?? "").trim();
|
|
89
|
+
if (legacyToken && !dedicatedToken) {
|
|
90
|
+
setEnvValue(lines, DEFAULT_CONTROL_PLANE_TOKEN_ENV, legacyToken);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return nextTokenEnvName;
|
|
94
|
+
}
|
|
80
95
|
function ensureEnvFile(envPath, examplePath) {
|
|
81
96
|
fs.mkdirSync(path.dirname(envPath), { recursive: true });
|
|
82
97
|
if (fs.existsSync(envPath)) {
|
|
@@ -88,67 +103,10 @@ function ensureEnvFile(envPath, examplePath) {
|
|
|
88
103
|
}
|
|
89
104
|
fs.writeFileSync(envPath, "", "utf8");
|
|
90
105
|
}
|
|
91
|
-
function readLines(filePath) {
|
|
92
|
-
const content = fs.readFileSync(filePath, "utf8");
|
|
93
|
-
return content.split(/\r?\n/);
|
|
94
|
-
}
|
|
95
|
-
function writeLines(filePath, lines) {
|
|
96
|
-
const normalized = [...lines];
|
|
97
|
-
if (normalized.length === 0 || normalized[normalized.length - 1] !== "") {
|
|
98
|
-
normalized.push("");
|
|
99
|
-
}
|
|
100
|
-
fs.writeFileSync(filePath, normalized.join("\n"), "utf8");
|
|
101
|
-
}
|
|
102
|
-
function parseEnvMap(lines) {
|
|
103
|
-
const map = {};
|
|
104
|
-
for (const line of lines) {
|
|
105
|
-
const match = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)\s*$/);
|
|
106
|
-
if (!match) {
|
|
107
|
-
continue;
|
|
108
|
-
}
|
|
109
|
-
const key = match[1];
|
|
110
|
-
const value = unquote(match[2] ?? "");
|
|
111
|
-
map[key] = value;
|
|
112
|
-
}
|
|
113
|
-
return map;
|
|
114
|
-
}
|
|
115
|
-
function setEnvValue(lines, key, value) {
|
|
116
|
-
const safeValue = sanitizeValue(value);
|
|
117
|
-
const pattern = new RegExp(`^\\s*${escapeRegex(key)}\\s*=`);
|
|
118
|
-
for (let index = 0; index < lines.length; index += 1) {
|
|
119
|
-
if (!pattern.test(lines[index])) {
|
|
120
|
-
continue;
|
|
121
|
-
}
|
|
122
|
-
lines[index] = `${key}=${safeValue}`;
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
if (lines.length > 0 && lines[lines.length - 1] !== "") {
|
|
126
|
-
lines.push("");
|
|
127
|
-
}
|
|
128
|
-
lines.push(`${key}=${safeValue}`);
|
|
129
|
-
}
|
|
130
|
-
function sanitizeValue(value) {
|
|
131
|
-
return String(value ?? "")
|
|
132
|
-
.replace(/[\r\n]/g, "")
|
|
133
|
-
.trim();
|
|
134
|
-
}
|
|
135
|
-
function unquote(value) {
|
|
136
|
-
const raw = String(value ?? "").trim();
|
|
137
|
-
if (!raw) {
|
|
138
|
-
return "";
|
|
139
|
-
}
|
|
140
|
-
if ((raw.startsWith('"') && raw.endsWith('"')) || (raw.startsWith("'") && raw.endsWith("'"))) {
|
|
141
|
-
return raw.slice(1, -1);
|
|
142
|
-
}
|
|
143
|
-
return raw;
|
|
144
|
-
}
|
|
145
106
|
function nonEmpty(value, fallback) {
|
|
146
107
|
const normalized = String(value ?? "").trim();
|
|
147
108
|
return normalized || fallback;
|
|
148
109
|
}
|
|
149
|
-
function escapeRegex(value) {
|
|
150
|
-
return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
151
|
-
}
|
|
152
110
|
async function askRequired(rl, label) {
|
|
153
111
|
while (true) {
|
|
154
112
|
const value = await rl.question(`${label}: `);
|
package/scripts/dist/daemon.mjs
CHANGED
|
@@ -508,6 +508,23 @@ function detectFatalStartupError(ensureResult) {
|
|
|
508
508
|
detectedAt: new Date().toISOString(),
|
|
509
509
|
};
|
|
510
510
|
}
|
|
511
|
+
const invalidHubTokenLine = findLineContaining(evidenceChunks, (line) => line.includes("hub telegram token in") && line.includes("is invalid"));
|
|
512
|
+
if (invalidHubTokenLine) {
|
|
513
|
+
return {
|
|
514
|
+
reason: invalidHubTokenLine,
|
|
515
|
+
action: "Run 'copilot-hub configure' to save a valid hub token in the control-plane config, then retry service.",
|
|
516
|
+
detectedAt: new Date().toISOString(),
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
const workspaceRootLine = findLineContaining(evidenceChunks, (line) => line.includes("default_workspace_root must be outside kernel directory") ||
|
|
520
|
+
line.includes("hub_workspace_root must be outside kernel directory"));
|
|
521
|
+
if (workspaceRootLine) {
|
|
522
|
+
return {
|
|
523
|
+
reason: workspaceRootLine,
|
|
524
|
+
action: "Set DEFAULT_WORKSPACE_ROOT to a folder outside the copilot-hub installation, then retry service.",
|
|
525
|
+
detectedAt: new Date().toISOString(),
|
|
526
|
+
};
|
|
527
|
+
}
|
|
511
528
|
return null;
|
|
512
529
|
}
|
|
513
530
|
function readLogTail(filePath, maxLines = 120) {
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
export function ensureEnvTextFile(filePath) {
|
|
3
|
+
if (fs.existsSync(filePath)) {
|
|
4
|
+
return;
|
|
5
|
+
}
|
|
6
|
+
fs.mkdirSync(requireParentDir(filePath), { recursive: true });
|
|
7
|
+
fs.writeFileSync(filePath, "", "utf8");
|
|
8
|
+
}
|
|
9
|
+
export function readEnvLines(filePath) {
|
|
10
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
11
|
+
return content.split(/\r?\n/);
|
|
12
|
+
}
|
|
13
|
+
export function writeEnvLines(filePath, lines) {
|
|
14
|
+
const normalized = [...lines];
|
|
15
|
+
if (normalized.length === 0 || normalized[normalized.length - 1] !== "") {
|
|
16
|
+
normalized.push("");
|
|
17
|
+
}
|
|
18
|
+
fs.writeFileSync(filePath, normalized.join("\n"), "utf8");
|
|
19
|
+
}
|
|
20
|
+
export function parseEnvMap(lines) {
|
|
21
|
+
const map = {};
|
|
22
|
+
for (const line of lines) {
|
|
23
|
+
const match = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)\s*$/);
|
|
24
|
+
if (!match) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
const key = match[1];
|
|
28
|
+
const value = unquote(match[2] ?? "");
|
|
29
|
+
map[key] = value;
|
|
30
|
+
}
|
|
31
|
+
return map;
|
|
32
|
+
}
|
|
33
|
+
export function setEnvValue(lines, key, value) {
|
|
34
|
+
const safeValue = sanitizeEnvValue(value);
|
|
35
|
+
const pattern = new RegExp(`^\\s*${escapeRegex(key)}\\s*=`);
|
|
36
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
37
|
+
if (!pattern.test(lines[index])) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
lines[index] = `${key}=${safeValue}`;
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (lines.length > 0 && lines[lines.length - 1] !== "") {
|
|
44
|
+
lines.push("");
|
|
45
|
+
}
|
|
46
|
+
lines.push(`${key}=${safeValue}`);
|
|
47
|
+
}
|
|
48
|
+
export function removeEnvKeys(lines, keys) {
|
|
49
|
+
const patterns = keys.map((key) => new RegExp(`^\\s*${escapeRegex(key)}\\s*=`));
|
|
50
|
+
const originalLength = lines.length;
|
|
51
|
+
const kept = lines.filter((line) => !patterns.some((pattern) => pattern.test(line)));
|
|
52
|
+
if (kept.length === originalLength) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
lines.splice(0, lines.length, ...kept);
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
export function sanitizeEnvValue(value) {
|
|
59
|
+
return String(value ?? "")
|
|
60
|
+
.replace(/[\r\n]/g, "")
|
|
61
|
+
.trim();
|
|
62
|
+
}
|
|
63
|
+
function unquote(value) {
|
|
64
|
+
const raw = String(value ?? "").trim();
|
|
65
|
+
if (!raw) {
|
|
66
|
+
return "";
|
|
67
|
+
}
|
|
68
|
+
if ((raw.startsWith('"') && raw.endsWith('"')) || (raw.startsWith("'") && raw.endsWith("'"))) {
|
|
69
|
+
return raw.slice(1, -1);
|
|
70
|
+
}
|
|
71
|
+
return raw;
|
|
72
|
+
}
|
|
73
|
+
function escapeRegex(value) {
|
|
74
|
+
return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
75
|
+
}
|
|
76
|
+
function requireParentDir(filePath) {
|
|
77
|
+
const parts = String(filePath ?? "").split(/[\\/]/);
|
|
78
|
+
if (parts.length <= 1) {
|
|
79
|
+
return ".";
|
|
80
|
+
}
|
|
81
|
+
parts.pop();
|
|
82
|
+
return parts.join("/") || ".";
|
|
83
|
+
}
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
|
+
import process from "node:process";
|
|
6
|
+
import { parseEnvMap, readEnvLines, removeEnvKeys, writeEnvLines } from "./env-file-utils.mjs";
|
|
5
7
|
export function resolveCopilotHubLayout({ repoRoot, env = process.env, platform = process.platform, homeDirectory = os.homedir(), }) {
|
|
6
8
|
const pathApi = getPathApi(platform);
|
|
7
9
|
const homeDir = resolveCopilotHubHomeDir({
|
|
@@ -30,7 +32,35 @@ export function resolveCopilotHubLayout({ repoRoot, env = process.env, platform
|
|
|
30
32
|
export function initializeCopilotHubLayout({ repoRoot, layout, }) {
|
|
31
33
|
ensureCopilotHubLayout(layout);
|
|
32
34
|
const migratedPaths = migrateLegacyLayout({ repoRoot, layout });
|
|
33
|
-
|
|
35
|
+
const normalizedEnvPaths = normalizePersistentEnvFiles(layout);
|
|
36
|
+
return { migratedPaths, normalizedEnvPaths };
|
|
37
|
+
}
|
|
38
|
+
export function resetCopilotHubConfig({ layout }) {
|
|
39
|
+
const removedPaths = [];
|
|
40
|
+
for (const target of [layout.configDir, layout.dataDir, layout.logsDir]) {
|
|
41
|
+
if (!fs.existsSync(target)) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
fs.rmSync(target, { recursive: true, force: true });
|
|
45
|
+
removedPaths.push(target);
|
|
46
|
+
}
|
|
47
|
+
const runtimeTargets = [
|
|
48
|
+
path.join(layout.runtimeDir, "pids"),
|
|
49
|
+
path.join(layout.runtimeDir, "services"),
|
|
50
|
+
path.join(layout.runtimeDir, "last-startup-error.json"),
|
|
51
|
+
layout.servicePromptStatePath,
|
|
52
|
+
];
|
|
53
|
+
for (const target of runtimeTargets) {
|
|
54
|
+
if (!fs.existsSync(target)) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
fs.rmSync(target, { recursive: true, force: true });
|
|
58
|
+
removedPaths.push(target);
|
|
59
|
+
}
|
|
60
|
+
ensureCopilotHubLayout(layout);
|
|
61
|
+
return {
|
|
62
|
+
removedPaths: removedPaths.sort(),
|
|
63
|
+
};
|
|
34
64
|
}
|
|
35
65
|
export function ensureCopilotHubLayout(layout) {
|
|
36
66
|
fs.mkdirSync(layout.homeDir, { recursive: true });
|
|
@@ -81,6 +111,98 @@ function migrateLegacyLayout({ repoRoot, layout, }) {
|
|
|
81
111
|
}
|
|
82
112
|
return migratedPaths;
|
|
83
113
|
}
|
|
114
|
+
function normalizePersistentEnvFiles(layout) {
|
|
115
|
+
const normalizedPaths = [];
|
|
116
|
+
if (normalizePersistentEnvFile(layout.agentEngineEnvPath, [
|
|
117
|
+
{
|
|
118
|
+
key: "BOT_DATA_DIR",
|
|
119
|
+
legacyValues: ["./data"],
|
|
120
|
+
wrongResolvedPath: path.join(layout.configDir, "data"),
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
key: "BOT_REGISTRY_FILE",
|
|
124
|
+
legacyValues: ["./data/bot-registry.json"],
|
|
125
|
+
wrongResolvedPath: path.join(layout.configDir, "data", "bot-registry.json"),
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
key: "SECRET_STORE_FILE",
|
|
129
|
+
legacyValues: ["./data/secrets.json"],
|
|
130
|
+
wrongResolvedPath: path.join(layout.configDir, "data", "secrets.json"),
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
key: "INSTANCE_LOCK_FILE",
|
|
134
|
+
legacyValues: ["./data/runtime.lock"],
|
|
135
|
+
wrongResolvedPath: path.join(layout.configDir, "data", "runtime.lock"),
|
|
136
|
+
},
|
|
137
|
+
])) {
|
|
138
|
+
normalizedPaths.push(layout.agentEngineEnvPath);
|
|
139
|
+
}
|
|
140
|
+
if (normalizePersistentEnvFile(layout.controlPlaneEnvPath, [
|
|
141
|
+
{
|
|
142
|
+
key: "BOT_DATA_DIR",
|
|
143
|
+
legacyValues: ["./data"],
|
|
144
|
+
wrongResolvedPath: path.join(layout.configDir, "data"),
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
key: "BOT_REGISTRY_FILE",
|
|
148
|
+
legacyValues: ["./data/bot-registry.json"],
|
|
149
|
+
wrongResolvedPath: path.join(layout.configDir, "data", "bot-registry.json"),
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
key: "SECRET_STORE_FILE",
|
|
153
|
+
legacyValues: ["./data/secrets.json"],
|
|
154
|
+
wrongResolvedPath: path.join(layout.configDir, "data", "secrets.json"),
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
key: "INSTANCE_LOCK_FILE",
|
|
158
|
+
legacyValues: ["./data/runtime.lock"],
|
|
159
|
+
wrongResolvedPath: path.join(layout.configDir, "data", "runtime.lock"),
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
key: "HUB_DATA_DIR",
|
|
163
|
+
legacyValues: ["./data/copilot_hub"],
|
|
164
|
+
wrongResolvedPath: path.join(layout.configDir, "data", "copilot_hub"),
|
|
165
|
+
},
|
|
166
|
+
])) {
|
|
167
|
+
normalizedPaths.push(layout.controlPlaneEnvPath);
|
|
168
|
+
}
|
|
169
|
+
return normalizedPaths.sort();
|
|
170
|
+
}
|
|
171
|
+
function normalizePersistentEnvFile(filePath, rules) {
|
|
172
|
+
if (!fs.existsSync(filePath)) {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
const lines = readEnvLines(filePath);
|
|
176
|
+
const envMap = parseEnvMap(lines);
|
|
177
|
+
const keysToRemove = rules
|
|
178
|
+
.filter((rule) => shouldRemoveLegacyManagedPath(envMap[rule.key], {
|
|
179
|
+
legacyValues: rule.legacyValues,
|
|
180
|
+
wrongResolvedPath: rule.wrongResolvedPath,
|
|
181
|
+
configBaseDir: path.dirname(filePath),
|
|
182
|
+
}))
|
|
183
|
+
.map((rule) => rule.key);
|
|
184
|
+
if (keysToRemove.length === 0) {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
removeEnvKeys(lines, keysToRemove);
|
|
188
|
+
writeEnvLines(filePath, lines);
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
function shouldRemoveLegacyManagedPath(rawValue, { legacyValues, wrongResolvedPath, configBaseDir, }) {
|
|
192
|
+
const value = String(rawValue ?? "").trim();
|
|
193
|
+
if (!value) {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
const normalizedValue = normalizeForCompare(value);
|
|
197
|
+
if (legacyValues.some((entry) => normalizeForCompare(entry) === normalizedValue)) {
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
if (path.isAbsolute(value)) {
|
|
201
|
+
return normalizeForCompare(value) === normalizeForCompare(wrongResolvedPath);
|
|
202
|
+
}
|
|
203
|
+
return (normalizeForCompare(path.resolve(configBaseDir, value)) ===
|
|
204
|
+
normalizeForCompare(wrongResolvedPath));
|
|
205
|
+
}
|
|
84
206
|
function resolveLegacyPaths(repoRoot) {
|
|
85
207
|
return {
|
|
86
208
|
agentEngineEnvPath: path.join(repoRoot, "apps", "agent-engine", ".env"),
|
|
@@ -129,6 +251,13 @@ function normalizePath(value, pathApi) {
|
|
|
129
251
|
const normalized = String(value ?? "").trim();
|
|
130
252
|
return normalized ? pathApi.resolve(normalized) : "";
|
|
131
253
|
}
|
|
254
|
+
function normalizeForCompare(value) {
|
|
255
|
+
const normalized = String(value ?? "").trim();
|
|
256
|
+
if (!normalized) {
|
|
257
|
+
return "";
|
|
258
|
+
}
|
|
259
|
+
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
260
|
+
}
|
|
132
261
|
function getPathApi(platform) {
|
|
133
262
|
return platform === "win32" ? path.win32 : path.posix;
|
|
134
263
|
}
|
|
@@ -307,6 +307,11 @@ function buildServiceEnvironment(service) {
|
|
|
307
307
|
BOT_REGISTRY_FILE: service.botRegistryFilePath,
|
|
308
308
|
SECRET_STORE_FILE: service.secretStoreFilePath,
|
|
309
309
|
INSTANCE_LOCK_FILE: service.instanceLockFilePath,
|
|
310
|
+
...(service.id === "control-plane"
|
|
311
|
+
? {
|
|
312
|
+
HUB_DATA_DIR: path.join(service.dataDir, "copilot_hub"),
|
|
313
|
+
}
|
|
314
|
+
: {}),
|
|
310
315
|
};
|
|
311
316
|
}
|
|
312
317
|
function printTail(filePath, lines) {
|
package/scripts/src/cli.mts
CHANGED
|
@@ -7,7 +7,11 @@ import { createInterface } from "node:readline/promises";
|
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import { spawnCodexSync } from "./codex-spawn.mjs";
|
|
9
9
|
import { codexInstallPackageSpec } from "./codex-version.mjs";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
initializeCopilotHubLayout,
|
|
12
|
+
resetCopilotHubConfig,
|
|
13
|
+
resolveCopilotHubLayout,
|
|
14
|
+
} from "./install-layout.mjs";
|
|
11
15
|
import {
|
|
12
16
|
buildCodexCompatibilityError,
|
|
13
17
|
buildCodexCompatibilityNotice,
|
|
@@ -35,6 +39,7 @@ const rawArgs = process.argv
|
|
|
35
39
|
.filter(Boolean);
|
|
36
40
|
const wantsVersion = rawArgs.includes("--version") || rawArgs.includes("-v");
|
|
37
41
|
const wantsHelp = rawArgs.includes("--help") || rawArgs.includes("-h");
|
|
42
|
+
const wantsYes = rawArgs.includes("--yes") || rawArgs.includes("-y");
|
|
38
43
|
|
|
39
44
|
const action = String(rawArgs[0] ?? "start")
|
|
40
45
|
.trim()
|
|
@@ -106,6 +111,13 @@ async function main() {
|
|
|
106
111
|
runNode(["scripts/dist/configure.mjs"]);
|
|
107
112
|
return;
|
|
108
113
|
}
|
|
114
|
+
case "reset-config":
|
|
115
|
+
case "reset_config": {
|
|
116
|
+
await resetConfig({
|
|
117
|
+
force: wantsYes,
|
|
118
|
+
});
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
109
121
|
case "service": {
|
|
110
122
|
const serviceAction = String(rawArgs[1] ?? "")
|
|
111
123
|
.trim()
|
|
@@ -126,6 +138,56 @@ async function main() {
|
|
|
126
138
|
}
|
|
127
139
|
}
|
|
128
140
|
|
|
141
|
+
async function resetConfig({ force }: { force: boolean }): Promise<void> {
|
|
142
|
+
if (!force) {
|
|
143
|
+
if (!process.stdin.isTTY) {
|
|
144
|
+
throw new Error(
|
|
145
|
+
"reset-config requires confirmation. Re-run with '--yes' in non-interactive mode.",
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const rl = createInterface({ input, output });
|
|
150
|
+
try {
|
|
151
|
+
const confirmed = await askYesNo(
|
|
152
|
+
rl,
|
|
153
|
+
[
|
|
154
|
+
"Reset Copilot Hub config and runtime state?",
|
|
155
|
+
"This removes persisted config, bot registry, secrets, logs, and runtime state.",
|
|
156
|
+
"Agent workspaces are kept.",
|
|
157
|
+
].join(" "),
|
|
158
|
+
false,
|
|
159
|
+
);
|
|
160
|
+
if (!confirmed) {
|
|
161
|
+
console.log("Reset canceled.");
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
} finally {
|
|
165
|
+
rl.close();
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (isServiceAlreadyInstalled()) {
|
|
170
|
+
runNodeCapture(["scripts/dist/service.mjs", "stop"], "inherit");
|
|
171
|
+
} else {
|
|
172
|
+
runNodeCapture(["scripts/dist/supervisor.mjs", "down"], "inherit");
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const reset = resetCopilotHubConfig({ layout });
|
|
176
|
+
initializeCopilotHubLayout({ repoRoot, layout });
|
|
177
|
+
|
|
178
|
+
console.log("Copilot Hub config reset completed.");
|
|
179
|
+
if (reset.removedPaths.length > 0) {
|
|
180
|
+
console.log("Removed:");
|
|
181
|
+
for (const removedPath of reset.removedPaths) {
|
|
182
|
+
console.log(`- ${removedPath}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
console.log("Kept:");
|
|
186
|
+
console.log("- package installation");
|
|
187
|
+
console.log("- external workspaces (for example Desktop/copilot_workspaces)");
|
|
188
|
+
console.log("Next step: run 'copilot-hub configure' then 'copilot-hub start'.");
|
|
189
|
+
}
|
|
190
|
+
|
|
129
191
|
function runNode(scriptArgs) {
|
|
130
192
|
const result = runNodeCapture(scriptArgs, "inherit");
|
|
131
193
|
const code = Number.isInteger(result.status) ? result.status : 1;
|
|
@@ -577,6 +639,8 @@ function printUsage() {
|
|
|
577
639
|
console.log(
|
|
578
640
|
[
|
|
579
641
|
"Usage: node scripts/dist/cli.mjs <start|stop|restart|status|logs|configure|service|version|help>",
|
|
642
|
+
"Reset persistent state:",
|
|
643
|
+
" node scripts/dist/cli.mjs reset-config [--yes]",
|
|
580
644
|
"Service management:",
|
|
581
645
|
" node scripts/dist/cli.mjs service <install|uninstall|status|start|stop|help>",
|
|
582
646
|
].join("\n"),
|