agendex-cli 0.4.0 → 0.6.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/README.md +32 -8
- package/dist/cli.js +123 -36
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,15 +14,39 @@ bun install -g agendex-cli
|
|
|
14
14
|
## Commands
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
|
-
agendex login
|
|
18
|
-
agendex login --url
|
|
19
|
-
agendex
|
|
20
|
-
agendex
|
|
21
|
-
agendex
|
|
22
|
-
agendex
|
|
23
|
-
agendex
|
|
24
|
-
agendex
|
|
17
|
+
agendex login # Authenticate via browser OAuth (agendex.dev)
|
|
18
|
+
agendex login --url <url> # Login to a self-hosted instance
|
|
19
|
+
agendex logout # Clear stored cloud token
|
|
20
|
+
agendex configure # Select which agents/adapters to index
|
|
21
|
+
agendex start # Start daemon (backgrounds itself)
|
|
22
|
+
agendex stop # Stop the running daemon
|
|
23
|
+
agendex sync # One-shot scan + sync to cloud
|
|
24
|
+
agendex status # Show config state, daemon status, uptime & hostname
|
|
25
|
+
agendex help # Show help message
|
|
26
|
+
agendex --version / -v # Print CLI version
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Status Output
|
|
30
|
+
|
|
31
|
+
`agendex status` prints a rich overview:
|
|
32
|
+
|
|
33
|
+
- Config version, local/cloud token state, Convex URL
|
|
34
|
+
- Enabled adapters
|
|
35
|
+
- Daemon running state with PID
|
|
36
|
+
- **Uptime** — how long the daemon has been running
|
|
37
|
+
- **Hostname** — machine the daemon is running on
|
|
38
|
+
- CLI version
|
|
39
|
+
|
|
40
|
+
## Auto-Update Check
|
|
41
|
+
|
|
42
|
+
Before running `start`, `configure`, or `sync`, the CLI checks for a newer published version. If an update is required the command is blocked and you are prompted to upgrade:
|
|
43
|
+
|
|
25
44
|
```
|
|
45
|
+
[agendex] update required: v0.1.0 → v0.2.0
|
|
46
|
+
[agendex] run: npm i -g agendex-cli
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The check is skipped for `stop`, `status`, `login`, `logout`, and `help`.
|
|
26
50
|
|
|
27
51
|
## Supported Runtime
|
|
28
52
|
|
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);
|
|
@@ -2477,11 +2495,17 @@ function startWatching(onChange) {
|
|
|
2477
2495
|
// src/auth.ts
|
|
2478
2496
|
import { spawn } from "node:child_process";
|
|
2479
2497
|
import { createServer } from "node:http";
|
|
2480
|
-
var
|
|
2498
|
+
var PROD_SITE_URL = "https://app.agendex.dev";
|
|
2499
|
+
var DEV_SITE_URL = "http://app.agendex.local:5174";
|
|
2500
|
+
function getDefaultSiteUrl() {
|
|
2501
|
+
if (process.env.AGENDEX_SITE_URL)
|
|
2502
|
+
return process.env.AGENDEX_SITE_URL;
|
|
2503
|
+
return process.env.AGENDEX_DEV === "1" ? DEV_SITE_URL : PROD_SITE_URL;
|
|
2504
|
+
}
|
|
2481
2505
|
async function login(siteUrlOverride) {
|
|
2482
2506
|
const { port, result } = await startCallbackServer();
|
|
2483
2507
|
const callbackUrl = `http://127.0.0.1:${port}/callback`;
|
|
2484
|
-
const siteUrl = siteUrlOverride ??
|
|
2508
|
+
const siteUrl = siteUrlOverride ?? getDefaultSiteUrl();
|
|
2485
2509
|
const authUrl = `${siteUrl}/auth/cli?callback=${encodeURIComponent(callbackUrl)}`;
|
|
2486
2510
|
console.log(`[agendex] Opening browser for authentication...`);
|
|
2487
2511
|
console.log(`[agendex] If it doesn't open, visit: ${authUrl}`);
|
|
@@ -2670,6 +2694,56 @@ import { fileURLToPath } from "node:url";
|
|
|
2670
2694
|
// src/api.ts
|
|
2671
2695
|
import { request as httpRequest } from "node:http";
|
|
2672
2696
|
import { request as httpsRequest } from "node:https";
|
|
2697
|
+
import { hostname as osHostname } from "node:os";
|
|
2698
|
+
|
|
2699
|
+
// src/pid.ts
|
|
2700
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync3, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
2701
|
+
import { homedir as homedir9, hostname } from "node:os";
|
|
2702
|
+
import { dirname, join as join10 } from "node:path";
|
|
2703
|
+
var pidPath = join10(homedir9(), ".agendex", "daemon.pid");
|
|
2704
|
+
function writePid() {
|
|
2705
|
+
mkdirSync2(dirname(pidPath), { recursive: true });
|
|
2706
|
+
const info = {
|
|
2707
|
+
pid: process.pid,
|
|
2708
|
+
startedAtMs: Date.now(),
|
|
2709
|
+
hostname: hostname()
|
|
2710
|
+
};
|
|
2711
|
+
writeFileSync2(pidPath, JSON.stringify(info));
|
|
2712
|
+
}
|
|
2713
|
+
function readPidInfo() {
|
|
2714
|
+
if (!existsSync6(pidPath))
|
|
2715
|
+
return null;
|
|
2716
|
+
const raw = readFileSync3(pidPath, "utf-8").trim();
|
|
2717
|
+
const asNumber = Number(raw);
|
|
2718
|
+
if (Number.isFinite(asNumber) && asNumber > 0 && !raw.startsWith("{")) {
|
|
2719
|
+
return { pid: asNumber };
|
|
2720
|
+
}
|
|
2721
|
+
try {
|
|
2722
|
+
const parsed = JSON.parse(raw);
|
|
2723
|
+
if (Number.isFinite(parsed.pid) && parsed.pid > 0)
|
|
2724
|
+
return parsed;
|
|
2725
|
+
} catch {}
|
|
2726
|
+
return null;
|
|
2727
|
+
}
|
|
2728
|
+
function readPid() {
|
|
2729
|
+
return readPidInfo()?.pid ?? null;
|
|
2730
|
+
}
|
|
2731
|
+
function removePid() {
|
|
2732
|
+
try {
|
|
2733
|
+
unlinkSync(pidPath);
|
|
2734
|
+
} catch {}
|
|
2735
|
+
}
|
|
2736
|
+
function isRunning(pid) {
|
|
2737
|
+
try {
|
|
2738
|
+
process.kill(pid, 0);
|
|
2739
|
+
return true;
|
|
2740
|
+
} catch {
|
|
2741
|
+
return false;
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
|
|
2745
|
+
// src/api.ts
|
|
2746
|
+
var cachedDeviceId;
|
|
2673
2747
|
function getCloudConfig() {
|
|
2674
2748
|
const config = loadConfig();
|
|
2675
2749
|
if (!config?.cloudToken)
|
|
@@ -2724,6 +2798,12 @@ async function refreshStoredToken(currentToken, convexUrl) {
|
|
|
2724
2798
|
async function sendHeartbeat() {
|
|
2725
2799
|
try {
|
|
2726
2800
|
const { token, convexUrl } = getCloudConfig();
|
|
2801
|
+
const pidInfo = readPidInfo();
|
|
2802
|
+
const heartbeatBody = JSON.stringify({
|
|
2803
|
+
deviceId: cachedDeviceId ??= loadOrCreateDeviceId(),
|
|
2804
|
+
hostname: pidInfo?.hostname ?? osHostname(),
|
|
2805
|
+
startedAtMs: pidInfo?.startedAtMs
|
|
2806
|
+
});
|
|
2727
2807
|
let activeToken = token;
|
|
2728
2808
|
let res = await requestText(`${convexUrl}/api/cli/heartbeat`, {
|
|
2729
2809
|
method: "POST",
|
|
@@ -2731,7 +2811,8 @@ async function sendHeartbeat() {
|
|
|
2731
2811
|
Authorization: `Bearer ${activeToken}`,
|
|
2732
2812
|
Connection: "close",
|
|
2733
2813
|
"Content-Type": "application/json"
|
|
2734
|
-
}
|
|
2814
|
+
},
|
|
2815
|
+
body: heartbeatBody
|
|
2735
2816
|
});
|
|
2736
2817
|
if (res.status === 401) {
|
|
2737
2818
|
const refreshedToken = await refreshStoredToken(activeToken, convexUrl);
|
|
@@ -2744,7 +2825,8 @@ async function sendHeartbeat() {
|
|
|
2744
2825
|
Authorization: `Bearer ${activeToken}`,
|
|
2745
2826
|
Connection: "close",
|
|
2746
2827
|
"Content-Type": "application/json"
|
|
2747
|
-
}
|
|
2828
|
+
},
|
|
2829
|
+
body: heartbeatBody
|
|
2748
2830
|
});
|
|
2749
2831
|
}
|
|
2750
2832
|
if (res.status === 401) {
|
|
@@ -2798,36 +2880,6 @@ function requestText(urlString, options) {
|
|
|
2798
2880
|
});
|
|
2799
2881
|
}
|
|
2800
2882
|
|
|
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 } 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
|
-
writeFileSync2(pidPath, String(process.pid));
|
|
2809
|
-
}
|
|
2810
|
-
function readPid() {
|
|
2811
|
-
if (!existsSync6(pidPath))
|
|
2812
|
-
return null;
|
|
2813
|
-
const raw = readFileSync3(pidPath, "utf-8").trim();
|
|
2814
|
-
const pid = Number(raw);
|
|
2815
|
-
return Number.isFinite(pid) && pid > 0 ? pid : null;
|
|
2816
|
-
}
|
|
2817
|
-
function removePid() {
|
|
2818
|
-
try {
|
|
2819
|
-
unlinkSync(pidPath);
|
|
2820
|
-
} catch {}
|
|
2821
|
-
}
|
|
2822
|
-
function isRunning(pid) {
|
|
2823
|
-
try {
|
|
2824
|
-
process.kill(pid, 0);
|
|
2825
|
-
return true;
|
|
2826
|
-
} catch {
|
|
2827
|
-
return false;
|
|
2828
|
-
}
|
|
2829
|
-
}
|
|
2830
|
-
|
|
2831
2883
|
// src/daemon.ts
|
|
2832
2884
|
var MAX_RESTARTS = 5;
|
|
2833
2885
|
var RESTART_WINDOW_MS = 60000;
|
|
@@ -3013,7 +3065,7 @@ import { join as join11 } from "node:path";
|
|
|
3013
3065
|
// package.json
|
|
3014
3066
|
var package_default = {
|
|
3015
3067
|
name: "agendex-cli",
|
|
3016
|
-
version: "0.
|
|
3068
|
+
version: "0.6.0",
|
|
3017
3069
|
description: "Agendex CLI for login, sync, and daemon workflows",
|
|
3018
3070
|
homepage: "https://github.com/Tyru5/Agendex#readme",
|
|
3019
3071
|
repository: {
|
|
@@ -3119,6 +3171,10 @@ var command = args[0] ?? "start";
|
|
|
3119
3171
|
var cliEntry = resolve5(process.argv[1] ?? fileURLToPath2(import.meta.url));
|
|
3120
3172
|
async function main() {
|
|
3121
3173
|
const isInternal = args.includes("--daemon") || args.includes("--worker");
|
|
3174
|
+
if (command === "--version" || command === "-v") {
|
|
3175
|
+
writeStdout(CLI_VERSION);
|
|
3176
|
+
return 0;
|
|
3177
|
+
}
|
|
3122
3178
|
const isPassthrough = ["stop", "status", "login", "logout", "help", "--help", "-h"].includes(command);
|
|
3123
3179
|
if (!isInternal && !isPassthrough) {
|
|
3124
3180
|
const { updateAvailable, current, latest } = await checkForUpdate();
|
|
@@ -3196,7 +3252,8 @@ async function main() {
|
|
|
3196
3252
|
}
|
|
3197
3253
|
case "status": {
|
|
3198
3254
|
const config = loadConfig();
|
|
3199
|
-
const
|
|
3255
|
+
const pidInfo = readPidInfo();
|
|
3256
|
+
const pid = pidInfo?.pid ?? null;
|
|
3200
3257
|
const running = pid ? isRunning(pid) : false;
|
|
3201
3258
|
writeStdout(`[agendex] Config version: ${config?.configVersion ?? "none"}`);
|
|
3202
3259
|
writeStdout(`[agendex] Local token: ${config?.token ? "set" : "not set"}`);
|
|
@@ -3204,6 +3261,20 @@ async function main() {
|
|
|
3204
3261
|
writeStdout(`[agendex] Convex URL: ${config?.convexUrl ?? "not set"}`);
|
|
3205
3262
|
writeStdout(`[agendex] Enabled adapters: ${config?.enabledAdapters.join(", ") || "none"}`);
|
|
3206
3263
|
writeStdout(`[agendex] Daemon: ${running ? `running (PID ${pid})` : "not running"}`);
|
|
3264
|
+
if (running && pidInfo?.startedAtMs) {
|
|
3265
|
+
writeStdout(`[agendex] Uptime: ${formatDuration(Date.now() - pidInfo.startedAtMs)}`);
|
|
3266
|
+
} else if (running) {
|
|
3267
|
+
writeStdout(`[agendex] Uptime: unknown (restart daemon to populate)`);
|
|
3268
|
+
} else {
|
|
3269
|
+
writeStdout(`[agendex] Uptime: n/a`);
|
|
3270
|
+
}
|
|
3271
|
+
if (running && pidInfo?.hostname) {
|
|
3272
|
+
writeStdout(`[agendex] Hostname: ${pidInfo.hostname}`);
|
|
3273
|
+
} else if (running) {
|
|
3274
|
+
writeStdout(`[agendex] Hostname: unknown (restart daemon to populate)`);
|
|
3275
|
+
} else {
|
|
3276
|
+
writeStdout(`[agendex] Hostname: n/a`);
|
|
3277
|
+
}
|
|
3207
3278
|
writeStdout(`[agendex] CLI version: ${CLI_VERSION}`);
|
|
3208
3279
|
return 0;
|
|
3209
3280
|
}
|
|
@@ -3224,6 +3295,8 @@ Usage:
|
|
|
3224
3295
|
agendex sync One-shot scan + sync to cloud
|
|
3225
3296
|
agendex status Show current config state + daemon status
|
|
3226
3297
|
agendex help Show this help message
|
|
3298
|
+
agendex --version Print CLI version
|
|
3299
|
+
agendex -v Print CLI version
|
|
3227
3300
|
`.trim());
|
|
3228
3301
|
return 0;
|
|
3229
3302
|
}
|
|
@@ -3264,3 +3337,17 @@ function writeStderr(message) {
|
|
|
3264
3337
|
writeSync(process.stderr.fd, `${message}
|
|
3265
3338
|
`);
|
|
3266
3339
|
}
|
|
3340
|
+
function formatDuration(ms) {
|
|
3341
|
+
const totalSeconds = Math.max(0, Math.floor(ms / 1000));
|
|
3342
|
+
const days = Math.floor(totalSeconds / 86400);
|
|
3343
|
+
const hours = Math.floor(totalSeconds % 86400 / 3600);
|
|
3344
|
+
const minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
3345
|
+
const seconds = totalSeconds % 60;
|
|
3346
|
+
if (days > 0)
|
|
3347
|
+
return `${days}d ${hours}h ${minutes}m ${seconds}s`;
|
|
3348
|
+
if (hours > 0)
|
|
3349
|
+
return `${hours}h ${minutes}m ${seconds}s`;
|
|
3350
|
+
if (minutes > 0)
|
|
3351
|
+
return `${minutes}m ${seconds}s`;
|
|
3352
|
+
return `${seconds}s`;
|
|
3353
|
+
}
|