agendex-cli 0.8.3 → 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 +196 -81
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { createRequire } from "node:module";
3
2
  var __create = Object.create;
4
3
  var __getProtoOf = Object.getPrototypeOf;
5
4
  var __defProp = Object.defineProperty;
@@ -45,7 +44,6 @@ var __export = (target, all) => {
45
44
  });
46
45
  };
47
46
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
48
- var __require = /* @__PURE__ */ createRequire(import.meta.url);
49
47
 
50
48
  // ../../node_modules/.bun/picocolors@1.1.1/node_modules/picocolors/picocolors.js
51
49
  var require_picocolors = __commonJS((exports, module) => {
@@ -1478,66 +1476,101 @@ var continueIdeAdapter = {
1478
1476
  };
1479
1477
 
1480
1478
  // ../shared/src/adapters/cursor.ts
1481
- import { existsSync } from "node:fs";
1482
- import { stat as stat4 } from "node:fs/promises";
1479
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
1480
+ import { readFile as readFile4, stat as stat4 } from "node:fs/promises";
1483
1481
  import { homedir as homedir4 } from "node:os";
1484
- import { join as join4 } from "node:path";
1485
- function quoteIdentifier(name) {
1486
- return `"${name.replaceAll('"', '""')}"`;
1482
+ import { basename as basename3, join as join4 } from "node:path";
1483
+ var cursorProjectsDir = join4(homedir4(), ".cursor", "projects");
1484
+ function discoverCursorPlanDirs() {
1485
+ if (!existsSync(cursorProjectsDir))
1486
+ return [];
1487
+ const dirs = [];
1488
+ let entries;
1489
+ try {
1490
+ entries = readdirSync(cursorProjectsDir);
1491
+ } catch {
1492
+ return [];
1493
+ }
1494
+ for (const entry of entries) {
1495
+ const trustedFile = join4(cursorProjectsDir, entry, ".workspace-trusted");
1496
+ if (!existsSync(trustedFile))
1497
+ continue;
1498
+ try {
1499
+ const raw = JSON.parse(readFileSync(trustedFile, "utf-8"));
1500
+ if (!raw.workspacePath)
1501
+ continue;
1502
+ const plansDir2 = join4(raw.workspacePath, ".cursor", "plans");
1503
+ if (existsSync(plansDir2)) {
1504
+ dirs.push(plansDir2);
1505
+ }
1506
+ } catch {}
1507
+ }
1508
+ return dirs;
1509
+ }
1510
+ function extractTitle3(content, filename) {
1511
+ const match = content.match(/^#\s+(.+)/m);
1512
+ if (match?.[1])
1513
+ return match[1].replace(/^Plan:\s*/i, "").trim();
1514
+ return basename3(filename, ".plan.md").split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
1515
+ }
1516
+ function stripFrontmatter(raw) {
1517
+ const text = raw.replace(/^<!--\s*[\w-]+\s*-->\s*\n?/, "");
1518
+ const fmMatch = text.match(/^---\s*\n([\s\S]*?)\n---\s*\n/);
1519
+ if (!fmMatch)
1520
+ return { body: text.trim(), metadata: {} };
1521
+ const metadata = {};
1522
+ const fmBody = fmMatch[1] ?? "";
1523
+ const todoIdMatches = fmBody.match(/^\s+-\s+id:\s/gm);
1524
+ if (todoIdMatches) {
1525
+ metadata.todoCount = todoIdMatches.length;
1526
+ }
1527
+ const projectMatch = fmBody.match(/^isProject:\s*(.+)$/m);
1528
+ if (projectMatch?.[1]) {
1529
+ metadata.isProject = projectMatch[1].trim() === "true";
1530
+ }
1531
+ const body = text.slice(fmMatch[0].length).trim();
1532
+ return { body, metadata };
1533
+ }
1534
+ function workspaceFromPlanPath(filePath) {
1535
+ const normalized = filePath.replaceAll("\\", "/");
1536
+ const idx = normalized.indexOf("/.cursor/plans/");
1537
+ if (idx === -1)
1538
+ return;
1539
+ return filePath.slice(0, idx);
1487
1540
  }
1488
1541
  var cursorAdapter = {
1489
1542
  agent: "cursor",
1490
1543
  writable: false,
1491
1544
  getSearchPaths() {
1492
- return [join4(homedir4(), ".cursor", "ai-tracking")];
1545
+ return discoverCursorPlanDirs();
1493
1546
  },
1494
1547
  getWatchPaths() {
1495
- return [join4(homedir4(), ".cursor", "ai-tracking")];
1548
+ return discoverCursorPlanDirs();
1496
1549
  },
1497
1550
  matches(filePath) {
1498
- return filePath.endsWith(".db");
1551
+ return filePath.endsWith(".plan.md");
1499
1552
  },
1500
1553
  async parse(filePath) {
1501
- if (!existsSync(filePath))
1502
- return [];
1503
- let db = null;
1504
1554
  try {
1505
- const { default: SqliteDatabase } = await import("better-sqlite3");
1506
- db = new SqliteDatabase(filePath, { fileMustExist: true, readonly: true });
1507
- const tables = db.prepare("SELECT name FROM sqlite_master WHERE type = 'table'").all();
1508
- const plans = [];
1555
+ const raw = await readFile4(filePath, "utf-8");
1509
1556
  const stats = await stat4(filePath);
1510
- for (const table of tables) {
1511
- try {
1512
- const rows = db.prepare(`SELECT * FROM ${quoteIdentifier(table.name)} LIMIT 50`).all();
1513
- if (rows.length === 0)
1514
- continue;
1515
- const content = rows.map((row) => {
1516
- return Object.entries(row).map(([k, v]) => `**${k}**: ${String(v)}`).join(`
1517
- `);
1518
- }).join(`
1519
-
1520
- ---
1521
-
1522
- `);
1523
- plans.push({
1524
- id: hashPath(`${filePath}:${table.name}`),
1525
- agent: "cursor",
1526
- title: `Cursor: ${table.name}`,
1527
- content,
1528
- filePath,
1529
- format: "sqlite",
1530
- createdAt: stats.birthtime,
1531
- updatedAt: stats.mtime,
1532
- metadata: { table: table.name }
1533
- });
1534
- } catch {}
1535
- }
1536
- return plans;
1557
+ const { body, metadata } = stripFrontmatter(raw);
1558
+ return [
1559
+ {
1560
+ id: hashPath(filePath),
1561
+ agent: "cursor",
1562
+ title: extractTitle3(body, filePath),
1563
+ content: body,
1564
+ filePath,
1565
+ format: "md",
1566
+ createdAt: stats.birthtime,
1567
+ updatedAt: stats.mtime,
1568
+ workspace: workspaceFromPlanPath(filePath),
1569
+ metadata
1570
+ }
1571
+ ];
1537
1572
  } catch {
1538
1573
  return [];
1539
- } finally {
1540
- db?.close();
1541
1574
  }
1542
1575
  },
1543
1576
  async write() {
@@ -1546,10 +1579,10 @@ var cursorAdapter = {
1546
1579
  };
1547
1580
 
1548
1581
  // ../shared/src/adapters/oh-my-opencode.ts
1549
- import { existsSync as existsSync2, readdirSync, readFileSync } from "node:fs";
1550
- import { readdir, readFile as readFile4, stat as stat5, writeFile as writeFile2 } from "node:fs/promises";
1582
+ import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2 } from "node:fs";
1583
+ import { readdir, readFile as readFile5, stat as stat5, writeFile as writeFile2 } from "node:fs/promises";
1551
1584
  import { homedir as homedir5 } from "node:os";
1552
- import { basename as basename3, join as join5 } from "node:path";
1585
+ import { basename as basename4, join as join5 } from "node:path";
1553
1586
  var dataHome = process.env.XDG_DATA_HOME || join5(homedir5(), ".local", "share");
1554
1587
  var opencodeSessionDir = join5(dataHome, "opencode", "storage", "session");
1555
1588
  var cwdPlansDir = join5(process.cwd(), ".sisyphus", "plans");
@@ -1569,13 +1602,13 @@ function isSessionFile(filePath) {
1569
1602
  const root = normalizePath(opencodeSessionDir);
1570
1603
  return normalized.startsWith(`${root}/`) && normalized.endsWith(".json");
1571
1604
  }
1572
- function extractTitle3(content, filePath) {
1605
+ function extractTitle4(content, filePath) {
1573
1606
  const heading = content.match(/^\s{0,3}#{1,6}\s+(.+?)\s*$/m)?.[1];
1574
1607
  if (heading)
1575
1608
  return heading.replace(/^Plan:\s*/i, "").trim();
1576
- return basename3(filePath, ".md").split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
1609
+ return basename4(filePath, ".md").split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
1577
1610
  }
1578
- function workspaceFromPlanPath(filePath) {
1611
+ function workspaceFromPlanPath2(filePath) {
1579
1612
  const normalized = normalizePath(filePath);
1580
1613
  const lower = normalized.toLowerCase();
1581
1614
  const markerIndex = lower.lastIndexOf(PLAN_PATH_MARKER);
@@ -1602,14 +1635,14 @@ function discoverPlanDirectories() {
1602
1635
  if (!existsSync2(opencodeSessionDir))
1603
1636
  return Array.from(dirs);
1604
1637
  try {
1605
- const projectDirs = readdirSync(opencodeSessionDir, { withFileTypes: true });
1638
+ const projectDirs = readdirSync2(opencodeSessionDir, { withFileTypes: true });
1606
1639
  for (const projectDir of projectDirs) {
1607
1640
  if (!projectDir.isDirectory())
1608
1641
  continue;
1609
1642
  const projectPath = join5(opencodeSessionDir, projectDir.name);
1610
1643
  let sessionFiles = [];
1611
1644
  try {
1612
- sessionFiles = readdirSync(projectPath);
1645
+ sessionFiles = readdirSync2(projectPath);
1613
1646
  } catch {
1614
1647
  continue;
1615
1648
  }
@@ -1617,7 +1650,7 @@ function discoverPlanDirectories() {
1617
1650
  if (!file.endsWith(".json"))
1618
1651
  continue;
1619
1652
  try {
1620
- const raw = readFileSync(join5(projectPath, file), "utf-8");
1653
+ const raw = readFileSync2(join5(projectPath, file), "utf-8");
1621
1654
  const session = parseSessionMeta(raw);
1622
1655
  if (!session?.directory)
1623
1656
  continue;
@@ -1632,13 +1665,13 @@ function discoverPlanDirectories() {
1632
1665
  }
1633
1666
  async function parsePlanFile(filePath, workspace, metadata = {}) {
1634
1667
  try {
1635
- const content = await readFile4(filePath, "utf-8");
1668
+ const content = await readFile5(filePath, "utf-8");
1636
1669
  const stats = await stat5(filePath);
1637
1670
  return [
1638
1671
  {
1639
1672
  id: hashPath(filePath),
1640
1673
  agent: "oh-my-opencode",
1641
- title: extractTitle3(content, filePath),
1674
+ title: extractTitle4(content, filePath),
1642
1675
  content,
1643
1676
  filePath,
1644
1677
  format: "md",
@@ -1654,7 +1687,7 @@ async function parsePlanFile(filePath, workspace, metadata = {}) {
1654
1687
  }
1655
1688
  async function parseSessionFile(filePath) {
1656
1689
  try {
1657
- const raw = await readFile4(filePath, "utf-8");
1690
+ const raw = await readFile5(filePath, "utf-8");
1658
1691
  const session = parseSessionMeta(raw);
1659
1692
  if (!session?.directory)
1660
1693
  return [];
@@ -1694,7 +1727,7 @@ var ohMyOpencodeAdapter = {
1694
1727
  },
1695
1728
  async parse(filePath) {
1696
1729
  if (isPlanMarkdown(filePath)) {
1697
- return parsePlanFile(filePath, workspaceFromPlanPath(filePath), {
1730
+ return parsePlanFile(filePath, workspaceFromPlanPath2(filePath), {
1698
1731
  source: "plan-file"
1699
1732
  });
1700
1733
  }
@@ -2163,7 +2196,7 @@ function getActiveAdapters() {
2163
2196
  var activeAdapters = resolveAdapters(getDefaultAdapterIds());
2164
2197
  // ../shared/src/config.ts
2165
2198
  import { randomBytes } from "node:crypto";
2166
- import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
2199
+ import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync3, writeFileSync } from "node:fs";
2167
2200
  import { homedir as homedir7 } from "node:os";
2168
2201
  import { join as join7 } from "node:path";
2169
2202
 
@@ -2230,7 +2263,7 @@ function readStoredConfig() {
2230
2263
  if (!existsSync3(configPath))
2231
2264
  return null;
2232
2265
  try {
2233
- const raw = JSON.parse(readFileSync2(configPath, "utf-8"));
2266
+ const raw = JSON.parse(readFileSync3(configPath, "utf-8"));
2234
2267
  if (!raw || typeof raw !== "object")
2235
2268
  return null;
2236
2269
  return raw;
@@ -2352,8 +2385,8 @@ async function loadOrInitConfig(options = {}) {
2352
2385
  var CLI_DAEMON_HEARTBEAT_INTERVAL_MS = 30000;
2353
2386
  var CLI_DAEMON_STALE_AFTER_MS = CLI_DAEMON_HEARTBEAT_INTERVAL_MS * 3;
2354
2387
  // ../shared/src/services/plan-service.ts
2355
- import { existsSync as existsSync4, readdirSync as readdirSync2, statSync } from "node:fs";
2356
- import { lstat, mkdir, readdir as readdir2, readFile as readFile5, stat as stat6, writeFile as writeFile3 } from "node:fs/promises";
2388
+ import { existsSync as existsSync4, readdirSync as readdirSync3, statSync } from "node:fs";
2389
+ import { lstat, mkdir, readdir as readdir2, readFile as readFile6, stat as stat6, writeFile as writeFile3 } from "node:fs/promises";
2357
2390
  import { homedir as homedir8 } from "node:os";
2358
2391
  import { join as join8, resolve as resolve2, sep as sep2 } from "node:path";
2359
2392
  var USER_PLANS_DIR = join8(homedir8(), ".agendex", "plans");
@@ -2408,7 +2441,7 @@ function discoverProjectPlanDirs() {
2408
2441
  }
2409
2442
  let names;
2410
2443
  try {
2411
- names = readdirSync2(dir);
2444
+ names = readdirSync3(dir);
2412
2445
  } catch {
2413
2446
  return;
2414
2447
  }
@@ -2470,7 +2503,7 @@ async function scanUserPlans() {
2470
2503
  if (!file.endsWith(".md"))
2471
2504
  continue;
2472
2505
  try {
2473
- const content = await readFile5(file, "utf-8");
2506
+ const content = await readFile6(file, "utf-8");
2474
2507
  const stats = await stat6(file);
2475
2508
  let agent = "unknown";
2476
2509
  const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n/);
@@ -2620,7 +2653,7 @@ import { request as httpsRequest } from "node:https";
2620
2653
  import { hostname as osHostname } from "node:os";
2621
2654
 
2622
2655
  // src/pid.ts
2623
- import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync3, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
2656
+ import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync4, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
2624
2657
  import { homedir as homedir9, hostname } from "node:os";
2625
2658
  import { dirname, join as join10 } from "node:path";
2626
2659
  var pidPath = join10(homedir9(), ".agendex", "daemon.pid");
@@ -2636,7 +2669,7 @@ function writePid() {
2636
2669
  function readPidInfo() {
2637
2670
  if (!existsSync6(pidPath))
2638
2671
  return null;
2639
- const raw = readFileSync3(pidPath, "utf-8").trim();
2672
+ const raw = readFileSync4(pidPath, "utf-8").trim();
2640
2673
  const asNumber = Number(raw);
2641
2674
  if (Number.isFinite(asNumber) && asNumber > 0 && !raw.startsWith("{")) {
2642
2675
  return { pid: asNumber };
@@ -3071,6 +3104,53 @@ function spawnBrowser(command, args, options = {}) {
3071
3104
  import { spawn as spawn2 } from "node:child_process";
3072
3105
  import { resolve as resolve4 } from "node:path";
3073
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
3074
3154
  var MAX_RESTARTS = 5;
3075
3155
  var RESTART_WINDOW_MS = 60000;
3076
3156
  var RESTART_DELAY_MS = 5000;
@@ -3094,6 +3174,7 @@ async function runWorker() {
3094
3174
  setActiveAdapters(adapters);
3095
3175
  console.log(`[agendex] daemon starting with ${config.enabledAdapters.length} adapters`);
3096
3176
  sendHeartbeat();
3177
+ const syncCache = loadSyncCache();
3097
3178
  const syncQueue = [];
3098
3179
  let syncing = false;
3099
3180
  async function tryRefreshToken() {
@@ -3134,6 +3215,7 @@ async function runWorker() {
3134
3215
  console.error(`[agendex] sync failed for "${payload.title}": ${result.error}`);
3135
3216
  } else {
3136
3217
  syncedCount++;
3218
+ syncCache[payload.localPlanId] = computePayloadHash(payload);
3137
3219
  }
3138
3220
  }
3139
3221
  } catch (err) {
@@ -3143,6 +3225,7 @@ async function runWorker() {
3143
3225
  syncing = false;
3144
3226
  }
3145
3227
  if (syncedCount > 0 || failedCount > 0) {
3228
+ saveSyncCache(syncCache);
3146
3229
  console.log(`[agendex] sync complete: ${syncedCount} synced, ${failedCount} failed`);
3147
3230
  }
3148
3231
  if (syncQueue.length > 0)
@@ -3152,10 +3235,23 @@ async function runWorker() {
3152
3235
  console.log(`[agendex] initial scan...`);
3153
3236
  await scan();
3154
3237
  const plans = getAll();
3155
- console.log(`[agendex] syncing ${plans.length} plans...`);
3238
+ let initialSkipped = 0;
3156
3239
  for (const plan of plans) {
3157
- 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);
3158
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];
3252
+ }
3253
+ saveSyncCache(syncCache, { replace: true });
3254
+ console.log(`[agendex] syncing ${syncQueue.length} plans (${initialSkipped} unchanged)...`);
3159
3255
  await processSyncQueue();
3160
3256
  setInterval(() => void sendHeartbeat(), CLI_DAEMON_HEARTBEAT_INTERVAL_MS);
3161
3257
  startWatching((changedPlans) => {
@@ -3216,7 +3312,7 @@ async function startSupervisor() {
3216
3312
  }
3217
3313
 
3218
3314
  // src/sync.ts
3219
- async function syncAll() {
3315
+ async function syncAll(force = false) {
3220
3316
  const config = await loadOrInitConfig();
3221
3317
  const adapters = resolveAdapters(config.enabledAdapters);
3222
3318
  setActiveAdapters(adapters);
@@ -3224,9 +3320,13 @@ async function syncAll() {
3224
3320
  await scan();
3225
3321
  const plans = getAll();
3226
3322
  console.log(`[agendex] Found ${plans.length} plans. Syncing to cloud...`);
3323
+ const cache = force ? {} : loadSyncCache();
3324
+ const activePlanIds = new Set;
3227
3325
  let synced = 0;
3326
+ let skipped = 0;
3228
3327
  let failed = 0;
3229
3328
  for (const plan of plans) {
3329
+ activePlanIds.add(plan.id);
3230
3330
  const payload = {
3231
3331
  localPlanId: plan.id,
3232
3332
  agent: plan.agent,
@@ -3235,27 +3335,40 @@ async function syncAll() {
3235
3335
  format: plan.format,
3236
3336
  filePath: plan.filePath,
3237
3337
  workspace: plan.workspace,
3238
- metadata: plan.metadata
3338
+ metadata: plan.metadata,
3339
+ createdAt: plan.createdAt.getTime(),
3340
+ updatedAt: plan.updatedAt.getTime()
3239
3341
  };
3342
+ const hash = computePayloadHash(payload);
3343
+ if (!force && cache[plan.id] === hash) {
3344
+ skipped++;
3345
+ continue;
3346
+ }
3240
3347
  const result = await syncPlan(payload);
3241
3348
  if (result.ok) {
3242
3349
  synced++;
3350
+ cache[plan.id] = hash;
3243
3351
  } else {
3244
3352
  failed++;
3245
3353
  console.error(`[agendex] Failed to sync "${plan.title}": ${result.error}`);
3246
3354
  }
3247
3355
  }
3248
- 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`);
3249
3362
  }
3250
3363
 
3251
3364
  // src/version.ts
3252
- import { existsSync as existsSync7, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
3365
+ import { existsSync as existsSync8, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
3253
3366
  import { tmpdir } from "node:os";
3254
- import { join as join11 } from "node:path";
3367
+ import { join as join12 } from "node:path";
3255
3368
  // package.json
3256
3369
  var package_default = {
3257
3370
  name: "agendex-cli",
3258
- version: "0.8.3",
3371
+ version: "0.8.5",
3259
3372
  description: "Agendex CLI for login, sync, and daemon workflows",
3260
3373
  homepage: "https://github.com/Tyru5/Agendex#readme",
3261
3374
  repository: {
@@ -3299,14 +3412,14 @@ var package_default = {
3299
3412
 
3300
3413
  // src/version.ts
3301
3414
  var CLI_VERSION = package_default.version;
3302
- 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");
3303
3416
  var CACHE_TTL_MS = 24 * 60 * 60 * 1000;
3304
3417
  var UPDATE_URL = process.env.AGENDEX_UPDATE_URL ?? "https://registry.npmjs.org/agendex-cli/latest";
3305
3418
  function readCache(current) {
3306
3419
  try {
3307
- if (!existsSync7(CACHE_FILE))
3420
+ if (!existsSync8(CACHE_FILE))
3308
3421
  return null;
3309
- const { result, ts } = JSON.parse(readFileSync4(CACHE_FILE, "utf8"));
3422
+ const { result, ts } = JSON.parse(readFileSync6(CACHE_FILE, "utf8"));
3310
3423
  if (Date.now() - ts > CACHE_TTL_MS)
3311
3424
  return null;
3312
3425
  return normalizeResult(result, current);
@@ -3316,7 +3429,7 @@ function readCache(current) {
3316
3429
  }
3317
3430
  function writeCache(result) {
3318
3431
  try {
3319
- writeFileSync3(CACHE_FILE, JSON.stringify({ result, ts: Date.now() }));
3432
+ writeFileSync4(CACHE_FILE, JSON.stringify({ result, ts: Date.now() }));
3320
3433
  } catch {}
3321
3434
  }
3322
3435
  async function checkForUpdate() {
@@ -3456,7 +3569,8 @@ async function main() {
3456
3569
  return 0;
3457
3570
  }
3458
3571
  case "sync": {
3459
- await syncAll();
3572
+ const force = args.includes("--force");
3573
+ await syncAll(force);
3460
3574
  return 0;
3461
3575
  }
3462
3576
  case "cleanup": {
@@ -3589,7 +3703,8 @@ Usage:
3589
3703
  agendex login --url <url> Login to a self-hosted instance
3590
3704
  agendex logout Clear stored cloud token
3591
3705
  agendex configure Select which agents/adapters to index
3592
- 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
3593
3708
  agendex cleanup Interactively remove cloud daemons
3594
3709
  agendex cleanup --stale Auto-remove all stale daemons
3595
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.3",
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": {