agendex-cli 0.5.0 → 0.7.0
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/cli.js +270 -182
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2127,11 +2127,13 @@ function normalizeStoredConfig(raw) {
|
|
|
2127
2127
|
const token = typeof raw.token === "string" && raw.token.trim() ? raw.token : undefined;
|
|
2128
2128
|
const cloudToken = typeof raw.cloudToken === "string" && raw.cloudToken.trim() ? raw.cloudToken : undefined;
|
|
2129
2129
|
const convexUrl = typeof raw.convexUrl === "string" && raw.convexUrl.trim() ? raw.convexUrl : undefined;
|
|
2130
|
+
const deviceId = typeof raw.deviceId === "string" && raw.deviceId.trim() ? raw.deviceId : undefined;
|
|
2130
2131
|
return {
|
|
2131
2132
|
configVersion: 3,
|
|
2132
2133
|
token,
|
|
2133
2134
|
cloudToken,
|
|
2134
2135
|
convexUrl,
|
|
2136
|
+
deviceId,
|
|
2135
2137
|
enabledAdapters: normalizeAdapterIds(raw.enabledAdapters)
|
|
2136
2138
|
};
|
|
2137
2139
|
}
|
|
@@ -2145,6 +2147,7 @@ function saveConfig(config) {
|
|
|
2145
2147
|
token: config.token,
|
|
2146
2148
|
cloudToken: config.cloudToken,
|
|
2147
2149
|
convexUrl: config.convexUrl,
|
|
2150
|
+
deviceId: config.deviceId,
|
|
2148
2151
|
enabledAdapters: sanitizeEnabledAdapterIds(config.enabledAdapters)
|
|
2149
2152
|
};
|
|
2150
2153
|
writeFileSync(configPath, JSON.stringify(payload, null, 2));
|
|
@@ -2170,6 +2173,19 @@ function loadOrCreateToken() {
|
|
|
2170
2173
|
`);
|
|
2171
2174
|
return token;
|
|
2172
2175
|
}
|
|
2176
|
+
function loadOrCreateDeviceId() {
|
|
2177
|
+
const existing = loadConfig();
|
|
2178
|
+
if (existing?.deviceId)
|
|
2179
|
+
return existing.deviceId;
|
|
2180
|
+
const deviceId = randomBytes(16).toString("hex");
|
|
2181
|
+
saveConfig({
|
|
2182
|
+
...existing ?? {},
|
|
2183
|
+
configVersion: 3,
|
|
2184
|
+
deviceId,
|
|
2185
|
+
enabledAdapters: existing?.enabledAdapters ?? []
|
|
2186
|
+
});
|
|
2187
|
+
return deviceId;
|
|
2188
|
+
}
|
|
2173
2189
|
async function loadOrInitConfig(options = {}) {
|
|
2174
2190
|
const configureAdapters = Boolean(options.configureAdapters);
|
|
2175
2191
|
const existing = loadConfig();
|
|
@@ -2195,11 +2211,13 @@ async function loadOrInitConfig(options = {}) {
|
|
|
2195
2211
|
if (enabledAdapters.length === 0)
|
|
2196
2212
|
enabledAdapters = getDefaultAdapterIds();
|
|
2197
2213
|
}
|
|
2214
|
+
const deviceId = existing?.deviceId || randomBytes(16).toString("hex");
|
|
2198
2215
|
const nextConfig = {
|
|
2199
2216
|
configVersion: 3,
|
|
2200
2217
|
token: tokenFromEnv ? existing?.token : currentToken,
|
|
2201
2218
|
cloudToken: existing?.cloudToken,
|
|
2202
2219
|
convexUrl: existing?.convexUrl,
|
|
2220
|
+
deviceId,
|
|
2203
2221
|
enabledAdapters
|
|
2204
2222
|
};
|
|
2205
2223
|
saveConfig(nextConfig);
|
|
@@ -2474,14 +2492,241 @@ function startWatching(onChange) {
|
|
|
2474
2492
|
watchDir(dir, (f) => adapter.matches(f), onChange);
|
|
2475
2493
|
}
|
|
2476
2494
|
}
|
|
2495
|
+
// src/api.ts
|
|
2496
|
+
import { request as httpRequest } from "node:http";
|
|
2497
|
+
import { request as httpsRequest } from "node:https";
|
|
2498
|
+
import { hostname as osHostname } from "node:os";
|
|
2499
|
+
|
|
2500
|
+
// src/pid.ts
|
|
2501
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync3, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
2502
|
+
import { homedir as homedir9, hostname } from "node:os";
|
|
2503
|
+
import { dirname, join as join10 } from "node:path";
|
|
2504
|
+
var pidPath = join10(homedir9(), ".agendex", "daemon.pid");
|
|
2505
|
+
function writePid() {
|
|
2506
|
+
mkdirSync2(dirname(pidPath), { recursive: true });
|
|
2507
|
+
const info = {
|
|
2508
|
+
pid: process.pid,
|
|
2509
|
+
startedAtMs: Date.now(),
|
|
2510
|
+
hostname: hostname()
|
|
2511
|
+
};
|
|
2512
|
+
writeFileSync2(pidPath, JSON.stringify(info));
|
|
2513
|
+
}
|
|
2514
|
+
function readPidInfo() {
|
|
2515
|
+
if (!existsSync6(pidPath))
|
|
2516
|
+
return null;
|
|
2517
|
+
const raw = readFileSync3(pidPath, "utf-8").trim();
|
|
2518
|
+
const asNumber = Number(raw);
|
|
2519
|
+
if (Number.isFinite(asNumber) && asNumber > 0 && !raw.startsWith("{")) {
|
|
2520
|
+
return { pid: asNumber };
|
|
2521
|
+
}
|
|
2522
|
+
try {
|
|
2523
|
+
const parsed = JSON.parse(raw);
|
|
2524
|
+
if (Number.isFinite(parsed.pid) && parsed.pid > 0)
|
|
2525
|
+
return parsed;
|
|
2526
|
+
} catch {}
|
|
2527
|
+
return null;
|
|
2528
|
+
}
|
|
2529
|
+
function readPid() {
|
|
2530
|
+
return readPidInfo()?.pid ?? null;
|
|
2531
|
+
}
|
|
2532
|
+
function removePid() {
|
|
2533
|
+
try {
|
|
2534
|
+
unlinkSync(pidPath);
|
|
2535
|
+
} catch {}
|
|
2536
|
+
}
|
|
2537
|
+
function isRunning(pid) {
|
|
2538
|
+
try {
|
|
2539
|
+
process.kill(pid, 0);
|
|
2540
|
+
return true;
|
|
2541
|
+
} catch {
|
|
2542
|
+
return false;
|
|
2543
|
+
}
|
|
2544
|
+
}
|
|
2545
|
+
|
|
2546
|
+
// src/api.ts
|
|
2547
|
+
var cachedDeviceId;
|
|
2548
|
+
function getCloudConfig() {
|
|
2549
|
+
const config = loadConfig();
|
|
2550
|
+
if (!config?.cloudToken)
|
|
2551
|
+
throw new Error("Not logged in. Run `agendex login` first.");
|
|
2552
|
+
if (!config.convexUrl)
|
|
2553
|
+
throw new Error("No Convex URL configured. Run `agendex login` first.");
|
|
2554
|
+
return { token: config.cloudToken, convexUrl: config.convexUrl };
|
|
2555
|
+
}
|
|
2556
|
+
async function syncPlan(plan) {
|
|
2557
|
+
const { token, convexUrl } = getCloudConfig();
|
|
2558
|
+
const url = `${convexUrl}/api/cli/sync`;
|
|
2559
|
+
let activeToken = token;
|
|
2560
|
+
let res = await requestText(url, {
|
|
2561
|
+
method: "POST",
|
|
2562
|
+
headers: {
|
|
2563
|
+
Authorization: `Bearer ${activeToken}`,
|
|
2564
|
+
Connection: "close",
|
|
2565
|
+
"Content-Type": "application/json"
|
|
2566
|
+
},
|
|
2567
|
+
body: JSON.stringify(plan)
|
|
2568
|
+
});
|
|
2569
|
+
if (res.status === 401) {
|
|
2570
|
+
const refreshed = await refreshStoredToken(activeToken, convexUrl);
|
|
2571
|
+
if (refreshed) {
|
|
2572
|
+
activeToken = refreshed;
|
|
2573
|
+
res = await requestText(url, {
|
|
2574
|
+
method: "POST",
|
|
2575
|
+
headers: {
|
|
2576
|
+
Authorization: `Bearer ${activeToken}`,
|
|
2577
|
+
Connection: "close",
|
|
2578
|
+
"Content-Type": "application/json"
|
|
2579
|
+
},
|
|
2580
|
+
body: JSON.stringify(plan)
|
|
2581
|
+
});
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
if (res.status < 200 || res.status >= 300) {
|
|
2585
|
+
return { ok: false, error: `${res.status}: ${res.body}` };
|
|
2586
|
+
}
|
|
2587
|
+
return { ok: true };
|
|
2588
|
+
}
|
|
2589
|
+
async function refreshStoredToken(currentToken, convexUrl) {
|
|
2590
|
+
const refreshed = await refreshToken(currentToken, convexUrl);
|
|
2591
|
+
if (!refreshed)
|
|
2592
|
+
return null;
|
|
2593
|
+
const config = loadConfig();
|
|
2594
|
+
if (config) {
|
|
2595
|
+
saveConfig({ ...config, cloudToken: refreshed.token });
|
|
2596
|
+
}
|
|
2597
|
+
return refreshed.token;
|
|
2598
|
+
}
|
|
2599
|
+
async function sendHeartbeat() {
|
|
2600
|
+
try {
|
|
2601
|
+
const { token, convexUrl } = getCloudConfig();
|
|
2602
|
+
const pidInfo = readPidInfo();
|
|
2603
|
+
cachedDeviceId ??= loadOrCreateDeviceId();
|
|
2604
|
+
const heartbeatBody = JSON.stringify({
|
|
2605
|
+
deviceId: cachedDeviceId,
|
|
2606
|
+
hostname: pidInfo?.hostname ?? osHostname(),
|
|
2607
|
+
startedAtMs: pidInfo?.startedAtMs,
|
|
2608
|
+
pid: pidInfo?.pid
|
|
2609
|
+
});
|
|
2610
|
+
let activeToken = token;
|
|
2611
|
+
let res = await requestText(`${convexUrl}/api/cli/heartbeat`, {
|
|
2612
|
+
method: "POST",
|
|
2613
|
+
headers: {
|
|
2614
|
+
Authorization: `Bearer ${activeToken}`,
|
|
2615
|
+
Connection: "close",
|
|
2616
|
+
"Content-Type": "application/json"
|
|
2617
|
+
},
|
|
2618
|
+
body: heartbeatBody
|
|
2619
|
+
});
|
|
2620
|
+
if (res.status === 401) {
|
|
2621
|
+
const refreshedToken = await refreshStoredToken(activeToken, convexUrl);
|
|
2622
|
+
if (!refreshedToken)
|
|
2623
|
+
return;
|
|
2624
|
+
activeToken = refreshedToken;
|
|
2625
|
+
res = await requestText(`${convexUrl}/api/cli/heartbeat`, {
|
|
2626
|
+
method: "POST",
|
|
2627
|
+
headers: {
|
|
2628
|
+
Authorization: `Bearer ${activeToken}`,
|
|
2629
|
+
Connection: "close",
|
|
2630
|
+
"Content-Type": "application/json"
|
|
2631
|
+
},
|
|
2632
|
+
body: heartbeatBody
|
|
2633
|
+
});
|
|
2634
|
+
}
|
|
2635
|
+
if (res.status === 401) {
|
|
2636
|
+
return;
|
|
2637
|
+
}
|
|
2638
|
+
} catch {}
|
|
2639
|
+
}
|
|
2640
|
+
async function refreshToken(currentToken, convexUrl) {
|
|
2641
|
+
const res = await requestText(`${convexUrl}/api/cli/refresh`, {
|
|
2642
|
+
method: "POST",
|
|
2643
|
+
headers: {
|
|
2644
|
+
Authorization: `Bearer ${currentToken}`,
|
|
2645
|
+
Connection: "close",
|
|
2646
|
+
"Content-Type": "application/json"
|
|
2647
|
+
}
|
|
2648
|
+
});
|
|
2649
|
+
if (res.status < 200 || res.status >= 300)
|
|
2650
|
+
return null;
|
|
2651
|
+
const body = JSON.parse(res.body);
|
|
2652
|
+
if (!body.token)
|
|
2653
|
+
return null;
|
|
2654
|
+
return { token: body.token, expiresAt: body.expiresAt ?? 0 };
|
|
2655
|
+
}
|
|
2656
|
+
function requestText(urlString, options) {
|
|
2657
|
+
const url = new URL(urlString);
|
|
2658
|
+
const request = url.protocol === "https:" ? httpsRequest : httpRequest;
|
|
2659
|
+
return new Promise((resolve4, reject) => {
|
|
2660
|
+
const req = request(url, {
|
|
2661
|
+
agent: false,
|
|
2662
|
+
headers: options.headers,
|
|
2663
|
+
method: options.method
|
|
2664
|
+
}, (res) => {
|
|
2665
|
+
let body = "";
|
|
2666
|
+
res.setEncoding("utf8");
|
|
2667
|
+
res.on("data", (chunk) => {
|
|
2668
|
+
body += chunk;
|
|
2669
|
+
});
|
|
2670
|
+
res.on("end", () => {
|
|
2671
|
+
resolve4({
|
|
2672
|
+
status: res.statusCode ?? 0,
|
|
2673
|
+
body
|
|
2674
|
+
});
|
|
2675
|
+
});
|
|
2676
|
+
res.on("error", reject);
|
|
2677
|
+
});
|
|
2678
|
+
req.on("error", reject);
|
|
2679
|
+
if (options.body) {
|
|
2680
|
+
req.write(options.body);
|
|
2681
|
+
}
|
|
2682
|
+
req.end();
|
|
2683
|
+
});
|
|
2684
|
+
}
|
|
2685
|
+
async function fetchDevices() {
|
|
2686
|
+
const { token, convexUrl } = getCloudConfig();
|
|
2687
|
+
const url = `${convexUrl}/api/cli/devices`;
|
|
2688
|
+
let activeToken = token;
|
|
2689
|
+
let res = await requestText(url, {
|
|
2690
|
+
method: "GET",
|
|
2691
|
+
headers: {
|
|
2692
|
+
Authorization: `Bearer ${activeToken}`,
|
|
2693
|
+
Connection: "close"
|
|
2694
|
+
}
|
|
2695
|
+
});
|
|
2696
|
+
if (res.status === 401) {
|
|
2697
|
+
const refreshed = await refreshStoredToken(activeToken, convexUrl);
|
|
2698
|
+
if (refreshed) {
|
|
2699
|
+
activeToken = refreshed;
|
|
2700
|
+
res = await requestText(url, {
|
|
2701
|
+
method: "GET",
|
|
2702
|
+
headers: {
|
|
2703
|
+
Authorization: `Bearer ${activeToken}`,
|
|
2704
|
+
Connection: "close"
|
|
2705
|
+
}
|
|
2706
|
+
});
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
if (res.status < 200 || res.status >= 300) {
|
|
2710
|
+
return [];
|
|
2711
|
+
}
|
|
2712
|
+
const body = JSON.parse(res.body);
|
|
2713
|
+
return body.devices ?? [];
|
|
2714
|
+
}
|
|
2715
|
+
|
|
2477
2716
|
// src/auth.ts
|
|
2478
2717
|
import { spawn } from "node:child_process";
|
|
2479
2718
|
import { createServer } from "node:http";
|
|
2480
|
-
var
|
|
2719
|
+
var PROD_SITE_URL = "https://app.agendex.dev";
|
|
2720
|
+
var DEV_SITE_URL = "http://app.agendex.local:5174";
|
|
2721
|
+
function getDefaultSiteUrl() {
|
|
2722
|
+
if (process.env.AGENDEX_SITE_URL)
|
|
2723
|
+
return process.env.AGENDEX_SITE_URL;
|
|
2724
|
+
return process.env.AGENDEX_DEV === "1" ? DEV_SITE_URL : PROD_SITE_URL;
|
|
2725
|
+
}
|
|
2481
2726
|
async function login(siteUrlOverride) {
|
|
2482
2727
|
const { port, result } = await startCallbackServer();
|
|
2483
2728
|
const callbackUrl = `http://127.0.0.1:${port}/callback`;
|
|
2484
|
-
const siteUrl = siteUrlOverride ??
|
|
2729
|
+
const siteUrl = siteUrlOverride ?? getDefaultSiteUrl();
|
|
2485
2730
|
const authUrl = `${siteUrl}/auth/cli?callback=${encodeURIComponent(callbackUrl)}`;
|
|
2486
2731
|
console.log(`[agendex] Opening browser for authentication...`);
|
|
2487
2732
|
console.log(`[agendex] If it doesn't open, visit: ${authUrl}`);
|
|
@@ -2666,185 +2911,6 @@ function spawnBrowser(command, args, options = {}) {
|
|
|
2666
2911
|
import { spawn as spawn2 } from "node:child_process";
|
|
2667
2912
|
import { resolve as resolve4 } from "node:path";
|
|
2668
2913
|
import { fileURLToPath } from "node:url";
|
|
2669
|
-
|
|
2670
|
-
// src/api.ts
|
|
2671
|
-
import { request as httpRequest } from "node:http";
|
|
2672
|
-
import { request as httpsRequest } from "node:https";
|
|
2673
|
-
function getCloudConfig() {
|
|
2674
|
-
const config = loadConfig();
|
|
2675
|
-
if (!config?.cloudToken)
|
|
2676
|
-
throw new Error("Not logged in. Run `agendex login` first.");
|
|
2677
|
-
if (!config.convexUrl)
|
|
2678
|
-
throw new Error("No Convex URL configured. Run `agendex login` first.");
|
|
2679
|
-
return { token: config.cloudToken, convexUrl: config.convexUrl };
|
|
2680
|
-
}
|
|
2681
|
-
async function syncPlan(plan) {
|
|
2682
|
-
const { token, convexUrl } = getCloudConfig();
|
|
2683
|
-
const url = `${convexUrl}/api/cli/sync`;
|
|
2684
|
-
let activeToken = token;
|
|
2685
|
-
let res = await requestText(url, {
|
|
2686
|
-
method: "POST",
|
|
2687
|
-
headers: {
|
|
2688
|
-
Authorization: `Bearer ${activeToken}`,
|
|
2689
|
-
Connection: "close",
|
|
2690
|
-
"Content-Type": "application/json"
|
|
2691
|
-
},
|
|
2692
|
-
body: JSON.stringify(plan)
|
|
2693
|
-
});
|
|
2694
|
-
if (res.status === 401) {
|
|
2695
|
-
const refreshed = await refreshStoredToken(activeToken, convexUrl);
|
|
2696
|
-
if (refreshed) {
|
|
2697
|
-
activeToken = refreshed;
|
|
2698
|
-
res = await requestText(url, {
|
|
2699
|
-
method: "POST",
|
|
2700
|
-
headers: {
|
|
2701
|
-
Authorization: `Bearer ${activeToken}`,
|
|
2702
|
-
Connection: "close",
|
|
2703
|
-
"Content-Type": "application/json"
|
|
2704
|
-
},
|
|
2705
|
-
body: JSON.stringify(plan)
|
|
2706
|
-
});
|
|
2707
|
-
}
|
|
2708
|
-
}
|
|
2709
|
-
if (res.status < 200 || res.status >= 300) {
|
|
2710
|
-
return { ok: false, error: `${res.status}: ${res.body}` };
|
|
2711
|
-
}
|
|
2712
|
-
return { ok: true };
|
|
2713
|
-
}
|
|
2714
|
-
async function refreshStoredToken(currentToken, convexUrl) {
|
|
2715
|
-
const refreshed = await refreshToken(currentToken, convexUrl);
|
|
2716
|
-
if (!refreshed)
|
|
2717
|
-
return null;
|
|
2718
|
-
const config = loadConfig();
|
|
2719
|
-
if (config) {
|
|
2720
|
-
saveConfig({ ...config, cloudToken: refreshed.token });
|
|
2721
|
-
}
|
|
2722
|
-
return refreshed.token;
|
|
2723
|
-
}
|
|
2724
|
-
async function sendHeartbeat() {
|
|
2725
|
-
try {
|
|
2726
|
-
const { token, convexUrl } = getCloudConfig();
|
|
2727
|
-
let activeToken = token;
|
|
2728
|
-
let res = await requestText(`${convexUrl}/api/cli/heartbeat`, {
|
|
2729
|
-
method: "POST",
|
|
2730
|
-
headers: {
|
|
2731
|
-
Authorization: `Bearer ${activeToken}`,
|
|
2732
|
-
Connection: "close",
|
|
2733
|
-
"Content-Type": "application/json"
|
|
2734
|
-
}
|
|
2735
|
-
});
|
|
2736
|
-
if (res.status === 401) {
|
|
2737
|
-
const refreshedToken = await refreshStoredToken(activeToken, convexUrl);
|
|
2738
|
-
if (!refreshedToken)
|
|
2739
|
-
return;
|
|
2740
|
-
activeToken = refreshedToken;
|
|
2741
|
-
res = await requestText(`${convexUrl}/api/cli/heartbeat`, {
|
|
2742
|
-
method: "POST",
|
|
2743
|
-
headers: {
|
|
2744
|
-
Authorization: `Bearer ${activeToken}`,
|
|
2745
|
-
Connection: "close",
|
|
2746
|
-
"Content-Type": "application/json"
|
|
2747
|
-
}
|
|
2748
|
-
});
|
|
2749
|
-
}
|
|
2750
|
-
if (res.status === 401) {
|
|
2751
|
-
return;
|
|
2752
|
-
}
|
|
2753
|
-
} catch {}
|
|
2754
|
-
}
|
|
2755
|
-
async function refreshToken(currentToken, convexUrl) {
|
|
2756
|
-
const res = await requestText(`${convexUrl}/api/cli/refresh`, {
|
|
2757
|
-
method: "POST",
|
|
2758
|
-
headers: {
|
|
2759
|
-
Authorization: `Bearer ${currentToken}`,
|
|
2760
|
-
Connection: "close",
|
|
2761
|
-
"Content-Type": "application/json"
|
|
2762
|
-
}
|
|
2763
|
-
});
|
|
2764
|
-
if (res.status < 200 || res.status >= 300)
|
|
2765
|
-
return null;
|
|
2766
|
-
const body = JSON.parse(res.body);
|
|
2767
|
-
if (!body.token)
|
|
2768
|
-
return null;
|
|
2769
|
-
return { token: body.token, expiresAt: body.expiresAt ?? 0 };
|
|
2770
|
-
}
|
|
2771
|
-
function requestText(urlString, options) {
|
|
2772
|
-
const url = new URL(urlString);
|
|
2773
|
-
const request = url.protocol === "https:" ? httpsRequest : httpRequest;
|
|
2774
|
-
return new Promise((resolve4, reject) => {
|
|
2775
|
-
const req = request(url, {
|
|
2776
|
-
agent: false,
|
|
2777
|
-
headers: options.headers,
|
|
2778
|
-
method: options.method
|
|
2779
|
-
}, (res) => {
|
|
2780
|
-
let body = "";
|
|
2781
|
-
res.setEncoding("utf8");
|
|
2782
|
-
res.on("data", (chunk) => {
|
|
2783
|
-
body += chunk;
|
|
2784
|
-
});
|
|
2785
|
-
res.on("end", () => {
|
|
2786
|
-
resolve4({
|
|
2787
|
-
status: res.statusCode ?? 0,
|
|
2788
|
-
body
|
|
2789
|
-
});
|
|
2790
|
-
});
|
|
2791
|
-
res.on("error", reject);
|
|
2792
|
-
});
|
|
2793
|
-
req.on("error", reject);
|
|
2794
|
-
if (options.body) {
|
|
2795
|
-
req.write(options.body);
|
|
2796
|
-
}
|
|
2797
|
-
req.end();
|
|
2798
|
-
});
|
|
2799
|
-
}
|
|
2800
|
-
|
|
2801
|
-
// src/pid.ts
|
|
2802
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync3, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
2803
|
-
import { homedir as homedir9, hostname } from "node:os";
|
|
2804
|
-
import { dirname, join as join10 } from "node:path";
|
|
2805
|
-
var pidPath = join10(homedir9(), ".agendex", "daemon.pid");
|
|
2806
|
-
function writePid() {
|
|
2807
|
-
mkdirSync2(dirname(pidPath), { recursive: true });
|
|
2808
|
-
const info = {
|
|
2809
|
-
pid: process.pid,
|
|
2810
|
-
startedAtMs: Date.now(),
|
|
2811
|
-
hostname: hostname()
|
|
2812
|
-
};
|
|
2813
|
-
writeFileSync2(pidPath, JSON.stringify(info));
|
|
2814
|
-
}
|
|
2815
|
-
function readPidInfo() {
|
|
2816
|
-
if (!existsSync6(pidPath))
|
|
2817
|
-
return null;
|
|
2818
|
-
const raw = readFileSync3(pidPath, "utf-8").trim();
|
|
2819
|
-
const asNumber = Number(raw);
|
|
2820
|
-
if (Number.isFinite(asNumber) && asNumber > 0 && !raw.startsWith("{")) {
|
|
2821
|
-
return { pid: asNumber };
|
|
2822
|
-
}
|
|
2823
|
-
try {
|
|
2824
|
-
const parsed = JSON.parse(raw);
|
|
2825
|
-
if (Number.isFinite(parsed.pid) && parsed.pid > 0)
|
|
2826
|
-
return parsed;
|
|
2827
|
-
} catch {}
|
|
2828
|
-
return null;
|
|
2829
|
-
}
|
|
2830
|
-
function readPid() {
|
|
2831
|
-
return readPidInfo()?.pid ?? null;
|
|
2832
|
-
}
|
|
2833
|
-
function removePid() {
|
|
2834
|
-
try {
|
|
2835
|
-
unlinkSync(pidPath);
|
|
2836
|
-
} catch {}
|
|
2837
|
-
}
|
|
2838
|
-
function isRunning(pid) {
|
|
2839
|
-
try {
|
|
2840
|
-
process.kill(pid, 0);
|
|
2841
|
-
return true;
|
|
2842
|
-
} catch {
|
|
2843
|
-
return false;
|
|
2844
|
-
}
|
|
2845
|
-
}
|
|
2846
|
-
|
|
2847
|
-
// src/daemon.ts
|
|
2848
2914
|
var MAX_RESTARTS = 5;
|
|
2849
2915
|
var RESTART_WINDOW_MS = 60000;
|
|
2850
2916
|
var RESTART_DELAY_MS = 5000;
|
|
@@ -3029,7 +3095,7 @@ import { join as join11 } from "node:path";
|
|
|
3029
3095
|
// package.json
|
|
3030
3096
|
var package_default = {
|
|
3031
3097
|
name: "agendex-cli",
|
|
3032
|
-
version: "0.
|
|
3098
|
+
version: "0.7.0",
|
|
3033
3099
|
description: "Agendex CLI for login, sync, and daemon workflows",
|
|
3034
3100
|
homepage: "https://github.com/Tyru5/Agendex#readme",
|
|
3035
3101
|
repository: {
|
|
@@ -3240,6 +3306,28 @@ async function main() {
|
|
|
3240
3306
|
writeStdout(`[agendex] Hostname: n/a`);
|
|
3241
3307
|
}
|
|
3242
3308
|
writeStdout(`[agendex] CLI version: ${CLI_VERSION}`);
|
|
3309
|
+
try {
|
|
3310
|
+
if (config?.cloudToken && config?.convexUrl) {
|
|
3311
|
+
const allDevices = await fetchDevices();
|
|
3312
|
+
if (allDevices.length > 0) {
|
|
3313
|
+
const now = Date.now();
|
|
3314
|
+
writeStdout(`[agendex] All daemons:`);
|
|
3315
|
+
for (const device of allDevices) {
|
|
3316
|
+
const age = device.lastSeenAt ? now - device.lastSeenAt : Number.POSITIVE_INFINITY;
|
|
3317
|
+
const status = age < CLI_DAEMON_STALE_AFTER_MS ? "alive" : "stale";
|
|
3318
|
+
const uptimeStr = device.startedAtMs != null ? formatDuration(now - device.startedAtMs) : "~";
|
|
3319
|
+
const pidStr = device.pid != null ? String(device.pid) : "~";
|
|
3320
|
+
const hostnameStr = device.hostname ?? "~";
|
|
3321
|
+
writeStdout(`- hostname: ${hostnameStr}
|
|
3322
|
+
pid: ${pidStr}
|
|
3323
|
+
uptime: ${uptimeStr}
|
|
3324
|
+
status: ${status}`);
|
|
3325
|
+
}
|
|
3326
|
+
} else {
|
|
3327
|
+
writeStdout(`[agendex] All daemons: none`);
|
|
3328
|
+
}
|
|
3329
|
+
}
|
|
3330
|
+
} catch {}
|
|
3243
3331
|
return 0;
|
|
3244
3332
|
}
|
|
3245
3333
|
case "help":
|