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.
Files changed (3) hide show
  1. package/README.md +29 -2
  2. package/dist/cli.js +96 -34
  3. 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` to use the local dev server.
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 configDir = join7(homedir7(), ".agendex");
2257
- var configPath = join7(configDir, "config.json");
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
- if (!existsSync3(configDir))
2260
- mkdirSync(configDir, { recursive: true });
2269
+ const dir = getConfigDir();
2270
+ if (!existsSync3(dir))
2271
+ mkdirSync(dir, { recursive: true });
2261
2272
  }
2262
2273
  function readStoredConfig() {
2263
- if (!existsSync3(configPath))
2274
+ const cfgPath = getConfigPath();
2275
+ if (!existsSync3(cfgPath))
2264
2276
  return null;
2265
2277
  try {
2266
- const raw = JSON.parse(readFileSync3(configPath, "utf-8"));
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(configPath, JSON.stringify(payload, null, 2));
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 ${configPath}
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 * 3;
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
- var USER_PLANS_DIR = join8(homedir8(), ".agendex", "plans");
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
- if (!existsSync4(USER_PLANS_DIR))
2516
+ const userPlansDir = getUserPlansDir();
2517
+ if (!existsSync4(userPlansDir))
2500
2518
  return;
2501
- const files = await walkDir(USER_PLANS_DIR);
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 { homedir as homedir9, hostname } from "node:os";
2675
+ import { hostname } from "node:os";
2658
2676
  import { dirname, join as join10 } from "node:path";
2659
- var pidPath = join10(homedir9(), ".agendex", "daemon.pid");
2677
+ function getPidPath() {
2678
+ return join10(getConfigDir(), "daemon.pid");
2679
+ }
2660
2680
  function writePid() {
2661
- mkdirSync2(dirname(pidPath), { recursive: true });
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(pidPath, JSON.stringify(info));
2688
+ writeFileSync2(path, JSON.stringify(info));
2668
2689
  }
2669
2690
  function readPidInfo() {
2670
- if (!existsSync6(pidPath))
2691
+ const path = getPidPath();
2692
+ if (!existsSync6(path))
2671
2693
  return null;
2672
- const raw = readFileSync4(pidPath, "utf-8").trim();
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(pidPath);
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 { token: config.cloudToken, convexUrl: config.convexUrl };
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 process.env.AGENDEX_DEV === "1" ? DEV_SITE_URL : PROD_SITE_URL;
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
- var CACHE_PATH = join11(homedir10(), ".agendex", "sync-cache.json");
3144
+ function getCachePath() {
3145
+ return join11(getConfigDir(), "sync-cache.json");
3146
+ }
3114
3147
  function loadSyncCache() {
3115
- if (!existsSync7(CACHE_PATH))
3148
+ const cachePath = getCachePath();
3149
+ if (!existsSync7(cachePath))
3116
3150
  return {};
3117
3151
  try {
3118
- const raw = JSON.parse(readFileSync5(CACHE_PATH, "utf-8"));
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 = join11(homedir10(), ".agendex");
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(CACHE_PATH, JSON.stringify(cache));
3166
+ writeFileSync3(cachePath, JSON.stringify(cache));
3132
3167
  return;
3133
3168
  }
3134
3169
  const existing = loadSyncCache();
3135
- writeFileSync3(CACHE_PATH, JSON.stringify({ ...existing, ...cache }));
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.8.5",
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 command = args[0] ?? "start";
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 child = spawn3(process.execPath, [cliEntry, "start", "--daemon"], {
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
- writeStdout(`- hostname: ${hostnameStr}
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agendex-cli",
3
- "version": "0.8.5",
3
+ "version": "0.9.1",
4
4
  "description": "Agendex CLI for login, sync, and daemon workflows",
5
5
  "homepage": "https://github.com/Tyru5/Agendex#readme",
6
6
  "repository": {