agendex-cli 0.10.1 → 0.12.0
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 +333 -70
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1057,8 +1057,8 @@ var init_cleanup = __esm(() => {
|
|
|
1057
1057
|
|
|
1058
1058
|
// src/cli.ts
|
|
1059
1059
|
import { spawn as spawn3 } from "node:child_process";
|
|
1060
|
-
import { writeSync } from "node:fs";
|
|
1061
|
-
import { resolve as
|
|
1060
|
+
import { existsSync as existsSync9, statSync as statSync2, writeSync } from "node:fs";
|
|
1061
|
+
import { resolve as resolve6 } from "node:path";
|
|
1062
1062
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
1063
1063
|
|
|
1064
1064
|
// ../shared/src/adapters/catalog.ts
|
|
@@ -2198,7 +2198,7 @@ var activeAdapters = resolveAdapters(getDefaultAdapterIds());
|
|
|
2198
2198
|
import { randomBytes } from "node:crypto";
|
|
2199
2199
|
import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync3, writeFileSync } from "node:fs";
|
|
2200
2200
|
import { homedir as homedir7 } from "node:os";
|
|
2201
|
-
import { join as join7 } from "node:path";
|
|
2201
|
+
import { join as join7, resolve as resolve2 } from "node:path";
|
|
2202
2202
|
|
|
2203
2203
|
// ../shared/src/setup/adapter-selection.ts
|
|
2204
2204
|
init_dist2();
|
|
@@ -2288,6 +2288,37 @@ function normalizeAdapterIds(input) {
|
|
|
2288
2288
|
return [];
|
|
2289
2289
|
return sanitizeEnabledAdapterIds(input.filter((item) => typeof item === "string"));
|
|
2290
2290
|
}
|
|
2291
|
+
function expandHomePath(p) {
|
|
2292
|
+
if (p.startsWith("~/") || p === "~")
|
|
2293
|
+
return join7(homedir7(), p.slice(1));
|
|
2294
|
+
return p;
|
|
2295
|
+
}
|
|
2296
|
+
function resolveCustomPlanDirPath(userPath) {
|
|
2297
|
+
const trimmed = userPath.trim();
|
|
2298
|
+
if (!trimmed) {
|
|
2299
|
+
throw new Error("Custom plan directory path must not be empty");
|
|
2300
|
+
}
|
|
2301
|
+
return resolve2(expandHomePath(trimmed));
|
|
2302
|
+
}
|
|
2303
|
+
function normalizeCustomPlanDirs(input) {
|
|
2304
|
+
if (!Array.isArray(input))
|
|
2305
|
+
return [];
|
|
2306
|
+
const seen = new Set;
|
|
2307
|
+
const result = [];
|
|
2308
|
+
for (const item of input) {
|
|
2309
|
+
if (typeof item !== "string")
|
|
2310
|
+
continue;
|
|
2311
|
+
const trimmed = item.trim();
|
|
2312
|
+
if (!trimmed)
|
|
2313
|
+
continue;
|
|
2314
|
+
const normalized = resolveCustomPlanDirPath(trimmed);
|
|
2315
|
+
if (seen.has(normalized))
|
|
2316
|
+
continue;
|
|
2317
|
+
seen.add(normalized);
|
|
2318
|
+
result.push(normalized);
|
|
2319
|
+
}
|
|
2320
|
+
return result;
|
|
2321
|
+
}
|
|
2291
2322
|
function normalizeStoredConfig(raw) {
|
|
2292
2323
|
if (!raw)
|
|
2293
2324
|
return null;
|
|
@@ -2301,7 +2332,8 @@ function normalizeStoredConfig(raw) {
|
|
|
2301
2332
|
cloudToken,
|
|
2302
2333
|
convexUrl,
|
|
2303
2334
|
deviceId,
|
|
2304
|
-
enabledAdapters: normalizeAdapterIds(raw.enabledAdapters)
|
|
2335
|
+
enabledAdapters: normalizeAdapterIds(raw.enabledAdapters),
|
|
2336
|
+
customPlanDirs: normalizeCustomPlanDirs(raw.customPlanDirs)
|
|
2305
2337
|
};
|
|
2306
2338
|
}
|
|
2307
2339
|
function loadConfig() {
|
|
@@ -2315,7 +2347,8 @@ function saveConfig(config) {
|
|
|
2315
2347
|
cloudToken: config.cloudToken,
|
|
2316
2348
|
convexUrl: config.convexUrl,
|
|
2317
2349
|
deviceId: config.deviceId,
|
|
2318
|
-
enabledAdapters: sanitizeEnabledAdapterIds(config.enabledAdapters)
|
|
2350
|
+
enabledAdapters: sanitizeEnabledAdapterIds(config.enabledAdapters),
|
|
2351
|
+
customPlanDirs: normalizeCustomPlanDirs(config.customPlanDirs)
|
|
2319
2352
|
};
|
|
2320
2353
|
writeFileSync(getConfigPath(), JSON.stringify(payload, null, 2));
|
|
2321
2354
|
}
|
|
@@ -2332,7 +2365,8 @@ function loadOrCreateToken() {
|
|
|
2332
2365
|
saveConfig({
|
|
2333
2366
|
configVersion: 3,
|
|
2334
2367
|
token,
|
|
2335
|
-
enabledAdapters: existing?.enabledAdapters ?? []
|
|
2368
|
+
enabledAdapters: existing?.enabledAdapters ?? [],
|
|
2369
|
+
customPlanDirs: existing?.customPlanDirs ?? []
|
|
2336
2370
|
});
|
|
2337
2371
|
console.log(`
|
|
2338
2372
|
[agendex] generated auth token: ${token}`);
|
|
@@ -2349,7 +2383,8 @@ function loadOrCreateDeviceId() {
|
|
|
2349
2383
|
...existing ?? {},
|
|
2350
2384
|
configVersion: 3,
|
|
2351
2385
|
deviceId,
|
|
2352
|
-
enabledAdapters: existing?.enabledAdapters ?? []
|
|
2386
|
+
enabledAdapters: existing?.enabledAdapters ?? [],
|
|
2387
|
+
customPlanDirs: existing?.customPlanDirs ?? []
|
|
2353
2388
|
});
|
|
2354
2389
|
return deviceId;
|
|
2355
2390
|
}
|
|
@@ -2388,7 +2423,8 @@ async function loadOrInitConfig(options = {}) {
|
|
|
2388
2423
|
cloudToken: existing?.cloudToken,
|
|
2389
2424
|
convexUrl: existing?.convexUrl,
|
|
2390
2425
|
deviceId,
|
|
2391
|
-
enabledAdapters
|
|
2426
|
+
enabledAdapters,
|
|
2427
|
+
customPlanDirs: existing?.customPlanDirs ?? []
|
|
2392
2428
|
};
|
|
2393
2429
|
saveConfig(nextConfig);
|
|
2394
2430
|
return {
|
|
@@ -2403,7 +2439,7 @@ var CLI_DAEMON_STALE_AFTER_MS = CLI_DAEMON_HEARTBEAT_INTERVAL_MS * 5;
|
|
|
2403
2439
|
import { existsSync as existsSync4, readdirSync as readdirSync3, statSync } from "node:fs";
|
|
2404
2440
|
import { lstat, mkdir, readdir as readdir2, readFile as readFile6, stat as stat6, writeFile as writeFile3 } from "node:fs/promises";
|
|
2405
2441
|
import { homedir as homedir8 } from "node:os";
|
|
2406
|
-
import { join as join8, resolve as
|
|
2442
|
+
import { join as join8, resolve as resolve3, sep as sep2 } from "node:path";
|
|
2407
2443
|
function getUserPlansDir() {
|
|
2408
2444
|
return join8(getConfigDir(), "plans");
|
|
2409
2445
|
}
|
|
@@ -2489,7 +2525,7 @@ async function walkDir(dir, depth = 0, seen = new Set) {
|
|
|
2489
2525
|
return [];
|
|
2490
2526
|
if (!existsSync4(dir))
|
|
2491
2527
|
return [];
|
|
2492
|
-
const real =
|
|
2528
|
+
const real = resolve3(dir);
|
|
2493
2529
|
if (seen.has(real))
|
|
2494
2530
|
return [];
|
|
2495
2531
|
seen.add(real);
|
|
@@ -2512,7 +2548,36 @@ async function walkDir(dir, depth = 0, seen = new Set) {
|
|
|
2512
2548
|
} catch {}
|
|
2513
2549
|
return files;
|
|
2514
2550
|
}
|
|
2515
|
-
async function
|
|
2551
|
+
async function parseGenericMarkdownPlan(filePath, extraMetadata) {
|
|
2552
|
+
try {
|
|
2553
|
+
const content = await readFile6(filePath, "utf-8");
|
|
2554
|
+
const stats = await stat6(filePath);
|
|
2555
|
+
let agent = "unknown";
|
|
2556
|
+
const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n/);
|
|
2557
|
+
if (fmMatch) {
|
|
2558
|
+
const agentLine = fmMatch[1]?.match(/^agent:\s*(.+)$/m);
|
|
2559
|
+
if (agentLine?.[1])
|
|
2560
|
+
agent = agentLine[1].trim();
|
|
2561
|
+
}
|
|
2562
|
+
const bodyContent = fmMatch ? content.slice(fmMatch[0].length) : content;
|
|
2563
|
+
const titleMatch = bodyContent.match(/^#\s+(.+)/m);
|
|
2564
|
+
const title = titleMatch?.[1]?.trim() || filePath.split("/").pop()?.replace(".md", "") || "Untitled";
|
|
2565
|
+
return {
|
|
2566
|
+
id: hashPath(filePath),
|
|
2567
|
+
agent,
|
|
2568
|
+
title,
|
|
2569
|
+
content: bodyContent,
|
|
2570
|
+
filePath,
|
|
2571
|
+
format: "md",
|
|
2572
|
+
createdAt: stats.birthtime,
|
|
2573
|
+
updatedAt: stats.mtime,
|
|
2574
|
+
metadata: extraMetadata
|
|
2575
|
+
};
|
|
2576
|
+
} catch {
|
|
2577
|
+
return null;
|
|
2578
|
+
}
|
|
2579
|
+
}
|
|
2580
|
+
async function scanUserPlans(into) {
|
|
2516
2581
|
const userPlansDir = getUserPlansDir();
|
|
2517
2582
|
if (!existsSync4(userPlansDir))
|
|
2518
2583
|
return;
|
|
@@ -2520,55 +2585,89 @@ async function scanUserPlans() {
|
|
|
2520
2585
|
for (const file of files) {
|
|
2521
2586
|
if (!file.endsWith(".md"))
|
|
2522
2587
|
continue;
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2588
|
+
const plan = await parseGenericMarkdownPlan(file, { userCreated: true });
|
|
2589
|
+
if (plan)
|
|
2590
|
+
into.set(plan.id, plan);
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
function getCustomPlanDirs() {
|
|
2594
|
+
return loadConfig()?.customPlanDirs ?? [];
|
|
2595
|
+
}
|
|
2596
|
+
function pathsOverlapFilesystemTree(a, b) {
|
|
2597
|
+
const ra = resolve3(a);
|
|
2598
|
+
const rb = resolve3(b);
|
|
2599
|
+
if (ra === rb)
|
|
2600
|
+
return true;
|
|
2601
|
+
if (ra.startsWith(rb + sep2))
|
|
2602
|
+
return true;
|
|
2603
|
+
if (rb.startsWith(ra + sep2))
|
|
2604
|
+
return true;
|
|
2605
|
+
return false;
|
|
2606
|
+
}
|
|
2607
|
+
function overlapsAnyRoot(candidate, roots) {
|
|
2608
|
+
const resolvedCandidate = resolve3(candidate);
|
|
2609
|
+
for (const root of roots) {
|
|
2610
|
+
if (pathsOverlapFilesystemTree(resolvedCandidate, root))
|
|
2611
|
+
return true;
|
|
2612
|
+
}
|
|
2613
|
+
return false;
|
|
2614
|
+
}
|
|
2615
|
+
async function scanCustomPlanDirs(coveredPaths, into) {
|
|
2616
|
+
const dirs = getCustomPlanDirs();
|
|
2617
|
+
const userPlansDir = resolve3(getUserPlansDir());
|
|
2618
|
+
for (const dir of dirs) {
|
|
2619
|
+
const resolved = resolve3(dir);
|
|
2620
|
+
if (overlapsAnyRoot(resolved, coveredPaths)) {
|
|
2621
|
+
console.log(`[agendex] skipping custom dir (overlaps adapter / discovered coverage): ${dir}`);
|
|
2622
|
+
continue;
|
|
2623
|
+
}
|
|
2624
|
+
if (pathsOverlapFilesystemTree(resolved, userPlansDir)) {
|
|
2625
|
+
console.log(`[agendex] skipping custom dir (overlaps user plans): ${dir}`);
|
|
2626
|
+
continue;
|
|
2627
|
+
}
|
|
2628
|
+
if (!existsSync4(dir)) {
|
|
2629
|
+
console.log(`[agendex] skipping custom dir (not found): ${dir}`);
|
|
2630
|
+
continue;
|
|
2631
|
+
}
|
|
2632
|
+
const files = await walkDir(dir);
|
|
2633
|
+
let count = 0;
|
|
2634
|
+
for (const file of files) {
|
|
2635
|
+
if (!file.endsWith(".md"))
|
|
2636
|
+
continue;
|
|
2637
|
+
const plan = await parseGenericMarkdownPlan(file, {
|
|
2638
|
+
source: "custom-dir",
|
|
2639
|
+
customDir: dir
|
|
2640
|
+
});
|
|
2641
|
+
if (plan) {
|
|
2642
|
+
into.set(plan.id, plan);
|
|
2643
|
+
count++;
|
|
2532
2644
|
}
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
const title = titleMatch?.[1]?.trim() || file.split("/").pop()?.replace(".md", "") || "Untitled";
|
|
2536
|
-
const plan = {
|
|
2537
|
-
id: hashPath(file),
|
|
2538
|
-
agent,
|
|
2539
|
-
title,
|
|
2540
|
-
content: bodyContent,
|
|
2541
|
-
filePath: file,
|
|
2542
|
-
format: "md",
|
|
2543
|
-
createdAt: stats.birthtime,
|
|
2544
|
-
updatedAt: stats.mtime,
|
|
2545
|
-
metadata: { userCreated: true }
|
|
2546
|
-
};
|
|
2547
|
-
store.set(plan.id, plan);
|
|
2548
|
-
} catch {}
|
|
2645
|
+
}
|
|
2646
|
+
console.log(`[agendex] custom dir: ${dir} (${count} plans)`);
|
|
2549
2647
|
}
|
|
2550
2648
|
}
|
|
2551
2649
|
async function scan() {
|
|
2552
2650
|
const adapters = getActiveAdapters();
|
|
2553
|
-
|
|
2651
|
+
const next = new Map;
|
|
2554
2652
|
const coveredPaths = new Set;
|
|
2555
2653
|
for (const adapter of adapters) {
|
|
2556
2654
|
for (const searchPath of adapter.getSearchPaths()) {
|
|
2557
|
-
coveredPaths.add(
|
|
2655
|
+
coveredPaths.add(resolve3(searchPath));
|
|
2558
2656
|
const files = await walkDir(searchPath);
|
|
2559
2657
|
for (const file of files) {
|
|
2560
2658
|
if (!adapter.matches(file))
|
|
2561
2659
|
continue;
|
|
2562
2660
|
const plans = await adapter.parse(file);
|
|
2563
2661
|
for (const plan of plans) {
|
|
2564
|
-
|
|
2662
|
+
next.set(plan.id, plan);
|
|
2565
2663
|
}
|
|
2566
2664
|
}
|
|
2567
2665
|
}
|
|
2568
2666
|
}
|
|
2569
2667
|
const discovered = discoverProjectPlanDirs();
|
|
2570
2668
|
for (const { dir, agent } of discovered) {
|
|
2571
|
-
|
|
2669
|
+
const resolvedDir = resolve3(dir);
|
|
2670
|
+
if (coveredPaths.has(resolvedDir))
|
|
2572
2671
|
continue;
|
|
2573
2672
|
const adapter = adapters.find((a) => a.agent === agent);
|
|
2574
2673
|
if (!adapter)
|
|
@@ -2579,12 +2678,15 @@ async function scan() {
|
|
|
2579
2678
|
continue;
|
|
2580
2679
|
const plans = await adapter.parse(file);
|
|
2581
2680
|
for (const plan of plans) {
|
|
2582
|
-
|
|
2681
|
+
next.set(plan.id, plan);
|
|
2583
2682
|
}
|
|
2584
2683
|
}
|
|
2684
|
+
coveredPaths.add(resolvedDir);
|
|
2585
2685
|
console.log(`[agendex] discovered project plans: ${dir}`);
|
|
2586
2686
|
}
|
|
2587
|
-
await scanUserPlans();
|
|
2687
|
+
await scanUserPlans(next);
|
|
2688
|
+
await scanCustomPlanDirs(coveredPaths, next);
|
|
2689
|
+
store = next;
|
|
2588
2690
|
notifyPlansChanged();
|
|
2589
2691
|
console.log(`[agendex] indexed ${store.size} plans from ${adapters.length} adapters`);
|
|
2590
2692
|
}
|
|
@@ -2593,13 +2695,13 @@ function getAll() {
|
|
|
2593
2695
|
}
|
|
2594
2696
|
async function rescanFile(filePath) {
|
|
2595
2697
|
const adapters = getActiveAdapters();
|
|
2596
|
-
const normalized =
|
|
2698
|
+
const normalized = resolve3(filePath);
|
|
2597
2699
|
for (const adapter of adapters) {
|
|
2598
2700
|
if (!adapter.matches(filePath))
|
|
2599
2701
|
continue;
|
|
2600
|
-
const discoveredDirs = discoverProjectPlanDirs().filter((d2) => d2.agent === adapter.agent).map((d2) =>
|
|
2702
|
+
const discoveredDirs = discoverProjectPlanDirs().filter((d2) => d2.agent === adapter.agent).map((d2) => resolve3(d2.dir));
|
|
2601
2703
|
const allSearchPaths = [
|
|
2602
|
-
...adapter.getSearchPaths().map((sp) =>
|
|
2704
|
+
...adapter.getSearchPaths().map((sp) => resolve3(sp)),
|
|
2603
2705
|
...discoveredDirs
|
|
2604
2706
|
];
|
|
2605
2707
|
const isInSearchPath = allSearchPaths.some((sp) => normalized.startsWith(sp + sep2) || normalized === sp);
|
|
@@ -2612,18 +2714,58 @@ async function rescanFile(filePath) {
|
|
|
2612
2714
|
notifyPlansChanged();
|
|
2613
2715
|
return plans;
|
|
2614
2716
|
}
|
|
2717
|
+
const userPlansDir = resolve3(getUserPlansDir());
|
|
2718
|
+
if (normalized.endsWith(".md") && (normalized.startsWith(userPlansDir + sep2) || normalized === userPlansDir)) {
|
|
2719
|
+
const plan = await parseGenericMarkdownPlan(filePath, { userCreated: true });
|
|
2720
|
+
if (plan) {
|
|
2721
|
+
store.set(plan.id, plan);
|
|
2722
|
+
notifyPlansChanged();
|
|
2723
|
+
return [plan];
|
|
2724
|
+
}
|
|
2725
|
+
}
|
|
2726
|
+
if (normalized.endsWith(".md")) {
|
|
2727
|
+
const customDirs = getCustomPlanDirs();
|
|
2728
|
+
for (const dir of customDirs) {
|
|
2729
|
+
const resolvedDir = resolve3(dir);
|
|
2730
|
+
if (normalized.startsWith(resolvedDir + sep2) || normalized === resolvedDir) {
|
|
2731
|
+
const plan = await parseGenericMarkdownPlan(filePath, {
|
|
2732
|
+
source: "custom-dir",
|
|
2733
|
+
customDir: dir
|
|
2734
|
+
});
|
|
2735
|
+
if (plan) {
|
|
2736
|
+
store.set(plan.id, plan);
|
|
2737
|
+
notifyPlansChanged();
|
|
2738
|
+
return [plan];
|
|
2739
|
+
}
|
|
2740
|
+
}
|
|
2741
|
+
}
|
|
2742
|
+
}
|
|
2615
2743
|
return [];
|
|
2616
2744
|
}
|
|
2617
2745
|
// ../shared/src/services/watcher.ts
|
|
2618
2746
|
import { existsSync as existsSync5, watch } from "node:fs";
|
|
2619
|
-
import { join as join9, resolve as
|
|
2747
|
+
import { join as join9, resolve as resolve4 } from "node:path";
|
|
2620
2748
|
var debounceTimer = null;
|
|
2621
2749
|
var pendingFiles = new Set;
|
|
2750
|
+
var activeWatchers = [];
|
|
2751
|
+
function closeAllWatchers() {
|
|
2752
|
+
if (debounceTimer) {
|
|
2753
|
+
clearTimeout(debounceTimer);
|
|
2754
|
+
debounceTimer = null;
|
|
2755
|
+
}
|
|
2756
|
+
pendingFiles.clear();
|
|
2757
|
+
for (const watcher of activeWatchers) {
|
|
2758
|
+
try {
|
|
2759
|
+
watcher.close();
|
|
2760
|
+
} catch {}
|
|
2761
|
+
}
|
|
2762
|
+
activeWatchers.length = 0;
|
|
2763
|
+
}
|
|
2622
2764
|
function watchDir(dir, matchFn, onChange) {
|
|
2623
2765
|
if (!existsSync5(dir))
|
|
2624
2766
|
return;
|
|
2625
2767
|
try {
|
|
2626
|
-
watch(dir, { recursive: true }, async (_event, filename) => {
|
|
2768
|
+
const watcher = watch(dir, { recursive: true }, async (_event, filename) => {
|
|
2627
2769
|
if (!filename)
|
|
2628
2770
|
return;
|
|
2629
2771
|
const fullPath = join9(dir, filename);
|
|
@@ -2643,27 +2785,51 @@ function watchDir(dir, matchFn, onChange) {
|
|
|
2643
2785
|
onChange?.(allPlans);
|
|
2644
2786
|
}, 300);
|
|
2645
2787
|
});
|
|
2788
|
+
activeWatchers.push(watcher);
|
|
2646
2789
|
console.log(`[agendex] watching ${dir}`);
|
|
2647
2790
|
} catch {}
|
|
2648
2791
|
}
|
|
2649
2792
|
function startWatching(onChange) {
|
|
2793
|
+
closeAllWatchers();
|
|
2794
|
+
setupWatchers(onChange);
|
|
2795
|
+
}
|
|
2796
|
+
function stopWatching() {
|
|
2797
|
+
closeAllWatchers();
|
|
2798
|
+
}
|
|
2799
|
+
function setupWatchers(onChange) {
|
|
2650
2800
|
const adapters = getActiveAdapters();
|
|
2651
2801
|
const watchedPaths = new Set;
|
|
2652
2802
|
for (const adapter of adapters) {
|
|
2653
2803
|
for (const watchPath of adapter.getWatchPaths()) {
|
|
2654
|
-
watchedPaths.add(
|
|
2804
|
+
watchedPaths.add(resolve4(watchPath));
|
|
2655
2805
|
watchDir(watchPath, (f) => adapter.matches(f), onChange);
|
|
2656
2806
|
}
|
|
2657
2807
|
}
|
|
2658
2808
|
const discovered = discoverProjectPlanDirs();
|
|
2659
2809
|
for (const { dir, agent } of discovered) {
|
|
2660
|
-
if (watchedPaths.has(
|
|
2810
|
+
if (watchedPaths.has(resolve4(dir)))
|
|
2661
2811
|
continue;
|
|
2662
2812
|
const adapter = adapters.find((a) => a.agent === agent);
|
|
2663
2813
|
if (!adapter)
|
|
2664
2814
|
continue;
|
|
2815
|
+
watchedPaths.add(resolve4(dir));
|
|
2665
2816
|
watchDir(dir, (f) => adapter.matches(f), onChange);
|
|
2666
2817
|
}
|
|
2818
|
+
const customDirs = getCustomPlanDirs();
|
|
2819
|
+
for (const dir of customDirs) {
|
|
2820
|
+
const resolvedCustom = resolve4(dir);
|
|
2821
|
+
let overlaps = false;
|
|
2822
|
+
for (const watched of watchedPaths) {
|
|
2823
|
+
if (pathsOverlapFilesystemTree(resolvedCustom, watched)) {
|
|
2824
|
+
overlaps = true;
|
|
2825
|
+
break;
|
|
2826
|
+
}
|
|
2827
|
+
}
|
|
2828
|
+
if (overlaps)
|
|
2829
|
+
continue;
|
|
2830
|
+
watchedPaths.add(resolvedCustom);
|
|
2831
|
+
watchDir(dir, (f) => f.endsWith(".md"), onChange);
|
|
2832
|
+
}
|
|
2667
2833
|
}
|
|
2668
2834
|
// src/api.ts
|
|
2669
2835
|
import { request as httpRequest } from "node:http";
|
|
@@ -2721,6 +2887,12 @@ function isRunning(pid) {
|
|
|
2721
2887
|
}
|
|
2722
2888
|
|
|
2723
2889
|
// src/api.ts
|
|
2890
|
+
class AuthExpiredError extends Error {
|
|
2891
|
+
constructor() {
|
|
2892
|
+
super("Cloud token expired. Run `agendex login` to re-authenticate.");
|
|
2893
|
+
this.name = "AuthExpiredError";
|
|
2894
|
+
}
|
|
2895
|
+
}
|
|
2724
2896
|
var cachedDeviceId;
|
|
2725
2897
|
function getCloudConfig() {
|
|
2726
2898
|
const config = loadConfig();
|
|
@@ -2847,7 +3019,7 @@ function requestText(urlString, options) {
|
|
|
2847
3019
|
if (options.body) {
|
|
2848
3020
|
headers["Content-Length"] = String(Buffer.byteLength(options.body));
|
|
2849
3021
|
}
|
|
2850
|
-
return new Promise((
|
|
3022
|
+
return new Promise((resolve5, reject) => {
|
|
2851
3023
|
const req = request(url, {
|
|
2852
3024
|
agent: false,
|
|
2853
3025
|
headers,
|
|
@@ -2859,7 +3031,7 @@ function requestText(urlString, options) {
|
|
|
2859
3031
|
body += chunk;
|
|
2860
3032
|
});
|
|
2861
3033
|
res.on("end", () => {
|
|
2862
|
-
|
|
3034
|
+
resolve5({
|
|
2863
3035
|
status: res.statusCode ?? 0,
|
|
2864
3036
|
body
|
|
2865
3037
|
});
|
|
@@ -2897,6 +3069,9 @@ async function fetchDevices() {
|
|
|
2897
3069
|
});
|
|
2898
3070
|
}
|
|
2899
3071
|
}
|
|
3072
|
+
if (res.status === 401) {
|
|
3073
|
+
throw new AuthExpiredError;
|
|
3074
|
+
}
|
|
2900
3075
|
if (res.status < 200 || res.status >= 300) {
|
|
2901
3076
|
return [];
|
|
2902
3077
|
}
|
|
@@ -2970,7 +3145,8 @@ async function login(siteUrlOverride) {
|
|
|
2970
3145
|
token: existing?.token,
|
|
2971
3146
|
cloudToken: callback.token,
|
|
2972
3147
|
convexUrl: callback.convexUrl,
|
|
2973
|
-
enabledAdapters: existing?.enabledAdapters ?? []
|
|
3148
|
+
enabledAdapters: existing?.enabledAdapters ?? [],
|
|
3149
|
+
customPlanDirs: existing?.customPlanDirs ?? []
|
|
2974
3150
|
};
|
|
2975
3151
|
saveConfig(config);
|
|
2976
3152
|
console.log(`[agendex] Logged in successfully!`);
|
|
@@ -2987,7 +3163,8 @@ function logout() {
|
|
|
2987
3163
|
token: existing.token,
|
|
2988
3164
|
cloudToken: undefined,
|
|
2989
3165
|
convexUrl: undefined,
|
|
2990
|
-
enabledAdapters: existing.enabledAdapters
|
|
3166
|
+
enabledAdapters: existing.enabledAdapters,
|
|
3167
|
+
customPlanDirs: existing.customPlanDirs
|
|
2991
3168
|
};
|
|
2992
3169
|
saveConfig(config);
|
|
2993
3170
|
console.log("[agendex] Logged out. Cloud token removed.");
|
|
@@ -2998,14 +3175,14 @@ async function startCallbackServer() {
|
|
|
2998
3175
|
let timeout;
|
|
2999
3176
|
let settle;
|
|
3000
3177
|
let fail;
|
|
3001
|
-
const result = new Promise((
|
|
3002
|
-
settle =
|
|
3178
|
+
const result = new Promise((resolve5, reject) => {
|
|
3179
|
+
settle = resolve5;
|
|
3003
3180
|
fail = reject;
|
|
3004
3181
|
});
|
|
3005
3182
|
const finish = (value) => {
|
|
3006
3183
|
if (!settle || !fail)
|
|
3007
3184
|
return;
|
|
3008
|
-
const
|
|
3185
|
+
const resolve5 = settle;
|
|
3009
3186
|
const reject = fail;
|
|
3010
3187
|
settle = undefined;
|
|
3011
3188
|
fail = undefined;
|
|
@@ -3022,7 +3199,7 @@ async function startCallbackServer() {
|
|
|
3022
3199
|
reject(value);
|
|
3023
3200
|
return;
|
|
3024
3201
|
}
|
|
3025
|
-
|
|
3202
|
+
resolve5(value);
|
|
3026
3203
|
};
|
|
3027
3204
|
server.on("request", (req, res) => {
|
|
3028
3205
|
const requestUrl = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
@@ -3056,8 +3233,8 @@ async function startCallbackServer() {
|
|
|
3056
3233
|
sockets.delete(socket);
|
|
3057
3234
|
});
|
|
3058
3235
|
});
|
|
3059
|
-
await new Promise((
|
|
3060
|
-
server.listen(0, "127.0.0.1", () =>
|
|
3236
|
+
await new Promise((resolve5, reject) => {
|
|
3237
|
+
server.listen(0, "127.0.0.1", () => resolve5());
|
|
3061
3238
|
server.once("error", reject);
|
|
3062
3239
|
});
|
|
3063
3240
|
server.once("error", (error) => {
|
|
@@ -3137,7 +3314,7 @@ function spawnBrowser(command, args, options = {}) {
|
|
|
3137
3314
|
|
|
3138
3315
|
// src/daemon.ts
|
|
3139
3316
|
import { spawn as spawn2 } from "node:child_process";
|
|
3140
|
-
import { resolve as
|
|
3317
|
+
import { resolve as resolve5 } from "node:path";
|
|
3141
3318
|
import { fileURLToPath } from "node:url";
|
|
3142
3319
|
|
|
3143
3320
|
// src/sync-cache.ts
|
|
@@ -3300,6 +3477,7 @@ async function runWorker() {
|
|
|
3300
3477
|
});
|
|
3301
3478
|
console.log(`[agendex] daemon running. Watching for file changes...`);
|
|
3302
3479
|
async function gracefulShutdown() {
|
|
3480
|
+
stopWatching();
|
|
3303
3481
|
await sendShutdown();
|
|
3304
3482
|
process.exit(0);
|
|
3305
3483
|
}
|
|
@@ -3323,17 +3501,17 @@ async function startSupervisor() {
|
|
|
3323
3501
|
};
|
|
3324
3502
|
process.on("SIGTERM", shutdown);
|
|
3325
3503
|
process.on("SIGINT", shutdown);
|
|
3326
|
-
const scriptPath =
|
|
3504
|
+
const scriptPath = resolve5(process.argv[1] ?? fileURLToPath(new URL("./cli.ts", import.meta.url)));
|
|
3327
3505
|
const restartTimes = [];
|
|
3328
3506
|
while (!stopping) {
|
|
3329
3507
|
workerProc = spawn2(process.execPath, [scriptPath, "start", "--worker"], {
|
|
3330
3508
|
stdio: ["ignore", "inherit", "inherit"]
|
|
3331
3509
|
});
|
|
3332
|
-
const exitCode = await new Promise((
|
|
3333
|
-
workerProc?.once("exit", (code) =>
|
|
3510
|
+
const exitCode = await new Promise((resolve6) => {
|
|
3511
|
+
workerProc?.once("exit", (code) => resolve6(code));
|
|
3334
3512
|
workerProc?.once("error", (error) => {
|
|
3335
3513
|
console.error("[agendex] failed to spawn worker:", error);
|
|
3336
|
-
|
|
3514
|
+
resolve6(1);
|
|
3337
3515
|
});
|
|
3338
3516
|
});
|
|
3339
3517
|
workerProc = null;
|
|
@@ -3412,7 +3590,7 @@ import { join as join12 } from "node:path";
|
|
|
3412
3590
|
// package.json
|
|
3413
3591
|
var package_default = {
|
|
3414
3592
|
name: "agendex-cli",
|
|
3415
|
-
version: "0.
|
|
3593
|
+
version: "0.12.0",
|
|
3416
3594
|
description: "Agendex CLI for login, sync, and daemon workflows",
|
|
3417
3595
|
homepage: "https://github.com/Tyru5/Agendex#readme",
|
|
3418
3596
|
repository: {
|
|
@@ -3559,7 +3737,7 @@ function firstCommandToken(argv) {
|
|
|
3559
3737
|
return;
|
|
3560
3738
|
}
|
|
3561
3739
|
var command = firstCommandToken(args) ?? "start";
|
|
3562
|
-
var cliEntry =
|
|
3740
|
+
var cliEntry = resolve6(process.argv[1] ?? fileURLToPath2(import.meta.url));
|
|
3563
3741
|
async function main() {
|
|
3564
3742
|
const isInternal = args.includes("--daemon") || args.includes("--worker");
|
|
3565
3743
|
if (command === "--version" || command === "-v") {
|
|
@@ -3574,6 +3752,9 @@ async function main() {
|
|
|
3574
3752
|
"open",
|
|
3575
3753
|
"view",
|
|
3576
3754
|
"cleanup",
|
|
3755
|
+
"add-dir",
|
|
3756
|
+
"remove-dir",
|
|
3757
|
+
"list-dirs",
|
|
3577
3758
|
"help",
|
|
3578
3759
|
"--help",
|
|
3579
3760
|
"-h"
|
|
@@ -3680,7 +3861,16 @@ async function main() {
|
|
|
3680
3861
|
writeStderr("[agendex] not logged in. Run `agendex login` first.");
|
|
3681
3862
|
return 1;
|
|
3682
3863
|
}
|
|
3683
|
-
|
|
3864
|
+
let allDevices;
|
|
3865
|
+
try {
|
|
3866
|
+
allDevices = await fetchDevices();
|
|
3867
|
+
} catch (err) {
|
|
3868
|
+
if (err instanceof AuthExpiredError) {
|
|
3869
|
+
writeStderr("[agendex] cloud token expired. Run `agendex login` to re-authenticate.");
|
|
3870
|
+
return 1;
|
|
3871
|
+
}
|
|
3872
|
+
throw err;
|
|
3873
|
+
}
|
|
3684
3874
|
if (allDevices.length === 0) {
|
|
3685
3875
|
writeStdout("[agendex] no daemons found");
|
|
3686
3876
|
return 0;
|
|
@@ -3740,6 +3930,66 @@ async function main() {
|
|
|
3740
3930
|
}
|
|
3741
3931
|
return 0;
|
|
3742
3932
|
}
|
|
3933
|
+
case "add-dir": {
|
|
3934
|
+
const dirPath = args.find((a) => a !== "add-dir" && a !== "--dev" && !a.startsWith("--"));
|
|
3935
|
+
if (!dirPath || !dirPath.trim()) {
|
|
3936
|
+
writeStderr("[agendex] usage: agendex add-dir <path>");
|
|
3937
|
+
return 1;
|
|
3938
|
+
}
|
|
3939
|
+
const resolved = resolveCustomPlanDirPath(dirPath);
|
|
3940
|
+
if (!existsSync9(resolved)) {
|
|
3941
|
+
writeStderr(`[agendex] path does not exist: ${resolved}`);
|
|
3942
|
+
return 1;
|
|
3943
|
+
}
|
|
3944
|
+
if (!statSync2(resolved).isDirectory()) {
|
|
3945
|
+
writeStderr(`[agendex] path is not a directory: ${resolved}`);
|
|
3946
|
+
return 1;
|
|
3947
|
+
}
|
|
3948
|
+
const cfg = loadConfig();
|
|
3949
|
+
const currentDirs = cfg?.customPlanDirs ?? [];
|
|
3950
|
+
const updated = normalizeCustomPlanDirs([...currentDirs, resolved]);
|
|
3951
|
+
saveConfig({
|
|
3952
|
+
...cfg ?? { configVersion: 3, enabledAdapters: [] },
|
|
3953
|
+
customPlanDirs: updated
|
|
3954
|
+
});
|
|
3955
|
+
writeStdout(`[agendex] added custom plan dir: ${resolved}`);
|
|
3956
|
+
writeStdout(`[agendex] daemon will pick up the change automatically`);
|
|
3957
|
+
return 0;
|
|
3958
|
+
}
|
|
3959
|
+
case "remove-dir": {
|
|
3960
|
+
const dirPath = args.find((a) => a !== "remove-dir" && a !== "--dev" && !a.startsWith("--"));
|
|
3961
|
+
if (!dirPath || !dirPath.trim()) {
|
|
3962
|
+
writeStderr("[agendex] usage: agendex remove-dir <path>");
|
|
3963
|
+
return 1;
|
|
3964
|
+
}
|
|
3965
|
+
const resolved = resolveCustomPlanDirPath(dirPath);
|
|
3966
|
+
const cfg = loadConfig();
|
|
3967
|
+
const currentDirs = cfg?.customPlanDirs ?? [];
|
|
3968
|
+
const updated = currentDirs.filter((d2) => d2 !== resolved);
|
|
3969
|
+
if (updated.length === currentDirs.length) {
|
|
3970
|
+
writeStderr(`[agendex] directory not in custom plan dirs: ${resolved}`);
|
|
3971
|
+
return 1;
|
|
3972
|
+
}
|
|
3973
|
+
saveConfig({
|
|
3974
|
+
...cfg ?? { configVersion: 3, enabledAdapters: [] },
|
|
3975
|
+
customPlanDirs: updated
|
|
3976
|
+
});
|
|
3977
|
+
writeStdout(`[agendex] removed custom plan dir: ${resolved}`);
|
|
3978
|
+
return 0;
|
|
3979
|
+
}
|
|
3980
|
+
case "list-dirs": {
|
|
3981
|
+
const cfg = loadConfig();
|
|
3982
|
+
const dirs = cfg?.customPlanDirs ?? [];
|
|
3983
|
+
if (dirs.length === 0) {
|
|
3984
|
+
writeStdout("[agendex] no custom plan directories configured");
|
|
3985
|
+
} else {
|
|
3986
|
+
writeStdout(`[agendex] custom plan directories (${dirs.length}):`);
|
|
3987
|
+
for (const dir of dirs) {
|
|
3988
|
+
writeStdout(` - ${dir}`);
|
|
3989
|
+
}
|
|
3990
|
+
}
|
|
3991
|
+
return 0;
|
|
3992
|
+
}
|
|
3743
3993
|
case "status": {
|
|
3744
3994
|
const config = loadConfig();
|
|
3745
3995
|
const pidInfo = readPidInfo();
|
|
@@ -3750,6 +4000,11 @@ async function main() {
|
|
|
3750
4000
|
writeStdout(`[agendex] Cloud token: ${config?.cloudToken ? "set" : "not set"}`);
|
|
3751
4001
|
writeStdout(`[agendex] Convex URL: ${config?.convexUrl ?? "not set"}`);
|
|
3752
4002
|
writeStdout(`[agendex] Enabled adapters: ${config?.enabledAdapters.join(", ") || "none"}`);
|
|
4003
|
+
const customDirs = config?.customPlanDirs ?? [];
|
|
4004
|
+
writeStdout(`[agendex] Custom plan dirs: ${customDirs.length > 0 ? customDirs.length : "none"}`);
|
|
4005
|
+
for (const dir of customDirs) {
|
|
4006
|
+
writeStdout(` - ${dir}`);
|
|
4007
|
+
}
|
|
3753
4008
|
writeStdout(`[agendex] Daemon: ${running ? `running (PID ${pid})` : "not running"}`);
|
|
3754
4009
|
if (running && pidInfo?.startedAtMs) {
|
|
3755
4010
|
writeStdout(`[agendex] Uptime: ${formatDuration(Date.now() - pidInfo.startedAtMs)}`);
|
|
@@ -3789,7 +4044,12 @@ async function main() {
|
|
|
3789
4044
|
writeStdout(`[agendex] All daemons: none`);
|
|
3790
4045
|
}
|
|
3791
4046
|
}
|
|
3792
|
-
} catch {
|
|
4047
|
+
} catch (err) {
|
|
4048
|
+
if (err instanceof AuthExpiredError) {
|
|
4049
|
+
writeStderr(`[agendex] Cloud token expired — cloud sync and daemon tracking are inactive.`);
|
|
4050
|
+
writeStderr(`[agendex] Run \`agendex login\` to re-authenticate.`);
|
|
4051
|
+
}
|
|
4052
|
+
}
|
|
3793
4053
|
return 0;
|
|
3794
4054
|
}
|
|
3795
4055
|
case "help":
|
|
@@ -3809,6 +4069,9 @@ Usage:
|
|
|
3809
4069
|
agendex view <url> Open a shared plan URL in your browser
|
|
3810
4070
|
agendex logout Clear stored cloud token
|
|
3811
4071
|
agendex configure Select which agents/adapters to index
|
|
4072
|
+
agendex add-dir <path> Add a custom directory to scan for plans
|
|
4073
|
+
agendex remove-dir <path> Remove a custom directory
|
|
4074
|
+
agendex list-dirs List custom plan directories
|
|
3812
4075
|
agendex sync One-shot scan + sync to cloud (skips unchanged plans)
|
|
3813
4076
|
agendex sync --force Re-sync all plans, ignoring cache
|
|
3814
4077
|
agendex cleanup Interactively remove cloud daemons
|
|
@@ -3842,13 +4105,13 @@ function flushStream(stream) {
|
|
|
3842
4105
|
if (stream.destroyed || !stream.writable) {
|
|
3843
4106
|
return Promise.resolve();
|
|
3844
4107
|
}
|
|
3845
|
-
return new Promise((
|
|
4108
|
+
return new Promise((resolve7, reject) => {
|
|
3846
4109
|
stream.write("", (error) => {
|
|
3847
4110
|
if (error) {
|
|
3848
4111
|
reject(error);
|
|
3849
4112
|
return;
|
|
3850
4113
|
}
|
|
3851
|
-
|
|
4114
|
+
resolve7();
|
|
3852
4115
|
});
|
|
3853
4116
|
});
|
|
3854
4117
|
}
|