@openacp/cli 0.4.10 → 0.4.11
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 +17 -2
- package/dist/api-client-UN7BXQOQ.js +11 -0
- package/dist/{autostart-DZ3MHHMM.js → autostart-K73RQZVV.js} +3 -3
- package/dist/chunk-3DIPXFZJ.js +650 -0
- package/dist/chunk-3DIPXFZJ.js.map +1 -0
- package/dist/{chunk-KPI4HGJC.js → chunk-66RVSUAR.js} +1423 -1141
- package/dist/chunk-66RVSUAR.js.map +1 -0
- package/dist/chunk-BGKQHQB4.js +276 -0
- package/dist/chunk-BGKQHQB4.js.map +1 -0
- package/dist/chunk-C33LTDZV.js +97 -0
- package/dist/chunk-C33LTDZV.js.map +1 -0
- package/dist/{chunk-LYKCQTH5.js → chunk-ESOPMQAY.js} +5 -1
- package/dist/chunk-ESOPMQAY.js.map +1 -0
- package/dist/{chunk-6MJLVZXV.js → chunk-FKOARMAE.js} +58 -21
- package/dist/{chunk-6MJLVZXV.js.map → chunk-FKOARMAE.js.map} +1 -1
- package/dist/chunk-OORPX73T.js +30 -0
- package/dist/chunk-OORPX73T.js.map +1 -0
- package/dist/{chunk-V3BA2MJ6.js → chunk-RF3DUYFO.js} +2 -2
- package/dist/{chunk-UAUTLC4E.js → chunk-W7QQA6CW.js} +4 -4
- package/dist/{chunk-ZRFBLD3W.js → chunk-WYZFGHHI.js} +14 -4
- package/dist/chunk-WYZFGHHI.js.map +1 -0
- package/dist/{chunk-MRKYJ422.js → chunk-X6LLG7XN.js} +2 -2
- package/dist/{chunk-C6YIUTGR.js → chunk-YRJEZD7R.js} +2 -2
- package/dist/{chunk-HZD3CGPK.js → chunk-ZW444AQY.js} +3 -3
- package/dist/cli.js +141 -52
- package/dist/cli.js.map +1 -1
- package/dist/{config-H2DSEHNW.js → config-XURP6B3S.js} +3 -3
- package/dist/config-editor-AALY3URF.js +11 -0
- package/dist/config-registry-OGX4YM2U.js +17 -0
- package/dist/{daemon-VF6HJQXD.js → daemon-GWJM2S4A.js} +4 -4
- package/dist/doctor-X477CVZN.js +9 -0
- package/dist/doctor-X477CVZN.js.map +1 -0
- package/dist/index.d.ts +76 -27
- package/dist/index.js +33 -13
- package/dist/install-cloudflared-BTGUD7SW.js +8 -0
- package/dist/install-cloudflared-BTGUD7SW.js.map +1 -0
- package/dist/log-SPS2S6FO.js +19 -0
- package/dist/log-SPS2S6FO.js.map +1 -0
- package/dist/{main-G6XDM7EZ.js → main-2QKD2EI2.js} +17 -14
- package/dist/{main-G6XDM7EZ.js.map → main-2QKD2EI2.js.map} +1 -1
- package/dist/menu-CARRTW2F.js +17 -0
- package/dist/menu-CARRTW2F.js.map +1 -0
- package/dist/{setup-FCVL75K6.js → setup-TTOL7XAN.js} +4 -4
- package/dist/setup-TTOL7XAN.js.map +1 -0
- package/dist/{tunnel-service-DASSH7OA.js → tunnel-service-LEVPLXAZ.js} +3 -3
- package/package.json +1 -1
- package/dist/chunk-KPI4HGJC.js.map +0 -1
- package/dist/chunk-LYKCQTH5.js.map +0 -1
- package/dist/chunk-ZRFBLD3W.js.map +0 -1
- package/dist/config-editor-SKS4LJLT.js +0 -11
- package/dist/install-cloudflared-ILUXKLAC.js +0 -8
- /package/dist/{autostart-DZ3MHHMM.js.map → api-client-UN7BXQOQ.js.map} +0 -0
- /package/dist/{config-H2DSEHNW.js.map → autostart-K73RQZVV.js.map} +0 -0
- /package/dist/{chunk-V3BA2MJ6.js.map → chunk-RF3DUYFO.js.map} +0 -0
- /package/dist/{chunk-UAUTLC4E.js.map → chunk-W7QQA6CW.js.map} +0 -0
- /package/dist/{chunk-MRKYJ422.js.map → chunk-X6LLG7XN.js.map} +0 -0
- /package/dist/{chunk-C6YIUTGR.js.map → chunk-YRJEZD7R.js.map} +0 -0
- /package/dist/{chunk-HZD3CGPK.js.map → chunk-ZW444AQY.js.map} +0 -0
- /package/dist/{config-editor-SKS4LJLT.js.map → config-XURP6B3S.js.map} +0 -0
- /package/dist/{daemon-VF6HJQXD.js.map → config-editor-AALY3URF.js.map} +0 -0
- /package/dist/{install-cloudflared-ILUXKLAC.js.map → config-registry-OGX4YM2U.js.map} +0 -0
- /package/dist/{setup-FCVL75K6.js.map → daemon-GWJM2S4A.js.map} +0 -0
- /package/dist/{tunnel-service-DASSH7OA.js.map → tunnel-service-LEVPLXAZ.js.map} +0 -0
|
@@ -0,0 +1,650 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ConfigManager,
|
|
3
|
+
ConfigSchema,
|
|
4
|
+
applyMigrations,
|
|
5
|
+
expandHome
|
|
6
|
+
} from "./chunk-WYZFGHHI.js";
|
|
7
|
+
|
|
8
|
+
// src/core/doctor/index.ts
|
|
9
|
+
import * as fs8 from "fs";
|
|
10
|
+
import * as path4 from "path";
|
|
11
|
+
import * as os2 from "os";
|
|
12
|
+
|
|
13
|
+
// src/core/doctor/checks/config.ts
|
|
14
|
+
import * as fs from "fs";
|
|
15
|
+
var configCheck = {
|
|
16
|
+
name: "Config",
|
|
17
|
+
order: 1,
|
|
18
|
+
async run(ctx) {
|
|
19
|
+
const results = [];
|
|
20
|
+
if (!fs.existsSync(ctx.configPath)) {
|
|
21
|
+
results.push({ status: "fail", message: "Config file not found" });
|
|
22
|
+
return results;
|
|
23
|
+
}
|
|
24
|
+
results.push({ status: "pass", message: "Config file exists" });
|
|
25
|
+
let raw;
|
|
26
|
+
try {
|
|
27
|
+
raw = JSON.parse(fs.readFileSync(ctx.configPath, "utf-8"));
|
|
28
|
+
} catch (err) {
|
|
29
|
+
results.push({
|
|
30
|
+
status: "fail",
|
|
31
|
+
message: `Config JSON invalid: ${err instanceof Error ? err.message : String(err)}`
|
|
32
|
+
});
|
|
33
|
+
return results;
|
|
34
|
+
}
|
|
35
|
+
results.push({ status: "pass", message: "JSON valid" });
|
|
36
|
+
const testRaw = structuredClone(raw);
|
|
37
|
+
const { changed } = applyMigrations(testRaw);
|
|
38
|
+
if (changed) {
|
|
39
|
+
results.push({
|
|
40
|
+
status: "warn",
|
|
41
|
+
message: "Pending config migrations",
|
|
42
|
+
fixable: true,
|
|
43
|
+
fixRisk: "safe",
|
|
44
|
+
fix: async () => {
|
|
45
|
+
applyMigrations(raw);
|
|
46
|
+
fs.writeFileSync(ctx.configPath, JSON.stringify(raw, null, 2));
|
|
47
|
+
return { success: true, message: "applied migrations" };
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
const result = ConfigSchema.safeParse(raw);
|
|
52
|
+
if (!result.success) {
|
|
53
|
+
for (const issue of result.error.issues) {
|
|
54
|
+
results.push({
|
|
55
|
+
status: "fail",
|
|
56
|
+
message: `Validation: ${issue.path.join(".")} \u2014 ${issue.message}`
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
} else {
|
|
60
|
+
results.push({ status: "pass", message: "Schema valid" });
|
|
61
|
+
}
|
|
62
|
+
return results;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// src/core/doctor/checks/agents.ts
|
|
67
|
+
import { execFileSync } from "child_process";
|
|
68
|
+
import * as fs2 from "fs";
|
|
69
|
+
import * as path from "path";
|
|
70
|
+
function commandExists(cmd) {
|
|
71
|
+
try {
|
|
72
|
+
execFileSync("which", [cmd], { stdio: "pipe" });
|
|
73
|
+
return true;
|
|
74
|
+
} catch {
|
|
75
|
+
}
|
|
76
|
+
let dir = process.cwd();
|
|
77
|
+
while (true) {
|
|
78
|
+
const binPath = path.join(dir, "node_modules", ".bin", cmd);
|
|
79
|
+
if (fs2.existsSync(binPath)) return true;
|
|
80
|
+
const parent = path.dirname(dir);
|
|
81
|
+
if (parent === dir) break;
|
|
82
|
+
dir = parent;
|
|
83
|
+
}
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
var agentsCheck = {
|
|
87
|
+
name: "Agents",
|
|
88
|
+
order: 2,
|
|
89
|
+
async run(ctx) {
|
|
90
|
+
const results = [];
|
|
91
|
+
if (!ctx.config) {
|
|
92
|
+
results.push({ status: "fail", message: "Cannot check agents \u2014 config not loaded" });
|
|
93
|
+
return results;
|
|
94
|
+
}
|
|
95
|
+
const agents = ctx.config.agents;
|
|
96
|
+
const defaultAgent = ctx.config.defaultAgent;
|
|
97
|
+
if (!agents[defaultAgent]) {
|
|
98
|
+
results.push({
|
|
99
|
+
status: "fail",
|
|
100
|
+
message: `Default agent "${defaultAgent}" not found in agents config`
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
for (const [name, agent] of Object.entries(agents)) {
|
|
104
|
+
const isDefault = name === defaultAgent;
|
|
105
|
+
if (commandExists(agent.command)) {
|
|
106
|
+
results.push({
|
|
107
|
+
status: "pass",
|
|
108
|
+
message: `${agent.command} found${isDefault ? " (default)" : ""}`
|
|
109
|
+
});
|
|
110
|
+
} else {
|
|
111
|
+
results.push({
|
|
112
|
+
status: isDefault ? "fail" : "warn",
|
|
113
|
+
message: `${agent.command} not found in PATH${isDefault ? " (default agent!)" : ""}`
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return results;
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
// src/core/doctor/checks/telegram.ts
|
|
122
|
+
var BOT_TOKEN_REGEX = /^\d+:[A-Za-z0-9_-]{35,}$/;
|
|
123
|
+
var telegramCheck = {
|
|
124
|
+
name: "Telegram",
|
|
125
|
+
order: 3,
|
|
126
|
+
async run(ctx) {
|
|
127
|
+
const results = [];
|
|
128
|
+
if (!ctx.config) {
|
|
129
|
+
results.push({ status: "fail", message: "Cannot check Telegram \u2014 config not loaded" });
|
|
130
|
+
return results;
|
|
131
|
+
}
|
|
132
|
+
const tgConfig = ctx.config.channels.telegram;
|
|
133
|
+
if (!tgConfig || !tgConfig.enabled) {
|
|
134
|
+
results.push({ status: "pass", message: "Telegram not enabled (skipped)" });
|
|
135
|
+
return results;
|
|
136
|
+
}
|
|
137
|
+
const botToken = tgConfig.botToken;
|
|
138
|
+
const chatId = tgConfig.chatId;
|
|
139
|
+
if (!botToken || !BOT_TOKEN_REGEX.test(botToken)) {
|
|
140
|
+
results.push({ status: "fail", message: "Bot token format invalid" });
|
|
141
|
+
return results;
|
|
142
|
+
}
|
|
143
|
+
results.push({ status: "pass", message: "Bot token format valid" });
|
|
144
|
+
let botId;
|
|
145
|
+
try {
|
|
146
|
+
const res = await fetch(`https://api.telegram.org/bot${botToken}/getMe`);
|
|
147
|
+
const data = await res.json();
|
|
148
|
+
if (data.ok && data.result) {
|
|
149
|
+
botId = data.result.id;
|
|
150
|
+
results.push({ status: "pass", message: `Bot token valid (@${data.result.username})` });
|
|
151
|
+
} else {
|
|
152
|
+
results.push({ status: "fail", message: `Bot token rejected: ${data.description || "unknown error"}` });
|
|
153
|
+
return results;
|
|
154
|
+
}
|
|
155
|
+
} catch (err) {
|
|
156
|
+
results.push({ status: "fail", message: `Cannot reach Telegram API: ${err instanceof Error ? err.message : String(err)}` });
|
|
157
|
+
return results;
|
|
158
|
+
}
|
|
159
|
+
if (!chatId || chatId === 0) {
|
|
160
|
+
results.push({ status: "fail", message: "Chat ID not configured" });
|
|
161
|
+
return results;
|
|
162
|
+
}
|
|
163
|
+
try {
|
|
164
|
+
const res = await fetch(`https://api.telegram.org/bot${botToken}/getChat`, {
|
|
165
|
+
method: "POST",
|
|
166
|
+
headers: { "Content-Type": "application/json" },
|
|
167
|
+
body: JSON.stringify({ chat_id: chatId })
|
|
168
|
+
});
|
|
169
|
+
const data = await res.json();
|
|
170
|
+
if (!data.ok || !data.result) {
|
|
171
|
+
results.push({ status: "fail", message: `Chat ID invalid: ${data.description || "unknown error"}` });
|
|
172
|
+
return results;
|
|
173
|
+
}
|
|
174
|
+
if (data.result.type !== "supergroup") {
|
|
175
|
+
results.push({ status: "fail", message: `Chat is "${data.result.type}", must be a supergroup` });
|
|
176
|
+
return results;
|
|
177
|
+
}
|
|
178
|
+
if (!data.result.is_forum) {
|
|
179
|
+
results.push({ status: "warn", message: "Chat does not have topics enabled" });
|
|
180
|
+
} else {
|
|
181
|
+
results.push({ status: "pass", message: `Chat is supergroup with topics ("${data.result.title}")` });
|
|
182
|
+
}
|
|
183
|
+
} catch (err) {
|
|
184
|
+
results.push({ status: "fail", message: `Cannot validate chat: ${err instanceof Error ? err.message : String(err)}` });
|
|
185
|
+
return results;
|
|
186
|
+
}
|
|
187
|
+
try {
|
|
188
|
+
const res = await fetch(`https://api.telegram.org/bot${botToken}/getChatMember`, {
|
|
189
|
+
method: "POST",
|
|
190
|
+
headers: { "Content-Type": "application/json" },
|
|
191
|
+
body: JSON.stringify({ chat_id: chatId, user_id: botId })
|
|
192
|
+
});
|
|
193
|
+
const data = await res.json();
|
|
194
|
+
if (!data.ok || !data.result) {
|
|
195
|
+
results.push({ status: "fail", message: `Cannot check bot membership: ${data.description || "unknown"}` });
|
|
196
|
+
} else if (data.result.status === "administrator" || data.result.status === "creator") {
|
|
197
|
+
results.push({ status: "pass", message: "Bot is admin in group" });
|
|
198
|
+
} else {
|
|
199
|
+
results.push({
|
|
200
|
+
status: "fail",
|
|
201
|
+
message: `Bot is "${data.result.status}" \u2014 must be admin. Promote bot in group settings.`
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
} catch (err) {
|
|
205
|
+
results.push({ status: "fail", message: `Admin check failed: ${err instanceof Error ? err.message : String(err)}` });
|
|
206
|
+
}
|
|
207
|
+
return results;
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// src/core/doctor/checks/storage.ts
|
|
212
|
+
import * as fs3 from "fs";
|
|
213
|
+
var storageCheck = {
|
|
214
|
+
name: "Storage",
|
|
215
|
+
order: 4,
|
|
216
|
+
async run(ctx) {
|
|
217
|
+
const results = [];
|
|
218
|
+
if (!fs3.existsSync(ctx.dataDir)) {
|
|
219
|
+
results.push({
|
|
220
|
+
status: "fail",
|
|
221
|
+
message: "Data directory ~/.openacp does not exist",
|
|
222
|
+
fixable: true,
|
|
223
|
+
fixRisk: "safe",
|
|
224
|
+
fix: async () => {
|
|
225
|
+
fs3.mkdirSync(ctx.dataDir, { recursive: true });
|
|
226
|
+
return { success: true, message: "created directory" };
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
} else {
|
|
230
|
+
try {
|
|
231
|
+
fs3.accessSync(ctx.dataDir, fs3.constants.W_OK);
|
|
232
|
+
results.push({ status: "pass", message: "Data directory exists and writable" });
|
|
233
|
+
} catch {
|
|
234
|
+
results.push({ status: "fail", message: "Data directory not writable" });
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (fs3.existsSync(ctx.sessionsPath)) {
|
|
238
|
+
try {
|
|
239
|
+
const content = fs3.readFileSync(ctx.sessionsPath, "utf-8");
|
|
240
|
+
const data = JSON.parse(content);
|
|
241
|
+
if (typeof data === "object" && data !== null && "sessions" in data) {
|
|
242
|
+
results.push({ status: "pass", message: "Sessions file valid" });
|
|
243
|
+
} else {
|
|
244
|
+
results.push({
|
|
245
|
+
status: "fail",
|
|
246
|
+
message: "Sessions file has invalid structure",
|
|
247
|
+
fixable: true,
|
|
248
|
+
fixRisk: "risky",
|
|
249
|
+
fix: async () => {
|
|
250
|
+
fs3.writeFileSync(ctx.sessionsPath, JSON.stringify({ version: 1, sessions: {} }, null, 2));
|
|
251
|
+
return { success: true, message: "reset sessions file" };
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
} catch {
|
|
256
|
+
results.push({
|
|
257
|
+
status: "fail",
|
|
258
|
+
message: "Sessions file corrupt (invalid JSON)",
|
|
259
|
+
fixable: true,
|
|
260
|
+
fixRisk: "risky",
|
|
261
|
+
fix: async () => {
|
|
262
|
+
fs3.writeFileSync(ctx.sessionsPath, JSON.stringify({ version: 1, sessions: {} }, null, 2));
|
|
263
|
+
return { success: true, message: "reset sessions file" };
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
} else {
|
|
268
|
+
results.push({ status: "pass", message: "Sessions file not present yet (created on first session)" });
|
|
269
|
+
}
|
|
270
|
+
if (!fs3.existsSync(ctx.logsDir)) {
|
|
271
|
+
results.push({
|
|
272
|
+
status: "warn",
|
|
273
|
+
message: "Log directory does not exist",
|
|
274
|
+
fixable: true,
|
|
275
|
+
fixRisk: "safe",
|
|
276
|
+
fix: async () => {
|
|
277
|
+
fs3.mkdirSync(ctx.logsDir, { recursive: true });
|
|
278
|
+
return { success: true, message: "created log directory" };
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
} else {
|
|
282
|
+
try {
|
|
283
|
+
fs3.accessSync(ctx.logsDir, fs3.constants.W_OK);
|
|
284
|
+
results.push({ status: "pass", message: "Log directory exists and writable" });
|
|
285
|
+
} catch {
|
|
286
|
+
results.push({ status: "fail", message: "Log directory not writable" });
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return results;
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
// src/core/doctor/checks/workspace.ts
|
|
294
|
+
import * as fs4 from "fs";
|
|
295
|
+
var workspaceCheck = {
|
|
296
|
+
name: "Workspace",
|
|
297
|
+
order: 5,
|
|
298
|
+
async run(ctx) {
|
|
299
|
+
const results = [];
|
|
300
|
+
if (!ctx.config) {
|
|
301
|
+
results.push({ status: "fail", message: "Cannot check workspace \u2014 config not loaded" });
|
|
302
|
+
return results;
|
|
303
|
+
}
|
|
304
|
+
const baseDir = expandHome(ctx.config.workspace.baseDir);
|
|
305
|
+
if (!fs4.existsSync(baseDir)) {
|
|
306
|
+
results.push({
|
|
307
|
+
status: "warn",
|
|
308
|
+
message: `Workspace directory does not exist: ${baseDir}`,
|
|
309
|
+
fixable: true,
|
|
310
|
+
fixRisk: "safe",
|
|
311
|
+
fix: async () => {
|
|
312
|
+
fs4.mkdirSync(baseDir, { recursive: true });
|
|
313
|
+
return { success: true, message: "created directory" };
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
} else {
|
|
317
|
+
try {
|
|
318
|
+
fs4.accessSync(baseDir, fs4.constants.W_OK);
|
|
319
|
+
results.push({ status: "pass", message: `Workspace directory exists: ${baseDir}` });
|
|
320
|
+
} catch {
|
|
321
|
+
results.push({ status: "fail", message: `Workspace directory not writable: ${baseDir}` });
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return results;
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
// src/core/doctor/checks/plugins.ts
|
|
329
|
+
import * as fs5 from "fs";
|
|
330
|
+
import * as path2 from "path";
|
|
331
|
+
var pluginsCheck = {
|
|
332
|
+
name: "Plugins",
|
|
333
|
+
order: 6,
|
|
334
|
+
async run(ctx) {
|
|
335
|
+
const results = [];
|
|
336
|
+
if (!fs5.existsSync(ctx.pluginsDir)) {
|
|
337
|
+
results.push({
|
|
338
|
+
status: "warn",
|
|
339
|
+
message: "Plugins directory does not exist",
|
|
340
|
+
fixable: true,
|
|
341
|
+
fixRisk: "safe",
|
|
342
|
+
fix: async () => {
|
|
343
|
+
fs5.mkdirSync(ctx.pluginsDir, { recursive: true });
|
|
344
|
+
fs5.writeFileSync(
|
|
345
|
+
path2.join(ctx.pluginsDir, "package.json"),
|
|
346
|
+
JSON.stringify({ name: "openacp-plugins", private: true, dependencies: {} }, null, 2)
|
|
347
|
+
);
|
|
348
|
+
return { success: true, message: "initialized plugins directory" };
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
return results;
|
|
352
|
+
}
|
|
353
|
+
results.push({ status: "pass", message: "Plugins directory exists" });
|
|
354
|
+
const pkgPath = path2.join(ctx.pluginsDir, "package.json");
|
|
355
|
+
if (!fs5.existsSync(pkgPath)) {
|
|
356
|
+
results.push({
|
|
357
|
+
status: "warn",
|
|
358
|
+
message: "Plugins package.json missing",
|
|
359
|
+
fixable: true,
|
|
360
|
+
fixRisk: "safe",
|
|
361
|
+
fix: async () => {
|
|
362
|
+
fs5.writeFileSync(
|
|
363
|
+
pkgPath,
|
|
364
|
+
JSON.stringify({ name: "openacp-plugins", private: true, dependencies: {} }, null, 2)
|
|
365
|
+
);
|
|
366
|
+
return { success: true, message: "created package.json" };
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
return results;
|
|
370
|
+
}
|
|
371
|
+
try {
|
|
372
|
+
const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf-8"));
|
|
373
|
+
const deps = pkg.dependencies || {};
|
|
374
|
+
const count = Object.keys(deps).length;
|
|
375
|
+
results.push({ status: "pass", message: `Plugins package.json valid (${count} plugins)` });
|
|
376
|
+
} catch {
|
|
377
|
+
results.push({
|
|
378
|
+
status: "fail",
|
|
379
|
+
message: "Plugins package.json is invalid JSON",
|
|
380
|
+
fixable: true,
|
|
381
|
+
fixRisk: "risky",
|
|
382
|
+
fix: async () => {
|
|
383
|
+
fs5.writeFileSync(
|
|
384
|
+
pkgPath,
|
|
385
|
+
JSON.stringify({ name: "openacp-plugins", private: true, dependencies: {} }, null, 2)
|
|
386
|
+
);
|
|
387
|
+
return { success: true, message: "reset package.json" };
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
return results;
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
// src/core/doctor/checks/daemon.ts
|
|
396
|
+
import * as fs6 from "fs";
|
|
397
|
+
import * as net from "net";
|
|
398
|
+
function isProcessAlive(pid) {
|
|
399
|
+
try {
|
|
400
|
+
process.kill(pid, 0);
|
|
401
|
+
return true;
|
|
402
|
+
} catch {
|
|
403
|
+
return false;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
function checkPortInUse(port) {
|
|
407
|
+
return new Promise((resolve) => {
|
|
408
|
+
const server = net.createServer();
|
|
409
|
+
server.once("error", () => resolve(true));
|
|
410
|
+
server.once("listening", () => {
|
|
411
|
+
server.close();
|
|
412
|
+
resolve(false);
|
|
413
|
+
});
|
|
414
|
+
server.listen(port, "127.0.0.1");
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
var daemonCheck = {
|
|
418
|
+
name: "Daemon",
|
|
419
|
+
order: 7,
|
|
420
|
+
async run(ctx) {
|
|
421
|
+
const results = [];
|
|
422
|
+
if (fs6.existsSync(ctx.pidPath)) {
|
|
423
|
+
const content = fs6.readFileSync(ctx.pidPath, "utf-8").trim();
|
|
424
|
+
const pid = parseInt(content, 10);
|
|
425
|
+
if (isNaN(pid)) {
|
|
426
|
+
results.push({
|
|
427
|
+
status: "warn",
|
|
428
|
+
message: "PID file contains invalid data",
|
|
429
|
+
fixable: true,
|
|
430
|
+
fixRisk: "safe",
|
|
431
|
+
fix: async () => {
|
|
432
|
+
fs6.unlinkSync(ctx.pidPath);
|
|
433
|
+
return { success: true, message: "removed invalid PID file" };
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
} else if (!isProcessAlive(pid)) {
|
|
437
|
+
results.push({
|
|
438
|
+
status: "warn",
|
|
439
|
+
message: `Stale PID file (PID ${pid} not running)`,
|
|
440
|
+
fixable: true,
|
|
441
|
+
fixRisk: "safe",
|
|
442
|
+
fix: async () => {
|
|
443
|
+
fs6.unlinkSync(ctx.pidPath);
|
|
444
|
+
return { success: true, message: "removed stale PID file" };
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
} else {
|
|
448
|
+
results.push({ status: "pass", message: `Daemon running (PID ${pid})` });
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
if (fs6.existsSync(ctx.portFilePath)) {
|
|
452
|
+
const content = fs6.readFileSync(ctx.portFilePath, "utf-8").trim();
|
|
453
|
+
const port = parseInt(content, 10);
|
|
454
|
+
if (isNaN(port)) {
|
|
455
|
+
results.push({
|
|
456
|
+
status: "warn",
|
|
457
|
+
message: "Port file contains invalid data",
|
|
458
|
+
fixable: true,
|
|
459
|
+
fixRisk: "safe",
|
|
460
|
+
fix: async () => {
|
|
461
|
+
fs6.unlinkSync(ctx.portFilePath);
|
|
462
|
+
return { success: true, message: "removed invalid port file" };
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
} else {
|
|
466
|
+
results.push({ status: "pass", message: `Port file valid (port ${port})` });
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
if (ctx.config) {
|
|
470
|
+
const apiPort = ctx.config.api.port;
|
|
471
|
+
const inUse = await checkPortInUse(apiPort);
|
|
472
|
+
if (inUse) {
|
|
473
|
+
if (fs6.existsSync(ctx.pidPath)) {
|
|
474
|
+
const pid = parseInt(fs6.readFileSync(ctx.pidPath, "utf-8").trim(), 10);
|
|
475
|
+
if (!isNaN(pid) && isProcessAlive(pid)) {
|
|
476
|
+
results.push({ status: "pass", message: `API port ${apiPort} in use by OpenACP daemon` });
|
|
477
|
+
} else {
|
|
478
|
+
results.push({ status: "warn", message: `API port ${apiPort} in use by another process` });
|
|
479
|
+
}
|
|
480
|
+
} else {
|
|
481
|
+
results.push({ status: "warn", message: `API port ${apiPort} in use by another process` });
|
|
482
|
+
}
|
|
483
|
+
} else {
|
|
484
|
+
results.push({ status: "pass", message: `API port ${apiPort} available` });
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
return results;
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
// src/core/doctor/checks/tunnel.ts
|
|
492
|
+
import * as fs7 from "fs";
|
|
493
|
+
import * as path3 from "path";
|
|
494
|
+
import * as os from "os";
|
|
495
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
496
|
+
var BIN_DIR = path3.join(os.homedir(), ".openacp", "bin");
|
|
497
|
+
var BIN_NAME = os.platform() === "win32" ? "cloudflared.exe" : "cloudflared";
|
|
498
|
+
var BIN_PATH = path3.join(BIN_DIR, BIN_NAME);
|
|
499
|
+
var tunnelCheck = {
|
|
500
|
+
name: "Tunnel",
|
|
501
|
+
order: 8,
|
|
502
|
+
async run(ctx) {
|
|
503
|
+
const results = [];
|
|
504
|
+
if (!ctx.config) {
|
|
505
|
+
results.push({ status: "fail", message: "Cannot check tunnel \u2014 config not loaded" });
|
|
506
|
+
return results;
|
|
507
|
+
}
|
|
508
|
+
if (!ctx.config.tunnel.enabled) {
|
|
509
|
+
results.push({ status: "pass", message: "Tunnel not enabled (skipped)" });
|
|
510
|
+
return results;
|
|
511
|
+
}
|
|
512
|
+
const provider = ctx.config.tunnel.provider;
|
|
513
|
+
results.push({ status: "pass", message: `Tunnel provider: ${provider}` });
|
|
514
|
+
if (provider === "cloudflare") {
|
|
515
|
+
let found = false;
|
|
516
|
+
if (fs7.existsSync(BIN_PATH)) {
|
|
517
|
+
found = true;
|
|
518
|
+
} else {
|
|
519
|
+
try {
|
|
520
|
+
execFileSync2("which", ["cloudflared"], { stdio: "pipe" });
|
|
521
|
+
found = true;
|
|
522
|
+
} catch {
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
if (found) {
|
|
526
|
+
results.push({ status: "pass", message: "cloudflared binary found" });
|
|
527
|
+
} else {
|
|
528
|
+
results.push({
|
|
529
|
+
status: "warn",
|
|
530
|
+
message: "cloudflared binary not found",
|
|
531
|
+
fixable: true,
|
|
532
|
+
fixRisk: "safe",
|
|
533
|
+
fix: async () => {
|
|
534
|
+
try {
|
|
535
|
+
const { ensureCloudflared } = await import("./install-cloudflared-BTGUD7SW.js");
|
|
536
|
+
await ensureCloudflared();
|
|
537
|
+
return { success: true, message: "installed cloudflared" };
|
|
538
|
+
} catch (err) {
|
|
539
|
+
return { success: false, message: err instanceof Error ? err.message : String(err) };
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
const tunnelPort = ctx.config.tunnel.port;
|
|
546
|
+
if (tunnelPort < 1 || tunnelPort > 65535) {
|
|
547
|
+
results.push({ status: "fail", message: `Invalid tunnel port: ${tunnelPort}` });
|
|
548
|
+
} else {
|
|
549
|
+
results.push({ status: "pass", message: `Tunnel port: ${tunnelPort}` });
|
|
550
|
+
}
|
|
551
|
+
return results;
|
|
552
|
+
}
|
|
553
|
+
};
|
|
554
|
+
|
|
555
|
+
// src/core/doctor/index.ts
|
|
556
|
+
var ALL_CHECKS = [
|
|
557
|
+
configCheck,
|
|
558
|
+
agentsCheck,
|
|
559
|
+
telegramCheck,
|
|
560
|
+
storageCheck,
|
|
561
|
+
workspaceCheck,
|
|
562
|
+
pluginsCheck,
|
|
563
|
+
daemonCheck,
|
|
564
|
+
tunnelCheck
|
|
565
|
+
];
|
|
566
|
+
var CHECK_TIMEOUT_MS = 1e4;
|
|
567
|
+
var DoctorEngine = class {
|
|
568
|
+
dryRun;
|
|
569
|
+
constructor(options) {
|
|
570
|
+
this.dryRun = options?.dryRun ?? false;
|
|
571
|
+
}
|
|
572
|
+
async runAll() {
|
|
573
|
+
const ctx = await this.buildContext();
|
|
574
|
+
const checks = [...ALL_CHECKS].sort((a, b) => a.order - b.order);
|
|
575
|
+
const categories = [];
|
|
576
|
+
const pendingFixes = [];
|
|
577
|
+
const summary = { passed: 0, warnings: 0, failed: 0, fixed: 0 };
|
|
578
|
+
for (const check of checks) {
|
|
579
|
+
let results;
|
|
580
|
+
try {
|
|
581
|
+
results = await Promise.race([
|
|
582
|
+
check.run(ctx),
|
|
583
|
+
new Promise(
|
|
584
|
+
(_, reject) => setTimeout(() => reject(new Error("timeout")), CHECK_TIMEOUT_MS)
|
|
585
|
+
)
|
|
586
|
+
]);
|
|
587
|
+
} catch {
|
|
588
|
+
results = [{ status: "fail", message: `${check.name} check timed out` }];
|
|
589
|
+
}
|
|
590
|
+
for (const result of results) {
|
|
591
|
+
if (result.fixable && result.fix) {
|
|
592
|
+
if (result.fixRisk === "safe" && !this.dryRun) {
|
|
593
|
+
try {
|
|
594
|
+
const fixResult = await result.fix();
|
|
595
|
+
if (fixResult.success) {
|
|
596
|
+
result.message += ` \u2192 Fixed (${fixResult.message})`;
|
|
597
|
+
result.status = "warn";
|
|
598
|
+
delete result.fix;
|
|
599
|
+
summary.fixed++;
|
|
600
|
+
}
|
|
601
|
+
} catch {
|
|
602
|
+
}
|
|
603
|
+
} else if (result.fixRisk === "risky") {
|
|
604
|
+
pendingFixes.push({
|
|
605
|
+
category: check.name,
|
|
606
|
+
message: result.message,
|
|
607
|
+
fix: result.fix
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
if (result.status === "pass") summary.passed++;
|
|
612
|
+
else if (result.status === "warn") summary.warnings++;
|
|
613
|
+
else if (result.status === "fail") summary.failed++;
|
|
614
|
+
}
|
|
615
|
+
categories.push({ name: check.name, results });
|
|
616
|
+
}
|
|
617
|
+
return { categories, summary, pendingFixes };
|
|
618
|
+
}
|
|
619
|
+
async buildContext() {
|
|
620
|
+
const dataDir = path4.join(os2.homedir(), ".openacp");
|
|
621
|
+
const configPath = process.env.OPENACP_CONFIG_PATH || path4.join(dataDir, "config.json");
|
|
622
|
+
let config = null;
|
|
623
|
+
let rawConfig = null;
|
|
624
|
+
try {
|
|
625
|
+
const content = fs8.readFileSync(configPath, "utf-8");
|
|
626
|
+
rawConfig = JSON.parse(content);
|
|
627
|
+
const cm = new ConfigManager();
|
|
628
|
+
await cm.load();
|
|
629
|
+
config = cm.get();
|
|
630
|
+
} catch {
|
|
631
|
+
}
|
|
632
|
+
const logsDir = config ? expandHome(config.logging.logDir) : path4.join(dataDir, "logs");
|
|
633
|
+
return {
|
|
634
|
+
config,
|
|
635
|
+
rawConfig,
|
|
636
|
+
configPath,
|
|
637
|
+
dataDir,
|
|
638
|
+
sessionsPath: path4.join(dataDir, "sessions.json"),
|
|
639
|
+
pidPath: path4.join(dataDir, "openacp.pid"),
|
|
640
|
+
portFilePath: path4.join(dataDir, "api.port"),
|
|
641
|
+
pluginsDir: path4.join(dataDir, "plugins"),
|
|
642
|
+
logsDir
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
};
|
|
646
|
+
|
|
647
|
+
export {
|
|
648
|
+
DoctorEngine
|
|
649
|
+
};
|
|
650
|
+
//# sourceMappingURL=chunk-3DIPXFZJ.js.map
|