@askexenow/exe-os 0.9.285 → 0.9.287
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/deploy/compose/docker-compose.yml +4 -4
- package/deploy/stack-manifests/v0.9.json +1 -1
- package/dist/bin/exe-doctor.js +1 -1
- package/dist/bin/exe-support.js +1 -1
- package/dist/bin/stack-update.js +2 -2
- package/dist/bin/vps-health-gate.js +1 -1
- package/dist/chunk-ASL5PHCT.js +1934 -0
- package/dist/chunk-JUVZX5J5.js +369 -0
- package/dist/chunk-MOTE4CFR.js +14219 -0
- package/dist/chunk-XLF5F55U.js +230 -0
- package/dist/chunk-ZKM6XX4N.js +1076 -0
- package/dist/hooks/manifest.json +3 -3
- package/dist/hooks/prompt-submit.js +1 -1
- package/dist/hooks/session-start.js +1 -1
- package/dist/mcp/register-tools.js +3 -3
- package/dist/mcp/server.js +3 -3
- package/dist/stack-update-45SNTDZ6.js +76 -0
- package/dist/support-outbox-P3RIIA4L.js +503 -0
- package/package.json +1 -1
- package/release-notes.json +67 -67
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolveSupportAdminToken,
|
|
3
|
+
supportAdminHeaders
|
|
4
|
+
} from "./chunk-2XZ6X3PJ.js";
|
|
5
|
+
import {
|
|
6
|
+
markSupportReportSentSync
|
|
7
|
+
} from "./chunk-MMRUBN3I.js";
|
|
8
|
+
import {
|
|
9
|
+
loadLicense,
|
|
10
|
+
readCachedLicenseToken
|
|
11
|
+
} from "./chunk-YMKUXZIG.js";
|
|
12
|
+
import {
|
|
13
|
+
EXE_AI_DIR,
|
|
14
|
+
loadConfig
|
|
15
|
+
} from "./chunk-T3B5RK4H.js";
|
|
16
|
+
|
|
17
|
+
// src/bin/exe-support.ts
|
|
18
|
+
import { mkdirSync, readFileSync, unlinkSync, writeFileSync } from "fs";
|
|
19
|
+
import path from "path";
|
|
20
|
+
import { randomUUID } from "crypto";
|
|
21
|
+
var DEFAULT_BUG_ENDPOINT = "https://api.askexe.com/v1/support/bug-reports";
|
|
22
|
+
var DEFAULT_ADMIN_ENDPOINT = "https://api.askexe.com/admin/support/bug-reports";
|
|
23
|
+
async function main(argv = process.argv.slice(2)) {
|
|
24
|
+
const command = argv[0] && !argv[0].startsWith("--") ? argv[0] : "health";
|
|
25
|
+
const json = argv.includes("--json");
|
|
26
|
+
const project = getArg(argv, "--project") ?? "support-smoke";
|
|
27
|
+
if (command === "help" || argv.includes("--help") || argv.includes("-h")) {
|
|
28
|
+
printHelp();
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (command === "health") {
|
|
32
|
+
const result = await runHealth();
|
|
33
|
+
output(result, json, "health");
|
|
34
|
+
process.exitCode = hasFailures(result) ? 1 : 0;
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (command === "test") {
|
|
38
|
+
const result = await runTest(project);
|
|
39
|
+
output(result, json, "test");
|
|
40
|
+
process.exitCode = hasFailures(result) ? 1 : 0;
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (command === "flush") {
|
|
44
|
+
await runFlush(json);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (command === "status") {
|
|
48
|
+
await runStatus(json);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
console.error("Usage: exe-os support health|test|flush|status [--project <name>] [--json]");
|
|
52
|
+
process.exitCode = 1;
|
|
53
|
+
}
|
|
54
|
+
async function runHealth() {
|
|
55
|
+
const checks = [];
|
|
56
|
+
const endpoints = await resolveEndpoints();
|
|
57
|
+
checks.push(checkLocalWrite());
|
|
58
|
+
checks.push({
|
|
59
|
+
check: "license_key_present",
|
|
60
|
+
level: loadLicense() ? "pass" : "fail",
|
|
61
|
+
detail: loadLicense() ? "license.key found" : "missing ~/.exe-os/license.key; run exe-os setup or exe-os cloud setup",
|
|
62
|
+
next: loadLicense() ? void 0 : "Run `exe-os setup` or ask AskExe for the customer license key."
|
|
63
|
+
});
|
|
64
|
+
checks.push({
|
|
65
|
+
check: "license_token_cached",
|
|
66
|
+
level: readCachedLicenseToken() ? "pass" : "warn",
|
|
67
|
+
detail: readCachedLicenseToken() ? "cached license token found" : "no cached token yet; support can still use license.key",
|
|
68
|
+
next: readCachedLicenseToken() ? void 0 : "This is OK if license.key exists. It refreshes after cloud/license validation."
|
|
69
|
+
});
|
|
70
|
+
try {
|
|
71
|
+
const res = await fetch(endpoints.healthEndpoint, { method: "GET", signal: AbortSignal.timeout(1e4) });
|
|
72
|
+
const body = await safeJson(res);
|
|
73
|
+
checks.push({
|
|
74
|
+
check: "support_health_endpoint",
|
|
75
|
+
level: res.ok && body?.status !== "down" ? "pass" : "fail",
|
|
76
|
+
detail: `${res.status} ${body?.status ?? res.statusText}`,
|
|
77
|
+
next: res.ok ? void 0 : "Check internet access, DNS, or AskExe support status."
|
|
78
|
+
});
|
|
79
|
+
for (const remote of body?.checks ?? []) {
|
|
80
|
+
const name = remote.name ?? "unknown";
|
|
81
|
+
checks.push({
|
|
82
|
+
check: `server_${name}`,
|
|
83
|
+
level: remote.ok === true ? "pass" : name === "admin_inbox_configured" ? "warn" : "fail",
|
|
84
|
+
detail: remote.detail ?? (remote.ok ? "ok" : "failed"),
|
|
85
|
+
next: remote.ok ? void 0 : "AskExe must fix this server-side."
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
} catch (err) {
|
|
89
|
+
checks.push({
|
|
90
|
+
check: "support_health_endpoint",
|
|
91
|
+
level: "fail",
|
|
92
|
+
detail: err instanceof Error ? err.message : String(err),
|
|
93
|
+
next: "Check internet access, then run `exe-os support health` again."
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return checks;
|
|
97
|
+
}
|
|
98
|
+
async function runTest(project) {
|
|
99
|
+
const checks = await runHealth();
|
|
100
|
+
const endpoints = await resolveEndpoints();
|
|
101
|
+
const config = await loadConfig();
|
|
102
|
+
const licenseKey = loadLicense();
|
|
103
|
+
const licenseToken = readCachedLicenseToken();
|
|
104
|
+
const id = randomUUID();
|
|
105
|
+
const version = readPackageVersion();
|
|
106
|
+
const reportPath = writeLocalTestReport(id, project, version);
|
|
107
|
+
checks.push({ check: "local_report_file", level: "pass", detail: reportPath });
|
|
108
|
+
if (!licenseKey && !licenseToken) {
|
|
109
|
+
checks.push({
|
|
110
|
+
check: "upstream_post",
|
|
111
|
+
level: "fail",
|
|
112
|
+
detail: "not sent because this device has no license key/token",
|
|
113
|
+
next: "Run `exe-os setup` or ask AskExe to provision this customer device."
|
|
114
|
+
});
|
|
115
|
+
return checks;
|
|
116
|
+
}
|
|
117
|
+
const payload = {
|
|
118
|
+
id,
|
|
119
|
+
title: `TEST \u2014 ${project} support intake (${version})`,
|
|
120
|
+
classification: "unclear",
|
|
121
|
+
severity: "p3",
|
|
122
|
+
summary: "Synthetic exe-os support intake smoke test. Safe to close.",
|
|
123
|
+
customer_impact: "No customer impact; diagnostic only.",
|
|
124
|
+
reproduction_steps: ["Run exe-os support test"],
|
|
125
|
+
expected: "The report reaches AskExe support intake and can be triaged.",
|
|
126
|
+
actual: "Smoke test submitted by exe-os support test.",
|
|
127
|
+
package_version: `@askexenow/exe-os@${version}`,
|
|
128
|
+
project_name: project,
|
|
129
|
+
agent_id: "support-test",
|
|
130
|
+
agent_role: "diagnostic",
|
|
131
|
+
report_path: reportPath,
|
|
132
|
+
markdown: readFileSync(reportPath, "utf8")
|
|
133
|
+
};
|
|
134
|
+
try {
|
|
135
|
+
const headers = {
|
|
136
|
+
"content-type": "application/json",
|
|
137
|
+
...supportAdminHeaders(config)
|
|
138
|
+
};
|
|
139
|
+
if (licenseKey) headers["x-exe-license-key"] = licenseKey;
|
|
140
|
+
if (licenseToken) headers["x-exe-license-token"] = licenseToken;
|
|
141
|
+
const res = await fetch(endpoints.bugEndpoint, {
|
|
142
|
+
method: "POST",
|
|
143
|
+
headers,
|
|
144
|
+
body: JSON.stringify(payload),
|
|
145
|
+
signal: AbortSignal.timeout(15e3)
|
|
146
|
+
});
|
|
147
|
+
const data = await safeJson(res);
|
|
148
|
+
checks.push({
|
|
149
|
+
check: "upstream_post",
|
|
150
|
+
level: res.ok ? "pass" : "fail",
|
|
151
|
+
detail: res.ok ? `sent id=${String(data?.id ?? id)}` : summarizeHttpFailure(res.status, data),
|
|
152
|
+
next: res.ok ? void 0 : nextForPostFailure(res.status)
|
|
153
|
+
});
|
|
154
|
+
if (res.ok) {
|
|
155
|
+
markSupportReportSentSync(reportPath);
|
|
156
|
+
checks.push(await maybeCloseAdmin(String(data?.id ?? id), endpoints.adminEndpoint, version, config));
|
|
157
|
+
}
|
|
158
|
+
} catch (err) {
|
|
159
|
+
checks.push({
|
|
160
|
+
check: "upstream_post",
|
|
161
|
+
level: "fail",
|
|
162
|
+
detail: err instanceof Error ? err.message : String(err),
|
|
163
|
+
next: "Check internet access, then run `exe-os support test` again."
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
return checks;
|
|
167
|
+
}
|
|
168
|
+
async function resolveEndpoints() {
|
|
169
|
+
const config = await loadConfig();
|
|
170
|
+
const bugEndpoint = process.env.EXE_SUPPORT_BUG_REPORT_ENDPOINT ?? config.support?.bugReportEndpoint ?? DEFAULT_BUG_ENDPOINT;
|
|
171
|
+
const healthEndpoint = process.env.EXE_SUPPORT_HEALTH_ENDPOINT ?? bugEndpoint.replace(/\/bug-reports\/?$/, "/health");
|
|
172
|
+
const adminEndpoint = process.env.ASKEXE_SUPPORT_ADMIN_ENDPOINT ?? process.env.EXE_SUPPORT_ADMIN_ENDPOINT ?? DEFAULT_ADMIN_ENDPOINT;
|
|
173
|
+
return { bugEndpoint, healthEndpoint, adminEndpoint };
|
|
174
|
+
}
|
|
175
|
+
function checkLocalWrite() {
|
|
176
|
+
const dir = path.join(EXE_AI_DIR, "bug-reports");
|
|
177
|
+
const testPath = path.join(dir, ".support-write-test");
|
|
178
|
+
try {
|
|
179
|
+
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
180
|
+
writeFileSync(testPath, "ok\n", { mode: 384 });
|
|
181
|
+
unlinkSync(testPath);
|
|
182
|
+
return { check: "local_bug_report_dir_writable", level: "pass", detail: dir };
|
|
183
|
+
} catch (err) {
|
|
184
|
+
return {
|
|
185
|
+
check: "local_bug_report_dir_writable",
|
|
186
|
+
level: "fail",
|
|
187
|
+
detail: err instanceof Error ? err.message : String(err),
|
|
188
|
+
next: "Fix permissions on ~/.exe-os or rerun setup on a writable user account."
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function writeLocalTestReport(id, project, version) {
|
|
193
|
+
const dir = path.join(EXE_AI_DIR, "bug-reports");
|
|
194
|
+
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
195
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
196
|
+
const filePath = path.join(dir, `${date}-support-intake-test-${id.slice(0, 8)}.md`);
|
|
197
|
+
writeFileSync(filePath, `# TEST \u2014 ${project} support intake
|
|
198
|
+
|
|
199
|
+
Report ID: ${id}
|
|
200
|
+
Package: @askexenow/exe-os@${version}
|
|
201
|
+
|
|
202
|
+
Synthetic smoke test from \`exe-os support test\`. Safe to close.
|
|
203
|
+
`, { mode: 384 });
|
|
204
|
+
return filePath;
|
|
205
|
+
}
|
|
206
|
+
async function maybeCloseAdmin(id, adminEndpoint, version, config) {
|
|
207
|
+
const token = resolveSupportAdminToken(config);
|
|
208
|
+
if (!token) {
|
|
209
|
+
return { check: "askexe_admin_autoclose", level: "warn", detail: "skipped; admin token is AskExe-only" };
|
|
210
|
+
}
|
|
211
|
+
try {
|
|
212
|
+
const res = await fetch(`${adminEndpoint}/${encodeURIComponent(id)}`, {
|
|
213
|
+
method: "PATCH",
|
|
214
|
+
headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
|
|
215
|
+
body: JSON.stringify({
|
|
216
|
+
status: "closed",
|
|
217
|
+
triage_notes: "Auto-closed synthetic support smoke test.",
|
|
218
|
+
fixed_version: version
|
|
219
|
+
}),
|
|
220
|
+
signal: AbortSignal.timeout(1e4)
|
|
221
|
+
});
|
|
222
|
+
return {
|
|
223
|
+
check: "askexe_admin_autoclose",
|
|
224
|
+
level: res.ok ? "pass" : "warn",
|
|
225
|
+
detail: res.ok ? "closed" : `${res.status} ${await res.text()}`,
|
|
226
|
+
next: res.ok ? void 0 : "Report was sent; AskExe can close the smoke report manually."
|
|
227
|
+
};
|
|
228
|
+
} catch (err) {
|
|
229
|
+
return {
|
|
230
|
+
check: "askexe_admin_autoclose",
|
|
231
|
+
level: "warn",
|
|
232
|
+
detail: err instanceof Error ? err.message : String(err),
|
|
233
|
+
next: "Report was sent; AskExe can close the smoke report manually."
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
function readPackageVersion() {
|
|
238
|
+
let dir = path.dirname(new URL(import.meta.url).pathname);
|
|
239
|
+
for (let i = 0; i < 6; i++) {
|
|
240
|
+
const pkg = path.join(dir, "package.json");
|
|
241
|
+
try {
|
|
242
|
+
const parsed = JSON.parse(readFileSync(pkg, "utf8"));
|
|
243
|
+
if (parsed.version) return parsed.version;
|
|
244
|
+
} catch {
|
|
245
|
+
}
|
|
246
|
+
dir = path.dirname(dir);
|
|
247
|
+
}
|
|
248
|
+
return "unknown";
|
|
249
|
+
}
|
|
250
|
+
async function safeJson(res) {
|
|
251
|
+
try {
|
|
252
|
+
return await res.json();
|
|
253
|
+
} catch {
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
function getArg(argv, name) {
|
|
258
|
+
const idx = argv.indexOf(name);
|
|
259
|
+
if (idx >= 0) return argv[idx + 1];
|
|
260
|
+
const prefix = `${name}=`;
|
|
261
|
+
const found = argv.find((arg) => arg.startsWith(prefix));
|
|
262
|
+
return found?.slice(prefix.length);
|
|
263
|
+
}
|
|
264
|
+
function hasFailures(rows) {
|
|
265
|
+
return rows.some((row) => row.level === "fail");
|
|
266
|
+
}
|
|
267
|
+
function summarizeHttpFailure(status, data) {
|
|
268
|
+
const error = data?.error;
|
|
269
|
+
return `${status} ${error?.message ?? JSON.stringify(data)}`;
|
|
270
|
+
}
|
|
271
|
+
function nextForPostFailure(status) {
|
|
272
|
+
if (status === 401) return "License credentials were not sent. Run `exe-os support health` and check license_key_present.";
|
|
273
|
+
if (status === 403) return "License is valid locally but not accepted by support intake. AskExe must check server-side license provisioning.";
|
|
274
|
+
if (status >= 500) return "AskExe support intake is unhealthy server-side. Try again shortly and report the local file path.";
|
|
275
|
+
return "Run `exe-os support health`; if it passes, send this output to AskExe.";
|
|
276
|
+
}
|
|
277
|
+
async function runFlush(json) {
|
|
278
|
+
const { flushSupportOutbox } = await import("./support-outbox-P3RIIA4L.js");
|
|
279
|
+
const result = await flushSupportOutbox({ maxPerFlush: 10 });
|
|
280
|
+
if (json) {
|
|
281
|
+
console.log(JSON.stringify(result, null, 2));
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
console.log("\nexe-os support flush\n");
|
|
285
|
+
console.log(` Bug reports sent: ${result.bugsSent}`);
|
|
286
|
+
console.log(` Feature requests sent: ${result.featuresSent}`);
|
|
287
|
+
console.log(` Errors: ${result.errors}`);
|
|
288
|
+
console.log(` Skipped: ${result.skipped}`);
|
|
289
|
+
console.log("");
|
|
290
|
+
const total = result.bugsSent + result.featuresSent;
|
|
291
|
+
if (total > 0) {
|
|
292
|
+
console.log(`Flushed ${total} item(s) to AskExe.`);
|
|
293
|
+
} else if (result.errors > 0) {
|
|
294
|
+
console.log("Nothing sent \u2014 network errors occurred. Retry later.");
|
|
295
|
+
} else {
|
|
296
|
+
console.log("Outbox is empty \u2014 nothing to flush.");
|
|
297
|
+
}
|
|
298
|
+
console.log("");
|
|
299
|
+
}
|
|
300
|
+
async function runStatus(json) {
|
|
301
|
+
const { getOutboxStatus } = await import("./support-outbox-P3RIIA4L.js");
|
|
302
|
+
const status = await getOutboxStatus();
|
|
303
|
+
if (json) {
|
|
304
|
+
console.log(JSON.stringify(status, null, 2));
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
console.log("\nexe-os support status\n");
|
|
308
|
+
const bugs = status.bugReports;
|
|
309
|
+
const features = status.featureRequests;
|
|
310
|
+
console.log(` Bug reports: ${bugs.unsent} unsent / ${bugs.total} total${bugs.oldest ? ` (oldest: ${bugs.oldest.slice(0, 10)})` : ""}`);
|
|
311
|
+
console.log(` Feature requests: ${features.unsent} unsent / ${features.total} total${features.oldest ? ` (oldest: ${features.oldest.slice(0, 10)})` : ""}`);
|
|
312
|
+
console.log("");
|
|
313
|
+
const totalUnsent = bugs.unsent + features.unsent;
|
|
314
|
+
if (totalUnsent > 0) {
|
|
315
|
+
console.log(`${totalUnsent} unsent item(s). Run \`exe-os support flush\` to send manually.`);
|
|
316
|
+
} else {
|
|
317
|
+
console.log("All items sent \u2014 outbox is clear.");
|
|
318
|
+
}
|
|
319
|
+
console.log("");
|
|
320
|
+
}
|
|
321
|
+
function printHelp() {
|
|
322
|
+
console.log(`
|
|
323
|
+
exe-os support
|
|
324
|
+
|
|
325
|
+
Customer-safe diagnostics for AskExe support intake.
|
|
326
|
+
|
|
327
|
+
Commands:
|
|
328
|
+
exe-os support health Check local setup + AskExe support server health
|
|
329
|
+
exe-os support test --project hygo Send a safe smoke report end-to-end
|
|
330
|
+
exe-os support flush Force-flush unsent bug reports + feature requests
|
|
331
|
+
exe-os support status Show outbox status (unsent counts)
|
|
332
|
+
|
|
333
|
+
What success means:
|
|
334
|
+
health = this device looks ready
|
|
335
|
+
test = AskExe received a real report from this device
|
|
336
|
+
flush = all unsent items attempted delivery
|
|
337
|
+
status = visibility into what's queued
|
|
338
|
+
`);
|
|
339
|
+
}
|
|
340
|
+
function output(rows, json, command) {
|
|
341
|
+
if (json) {
|
|
342
|
+
console.log(JSON.stringify({ ok: !hasFailures(rows), checks: rows }, null, 2));
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
console.log(`
|
|
346
|
+
exe-os support ${command}
|
|
347
|
+
`);
|
|
348
|
+
for (const row of rows) {
|
|
349
|
+
const icon = row.level === "pass" ? "\u2705" : row.level === "warn" ? "\u26A0\uFE0F " : "\u274C";
|
|
350
|
+
console.log(`${icon} ${row.check}: ${row.detail}`);
|
|
351
|
+
if (row.next) console.log(` \u2192 ${row.next}`);
|
|
352
|
+
}
|
|
353
|
+
console.log("");
|
|
354
|
+
if (hasFailures(rows)) {
|
|
355
|
+
console.log("Result: not ready. Fix the failed item(s), then rerun this command.");
|
|
356
|
+
} else if (rows.some((row) => row.level === "warn")) {
|
|
357
|
+
console.log("Result: ready with warnings. Bug reports can be sent; warnings are informational unless AskExe asked for stricter validation.");
|
|
358
|
+
} else {
|
|
359
|
+
console.log(command === "test" ? "Result: ready. AskExe received the test report." : "Result: ready. Run `exe-os support test --project <customer>` for an end-to-end send test.");
|
|
360
|
+
}
|
|
361
|
+
console.log("");
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export {
|
|
365
|
+
main,
|
|
366
|
+
runHealth,
|
|
367
|
+
runTest,
|
|
368
|
+
hasFailures
|
|
369
|
+
};
|