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.
- package/dist/cli.js +196 -81
- 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
|
-
|
|
1486
|
-
|
|
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
|
|
1545
|
+
return discoverCursorPlanDirs();
|
|
1493
1546
|
},
|
|
1494
1547
|
getWatchPaths() {
|
|
1495
|
-
return
|
|
1548
|
+
return discoverCursorPlanDirs();
|
|
1496
1549
|
},
|
|
1497
1550
|
matches(filePath) {
|
|
1498
|
-
return filePath.endsWith(".
|
|
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
|
|
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
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1609
|
+
return basename4(filePath, ".md").split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
1577
1610
|
}
|
|
1578
|
-
function
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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:
|
|
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
|
|
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,
|
|
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
|
|
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(
|
|
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
|
|
2356
|
-
import { lstat, mkdir, readdir as readdir2, readFile as
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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
|
-
|
|
3238
|
+
let initialSkipped = 0;
|
|
3156
3239
|
for (const plan of plans) {
|
|
3157
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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.
|
|
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 ??
|
|
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 (!
|
|
3420
|
+
if (!existsSync8(CACHE_FILE))
|
|
3308
3421
|
return null;
|
|
3309
|
-
const { result, ts } = JSON.parse(
|
|
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
|
-
|
|
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
|
-
|
|
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
|