agendex-cli 0.8.5 → 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/README.md +29 -2
- package/dist/cli.js +96 -34
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,6 +28,33 @@ agendex help # Show help message
|
|
|
28
28
|
agendex --version / -v # Print CLI version
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
+
## Dev vs prod (config directory)
|
|
32
|
+
|
|
33
|
+
By default the CLI uses **`~/.agendex/`** for all on-disk state:
|
|
34
|
+
|
|
35
|
+
- `config.json` — local token, cloud token, Convex URL, device id, enabled adapters
|
|
36
|
+
- `daemon.pid` — supervisor PID and metadata
|
|
37
|
+
- `sync-cache.json` — hashes used to skip unchanged plans on sync
|
|
38
|
+
|
|
39
|
+
To use a **separate dev environment** (so local cloud / dev login does not overwrite prod credentials), use either:
|
|
40
|
+
|
|
41
|
+
- **`--dev`** on any command (recommended), or
|
|
42
|
+
- **`AGENDEX_DEV=1`** in the environment
|
|
43
|
+
|
|
44
|
+
That switches the directory to **`~/.agendex-dev/`** with the same filenames inside it.
|
|
45
|
+
|
|
46
|
+
`--dev` takes precedence when set programmatically; otherwise `AGENDEX_DEV=1` is read. When you start the daemon with `agendex start --dev`, the background supervisor and worker inherit `AGENDEX_DEV=1` so they stay on the dev config.
|
|
47
|
+
|
|
48
|
+
Examples:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
agendex --dev login
|
|
52
|
+
agendex --dev status
|
|
53
|
+
AGENDEX_DEV=1 agendex sync
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
In dev mode the default OAuth site (when you do not pass `--url` and do not set `AGENDEX_SITE_URL`) points at the local EE app URL used for development.
|
|
57
|
+
|
|
31
58
|
## Daemon Cleanup
|
|
32
59
|
|
|
33
60
|
`agendex cleanup` manages registered daemon devices in the cloud.
|
|
@@ -76,6 +103,6 @@ For self-hosted deployments, pass your site URL explicitly:
|
|
|
76
103
|
agendex login --url https://agendex.yourdomain.com
|
|
77
104
|
```
|
|
78
105
|
|
|
79
|
-
This opens your deployment's OAuth flow and stores the returned `cloudToken` and `convexUrl` in `~/.agendex/config.json
|
|
106
|
+
This opens your deployment's OAuth flow and stores the returned `cloudToken` and `convexUrl` in your active config directory (`~/.agendex/config.json` for prod, `~/.agendex-dev/config.json` when using `--dev` or `AGENDEX_DEV=1`).
|
|
80
107
|
|
|
81
|
-
The target can also be set via `AGENDEX_SITE_URL` env var. For local development, set `AGENDEX_DEV=1`
|
|
108
|
+
The target can also be set via `AGENDEX_SITE_URL` env var. For local development against the default dev app URL, use `agendex login --dev` or set `AGENDEX_DEV=1` (see [Dev vs prod](#dev-vs-prod-config-directory) above).
|
package/dist/cli.js
CHANGED
|
@@ -2253,17 +2253,29 @@ async function promptForAdapterSelection(options = {}) {
|
|
|
2253
2253
|
}
|
|
2254
2254
|
|
|
2255
2255
|
// ../shared/src/config.ts
|
|
2256
|
-
var
|
|
2257
|
-
|
|
2256
|
+
var devModeOverride;
|
|
2257
|
+
function setDevMode(dev) {
|
|
2258
|
+
devModeOverride = dev;
|
|
2259
|
+
}
|
|
2260
|
+
function isDevMode() {
|
|
2261
|
+
if (devModeOverride !== undefined)
|
|
2262
|
+
return devModeOverride;
|
|
2263
|
+
return process.env.AGENDEX_DEV === "1";
|
|
2264
|
+
}
|
|
2265
|
+
function getConfigDir() {
|
|
2266
|
+
return join7(homedir7(), isDevMode() ? ".agendex-dev" : ".agendex");
|
|
2267
|
+
}
|
|
2258
2268
|
function ensureConfigDir() {
|
|
2259
|
-
|
|
2260
|
-
|
|
2269
|
+
const dir = getConfigDir();
|
|
2270
|
+
if (!existsSync3(dir))
|
|
2271
|
+
mkdirSync(dir, { recursive: true });
|
|
2261
2272
|
}
|
|
2262
2273
|
function readStoredConfig() {
|
|
2263
|
-
|
|
2274
|
+
const cfgPath = getConfigPath();
|
|
2275
|
+
if (!existsSync3(cfgPath))
|
|
2264
2276
|
return null;
|
|
2265
2277
|
try {
|
|
2266
|
-
const raw = JSON.parse(readFileSync3(
|
|
2278
|
+
const raw = JSON.parse(readFileSync3(cfgPath, "utf-8"));
|
|
2267
2279
|
if (!raw || typeof raw !== "object")
|
|
2268
2280
|
return null;
|
|
2269
2281
|
return raw;
|
|
@@ -2305,7 +2317,7 @@ function saveConfig(config) {
|
|
|
2305
2317
|
deviceId: config.deviceId,
|
|
2306
2318
|
enabledAdapters: sanitizeEnabledAdapterIds(config.enabledAdapters)
|
|
2307
2319
|
};
|
|
2308
|
-
writeFileSync(
|
|
2320
|
+
writeFileSync(getConfigPath(), JSON.stringify(payload, null, 2));
|
|
2309
2321
|
}
|
|
2310
2322
|
function generateToken() {
|
|
2311
2323
|
return randomBytes(32).toString("hex");
|
|
@@ -2324,7 +2336,7 @@ function loadOrCreateToken() {
|
|
|
2324
2336
|
});
|
|
2325
2337
|
console.log(`
|
|
2326
2338
|
[agendex] generated auth token: ${token}`);
|
|
2327
|
-
console.log(`[agendex] saved to ${
|
|
2339
|
+
console.log(`[agendex] saved to ${getConfigPath()}
|
|
2328
2340
|
`);
|
|
2329
2341
|
return token;
|
|
2330
2342
|
}
|
|
@@ -2341,6 +2353,9 @@ function loadOrCreateDeviceId() {
|
|
|
2341
2353
|
});
|
|
2342
2354
|
return deviceId;
|
|
2343
2355
|
}
|
|
2356
|
+
function getConfigPath() {
|
|
2357
|
+
return join7(getConfigDir(), "config.json");
|
|
2358
|
+
}
|
|
2344
2359
|
async function loadOrInitConfig(options = {}) {
|
|
2345
2360
|
const configureAdapters = Boolean(options.configureAdapters);
|
|
2346
2361
|
const existing = loadConfig();
|
|
@@ -2383,13 +2398,15 @@ async function loadOrInitConfig(options = {}) {
|
|
|
2383
2398
|
}
|
|
2384
2399
|
// ../shared/src/daemon-status.ts
|
|
2385
2400
|
var CLI_DAEMON_HEARTBEAT_INTERVAL_MS = 30000;
|
|
2386
|
-
var CLI_DAEMON_STALE_AFTER_MS = CLI_DAEMON_HEARTBEAT_INTERVAL_MS *
|
|
2401
|
+
var CLI_DAEMON_STALE_AFTER_MS = CLI_DAEMON_HEARTBEAT_INTERVAL_MS * 5;
|
|
2387
2402
|
// ../shared/src/services/plan-service.ts
|
|
2388
2403
|
import { existsSync as existsSync4, readdirSync as readdirSync3, statSync } from "node:fs";
|
|
2389
2404
|
import { lstat, mkdir, readdir as readdir2, readFile as readFile6, stat as stat6, writeFile as writeFile3 } from "node:fs/promises";
|
|
2390
2405
|
import { homedir as homedir8 } from "node:os";
|
|
2391
2406
|
import { join as join8, resolve as resolve2, sep as sep2 } from "node:path";
|
|
2392
|
-
|
|
2407
|
+
function getUserPlansDir() {
|
|
2408
|
+
return join8(getConfigDir(), "plans");
|
|
2409
|
+
}
|
|
2393
2410
|
var store = new Map;
|
|
2394
2411
|
var MAX_DEPTH = 6;
|
|
2395
2412
|
var DISCOVERY_MAX_DEPTH = 4;
|
|
@@ -2496,9 +2513,10 @@ async function walkDir(dir, depth = 0, seen = new Set) {
|
|
|
2496
2513
|
return files;
|
|
2497
2514
|
}
|
|
2498
2515
|
async function scanUserPlans() {
|
|
2499
|
-
|
|
2516
|
+
const userPlansDir = getUserPlansDir();
|
|
2517
|
+
if (!existsSync4(userPlansDir))
|
|
2500
2518
|
return;
|
|
2501
|
-
const files = await walkDir(
|
|
2519
|
+
const files = await walkDir(userPlansDir);
|
|
2502
2520
|
for (const file of files) {
|
|
2503
2521
|
if (!file.endsWith(".md"))
|
|
2504
2522
|
continue;
|
|
@@ -2654,22 +2672,26 @@ import { hostname as osHostname } from "node:os";
|
|
|
2654
2672
|
|
|
2655
2673
|
// src/pid.ts
|
|
2656
2674
|
import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync4, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
2657
|
-
import {
|
|
2675
|
+
import { hostname } from "node:os";
|
|
2658
2676
|
import { dirname, join as join10 } from "node:path";
|
|
2659
|
-
|
|
2677
|
+
function getPidPath() {
|
|
2678
|
+
return join10(getConfigDir(), "daemon.pid");
|
|
2679
|
+
}
|
|
2660
2680
|
function writePid() {
|
|
2661
|
-
|
|
2681
|
+
const path = getPidPath();
|
|
2682
|
+
mkdirSync2(dirname(path), { recursive: true });
|
|
2662
2683
|
const info = {
|
|
2663
2684
|
pid: process.pid,
|
|
2664
2685
|
startedAtMs: Date.now(),
|
|
2665
2686
|
hostname: hostname()
|
|
2666
2687
|
};
|
|
2667
|
-
writeFileSync2(
|
|
2688
|
+
writeFileSync2(path, JSON.stringify(info));
|
|
2668
2689
|
}
|
|
2669
2690
|
function readPidInfo() {
|
|
2670
|
-
|
|
2691
|
+
const path = getPidPath();
|
|
2692
|
+
if (!existsSync6(path))
|
|
2671
2693
|
return null;
|
|
2672
|
-
const raw = readFileSync4(
|
|
2694
|
+
const raw = readFileSync4(path, "utf-8").trim();
|
|
2673
2695
|
const asNumber = Number(raw);
|
|
2674
2696
|
if (Number.isFinite(asNumber) && asNumber > 0 && !raw.startsWith("{")) {
|
|
2675
2697
|
return { pid: asNumber };
|
|
@@ -2686,7 +2708,7 @@ function readPid() {
|
|
|
2686
2708
|
}
|
|
2687
2709
|
function removePid() {
|
|
2688
2710
|
try {
|
|
2689
|
-
unlinkSync(
|
|
2711
|
+
unlinkSync(getPidPath());
|
|
2690
2712
|
} catch {}
|
|
2691
2713
|
}
|
|
2692
2714
|
function isRunning(pid) {
|
|
@@ -2706,7 +2728,10 @@ function getCloudConfig() {
|
|
|
2706
2728
|
throw new Error("Not logged in. Run `agendex login` first.");
|
|
2707
2729
|
if (!config.convexUrl)
|
|
2708
2730
|
throw new Error("No Convex URL configured. Run `agendex login` first.");
|
|
2709
|
-
return {
|
|
2731
|
+
return {
|
|
2732
|
+
token: config.cloudToken,
|
|
2733
|
+
convexUrl: config.convexUrl
|
|
2734
|
+
};
|
|
2710
2735
|
}
|
|
2711
2736
|
async function syncPlan(plan) {
|
|
2712
2737
|
const { token, convexUrl } = getCloudConfig();
|
|
@@ -2792,6 +2817,13 @@ async function sendHeartbeat() {
|
|
|
2792
2817
|
}
|
|
2793
2818
|
} catch {}
|
|
2794
2819
|
}
|
|
2820
|
+
async function sendShutdown() {
|
|
2821
|
+
try {
|
|
2822
|
+
const { token, convexUrl } = getCloudConfig();
|
|
2823
|
+
cachedDeviceId ??= loadOrCreateDeviceId();
|
|
2824
|
+
await deleteDaemons([cachedDeviceId]);
|
|
2825
|
+
} catch {}
|
|
2826
|
+
}
|
|
2795
2827
|
async function refreshToken(currentToken, convexUrl) {
|
|
2796
2828
|
const res = await requestText(`${convexUrl}/api/cli/refresh`, {
|
|
2797
2829
|
method: "POST",
|
|
@@ -2914,7 +2946,7 @@ var DEV_SITE_URL = "http://app.agendex.local:5174";
|
|
|
2914
2946
|
function getDefaultSiteUrl() {
|
|
2915
2947
|
if (process.env.AGENDEX_SITE_URL)
|
|
2916
2948
|
return process.env.AGENDEX_SITE_URL;
|
|
2917
|
-
return
|
|
2949
|
+
return isDevMode() ? DEV_SITE_URL : PROD_SITE_URL;
|
|
2918
2950
|
}
|
|
2919
2951
|
async function login(siteUrlOverride) {
|
|
2920
2952
|
const { port, result } = await startCallbackServer();
|
|
@@ -3108,14 +3140,16 @@ import { fileURLToPath } from "node:url";
|
|
|
3108
3140
|
// src/sync-cache.ts
|
|
3109
3141
|
import { createHash as createHash2 } from "node:crypto";
|
|
3110
3142
|
import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
|
|
3111
|
-
import { homedir as homedir10 } from "node:os";
|
|
3112
3143
|
import { join as join11 } from "node:path";
|
|
3113
|
-
|
|
3144
|
+
function getCachePath() {
|
|
3145
|
+
return join11(getConfigDir(), "sync-cache.json");
|
|
3146
|
+
}
|
|
3114
3147
|
function loadSyncCache() {
|
|
3115
|
-
|
|
3148
|
+
const cachePath = getCachePath();
|
|
3149
|
+
if (!existsSync7(cachePath))
|
|
3116
3150
|
return {};
|
|
3117
3151
|
try {
|
|
3118
|
-
const raw = JSON.parse(readFileSync5(
|
|
3152
|
+
const raw = JSON.parse(readFileSync5(cachePath, "utf-8"));
|
|
3119
3153
|
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
3120
3154
|
return {};
|
|
3121
3155
|
return raw;
|
|
@@ -3124,15 +3158,16 @@ function loadSyncCache() {
|
|
|
3124
3158
|
}
|
|
3125
3159
|
}
|
|
3126
3160
|
function saveSyncCache(cache, options) {
|
|
3127
|
-
const dir =
|
|
3161
|
+
const dir = getConfigDir();
|
|
3128
3162
|
if (!existsSync7(dir))
|
|
3129
3163
|
mkdirSync3(dir, { recursive: true });
|
|
3164
|
+
const cachePath = getCachePath();
|
|
3130
3165
|
if (options?.replace) {
|
|
3131
|
-
writeFileSync3(
|
|
3166
|
+
writeFileSync3(cachePath, JSON.stringify(cache));
|
|
3132
3167
|
return;
|
|
3133
3168
|
}
|
|
3134
3169
|
const existing = loadSyncCache();
|
|
3135
|
-
writeFileSync3(
|
|
3170
|
+
writeFileSync3(cachePath, JSON.stringify({ ...existing, ...cache }));
|
|
3136
3171
|
}
|
|
3137
3172
|
function computePayloadHash(payload) {
|
|
3138
3173
|
const canonical = JSON.stringify([
|
|
@@ -3173,7 +3208,7 @@ async function runWorker() {
|
|
|
3173
3208
|
const adapters = resolveAdapters(config.enabledAdapters);
|
|
3174
3209
|
setActiveAdapters(adapters);
|
|
3175
3210
|
console.log(`[agendex] daemon starting with ${config.enabledAdapters.length} adapters`);
|
|
3176
|
-
sendHeartbeat();
|
|
3211
|
+
await sendHeartbeat();
|
|
3177
3212
|
const syncCache = loadSyncCache();
|
|
3178
3213
|
const syncQueue = [];
|
|
3179
3214
|
let syncing = false;
|
|
@@ -3261,6 +3296,12 @@ async function runWorker() {
|
|
|
3261
3296
|
processSyncQueue();
|
|
3262
3297
|
});
|
|
3263
3298
|
console.log(`[agendex] daemon running. Watching for file changes...`);
|
|
3299
|
+
async function gracefulShutdown() {
|
|
3300
|
+
await sendShutdown();
|
|
3301
|
+
process.exit(0);
|
|
3302
|
+
}
|
|
3303
|
+
process.on("SIGTERM", () => void gracefulShutdown());
|
|
3304
|
+
process.on("SIGINT", () => void gracefulShutdown());
|
|
3264
3305
|
await new Promise(() => {});
|
|
3265
3306
|
}
|
|
3266
3307
|
async function startSupervisor() {
|
|
@@ -3368,7 +3409,7 @@ import { join as join12 } from "node:path";
|
|
|
3368
3409
|
// package.json
|
|
3369
3410
|
var package_default = {
|
|
3370
3411
|
name: "agendex-cli",
|
|
3371
|
-
version: "0.
|
|
3412
|
+
version: "0.9.1",
|
|
3372
3413
|
description: "Agendex CLI for login, sync, and daemon workflows",
|
|
3373
3414
|
homepage: "https://github.com/Tyru5/Agendex#readme",
|
|
3374
3415
|
repository: {
|
|
@@ -3480,7 +3521,18 @@ function isNewer(latest, current) {
|
|
|
3480
3521
|
|
|
3481
3522
|
// src/cli.ts
|
|
3482
3523
|
var args = process.argv.slice(2);
|
|
3483
|
-
var
|
|
3524
|
+
var devFlag = args.includes("--dev");
|
|
3525
|
+
if (devFlag)
|
|
3526
|
+
setDevMode(true);
|
|
3527
|
+
function firstCommandToken(argv) {
|
|
3528
|
+
for (const a of argv) {
|
|
3529
|
+
if (a === "--dev")
|
|
3530
|
+
continue;
|
|
3531
|
+
return a;
|
|
3532
|
+
}
|
|
3533
|
+
return;
|
|
3534
|
+
}
|
|
3535
|
+
var command = firstCommandToken(args) ?? "start";
|
|
3484
3536
|
var cliEntry = resolve5(process.argv[1] ?? fileURLToPath2(import.meta.url));
|
|
3485
3537
|
async function main() {
|
|
3486
3538
|
const isInternal = args.includes("--daemon") || args.includes("--worker");
|
|
@@ -3523,9 +3575,13 @@ async function main() {
|
|
|
3523
3575
|
}
|
|
3524
3576
|
if (existingPid)
|
|
3525
3577
|
removePid();
|
|
3526
|
-
const
|
|
3578
|
+
const daemonArgs = [cliEntry, "start", "--daemon"];
|
|
3579
|
+
if (devFlag)
|
|
3580
|
+
daemonArgs.push("--dev");
|
|
3581
|
+
const child = spawn3(process.execPath, daemonArgs, {
|
|
3527
3582
|
detached: true,
|
|
3528
|
-
stdio: "ignore"
|
|
3583
|
+
stdio: "ignore",
|
|
3584
|
+
env: { ...process.env, ...devFlag ? { AGENDEX_DEV: "1" } : {} }
|
|
3529
3585
|
});
|
|
3530
3586
|
child.unref();
|
|
3531
3587
|
await new Promise((r) => setTimeout(r, 500));
|
|
@@ -3549,6 +3605,7 @@ async function main() {
|
|
|
3549
3605
|
writeStderr("[agendex] daemon did not stop in time");
|
|
3550
3606
|
} else {
|
|
3551
3607
|
removePid();
|
|
3608
|
+
await sendShutdown();
|
|
3552
3609
|
writeStdout(`[agendex] daemon stopped (PID ${pid})`);
|
|
3553
3610
|
}
|
|
3554
3611
|
return isRunning(pid) ? 1 : 0;
|
|
@@ -3670,6 +3727,7 @@ async function main() {
|
|
|
3670
3727
|
const allDevices = await fetchDevices();
|
|
3671
3728
|
if (allDevices.length > 0) {
|
|
3672
3729
|
const now = Date.now();
|
|
3730
|
+
const localDeviceId = config.deviceId;
|
|
3673
3731
|
writeStdout(`[agendex] All daemons:`);
|
|
3674
3732
|
for (const device of allDevices) {
|
|
3675
3733
|
const age = device.lastSeenAt ? now - device.lastSeenAt : Number.POSITIVE_INFINITY;
|
|
@@ -3677,7 +3735,8 @@ async function main() {
|
|
|
3677
3735
|
const uptimeStr = device.startedAtMs != null ? formatDuration(now - device.startedAtMs) : "~";
|
|
3678
3736
|
const pidStr = device.pid != null ? String(device.pid) : "~";
|
|
3679
3737
|
const hostnameStr = device.hostname ?? "~";
|
|
3680
|
-
|
|
3738
|
+
const isLocal = localDeviceId && device.deviceId === localDeviceId;
|
|
3739
|
+
writeStdout(`- hostname: ${hostnameStr}${isLocal ? " (this machine)" : ""}
|
|
3681
3740
|
pid: ${pidStr}
|
|
3682
3741
|
uptime: ${uptimeStr}
|
|
3683
3742
|
status: ${status}`);
|
|
@@ -3711,6 +3770,9 @@ Usage:
|
|
|
3711
3770
|
agendex help Show this help message
|
|
3712
3771
|
agendex --version Print CLI version
|
|
3713
3772
|
agendex -v Print CLI version
|
|
3773
|
+
|
|
3774
|
+
Flags:
|
|
3775
|
+
--dev Use dev environment (~/.agendex-dev/ config dir)
|
|
3714
3776
|
`.trim());
|
|
3715
3777
|
return 0;
|
|
3716
3778
|
}
|