@askexenow/exe-os 0.9.0 → 0.9.1
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/dist/bin/cli.js +124 -32
- package/dist/bin/exe-boot.js +89 -5
- package/dist/bin/exe-dispatch.js +96 -6
- package/dist/bin/exe-doctor.js +55 -2
- package/dist/bin/exe-gateway.js +89 -5
- package/dist/bin/exe-new-employee.js +17 -13
- package/dist/bin/exe-session-cleanup.js +91 -1
- package/dist/bin/exe-start-codex.js +12 -8
- package/dist/bin/git-sweep.js +96 -6
- package/dist/bin/install.js +17 -13
- package/dist/bin/scan-tasks.js +96 -6
- package/dist/gateway/index.js +95 -5
- package/dist/hooks/bug-report-worker.js +91 -1
- package/dist/hooks/commit-complete.js +96 -6
- package/dist/hooks/error-recall.js +14 -0
- package/dist/hooks/ingest-worker.js +85 -1
- package/dist/hooks/ingest.js +11 -0
- package/dist/hooks/pre-compact.js +96 -6
- package/dist/hooks/prompt-submit.js +17 -0
- package/dist/hooks/session-end.js +96 -6
- package/dist/hooks/stop.js +18 -0
- package/dist/index.js +94 -10
- package/dist/lib/exe-daemon.js +17 -0
- package/dist/lib/tasks.js +91 -1
- package/dist/lib/tmux-routing.js +91 -1
- package/dist/mcp/server.js +339 -34
- package/dist/mcp/tools/create-task.js +91 -1
- package/dist/mcp/tools/update-task.js +91 -1
- package/dist/runtime/index.js +90 -6
- package/dist/tui/App.js +90 -6
- package/package.json +1 -1
package/dist/mcp/server.js
CHANGED
|
@@ -6125,6 +6125,14 @@ var init_agent_config = __esm({
|
|
|
6125
6125
|
});
|
|
6126
6126
|
|
|
6127
6127
|
// src/lib/intercom-queue.ts
|
|
6128
|
+
var intercom_queue_exports = {};
|
|
6129
|
+
__export(intercom_queue_exports, {
|
|
6130
|
+
clearQueueForAgent: () => clearQueueForAgent,
|
|
6131
|
+
drainForSession: () => drainForSession,
|
|
6132
|
+
drainQueue: () => drainQueue,
|
|
6133
|
+
queueIntercom: () => queueIntercom,
|
|
6134
|
+
readQueue: () => readQueue
|
|
6135
|
+
});
|
|
6128
6136
|
import { readFileSync as readFileSync11, writeFileSync as writeFileSync8, renameSync as renameSync3, existsSync as existsSync13, mkdirSync as mkdirSync6 } from "fs";
|
|
6129
6137
|
import path17 from "path";
|
|
6130
6138
|
import os7 from "os";
|
|
@@ -6163,11 +6171,80 @@ function queueIntercom(targetSession, reason) {
|
|
|
6163
6171
|
}
|
|
6164
6172
|
writeQueue(queue);
|
|
6165
6173
|
}
|
|
6166
|
-
|
|
6174
|
+
function drainQueue(isSessionBusy2, sendKeys) {
|
|
6175
|
+
const queue = readQueue();
|
|
6176
|
+
if (queue.length === 0) return { drained: 0, failed: 0 };
|
|
6177
|
+
const remaining = [];
|
|
6178
|
+
let drained = 0;
|
|
6179
|
+
let failed = 0;
|
|
6180
|
+
for (const item of queue) {
|
|
6181
|
+
const age = Date.now() - new Date(item.queuedAt).getTime();
|
|
6182
|
+
if (age > TTL_MS) {
|
|
6183
|
+
logQueue(`EXPIRED \u2192 ${item.targetSession} (${Math.round(age / 6e4)}min old, reason: ${item.reason})`);
|
|
6184
|
+
failed++;
|
|
6185
|
+
continue;
|
|
6186
|
+
}
|
|
6187
|
+
try {
|
|
6188
|
+
if (!isSessionBusy2(item.targetSession)) {
|
|
6189
|
+
const success = sendKeys(item.targetSession);
|
|
6190
|
+
if (success) {
|
|
6191
|
+
logQueue(`DRAINED \u2192 ${item.targetSession} (after ${item.attempts} retries)`);
|
|
6192
|
+
drained++;
|
|
6193
|
+
continue;
|
|
6194
|
+
}
|
|
6195
|
+
}
|
|
6196
|
+
} catch {
|
|
6197
|
+
}
|
|
6198
|
+
item.attempts++;
|
|
6199
|
+
if (item.attempts >= MAX_RETRIES2) {
|
|
6200
|
+
logQueue(`FAILED \u2192 ${item.targetSession} (${MAX_RETRIES2} retries exhausted, reason: ${item.reason})`);
|
|
6201
|
+
failed++;
|
|
6202
|
+
continue;
|
|
6203
|
+
}
|
|
6204
|
+
remaining.push(item);
|
|
6205
|
+
}
|
|
6206
|
+
writeQueue(remaining);
|
|
6207
|
+
return { drained, failed };
|
|
6208
|
+
}
|
|
6209
|
+
function drainForSession(targetSession, sendKeys) {
|
|
6210
|
+
const queue = readQueue();
|
|
6211
|
+
const match = queue.findIndex((q) => q.targetSession === targetSession);
|
|
6212
|
+
if (match < 0) return false;
|
|
6213
|
+
const success = sendKeys(targetSession);
|
|
6214
|
+
if (success) {
|
|
6215
|
+
queue.splice(match, 1);
|
|
6216
|
+
writeQueue(queue);
|
|
6217
|
+
logQueue(`DRAINED \u2192 ${targetSession} (prompt-submit trigger)`);
|
|
6218
|
+
return true;
|
|
6219
|
+
}
|
|
6220
|
+
return false;
|
|
6221
|
+
}
|
|
6222
|
+
function clearQueueForAgent(agentName) {
|
|
6223
|
+
const queue = readQueue();
|
|
6224
|
+
const before = queue.length;
|
|
6225
|
+
const filtered = queue.filter((q) => !q.targetSession.startsWith(`${agentName}-`));
|
|
6226
|
+
if (filtered.length < before) {
|
|
6227
|
+
writeQueue(filtered);
|
|
6228
|
+
logQueue(`CLEARED ${before - filtered.length} stale item(s) for ${agentName}`);
|
|
6229
|
+
}
|
|
6230
|
+
}
|
|
6231
|
+
function logQueue(msg) {
|
|
6232
|
+
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] [queue] ${msg}
|
|
6233
|
+
`;
|
|
6234
|
+
process.stderr.write(`[intercom-queue] ${msg}
|
|
6235
|
+
`);
|
|
6236
|
+
try {
|
|
6237
|
+
const { appendFileSync: appendFileSync3 } = __require("fs");
|
|
6238
|
+
appendFileSync3(INTERCOM_LOG, line);
|
|
6239
|
+
} catch {
|
|
6240
|
+
}
|
|
6241
|
+
}
|
|
6242
|
+
var QUEUE_PATH, MAX_RETRIES2, TTL_MS, INTERCOM_LOG;
|
|
6167
6243
|
var init_intercom_queue = __esm({
|
|
6168
6244
|
"src/lib/intercom-queue.ts"() {
|
|
6169
6245
|
"use strict";
|
|
6170
6246
|
QUEUE_PATH = path17.join(os7.homedir(), ".exe-os", "intercom-queue.json");
|
|
6247
|
+
MAX_RETRIES2 = 5;
|
|
6171
6248
|
TTL_MS = 60 * 60 * 1e3;
|
|
6172
6249
|
INTERCOM_LOG = path17.join(os7.homedir(), ".exe-os", "intercom.log");
|
|
6173
6250
|
}
|
|
@@ -7806,6 +7883,13 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
7806
7883
|
await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
|
|
7807
7884
|
} catch {
|
|
7808
7885
|
}
|
|
7886
|
+
if (input.status === "done" || input.status === "cancelled") {
|
|
7887
|
+
try {
|
|
7888
|
+
const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
7889
|
+
clearQueueForAgent2(String(row.assigned_to));
|
|
7890
|
+
} catch {
|
|
7891
|
+
}
|
|
7892
|
+
}
|
|
7809
7893
|
try {
|
|
7810
7894
|
await writeCheckpoint({
|
|
7811
7895
|
taskId,
|
|
@@ -9651,7 +9735,7 @@ async function deliverLocalMessage(messageId) {
|
|
|
9651
9735
|
return true;
|
|
9652
9736
|
} catch {
|
|
9653
9737
|
const newRetryCount = msg.retryCount + 1;
|
|
9654
|
-
if (newRetryCount >=
|
|
9738
|
+
if (newRetryCount >= MAX_RETRIES3) {
|
|
9655
9739
|
await markFailed(messageId, "session unavailable after 10 retries");
|
|
9656
9740
|
} else {
|
|
9657
9741
|
await client.execute({
|
|
@@ -9669,13 +9753,13 @@ async function markFailed(messageId, reason) {
|
|
|
9669
9753
|
args: [(/* @__PURE__ */ new Date()).toISOString(), reason, messageId]
|
|
9670
9754
|
});
|
|
9671
9755
|
}
|
|
9672
|
-
var
|
|
9756
|
+
var MAX_RETRIES3, _wsClientSend;
|
|
9673
9757
|
var init_messaging = __esm({
|
|
9674
9758
|
"src/lib/messaging.ts"() {
|
|
9675
9759
|
"use strict";
|
|
9676
9760
|
init_database();
|
|
9677
9761
|
init_tmux_routing();
|
|
9678
|
-
|
|
9762
|
+
MAX_RETRIES3 = 10;
|
|
9679
9763
|
_wsClientSend = null;
|
|
9680
9764
|
}
|
|
9681
9765
|
});
|
|
@@ -10107,7 +10191,7 @@ async function wikiFetch(config2, path38, method = "GET", body) {
|
|
|
10107
10191
|
"Content-Type": "application/json"
|
|
10108
10192
|
};
|
|
10109
10193
|
const controller = new AbortController();
|
|
10110
|
-
const timeout = setTimeout(() => controller.abort(),
|
|
10194
|
+
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS3);
|
|
10111
10195
|
try {
|
|
10112
10196
|
let response;
|
|
10113
10197
|
try {
|
|
@@ -10120,7 +10204,7 @@ async function wikiFetch(config2, path38, method = "GET", body) {
|
|
|
10120
10204
|
} catch {
|
|
10121
10205
|
clearTimeout(timeout);
|
|
10122
10206
|
const retryController = new AbortController();
|
|
10123
|
-
const retryTimeout = setTimeout(() => retryController.abort(),
|
|
10207
|
+
const retryTimeout = setTimeout(() => retryController.abort(), REQUEST_TIMEOUT_MS3);
|
|
10124
10208
|
try {
|
|
10125
10209
|
await new Promise((r) => setTimeout(r, 500));
|
|
10126
10210
|
response = await fetch(url, {
|
|
@@ -10224,12 +10308,12 @@ async function getChatHistory(client, workspaceSlug, limit = 50) {
|
|
|
10224
10308
|
sentAt: h.sentAt ?? 0
|
|
10225
10309
|
}));
|
|
10226
10310
|
}
|
|
10227
|
-
var LOCAL_WIKI_URL,
|
|
10311
|
+
var LOCAL_WIKI_URL, REQUEST_TIMEOUT_MS3;
|
|
10228
10312
|
var init_wiki_client = __esm({
|
|
10229
10313
|
"src/lib/wiki-client.ts"() {
|
|
10230
10314
|
"use strict";
|
|
10231
10315
|
LOCAL_WIKI_URL = process.env.EXE_WIKI_URL || "http://localhost:3001";
|
|
10232
|
-
|
|
10316
|
+
REQUEST_TIMEOUT_MS3 = 8e3;
|
|
10233
10317
|
}
|
|
10234
10318
|
});
|
|
10235
10319
|
|
|
@@ -10362,7 +10446,7 @@ __export(crdt_sync_exports, {
|
|
|
10362
10446
|
rebuildFromDb: () => rebuildFromDb
|
|
10363
10447
|
});
|
|
10364
10448
|
import * as Y from "yjs";
|
|
10365
|
-
import { readFileSync as
|
|
10449
|
+
import { readFileSync as readFileSync22, writeFileSync as writeFileSync16, existsSync as existsSync27, mkdirSync as mkdirSync13, unlinkSync as unlinkSync9 } from "fs";
|
|
10366
10450
|
import path34 from "path";
|
|
10367
10451
|
import { homedir as homedir5 } from "os";
|
|
10368
10452
|
function getStatePath() {
|
|
@@ -10377,7 +10461,7 @@ function initCrdtDoc() {
|
|
|
10377
10461
|
const sp = getStatePath();
|
|
10378
10462
|
if (existsSync27(sp)) {
|
|
10379
10463
|
try {
|
|
10380
|
-
const state =
|
|
10464
|
+
const state = readFileSync22(sp);
|
|
10381
10465
|
Y.applyUpdate(doc, new Uint8Array(state));
|
|
10382
10466
|
} catch {
|
|
10383
10467
|
console.warn("[crdt-sync] WARN: corrupted state file, rebuilding from DB");
|
|
@@ -14645,13 +14729,123 @@ var HostingerError = class extends Error {
|
|
|
14645
14729
|
}
|
|
14646
14730
|
};
|
|
14647
14731
|
|
|
14732
|
+
// src/lib/cloudflare-dns.ts
|
|
14733
|
+
var CLOUDFLARE_API_BASE_URL = "https://api.cloudflare.com/client/v4";
|
|
14734
|
+
var DNS_RECORD_TYPE_A = "A";
|
|
14735
|
+
var DEFAULT_PROXIED = true;
|
|
14736
|
+
var DEFAULT_TTL = 1;
|
|
14737
|
+
var REQUEST_TIMEOUT_MS2 = 3e4;
|
|
14738
|
+
var UNKNOWN_ERROR_CODE = "unknown";
|
|
14739
|
+
async function createARecord(cfApiToken, zoneId, domain, ip, opts) {
|
|
14740
|
+
const proxied = opts?.proxied ?? DEFAULT_PROXIED;
|
|
14741
|
+
const ttl = opts?.ttl ?? DEFAULT_TTL;
|
|
14742
|
+
const response = await requestCloudflare(cfApiToken, zoneId, {
|
|
14743
|
+
method: "POST",
|
|
14744
|
+
body: {
|
|
14745
|
+
type: DNS_RECORD_TYPE_A,
|
|
14746
|
+
name: domain,
|
|
14747
|
+
content: ip,
|
|
14748
|
+
proxied,
|
|
14749
|
+
ttl
|
|
14750
|
+
}
|
|
14751
|
+
});
|
|
14752
|
+
return mapDnsRecord(response);
|
|
14753
|
+
}
|
|
14754
|
+
async function requestCloudflare(cfApiToken, zoneId, options) {
|
|
14755
|
+
const response = await fetch(buildUrl(zoneId, options.path, options.query), {
|
|
14756
|
+
method: options.method,
|
|
14757
|
+
headers: buildHeaders(cfApiToken),
|
|
14758
|
+
body: options.body ? JSON.stringify(options.body) : void 0,
|
|
14759
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS2)
|
|
14760
|
+
});
|
|
14761
|
+
const envelope = await parseEnvelope(response);
|
|
14762
|
+
if (!response.ok || !envelope.success) {
|
|
14763
|
+
throw toCloudflareError(response, envelope);
|
|
14764
|
+
}
|
|
14765
|
+
return envelope.result;
|
|
14766
|
+
}
|
|
14767
|
+
function buildUrl(zoneId, path38 = "/dns_records", query) {
|
|
14768
|
+
const normalizedPath = path38.startsWith("/") ? path38 : `/${path38}`;
|
|
14769
|
+
const url = new URL(
|
|
14770
|
+
`${CLOUDFLARE_API_BASE_URL}/zones/${zoneId}${normalizedPath}`
|
|
14771
|
+
);
|
|
14772
|
+
if (query) {
|
|
14773
|
+
url.search = query.toString();
|
|
14774
|
+
}
|
|
14775
|
+
return url.toString();
|
|
14776
|
+
}
|
|
14777
|
+
function buildHeaders(cfApiToken) {
|
|
14778
|
+
return {
|
|
14779
|
+
Authorization: `Bearer ${cfApiToken}`,
|
|
14780
|
+
"Content-Type": "application/json",
|
|
14781
|
+
Accept: "application/json"
|
|
14782
|
+
};
|
|
14783
|
+
}
|
|
14784
|
+
async function parseEnvelope(response) {
|
|
14785
|
+
try {
|
|
14786
|
+
return await response.json();
|
|
14787
|
+
} catch {
|
|
14788
|
+
return {
|
|
14789
|
+
success: false,
|
|
14790
|
+
errors: [
|
|
14791
|
+
{
|
|
14792
|
+
code: UNKNOWN_ERROR_CODE,
|
|
14793
|
+
message: `HTTP ${response.status}: ${response.statusText}`
|
|
14794
|
+
}
|
|
14795
|
+
],
|
|
14796
|
+
messages: [],
|
|
14797
|
+
result: null
|
|
14798
|
+
};
|
|
14799
|
+
}
|
|
14800
|
+
}
|
|
14801
|
+
function toCloudflareError(response, envelope) {
|
|
14802
|
+
const firstError = envelope.errors[0];
|
|
14803
|
+
const errorCode = String(firstError?.code ?? UNKNOWN_ERROR_CODE);
|
|
14804
|
+
const message = firstError?.message ?? `HTTP ${response.status}: ${response.statusText}`;
|
|
14805
|
+
return new CloudflareError(response.status, errorCode, message);
|
|
14806
|
+
}
|
|
14807
|
+
function mapDnsRecord(record) {
|
|
14808
|
+
return {
|
|
14809
|
+
recordId: record.id,
|
|
14810
|
+
name: record.name,
|
|
14811
|
+
ip: record.content,
|
|
14812
|
+
proxied: record.proxied
|
|
14813
|
+
};
|
|
14814
|
+
}
|
|
14815
|
+
var CloudflareError = class extends Error {
|
|
14816
|
+
status;
|
|
14817
|
+
errorCode;
|
|
14818
|
+
constructor(status, errorCode, message) {
|
|
14819
|
+
super(message);
|
|
14820
|
+
this.name = "CloudflareError";
|
|
14821
|
+
this.status = status;
|
|
14822
|
+
this.errorCode = errorCode;
|
|
14823
|
+
}
|
|
14824
|
+
};
|
|
14825
|
+
|
|
14648
14826
|
// src/mcp/tools/deploy-client.ts
|
|
14649
14827
|
init_config();
|
|
14650
14828
|
var execFileAsync = promisify(execFile);
|
|
14651
14829
|
var POLL_INTERVAL_MS = 1e4;
|
|
14652
14830
|
var POLL_MAX_ATTEMPTS = 60;
|
|
14831
|
+
var DEFAULT_VPS_TEMPLATE = "ubuntu-24.04";
|
|
14832
|
+
var DNS_PROXIED_OPTIONS = { proxied: true };
|
|
14833
|
+
var DEPLOYED_STATUS = "deployed";
|
|
14834
|
+
var PLAYBOOK_COMPLETE_HEALTH_PENDING_STATUS = "playbook_complete_health_pending";
|
|
14835
|
+
var ACTIVE_INVENTORY_STATUS = "active";
|
|
14836
|
+
var PROVISIONING_INVENTORY_STATUS = "provisioning";
|
|
14653
14837
|
async function executeDeployment(params, client) {
|
|
14654
|
-
const {
|
|
14838
|
+
const {
|
|
14839
|
+
client_name,
|
|
14840
|
+
domain,
|
|
14841
|
+
region,
|
|
14842
|
+
plan,
|
|
14843
|
+
ssl_email,
|
|
14844
|
+
user_id,
|
|
14845
|
+
apiKey,
|
|
14846
|
+
cfApiToken,
|
|
14847
|
+
cfZoneId
|
|
14848
|
+
} = params;
|
|
14655
14849
|
const hostinger = client ?? new HostingerApiClient(apiKey);
|
|
14656
14850
|
const hostname = `exe-${client_name.toLowerCase().replace(/[^a-z0-9-]/g, "-")}`;
|
|
14657
14851
|
process.stderr.write(`[deploy_client] Provisioning VPS: ${hostname} (${plan}, ${region})...
|
|
@@ -14660,7 +14854,7 @@ async function executeDeployment(params, client) {
|
|
|
14660
14854
|
hostname,
|
|
14661
14855
|
plan,
|
|
14662
14856
|
region,
|
|
14663
|
-
os_template:
|
|
14857
|
+
os_template: DEFAULT_VPS_TEMPLATE
|
|
14664
14858
|
});
|
|
14665
14859
|
process.stderr.write(`[deploy_client] VPS created (ID: ${created.id}). Waiting for ready state...
|
|
14666
14860
|
`);
|
|
@@ -14668,9 +14862,27 @@ async function executeDeployment(params, client) {
|
|
|
14668
14862
|
process.stderr.write(`[deploy_client] VPS ready at ${vps.ip_address}. Running Ansible playbook...
|
|
14669
14863
|
`);
|
|
14670
14864
|
const playbookResult = await runAnsiblePlaybook(vps.ip_address, domain, ssl_email, client_name);
|
|
14865
|
+
const dnsRecordId = await createDnsRecordIfConfigured({
|
|
14866
|
+
domain,
|
|
14867
|
+
ipAddress: vps.ip_address,
|
|
14868
|
+
cfApiToken,
|
|
14869
|
+
cfZoneId
|
|
14870
|
+
});
|
|
14671
14871
|
process.stderr.write(`[deploy_client] Playbook complete. Verifying health...
|
|
14672
14872
|
`);
|
|
14673
14873
|
const healthy = await verifyHealth(domain);
|
|
14874
|
+
const status = healthy ? DEPLOYED_STATUS : PLAYBOOK_COMPLETE_HEALTH_PENDING_STATUS;
|
|
14875
|
+
process.stderr.write("[deploy_client] Recording VPS inventory...\n");
|
|
14876
|
+
const inventory = buildInventoryRecord({
|
|
14877
|
+
userId: user_id,
|
|
14878
|
+
clientName: client_name,
|
|
14879
|
+
vpsId: vps.id,
|
|
14880
|
+
ipAddress: vps.ip_address,
|
|
14881
|
+
domain,
|
|
14882
|
+
region: vps.region,
|
|
14883
|
+
plan: vps.plan,
|
|
14884
|
+
healthy
|
|
14885
|
+
});
|
|
14674
14886
|
return {
|
|
14675
14887
|
vps_id: vps.id,
|
|
14676
14888
|
hostname: vps.hostname,
|
|
@@ -14680,8 +14892,10 @@ async function executeDeployment(params, client) {
|
|
|
14680
14892
|
plan: vps.plan,
|
|
14681
14893
|
ssh_port: vps.ssh_port,
|
|
14682
14894
|
ssh_access: `ssh exeai@${vps.ip_address}`,
|
|
14683
|
-
status
|
|
14684
|
-
ansible_output: playbookResult
|
|
14895
|
+
status,
|
|
14896
|
+
ansible_output: playbookResult,
|
|
14897
|
+
dns_record_id: dnsRecordId,
|
|
14898
|
+
inventory
|
|
14685
14899
|
};
|
|
14686
14900
|
}
|
|
14687
14901
|
function registerDeployClient(server2) {
|
|
@@ -14695,12 +14909,15 @@ function registerDeployClient(server2) {
|
|
|
14695
14909
|
domain: z37.string().min(1).describe("Domain name for the deployment (e.g., client.exe.ai)"),
|
|
14696
14910
|
region: z37.string().default("jakarta").describe("VPS region (default: jakarta)"),
|
|
14697
14911
|
plan: z37.string().default("kvm-2").describe("Hostinger VPS plan (default: kvm-2)"),
|
|
14698
|
-
ssl_email: z37.string().email().describe("Email for Let's Encrypt SSL certificate")
|
|
14912
|
+
ssl_email: z37.string().email().describe("Email for Let's Encrypt SSL certificate"),
|
|
14913
|
+
user_id: z37.string().min(1).describe("User/customer ID for inventory tracking")
|
|
14699
14914
|
}
|
|
14700
14915
|
},
|
|
14701
|
-
async ({ client_name, domain, region, plan, ssl_email }) => {
|
|
14916
|
+
async ({ client_name, domain, region, plan, ssl_email, user_id }) => {
|
|
14702
14917
|
const config2 = await loadConfig();
|
|
14703
14918
|
const apiKey = config2.hostinger?.apiKey;
|
|
14919
|
+
const cfApiToken = config2.cloudflare?.apiToken;
|
|
14920
|
+
const cfZoneId = config2.cloudflare?.zoneId;
|
|
14704
14921
|
if (!apiKey) {
|
|
14705
14922
|
return {
|
|
14706
14923
|
content: [
|
|
@@ -14718,7 +14935,10 @@ function registerDeployClient(server2) {
|
|
|
14718
14935
|
region,
|
|
14719
14936
|
plan,
|
|
14720
14937
|
ssl_email,
|
|
14721
|
-
|
|
14938
|
+
user_id,
|
|
14939
|
+
apiKey,
|
|
14940
|
+
cfApiToken,
|
|
14941
|
+
cfZoneId
|
|
14722
14942
|
});
|
|
14723
14943
|
return {
|
|
14724
14944
|
content: [
|
|
@@ -14816,6 +15036,39 @@ async function verifyHealth(domain) {
|
|
|
14816
15036
|
return false;
|
|
14817
15037
|
}
|
|
14818
15038
|
}
|
|
15039
|
+
async function createDnsRecordIfConfigured(params) {
|
|
15040
|
+
const { domain, ipAddress, cfApiToken, cfZoneId } = params;
|
|
15041
|
+
if (!cfApiToken || !cfZoneId) {
|
|
15042
|
+
process.stderr.write(
|
|
15043
|
+
`[deploy_client] Warning: Cloudflare config missing. Skipping DNS setup for ${domain}; configure cloudflare.apiToken and cloudflare.zoneId to automate this step.
|
|
15044
|
+
`
|
|
15045
|
+
);
|
|
15046
|
+
return void 0;
|
|
15047
|
+
}
|
|
15048
|
+
process.stderr.write(`[deploy_client] Setting up DNS: ${domain} \u2192 ${ipAddress}...
|
|
15049
|
+
`);
|
|
15050
|
+
const dnsResult = await createARecord(
|
|
15051
|
+
cfApiToken,
|
|
15052
|
+
cfZoneId,
|
|
15053
|
+
domain,
|
|
15054
|
+
ipAddress,
|
|
15055
|
+
DNS_PROXIED_OPTIONS
|
|
15056
|
+
);
|
|
15057
|
+
return dnsResult.recordId;
|
|
15058
|
+
}
|
|
15059
|
+
function buildInventoryRecord(params) {
|
|
15060
|
+
const { userId, clientName, vpsId, ipAddress, domain, region, plan, healthy } = params;
|
|
15061
|
+
return {
|
|
15062
|
+
user_id: userId,
|
|
15063
|
+
client_name: clientName,
|
|
15064
|
+
hostinger_vps_id: vpsId,
|
|
15065
|
+
ip_address: ipAddress,
|
|
15066
|
+
domain,
|
|
15067
|
+
region,
|
|
15068
|
+
plan,
|
|
15069
|
+
status: healthy ? ACTIVE_INVENTORY_STATUS : PROVISIONING_INVENTORY_STATUS
|
|
15070
|
+
};
|
|
15071
|
+
}
|
|
14819
15072
|
|
|
14820
15073
|
// src/mcp/tools/query-conversations.ts
|
|
14821
15074
|
init_database();
|
|
@@ -16790,7 +17043,7 @@ function isMainModule(importMetaUrl) {
|
|
|
16790
17043
|
}
|
|
16791
17044
|
|
|
16792
17045
|
// src/bin/exe-doctor.ts
|
|
16793
|
-
import { existsSync as existsSync26 } from "fs";
|
|
17046
|
+
import { existsSync as existsSync26, readFileSync as readFileSync21 } from "fs";
|
|
16794
17047
|
import { spawn as spawn2 } from "child_process";
|
|
16795
17048
|
import path33 from "path";
|
|
16796
17049
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
@@ -17202,6 +17455,46 @@ async function auditOrphanedProjects(client) {
|
|
|
17202
17455
|
}
|
|
17203
17456
|
return orphans;
|
|
17204
17457
|
}
|
|
17458
|
+
function auditHookHealth() {
|
|
17459
|
+
const logPath = path33.join(
|
|
17460
|
+
process.env.HOME ?? process.env.USERPROFILE ?? "",
|
|
17461
|
+
".exe-os",
|
|
17462
|
+
"logs",
|
|
17463
|
+
"hooks.log"
|
|
17464
|
+
);
|
|
17465
|
+
if (!existsSync26(logPath)) {
|
|
17466
|
+
return { logExists: false, totalLines: 0, errorsLastHour: 0, topPatterns: [] };
|
|
17467
|
+
}
|
|
17468
|
+
let content;
|
|
17469
|
+
try {
|
|
17470
|
+
content = readFileSync21(logPath, "utf-8");
|
|
17471
|
+
} catch {
|
|
17472
|
+
return { logExists: false, totalLines: 0, errorsLastHour: 0, topPatterns: [] };
|
|
17473
|
+
}
|
|
17474
|
+
const lines = content.split("\n").filter(Boolean);
|
|
17475
|
+
const totalLines = lines.length;
|
|
17476
|
+
const recent = lines.slice(-200);
|
|
17477
|
+
const oneHourAgo = new Date(Date.now() - 36e5).toISOString();
|
|
17478
|
+
let errorsLastHour = 0;
|
|
17479
|
+
const patternCounts = /* @__PURE__ */ new Map();
|
|
17480
|
+
for (const line of recent) {
|
|
17481
|
+
const isError = /error|Error|ERR|FAIL|throw|exception|TypeError|ReferenceError|SyntaxError/i.test(line);
|
|
17482
|
+
if (!isError) continue;
|
|
17483
|
+
const tsMatch = line.match(/(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})/);
|
|
17484
|
+
if (tsMatch && tsMatch[1] >= oneHourAgo) {
|
|
17485
|
+
errorsLastHour++;
|
|
17486
|
+
} else if (!tsMatch) {
|
|
17487
|
+
errorsLastHour++;
|
|
17488
|
+
}
|
|
17489
|
+
const patternMatch = line.match(/((?:TypeError|ReferenceError|SyntaxError|Error):[^\n]{0,80})/);
|
|
17490
|
+
if (patternMatch) {
|
|
17491
|
+
const p = patternMatch[1];
|
|
17492
|
+
patternCounts.set(p, (patternCounts.get(p) ?? 0) + 1);
|
|
17493
|
+
}
|
|
17494
|
+
}
|
|
17495
|
+
const topPatterns = [...patternCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([pattern, count]) => ({ pattern, count }));
|
|
17496
|
+
return { logExists: true, totalLines, errorsLastHour, topPatterns };
|
|
17497
|
+
}
|
|
17205
17498
|
async function runAudit(client, flags) {
|
|
17206
17499
|
const [stats, nullVectors, duplicates, bloated, fts, orphanedProjects, conflicts] = await Promise.all([
|
|
17207
17500
|
auditStats(client, flags),
|
|
@@ -17213,7 +17506,8 @@ async function runAudit(client, flags) {
|
|
|
17213
17506
|
detectConflicts(client, flags.project, flags.agent)
|
|
17214
17507
|
]);
|
|
17215
17508
|
const duplicateCount = duplicates.reduce((sum, d) => sum + d.delete_ids.length, 0);
|
|
17216
|
-
|
|
17509
|
+
const hookHealth = auditHookHealth();
|
|
17510
|
+
return { stats, nullVectors, duplicates, duplicateCount, bloated, fts, orphanedProjects, conflicts, hookHealth };
|
|
17217
17511
|
}
|
|
17218
17512
|
function indicator(value, warn) {
|
|
17219
17513
|
if (value === 0) return "\u{1F7E2}";
|
|
@@ -17281,6 +17575,17 @@ function formatReport(report, flags) {
|
|
|
17281
17575
|
} else {
|
|
17282
17576
|
lines.push("\u{1F7E2} Conflicts: none detected");
|
|
17283
17577
|
}
|
|
17578
|
+
const hh = report.hookHealth;
|
|
17579
|
+
if (!hh.logExists) {
|
|
17580
|
+
lines.push("\u{1F7E0} Hook logs: no log file (run installer to enable stderr capture)");
|
|
17581
|
+
} else if (hh.errorsLastHour === 0) {
|
|
17582
|
+
lines.push(`\u{1F7E2} Hook logs: ${fmtNum(hh.totalLines)} lines, 0 errors in last hour`);
|
|
17583
|
+
} else {
|
|
17584
|
+
lines.push(`\u{1F534} Hook logs: ${fmtNum(hh.errorsLastHour)} errors in last hour (${fmtNum(hh.totalLines)} total lines)`);
|
|
17585
|
+
for (const p of hh.topPatterns) {
|
|
17586
|
+
lines.push(` ${p.count}x: ${p.pattern}`);
|
|
17587
|
+
}
|
|
17588
|
+
}
|
|
17284
17589
|
lines.push("");
|
|
17285
17590
|
if (flags.verbose) {
|
|
17286
17591
|
if (report.duplicates.length > 0) {
|
|
@@ -17557,7 +17862,7 @@ import { z as z58 } from "zod";
|
|
|
17557
17862
|
|
|
17558
17863
|
// src/lib/cloud-sync.ts
|
|
17559
17864
|
init_database();
|
|
17560
|
-
import { readFileSync as
|
|
17865
|
+
import { readFileSync as readFileSync23, writeFileSync as writeFileSync17, existsSync as existsSync28, readdirSync as readdirSync11, mkdirSync as mkdirSync14, appendFileSync as appendFileSync2, unlinkSync as unlinkSync10, openSync as openSync2, closeSync as closeSync2 } from "fs";
|
|
17561
17866
|
import crypto16 from "crypto";
|
|
17562
17867
|
import path35 from "path";
|
|
17563
17868
|
import { homedir as homedir6 } from "os";
|
|
@@ -17652,7 +17957,7 @@ async function withRosterLock(fn) {
|
|
|
17652
17957
|
} catch (err) {
|
|
17653
17958
|
if (err.code === "EEXIST") {
|
|
17654
17959
|
try {
|
|
17655
|
-
const ts2 = parseInt(
|
|
17960
|
+
const ts2 = parseInt(readFileSync23(ROSTER_LOCK_PATH, "utf-8"), 10);
|
|
17656
17961
|
if (Date.now() - ts2 < LOCK_STALE_MS) {
|
|
17657
17962
|
throw new Error("Roster merge already in progress \u2014 another sync is running");
|
|
17658
17963
|
}
|
|
@@ -17678,21 +17983,21 @@ async function withRosterLock(fn) {
|
|
|
17678
17983
|
}
|
|
17679
17984
|
}
|
|
17680
17985
|
async function fetchWithRetry(url, init) {
|
|
17681
|
-
const
|
|
17986
|
+
const MAX_RETRIES4 = 3;
|
|
17682
17987
|
const BASE_DELAY_MS2 = 200;
|
|
17683
17988
|
let lastError;
|
|
17684
|
-
for (let attempt = 0; attempt <=
|
|
17989
|
+
for (let attempt = 0; attempt <= MAX_RETRIES4; attempt++) {
|
|
17685
17990
|
try {
|
|
17686
17991
|
const signal = AbortSignal.timeout(FETCH_TIMEOUT_MS4);
|
|
17687
17992
|
const resp = await fetch(url, { ...init, signal });
|
|
17688
|
-
if (resp && resp.status >= 500 && attempt <
|
|
17993
|
+
if (resp && resp.status >= 500 && attempt < MAX_RETRIES4) {
|
|
17689
17994
|
await new Promise((r) => setTimeout(r, BASE_DELAY_MS2 * Math.pow(2, attempt)));
|
|
17690
17995
|
continue;
|
|
17691
17996
|
}
|
|
17692
17997
|
return resp;
|
|
17693
17998
|
} catch (err) {
|
|
17694
17999
|
lastError = err;
|
|
17695
|
-
if (attempt ===
|
|
18000
|
+
if (attempt === MAX_RETRIES4) throw err;
|
|
17696
18001
|
await new Promise((r) => setTimeout(r, BASE_DELAY_MS2 * Math.pow(2, attempt)));
|
|
17697
18002
|
}
|
|
17698
18003
|
}
|
|
@@ -18056,7 +18361,7 @@ var ROSTER_DELETIONS_PATH = path35.join(EXE_AI_DIR, "roster-deletions.json");
|
|
|
18056
18361
|
function consumeRosterDeletions() {
|
|
18057
18362
|
try {
|
|
18058
18363
|
if (!existsSync28(ROSTER_DELETIONS_PATH)) return [];
|
|
18059
|
-
const deletions = JSON.parse(
|
|
18364
|
+
const deletions = JSON.parse(readFileSync23(ROSTER_DELETIONS_PATH, "utf-8"));
|
|
18060
18365
|
writeFileSync17(ROSTER_DELETIONS_PATH, "[]");
|
|
18061
18366
|
return deletions;
|
|
18062
18367
|
} catch {
|
|
@@ -18070,7 +18375,7 @@ function buildRosterBlob(paths) {
|
|
|
18070
18375
|
let roster = [];
|
|
18071
18376
|
if (existsSync28(rosterPath)) {
|
|
18072
18377
|
try {
|
|
18073
|
-
roster = JSON.parse(
|
|
18378
|
+
roster = JSON.parse(readFileSync23(rosterPath, "utf-8"));
|
|
18074
18379
|
} catch {
|
|
18075
18380
|
}
|
|
18076
18381
|
}
|
|
@@ -18078,7 +18383,7 @@ function buildRosterBlob(paths) {
|
|
|
18078
18383
|
if (existsSync28(identityDir)) {
|
|
18079
18384
|
for (const file of readdirSync11(identityDir).filter((f) => f.endsWith(".md"))) {
|
|
18080
18385
|
try {
|
|
18081
|
-
identities[file] =
|
|
18386
|
+
identities[file] = readFileSync23(path35.join(identityDir, file), "utf-8");
|
|
18082
18387
|
} catch {
|
|
18083
18388
|
}
|
|
18084
18389
|
}
|
|
@@ -18086,7 +18391,7 @@ function buildRosterBlob(paths) {
|
|
|
18086
18391
|
let config2;
|
|
18087
18392
|
if (existsSync28(configPath)) {
|
|
18088
18393
|
try {
|
|
18089
|
-
config2 = JSON.parse(
|
|
18394
|
+
config2 = JSON.parse(readFileSync23(configPath, "utf-8"));
|
|
18090
18395
|
} catch {
|
|
18091
18396
|
}
|
|
18092
18397
|
}
|
|
@@ -18094,7 +18399,7 @@ function buildRosterBlob(paths) {
|
|
|
18094
18399
|
const agentConfigPath = path35.join(EXE_AI_DIR, "agent-config.json");
|
|
18095
18400
|
if (existsSync28(agentConfigPath)) {
|
|
18096
18401
|
try {
|
|
18097
|
-
agentConfig = JSON.parse(
|
|
18402
|
+
agentConfig = JSON.parse(readFileSync23(agentConfigPath, "utf-8"));
|
|
18098
18403
|
} catch {
|
|
18099
18404
|
}
|
|
18100
18405
|
}
|
|
@@ -18174,7 +18479,7 @@ function mergeConfig(remoteConfig, configPath) {
|
|
|
18174
18479
|
let local = {};
|
|
18175
18480
|
if (existsSync28(cfgPath)) {
|
|
18176
18481
|
try {
|
|
18177
|
-
local = JSON.parse(
|
|
18482
|
+
local = JSON.parse(readFileSync23(cfgPath, "utf-8"));
|
|
18178
18483
|
} catch {
|
|
18179
18484
|
}
|
|
18180
18485
|
}
|
|
@@ -18211,7 +18516,7 @@ async function mergeRosterFromRemote(remote, paths) {
|
|
|
18211
18516
|
const idPath = path35.join(identityDir, `${remoteEmp.name}.md`);
|
|
18212
18517
|
let localIdentity = null;
|
|
18213
18518
|
try {
|
|
18214
|
-
localIdentity = existsSync28(idPath) ?
|
|
18519
|
+
localIdentity = existsSync28(idPath) ? readFileSync23(idPath, "utf-8") : null;
|
|
18215
18520
|
} catch {
|
|
18216
18521
|
}
|
|
18217
18522
|
if (localIdentity !== remoteIdentity) {
|
|
@@ -18245,7 +18550,7 @@ async function mergeRosterFromRemote(remote, paths) {
|
|
|
18245
18550
|
let local = {};
|
|
18246
18551
|
if (existsSync28(agentConfigPath)) {
|
|
18247
18552
|
try {
|
|
18248
|
-
local = JSON.parse(
|
|
18553
|
+
local = JSON.parse(readFileSync23(agentConfigPath, "utf-8"));
|
|
18249
18554
|
} catch {
|
|
18250
18555
|
}
|
|
18251
18556
|
}
|
|
@@ -18976,7 +19281,7 @@ import { z as z62 } from "zod";
|
|
|
18976
19281
|
// src/lib/people.ts
|
|
18977
19282
|
init_config();
|
|
18978
19283
|
import { readFile as readFile5, writeFile as writeFile6, mkdir as mkdir5 } from "fs/promises";
|
|
18979
|
-
import { existsSync as existsSync29, readFileSync as
|
|
19284
|
+
import { existsSync as existsSync29, readFileSync as readFileSync24 } from "fs";
|
|
18980
19285
|
import path36 from "path";
|
|
18981
19286
|
var PEOPLE_PATH = path36.join(EXE_AI_DIR, "people.json");
|
|
18982
19287
|
async function loadPeople() {
|