agendex-cli 0.8.4 → 0.8.5

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 (2) hide show
  1. package/dist/cli.js +96 -14
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -3104,6 +3104,53 @@ function spawnBrowser(command, args, options = {}) {
3104
3104
  import { spawn as spawn2 } from "node:child_process";
3105
3105
  import { resolve as resolve4 } from "node:path";
3106
3106
  import { fileURLToPath } from "node:url";
3107
+
3108
+ // src/sync-cache.ts
3109
+ import { createHash as createHash2 } from "node:crypto";
3110
+ 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
+ import { join as join11 } from "node:path";
3113
+ var CACHE_PATH = join11(homedir10(), ".agendex", "sync-cache.json");
3114
+ function loadSyncCache() {
3115
+ if (!existsSync7(CACHE_PATH))
3116
+ return {};
3117
+ try {
3118
+ const raw = JSON.parse(readFileSync5(CACHE_PATH, "utf-8"));
3119
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
3120
+ return {};
3121
+ return raw;
3122
+ } catch {
3123
+ return {};
3124
+ }
3125
+ }
3126
+ function saveSyncCache(cache, options) {
3127
+ const dir = join11(homedir10(), ".agendex");
3128
+ if (!existsSync7(dir))
3129
+ mkdirSync3(dir, { recursive: true });
3130
+ if (options?.replace) {
3131
+ writeFileSync3(CACHE_PATH, JSON.stringify(cache));
3132
+ return;
3133
+ }
3134
+ const existing = loadSyncCache();
3135
+ writeFileSync3(CACHE_PATH, JSON.stringify({ ...existing, ...cache }));
3136
+ }
3137
+ function computePayloadHash(payload) {
3138
+ const canonical = JSON.stringify([
3139
+ payload.localPlanId,
3140
+ payload.agent,
3141
+ payload.title,
3142
+ payload.content,
3143
+ payload.format,
3144
+ payload.filePath ?? null,
3145
+ payload.workspace ?? null,
3146
+ payload.metadata ?? null,
3147
+ payload.createdAt ?? null,
3148
+ payload.updatedAt ?? null
3149
+ ]);
3150
+ return createHash2("sha256").update(canonical).digest("hex").slice(0, 20);
3151
+ }
3152
+
3153
+ // src/daemon.ts
3107
3154
  var MAX_RESTARTS = 5;
3108
3155
  var RESTART_WINDOW_MS = 60000;
3109
3156
  var RESTART_DELAY_MS = 5000;
@@ -3127,6 +3174,7 @@ async function runWorker() {
3127
3174
  setActiveAdapters(adapters);
3128
3175
  console.log(`[agendex] daemon starting with ${config.enabledAdapters.length} adapters`);
3129
3176
  sendHeartbeat();
3177
+ const syncCache = loadSyncCache();
3130
3178
  const syncQueue = [];
3131
3179
  let syncing = false;
3132
3180
  async function tryRefreshToken() {
@@ -3167,6 +3215,7 @@ async function runWorker() {
3167
3215
  console.error(`[agendex] sync failed for "${payload.title}": ${result.error}`);
3168
3216
  } else {
3169
3217
  syncedCount++;
3218
+ syncCache[payload.localPlanId] = computePayloadHash(payload);
3170
3219
  }
3171
3220
  }
3172
3221
  } catch (err) {
@@ -3176,6 +3225,7 @@ async function runWorker() {
3176
3225
  syncing = false;
3177
3226
  }
3178
3227
  if (syncedCount > 0 || failedCount > 0) {
3228
+ saveSyncCache(syncCache);
3179
3229
  console.log(`[agendex] sync complete: ${syncedCount} synced, ${failedCount} failed`);
3180
3230
  }
3181
3231
  if (syncQueue.length > 0)
@@ -3185,10 +3235,23 @@ async function runWorker() {
3185
3235
  console.log(`[agendex] initial scan...`);
3186
3236
  await scan();
3187
3237
  const plans = getAll();
3188
- console.log(`[agendex] syncing ${plans.length} plans...`);
3238
+ let initialSkipped = 0;
3189
3239
  for (const plan of plans) {
3190
- syncQueue.push(planToPayload(plan));
3240
+ const payload = planToPayload(plan);
3241
+ const hash = computePayloadHash(payload);
3242
+ if (syncCache[plan.id] === hash) {
3243
+ initialSkipped++;
3244
+ continue;
3245
+ }
3246
+ syncQueue.push(payload);
3247
+ }
3248
+ const activePlanIds = new Set(plans.map((plan) => plan.id));
3249
+ for (const id of Object.keys(syncCache)) {
3250
+ if (!activePlanIds.has(id))
3251
+ delete syncCache[id];
3191
3252
  }
3253
+ saveSyncCache(syncCache, { replace: true });
3254
+ console.log(`[agendex] syncing ${syncQueue.length} plans (${initialSkipped} unchanged)...`);
3192
3255
  await processSyncQueue();
3193
3256
  setInterval(() => void sendHeartbeat(), CLI_DAEMON_HEARTBEAT_INTERVAL_MS);
3194
3257
  startWatching((changedPlans) => {
@@ -3249,7 +3312,7 @@ async function startSupervisor() {
3249
3312
  }
3250
3313
 
3251
3314
  // src/sync.ts
3252
- async function syncAll() {
3315
+ async function syncAll(force = false) {
3253
3316
  const config = await loadOrInitConfig();
3254
3317
  const adapters = resolveAdapters(config.enabledAdapters);
3255
3318
  setActiveAdapters(adapters);
@@ -3257,9 +3320,13 @@ async function syncAll() {
3257
3320
  await scan();
3258
3321
  const plans = getAll();
3259
3322
  console.log(`[agendex] Found ${plans.length} plans. Syncing to cloud...`);
3323
+ const cache = force ? {} : loadSyncCache();
3324
+ const activePlanIds = new Set;
3260
3325
  let synced = 0;
3326
+ let skipped = 0;
3261
3327
  let failed = 0;
3262
3328
  for (const plan of plans) {
3329
+ activePlanIds.add(plan.id);
3263
3330
  const payload = {
3264
3331
  localPlanId: plan.id,
3265
3332
  agent: plan.agent,
@@ -3268,27 +3335,40 @@ async function syncAll() {
3268
3335
  format: plan.format,
3269
3336
  filePath: plan.filePath,
3270
3337
  workspace: plan.workspace,
3271
- metadata: plan.metadata
3338
+ metadata: plan.metadata,
3339
+ createdAt: plan.createdAt.getTime(),
3340
+ updatedAt: plan.updatedAt.getTime()
3272
3341
  };
3342
+ const hash = computePayloadHash(payload);
3343
+ if (!force && cache[plan.id] === hash) {
3344
+ skipped++;
3345
+ continue;
3346
+ }
3273
3347
  const result = await syncPlan(payload);
3274
3348
  if (result.ok) {
3275
3349
  synced++;
3350
+ cache[plan.id] = hash;
3276
3351
  } else {
3277
3352
  failed++;
3278
3353
  console.error(`[agendex] Failed to sync "${plan.title}": ${result.error}`);
3279
3354
  }
3280
3355
  }
3281
- console.log(`[agendex] Sync complete: ${synced} synced, ${failed} failed`);
3356
+ for (const id of Object.keys(cache)) {
3357
+ if (!activePlanIds.has(id))
3358
+ delete cache[id];
3359
+ }
3360
+ saveSyncCache(cache, { replace: true });
3361
+ console.log(`[agendex] Sync complete: ${synced} synced, ${skipped} unchanged, ${failed} failed`);
3282
3362
  }
3283
3363
 
3284
3364
  // src/version.ts
3285
- import { existsSync as existsSync7, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
3365
+ import { existsSync as existsSync8, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
3286
3366
  import { tmpdir } from "node:os";
3287
- import { join as join11 } from "node:path";
3367
+ import { join as join12 } from "node:path";
3288
3368
  // package.json
3289
3369
  var package_default = {
3290
3370
  name: "agendex-cli",
3291
- version: "0.8.4",
3371
+ version: "0.8.5",
3292
3372
  description: "Agendex CLI for login, sync, and daemon workflows",
3293
3373
  homepage: "https://github.com/Tyru5/Agendex#readme",
3294
3374
  repository: {
@@ -3332,14 +3412,14 @@ var package_default = {
3332
3412
 
3333
3413
  // src/version.ts
3334
3414
  var CLI_VERSION = package_default.version;
3335
- var CACHE_FILE = process.env.AGENDEX_UPDATE_CACHE_FILE ?? join11(tmpdir(), ".agendex-update-cache.json");
3415
+ var CACHE_FILE = process.env.AGENDEX_UPDATE_CACHE_FILE ?? join12(tmpdir(), ".agendex-update-cache.json");
3336
3416
  var CACHE_TTL_MS = 24 * 60 * 60 * 1000;
3337
3417
  var UPDATE_URL = process.env.AGENDEX_UPDATE_URL ?? "https://registry.npmjs.org/agendex-cli/latest";
3338
3418
  function readCache(current) {
3339
3419
  try {
3340
- if (!existsSync7(CACHE_FILE))
3420
+ if (!existsSync8(CACHE_FILE))
3341
3421
  return null;
3342
- const { result, ts } = JSON.parse(readFileSync5(CACHE_FILE, "utf8"));
3422
+ const { result, ts } = JSON.parse(readFileSync6(CACHE_FILE, "utf8"));
3343
3423
  if (Date.now() - ts > CACHE_TTL_MS)
3344
3424
  return null;
3345
3425
  return normalizeResult(result, current);
@@ -3349,7 +3429,7 @@ function readCache(current) {
3349
3429
  }
3350
3430
  function writeCache(result) {
3351
3431
  try {
3352
- writeFileSync3(CACHE_FILE, JSON.stringify({ result, ts: Date.now() }));
3432
+ writeFileSync4(CACHE_FILE, JSON.stringify({ result, ts: Date.now() }));
3353
3433
  } catch {}
3354
3434
  }
3355
3435
  async function checkForUpdate() {
@@ -3489,7 +3569,8 @@ async function main() {
3489
3569
  return 0;
3490
3570
  }
3491
3571
  case "sync": {
3492
- await syncAll();
3572
+ const force = args.includes("--force");
3573
+ await syncAll(force);
3493
3574
  return 0;
3494
3575
  }
3495
3576
  case "cleanup": {
@@ -3622,7 +3703,8 @@ Usage:
3622
3703
  agendex login --url <url> Login to a self-hosted instance
3623
3704
  agendex logout Clear stored cloud token
3624
3705
  agendex configure Select which agents/adapters to index
3625
- agendex sync One-shot scan + sync to cloud
3706
+ agendex sync One-shot scan + sync to cloud (skips unchanged plans)
3707
+ agendex sync --force Re-sync all plans, ignoring cache
3626
3708
  agendex cleanup Interactively remove cloud daemons
3627
3709
  agendex cleanup --stale Auto-remove all stale daemons
3628
3710
  agendex status Show current config state + daemon status
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agendex-cli",
3
- "version": "0.8.4",
3
+ "version": "0.8.5",
4
4
  "description": "Agendex CLI for login, sync, and daemon workflows",
5
5
  "homepage": "https://github.com/Tyru5/Agendex#readme",
6
6
  "repository": {