agendex-cli 0.11.0 → 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 +308 -68
- 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";
|
|
@@ -2853,7 +3019,7 @@ function requestText(urlString, options) {
|
|
|
2853
3019
|
if (options.body) {
|
|
2854
3020
|
headers["Content-Length"] = String(Buffer.byteLength(options.body));
|
|
2855
3021
|
}
|
|
2856
|
-
return new Promise((
|
|
3022
|
+
return new Promise((resolve5, reject) => {
|
|
2857
3023
|
const req = request(url, {
|
|
2858
3024
|
agent: false,
|
|
2859
3025
|
headers,
|
|
@@ -2865,7 +3031,7 @@ function requestText(urlString, options) {
|
|
|
2865
3031
|
body += chunk;
|
|
2866
3032
|
});
|
|
2867
3033
|
res.on("end", () => {
|
|
2868
|
-
|
|
3034
|
+
resolve5({
|
|
2869
3035
|
status: res.statusCode ?? 0,
|
|
2870
3036
|
body
|
|
2871
3037
|
});
|
|
@@ -2979,7 +3145,8 @@ async function login(siteUrlOverride) {
|
|
|
2979
3145
|
token: existing?.token,
|
|
2980
3146
|
cloudToken: callback.token,
|
|
2981
3147
|
convexUrl: callback.convexUrl,
|
|
2982
|
-
enabledAdapters: existing?.enabledAdapters ?? []
|
|
3148
|
+
enabledAdapters: existing?.enabledAdapters ?? [],
|
|
3149
|
+
customPlanDirs: existing?.customPlanDirs ?? []
|
|
2983
3150
|
};
|
|
2984
3151
|
saveConfig(config);
|
|
2985
3152
|
console.log(`[agendex] Logged in successfully!`);
|
|
@@ -2996,7 +3163,8 @@ function logout() {
|
|
|
2996
3163
|
token: existing.token,
|
|
2997
3164
|
cloudToken: undefined,
|
|
2998
3165
|
convexUrl: undefined,
|
|
2999
|
-
enabledAdapters: existing.enabledAdapters
|
|
3166
|
+
enabledAdapters: existing.enabledAdapters,
|
|
3167
|
+
customPlanDirs: existing.customPlanDirs
|
|
3000
3168
|
};
|
|
3001
3169
|
saveConfig(config);
|
|
3002
3170
|
console.log("[agendex] Logged out. Cloud token removed.");
|
|
@@ -3007,14 +3175,14 @@ async function startCallbackServer() {
|
|
|
3007
3175
|
let timeout;
|
|
3008
3176
|
let settle;
|
|
3009
3177
|
let fail;
|
|
3010
|
-
const result = new Promise((
|
|
3011
|
-
settle =
|
|
3178
|
+
const result = new Promise((resolve5, reject) => {
|
|
3179
|
+
settle = resolve5;
|
|
3012
3180
|
fail = reject;
|
|
3013
3181
|
});
|
|
3014
3182
|
const finish = (value) => {
|
|
3015
3183
|
if (!settle || !fail)
|
|
3016
3184
|
return;
|
|
3017
|
-
const
|
|
3185
|
+
const resolve5 = settle;
|
|
3018
3186
|
const reject = fail;
|
|
3019
3187
|
settle = undefined;
|
|
3020
3188
|
fail = undefined;
|
|
@@ -3031,7 +3199,7 @@ async function startCallbackServer() {
|
|
|
3031
3199
|
reject(value);
|
|
3032
3200
|
return;
|
|
3033
3201
|
}
|
|
3034
|
-
|
|
3202
|
+
resolve5(value);
|
|
3035
3203
|
};
|
|
3036
3204
|
server.on("request", (req, res) => {
|
|
3037
3205
|
const requestUrl = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
@@ -3065,8 +3233,8 @@ async function startCallbackServer() {
|
|
|
3065
3233
|
sockets.delete(socket);
|
|
3066
3234
|
});
|
|
3067
3235
|
});
|
|
3068
|
-
await new Promise((
|
|
3069
|
-
server.listen(0, "127.0.0.1", () =>
|
|
3236
|
+
await new Promise((resolve5, reject) => {
|
|
3237
|
+
server.listen(0, "127.0.0.1", () => resolve5());
|
|
3070
3238
|
server.once("error", reject);
|
|
3071
3239
|
});
|
|
3072
3240
|
server.once("error", (error) => {
|
|
@@ -3146,7 +3314,7 @@ function spawnBrowser(command, args, options = {}) {
|
|
|
3146
3314
|
|
|
3147
3315
|
// src/daemon.ts
|
|
3148
3316
|
import { spawn as spawn2 } from "node:child_process";
|
|
3149
|
-
import { resolve as
|
|
3317
|
+
import { resolve as resolve5 } from "node:path";
|
|
3150
3318
|
import { fileURLToPath } from "node:url";
|
|
3151
3319
|
|
|
3152
3320
|
// src/sync-cache.ts
|
|
@@ -3309,6 +3477,7 @@ async function runWorker() {
|
|
|
3309
3477
|
});
|
|
3310
3478
|
console.log(`[agendex] daemon running. Watching for file changes...`);
|
|
3311
3479
|
async function gracefulShutdown() {
|
|
3480
|
+
stopWatching();
|
|
3312
3481
|
await sendShutdown();
|
|
3313
3482
|
process.exit(0);
|
|
3314
3483
|
}
|
|
@@ -3332,17 +3501,17 @@ async function startSupervisor() {
|
|
|
3332
3501
|
};
|
|
3333
3502
|
process.on("SIGTERM", shutdown);
|
|
3334
3503
|
process.on("SIGINT", shutdown);
|
|
3335
|
-
const scriptPath =
|
|
3504
|
+
const scriptPath = resolve5(process.argv[1] ?? fileURLToPath(new URL("./cli.ts", import.meta.url)));
|
|
3336
3505
|
const restartTimes = [];
|
|
3337
3506
|
while (!stopping) {
|
|
3338
3507
|
workerProc = spawn2(process.execPath, [scriptPath, "start", "--worker"], {
|
|
3339
3508
|
stdio: ["ignore", "inherit", "inherit"]
|
|
3340
3509
|
});
|
|
3341
|
-
const exitCode = await new Promise((
|
|
3342
|
-
workerProc?.once("exit", (code) =>
|
|
3510
|
+
const exitCode = await new Promise((resolve6) => {
|
|
3511
|
+
workerProc?.once("exit", (code) => resolve6(code));
|
|
3343
3512
|
workerProc?.once("error", (error) => {
|
|
3344
3513
|
console.error("[agendex] failed to spawn worker:", error);
|
|
3345
|
-
|
|
3514
|
+
resolve6(1);
|
|
3346
3515
|
});
|
|
3347
3516
|
});
|
|
3348
3517
|
workerProc = null;
|
|
@@ -3421,7 +3590,7 @@ import { join as join12 } from "node:path";
|
|
|
3421
3590
|
// package.json
|
|
3422
3591
|
var package_default = {
|
|
3423
3592
|
name: "agendex-cli",
|
|
3424
|
-
version: "0.
|
|
3593
|
+
version: "0.12.0",
|
|
3425
3594
|
description: "Agendex CLI for login, sync, and daemon workflows",
|
|
3426
3595
|
homepage: "https://github.com/Tyru5/Agendex#readme",
|
|
3427
3596
|
repository: {
|
|
@@ -3568,7 +3737,7 @@ function firstCommandToken(argv) {
|
|
|
3568
3737
|
return;
|
|
3569
3738
|
}
|
|
3570
3739
|
var command = firstCommandToken(args) ?? "start";
|
|
3571
|
-
var cliEntry =
|
|
3740
|
+
var cliEntry = resolve6(process.argv[1] ?? fileURLToPath2(import.meta.url));
|
|
3572
3741
|
async function main() {
|
|
3573
3742
|
const isInternal = args.includes("--daemon") || args.includes("--worker");
|
|
3574
3743
|
if (command === "--version" || command === "-v") {
|
|
@@ -3583,6 +3752,9 @@ async function main() {
|
|
|
3583
3752
|
"open",
|
|
3584
3753
|
"view",
|
|
3585
3754
|
"cleanup",
|
|
3755
|
+
"add-dir",
|
|
3756
|
+
"remove-dir",
|
|
3757
|
+
"list-dirs",
|
|
3586
3758
|
"help",
|
|
3587
3759
|
"--help",
|
|
3588
3760
|
"-h"
|
|
@@ -3758,6 +3930,66 @@ async function main() {
|
|
|
3758
3930
|
}
|
|
3759
3931
|
return 0;
|
|
3760
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
|
+
}
|
|
3761
3993
|
case "status": {
|
|
3762
3994
|
const config = loadConfig();
|
|
3763
3995
|
const pidInfo = readPidInfo();
|
|
@@ -3768,6 +4000,11 @@ async function main() {
|
|
|
3768
4000
|
writeStdout(`[agendex] Cloud token: ${config?.cloudToken ? "set" : "not set"}`);
|
|
3769
4001
|
writeStdout(`[agendex] Convex URL: ${config?.convexUrl ?? "not set"}`);
|
|
3770
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
|
+
}
|
|
3771
4008
|
writeStdout(`[agendex] Daemon: ${running ? `running (PID ${pid})` : "not running"}`);
|
|
3772
4009
|
if (running && pidInfo?.startedAtMs) {
|
|
3773
4010
|
writeStdout(`[agendex] Uptime: ${formatDuration(Date.now() - pidInfo.startedAtMs)}`);
|
|
@@ -3832,6 +4069,9 @@ Usage:
|
|
|
3832
4069
|
agendex view <url> Open a shared plan URL in your browser
|
|
3833
4070
|
agendex logout Clear stored cloud token
|
|
3834
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
|
|
3835
4075
|
agendex sync One-shot scan + sync to cloud (skips unchanged plans)
|
|
3836
4076
|
agendex sync --force Re-sync all plans, ignoring cache
|
|
3837
4077
|
agendex cleanup Interactively remove cloud daemons
|
|
@@ -3865,13 +4105,13 @@ function flushStream(stream) {
|
|
|
3865
4105
|
if (stream.destroyed || !stream.writable) {
|
|
3866
4106
|
return Promise.resolve();
|
|
3867
4107
|
}
|
|
3868
|
-
return new Promise((
|
|
4108
|
+
return new Promise((resolve7, reject) => {
|
|
3869
4109
|
stream.write("", (error) => {
|
|
3870
4110
|
if (error) {
|
|
3871
4111
|
reject(error);
|
|
3872
4112
|
return;
|
|
3873
4113
|
}
|
|
3874
|
-
|
|
4114
|
+
resolve7();
|
|
3875
4115
|
});
|
|
3876
4116
|
});
|
|
3877
4117
|
}
|