agendex-cli 0.11.0 → 0.13.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 +359 -68
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from "node:module";
|
|
2
3
|
var __create = Object.create;
|
|
3
4
|
var __getProtoOf = Object.getPrototypeOf;
|
|
4
5
|
var __defProp = Object.defineProperty;
|
|
@@ -44,6 +45,7 @@ var __export = (target, all) => {
|
|
|
44
45
|
});
|
|
45
46
|
};
|
|
46
47
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
48
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
47
49
|
|
|
48
50
|
// ../../node_modules/.bun/picocolors@1.1.1/node_modules/picocolors/picocolors.js
|
|
49
51
|
var require_picocolors = __commonJS((exports, module) => {
|
|
@@ -1057,8 +1059,8 @@ var init_cleanup = __esm(() => {
|
|
|
1057
1059
|
|
|
1058
1060
|
// src/cli.ts
|
|
1059
1061
|
import { spawn as spawn3 } from "node:child_process";
|
|
1060
|
-
import { writeSync } from "node:fs";
|
|
1061
|
-
import { resolve as
|
|
1062
|
+
import { existsSync as existsSync9, statSync as statSync2, writeSync } from "node:fs";
|
|
1063
|
+
import { resolve as resolve6 } from "node:path";
|
|
1062
1064
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
1063
1065
|
|
|
1064
1066
|
// ../shared/src/adapters/catalog.ts
|
|
@@ -2198,7 +2200,7 @@ var activeAdapters = resolveAdapters(getDefaultAdapterIds());
|
|
|
2198
2200
|
import { randomBytes } from "node:crypto";
|
|
2199
2201
|
import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync3, writeFileSync } from "node:fs";
|
|
2200
2202
|
import { homedir as homedir7 } from "node:os";
|
|
2201
|
-
import { join as join7 } from "node:path";
|
|
2203
|
+
import { join as join7, resolve as resolve2 } from "node:path";
|
|
2202
2204
|
|
|
2203
2205
|
// ../shared/src/setup/adapter-selection.ts
|
|
2204
2206
|
init_dist2();
|
|
@@ -2288,6 +2290,37 @@ function normalizeAdapterIds(input) {
|
|
|
2288
2290
|
return [];
|
|
2289
2291
|
return sanitizeEnabledAdapterIds(input.filter((item) => typeof item === "string"));
|
|
2290
2292
|
}
|
|
2293
|
+
function expandHomePath(p) {
|
|
2294
|
+
if (p.startsWith("~/") || p === "~")
|
|
2295
|
+
return join7(homedir7(), p.slice(1));
|
|
2296
|
+
return p;
|
|
2297
|
+
}
|
|
2298
|
+
function resolveCustomPlanDirPath(userPath) {
|
|
2299
|
+
const trimmed = userPath.trim();
|
|
2300
|
+
if (!trimmed) {
|
|
2301
|
+
throw new Error("Custom plan directory path must not be empty");
|
|
2302
|
+
}
|
|
2303
|
+
return resolve2(expandHomePath(trimmed));
|
|
2304
|
+
}
|
|
2305
|
+
function normalizeCustomPlanDirs(input) {
|
|
2306
|
+
if (!Array.isArray(input))
|
|
2307
|
+
return [];
|
|
2308
|
+
const seen = new Set;
|
|
2309
|
+
const result = [];
|
|
2310
|
+
for (const item of input) {
|
|
2311
|
+
if (typeof item !== "string")
|
|
2312
|
+
continue;
|
|
2313
|
+
const trimmed = item.trim();
|
|
2314
|
+
if (!trimmed)
|
|
2315
|
+
continue;
|
|
2316
|
+
const normalized = resolveCustomPlanDirPath(trimmed);
|
|
2317
|
+
if (seen.has(normalized))
|
|
2318
|
+
continue;
|
|
2319
|
+
seen.add(normalized);
|
|
2320
|
+
result.push(normalized);
|
|
2321
|
+
}
|
|
2322
|
+
return result;
|
|
2323
|
+
}
|
|
2291
2324
|
function normalizeStoredConfig(raw) {
|
|
2292
2325
|
if (!raw)
|
|
2293
2326
|
return null;
|
|
@@ -2301,7 +2334,8 @@ function normalizeStoredConfig(raw) {
|
|
|
2301
2334
|
cloudToken,
|
|
2302
2335
|
convexUrl,
|
|
2303
2336
|
deviceId,
|
|
2304
|
-
enabledAdapters: normalizeAdapterIds(raw.enabledAdapters)
|
|
2337
|
+
enabledAdapters: normalizeAdapterIds(raw.enabledAdapters),
|
|
2338
|
+
customPlanDirs: normalizeCustomPlanDirs(raw.customPlanDirs)
|
|
2305
2339
|
};
|
|
2306
2340
|
}
|
|
2307
2341
|
function loadConfig() {
|
|
@@ -2315,7 +2349,8 @@ function saveConfig(config) {
|
|
|
2315
2349
|
cloudToken: config.cloudToken,
|
|
2316
2350
|
convexUrl: config.convexUrl,
|
|
2317
2351
|
deviceId: config.deviceId,
|
|
2318
|
-
enabledAdapters: sanitizeEnabledAdapterIds(config.enabledAdapters)
|
|
2352
|
+
enabledAdapters: sanitizeEnabledAdapterIds(config.enabledAdapters),
|
|
2353
|
+
customPlanDirs: normalizeCustomPlanDirs(config.customPlanDirs)
|
|
2319
2354
|
};
|
|
2320
2355
|
writeFileSync(getConfigPath(), JSON.stringify(payload, null, 2));
|
|
2321
2356
|
}
|
|
@@ -2332,7 +2367,8 @@ function loadOrCreateToken() {
|
|
|
2332
2367
|
saveConfig({
|
|
2333
2368
|
configVersion: 3,
|
|
2334
2369
|
token,
|
|
2335
|
-
enabledAdapters: existing?.enabledAdapters ?? []
|
|
2370
|
+
enabledAdapters: existing?.enabledAdapters ?? [],
|
|
2371
|
+
customPlanDirs: existing?.customPlanDirs ?? []
|
|
2336
2372
|
});
|
|
2337
2373
|
console.log(`
|
|
2338
2374
|
[agendex] generated auth token: ${token}`);
|
|
@@ -2349,7 +2385,8 @@ function loadOrCreateDeviceId() {
|
|
|
2349
2385
|
...existing ?? {},
|
|
2350
2386
|
configVersion: 3,
|
|
2351
2387
|
deviceId,
|
|
2352
|
-
enabledAdapters: existing?.enabledAdapters ?? []
|
|
2388
|
+
enabledAdapters: existing?.enabledAdapters ?? [],
|
|
2389
|
+
customPlanDirs: existing?.customPlanDirs ?? []
|
|
2353
2390
|
});
|
|
2354
2391
|
return deviceId;
|
|
2355
2392
|
}
|
|
@@ -2388,7 +2425,8 @@ async function loadOrInitConfig(options = {}) {
|
|
|
2388
2425
|
cloudToken: existing?.cloudToken,
|
|
2389
2426
|
convexUrl: existing?.convexUrl,
|
|
2390
2427
|
deviceId,
|
|
2391
|
-
enabledAdapters
|
|
2428
|
+
enabledAdapters,
|
|
2429
|
+
customPlanDirs: existing?.customPlanDirs ?? []
|
|
2392
2430
|
};
|
|
2393
2431
|
saveConfig(nextConfig);
|
|
2394
2432
|
return {
|
|
@@ -2403,7 +2441,7 @@ var CLI_DAEMON_STALE_AFTER_MS = CLI_DAEMON_HEARTBEAT_INTERVAL_MS * 5;
|
|
|
2403
2441
|
import { existsSync as existsSync4, readdirSync as readdirSync3, statSync } from "node:fs";
|
|
2404
2442
|
import { lstat, mkdir, readdir as readdir2, readFile as readFile6, stat as stat6, writeFile as writeFile3 } from "node:fs/promises";
|
|
2405
2443
|
import { homedir as homedir8 } from "node:os";
|
|
2406
|
-
import { join as join8, resolve as
|
|
2444
|
+
import { join as join8, resolve as resolve3, sep as sep2 } from "node:path";
|
|
2407
2445
|
function getUserPlansDir() {
|
|
2408
2446
|
return join8(getConfigDir(), "plans");
|
|
2409
2447
|
}
|
|
@@ -2489,7 +2527,7 @@ async function walkDir(dir, depth = 0, seen = new Set) {
|
|
|
2489
2527
|
return [];
|
|
2490
2528
|
if (!existsSync4(dir))
|
|
2491
2529
|
return [];
|
|
2492
|
-
const real =
|
|
2530
|
+
const real = resolve3(dir);
|
|
2493
2531
|
if (seen.has(real))
|
|
2494
2532
|
return [];
|
|
2495
2533
|
seen.add(real);
|
|
@@ -2512,7 +2550,36 @@ async function walkDir(dir, depth = 0, seen = new Set) {
|
|
|
2512
2550
|
} catch {}
|
|
2513
2551
|
return files;
|
|
2514
2552
|
}
|
|
2515
|
-
async function
|
|
2553
|
+
async function parseGenericMarkdownPlan(filePath, extraMetadata) {
|
|
2554
|
+
try {
|
|
2555
|
+
const content = await readFile6(filePath, "utf-8");
|
|
2556
|
+
const stats = await stat6(filePath);
|
|
2557
|
+
let agent = (typeof extraMetadata.agentHint === "string" ? extraMetadata.agentHint : "") || "unknown";
|
|
2558
|
+
const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n/);
|
|
2559
|
+
if (fmMatch) {
|
|
2560
|
+
const agentLine = fmMatch[1]?.match(/^agent:\s*(.+)$/m);
|
|
2561
|
+
if (agentLine?.[1])
|
|
2562
|
+
agent = agentLine[1].trim();
|
|
2563
|
+
}
|
|
2564
|
+
const bodyContent = fmMatch ? content.slice(fmMatch[0].length) : content;
|
|
2565
|
+
const titleMatch = bodyContent.match(/^#\s+(.+)/m);
|
|
2566
|
+
const title = titleMatch?.[1]?.trim() || filePath.split("/").pop()?.replace(".md", "") || "Untitled";
|
|
2567
|
+
return {
|
|
2568
|
+
id: hashPath(filePath),
|
|
2569
|
+
agent,
|
|
2570
|
+
title,
|
|
2571
|
+
content: bodyContent,
|
|
2572
|
+
filePath,
|
|
2573
|
+
format: "md",
|
|
2574
|
+
createdAt: stats.birthtime,
|
|
2575
|
+
updatedAt: stats.mtime,
|
|
2576
|
+
metadata: extraMetadata
|
|
2577
|
+
};
|
|
2578
|
+
} catch {
|
|
2579
|
+
return null;
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
async function scanUserPlans(into) {
|
|
2516
2583
|
const userPlansDir = getUserPlansDir();
|
|
2517
2584
|
if (!existsSync4(userPlansDir))
|
|
2518
2585
|
return;
|
|
@@ -2520,55 +2587,91 @@ async function scanUserPlans() {
|
|
|
2520
2587
|
for (const file of files) {
|
|
2521
2588
|
if (!file.endsWith(".md"))
|
|
2522
2589
|
continue;
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2590
|
+
const plan = await parseGenericMarkdownPlan(file, { userCreated: true });
|
|
2591
|
+
if (plan)
|
|
2592
|
+
into.set(plan.id, plan);
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2595
|
+
function getCustomPlanDirs() {
|
|
2596
|
+
return loadConfig()?.customPlanDirs ?? [];
|
|
2597
|
+
}
|
|
2598
|
+
function pathsOverlapFilesystemTree(a, b) {
|
|
2599
|
+
const ra = resolve3(a);
|
|
2600
|
+
const rb = resolve3(b);
|
|
2601
|
+
if (ra === rb)
|
|
2602
|
+
return true;
|
|
2603
|
+
if (ra.startsWith(rb + sep2))
|
|
2604
|
+
return true;
|
|
2605
|
+
if (rb.startsWith(ra + sep2))
|
|
2606
|
+
return true;
|
|
2607
|
+
return false;
|
|
2608
|
+
}
|
|
2609
|
+
function overlapsAnyRoot(candidate, roots) {
|
|
2610
|
+
const resolvedCandidate = resolve3(candidate);
|
|
2611
|
+
for (const root of roots) {
|
|
2612
|
+
if (pathsOverlapFilesystemTree(resolvedCandidate, root))
|
|
2613
|
+
return true;
|
|
2614
|
+
}
|
|
2615
|
+
return false;
|
|
2616
|
+
}
|
|
2617
|
+
async function scanCustomPlanDirs(coveredPaths, into) {
|
|
2618
|
+
const dirs = getCustomPlanDirs();
|
|
2619
|
+
const userPlansDir = resolve3(getUserPlansDir());
|
|
2620
|
+
for (const dir of dirs) {
|
|
2621
|
+
const resolved = resolve3(dir);
|
|
2622
|
+
if (overlapsAnyRoot(resolved, coveredPaths)) {
|
|
2623
|
+
console.log(`[agendex] skipping custom dir (overlaps adapter / discovered coverage): ${dir}`);
|
|
2624
|
+
continue;
|
|
2625
|
+
}
|
|
2626
|
+
if (pathsOverlapFilesystemTree(resolved, userPlansDir)) {
|
|
2627
|
+
console.log(`[agendex] skipping custom dir (overlaps user plans): ${dir}`);
|
|
2628
|
+
continue;
|
|
2629
|
+
}
|
|
2630
|
+
if (!existsSync4(dir)) {
|
|
2631
|
+
console.log(`[agendex] skipping custom dir (not found): ${dir}`);
|
|
2632
|
+
continue;
|
|
2633
|
+
}
|
|
2634
|
+
const files = await walkDir(dir);
|
|
2635
|
+
const dirBasename = dir.replace(/[\\/]+$/, "").split(/[\\/]/).pop() ?? "custom";
|
|
2636
|
+
let count = 0;
|
|
2637
|
+
for (const file of files) {
|
|
2638
|
+
if (!file.endsWith(".md"))
|
|
2639
|
+
continue;
|
|
2640
|
+
const plan = await parseGenericMarkdownPlan(file, {
|
|
2641
|
+
source: "custom-dir",
|
|
2642
|
+
customDir: dir,
|
|
2643
|
+
agentHint: dirBasename
|
|
2644
|
+
});
|
|
2645
|
+
if (plan) {
|
|
2646
|
+
into.set(plan.id, plan);
|
|
2647
|
+
count++;
|
|
2532
2648
|
}
|
|
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 {}
|
|
2649
|
+
}
|
|
2650
|
+
console.log(`[agendex] custom dir: ${dir} (${count} plans)`);
|
|
2549
2651
|
}
|
|
2550
2652
|
}
|
|
2551
2653
|
async function scan() {
|
|
2552
2654
|
const adapters = getActiveAdapters();
|
|
2553
|
-
|
|
2655
|
+
const next = new Map;
|
|
2554
2656
|
const coveredPaths = new Set;
|
|
2555
2657
|
for (const adapter of adapters) {
|
|
2556
2658
|
for (const searchPath of adapter.getSearchPaths()) {
|
|
2557
|
-
coveredPaths.add(
|
|
2659
|
+
coveredPaths.add(resolve3(searchPath));
|
|
2558
2660
|
const files = await walkDir(searchPath);
|
|
2559
2661
|
for (const file of files) {
|
|
2560
2662
|
if (!adapter.matches(file))
|
|
2561
2663
|
continue;
|
|
2562
2664
|
const plans = await adapter.parse(file);
|
|
2563
2665
|
for (const plan of plans) {
|
|
2564
|
-
|
|
2666
|
+
next.set(plan.id, plan);
|
|
2565
2667
|
}
|
|
2566
2668
|
}
|
|
2567
2669
|
}
|
|
2568
2670
|
}
|
|
2569
2671
|
const discovered = discoverProjectPlanDirs();
|
|
2570
2672
|
for (const { dir, agent } of discovered) {
|
|
2571
|
-
|
|
2673
|
+
const resolvedDir = resolve3(dir);
|
|
2674
|
+
if (coveredPaths.has(resolvedDir))
|
|
2572
2675
|
continue;
|
|
2573
2676
|
const adapter = adapters.find((a) => a.agent === agent);
|
|
2574
2677
|
if (!adapter)
|
|
@@ -2579,12 +2682,15 @@ async function scan() {
|
|
|
2579
2682
|
continue;
|
|
2580
2683
|
const plans = await adapter.parse(file);
|
|
2581
2684
|
for (const plan of plans) {
|
|
2582
|
-
|
|
2685
|
+
next.set(plan.id, plan);
|
|
2583
2686
|
}
|
|
2584
2687
|
}
|
|
2688
|
+
coveredPaths.add(resolvedDir);
|
|
2585
2689
|
console.log(`[agendex] discovered project plans: ${dir}`);
|
|
2586
2690
|
}
|
|
2587
|
-
await scanUserPlans();
|
|
2691
|
+
await scanUserPlans(next);
|
|
2692
|
+
await scanCustomPlanDirs(coveredPaths, next);
|
|
2693
|
+
store = next;
|
|
2588
2694
|
notifyPlansChanged();
|
|
2589
2695
|
console.log(`[agendex] indexed ${store.size} plans from ${adapters.length} adapters`);
|
|
2590
2696
|
}
|
|
@@ -2593,13 +2699,13 @@ function getAll() {
|
|
|
2593
2699
|
}
|
|
2594
2700
|
async function rescanFile(filePath) {
|
|
2595
2701
|
const adapters = getActiveAdapters();
|
|
2596
|
-
const normalized =
|
|
2702
|
+
const normalized = resolve3(filePath);
|
|
2597
2703
|
for (const adapter of adapters) {
|
|
2598
2704
|
if (!adapter.matches(filePath))
|
|
2599
2705
|
continue;
|
|
2600
|
-
const discoveredDirs = discoverProjectPlanDirs().filter((d2) => d2.agent === adapter.agent).map((d2) =>
|
|
2706
|
+
const discoveredDirs = discoverProjectPlanDirs().filter((d2) => d2.agent === adapter.agent).map((d2) => resolve3(d2.dir));
|
|
2601
2707
|
const allSearchPaths = [
|
|
2602
|
-
...adapter.getSearchPaths().map((sp) =>
|
|
2708
|
+
...adapter.getSearchPaths().map((sp) => resolve3(sp)),
|
|
2603
2709
|
...discoveredDirs
|
|
2604
2710
|
];
|
|
2605
2711
|
const isInSearchPath = allSearchPaths.some((sp) => normalized.startsWith(sp + sep2) || normalized === sp);
|
|
@@ -2612,18 +2718,58 @@ async function rescanFile(filePath) {
|
|
|
2612
2718
|
notifyPlansChanged();
|
|
2613
2719
|
return plans;
|
|
2614
2720
|
}
|
|
2721
|
+
const userPlansDir = resolve3(getUserPlansDir());
|
|
2722
|
+
if (normalized.endsWith(".md") && (normalized.startsWith(userPlansDir + sep2) || normalized === userPlansDir)) {
|
|
2723
|
+
const plan = await parseGenericMarkdownPlan(filePath, { userCreated: true });
|
|
2724
|
+
if (plan) {
|
|
2725
|
+
store.set(plan.id, plan);
|
|
2726
|
+
notifyPlansChanged();
|
|
2727
|
+
return [plan];
|
|
2728
|
+
}
|
|
2729
|
+
}
|
|
2730
|
+
if (normalized.endsWith(".md")) {
|
|
2731
|
+
const customDirs = getCustomPlanDirs();
|
|
2732
|
+
for (const dir of customDirs) {
|
|
2733
|
+
const resolvedDir = resolve3(dir);
|
|
2734
|
+
if (normalized.startsWith(resolvedDir + sep2) || normalized === resolvedDir) {
|
|
2735
|
+
const plan = await parseGenericMarkdownPlan(filePath, {
|
|
2736
|
+
source: "custom-dir",
|
|
2737
|
+
customDir: dir
|
|
2738
|
+
});
|
|
2739
|
+
if (plan) {
|
|
2740
|
+
store.set(plan.id, plan);
|
|
2741
|
+
notifyPlansChanged();
|
|
2742
|
+
return [plan];
|
|
2743
|
+
}
|
|
2744
|
+
}
|
|
2745
|
+
}
|
|
2746
|
+
}
|
|
2615
2747
|
return [];
|
|
2616
2748
|
}
|
|
2617
2749
|
// ../shared/src/services/watcher.ts
|
|
2618
2750
|
import { existsSync as existsSync5, watch } from "node:fs";
|
|
2619
|
-
import { join as join9, resolve as
|
|
2751
|
+
import { join as join9, resolve as resolve4 } from "node:path";
|
|
2620
2752
|
var debounceTimer = null;
|
|
2621
2753
|
var pendingFiles = new Set;
|
|
2754
|
+
var activeWatchers = [];
|
|
2755
|
+
function closeAllWatchers() {
|
|
2756
|
+
if (debounceTimer) {
|
|
2757
|
+
clearTimeout(debounceTimer);
|
|
2758
|
+
debounceTimer = null;
|
|
2759
|
+
}
|
|
2760
|
+
pendingFiles.clear();
|
|
2761
|
+
for (const watcher of activeWatchers) {
|
|
2762
|
+
try {
|
|
2763
|
+
watcher.close();
|
|
2764
|
+
} catch {}
|
|
2765
|
+
}
|
|
2766
|
+
activeWatchers.length = 0;
|
|
2767
|
+
}
|
|
2622
2768
|
function watchDir(dir, matchFn, onChange) {
|
|
2623
2769
|
if (!existsSync5(dir))
|
|
2624
2770
|
return;
|
|
2625
2771
|
try {
|
|
2626
|
-
watch(dir, { recursive: true }, async (_event, filename) => {
|
|
2772
|
+
const watcher = watch(dir, { recursive: true }, async (_event, filename) => {
|
|
2627
2773
|
if (!filename)
|
|
2628
2774
|
return;
|
|
2629
2775
|
const fullPath = join9(dir, filename);
|
|
@@ -2643,27 +2789,51 @@ function watchDir(dir, matchFn, onChange) {
|
|
|
2643
2789
|
onChange?.(allPlans);
|
|
2644
2790
|
}, 300);
|
|
2645
2791
|
});
|
|
2792
|
+
activeWatchers.push(watcher);
|
|
2646
2793
|
console.log(`[agendex] watching ${dir}`);
|
|
2647
2794
|
} catch {}
|
|
2648
2795
|
}
|
|
2649
2796
|
function startWatching(onChange) {
|
|
2797
|
+
closeAllWatchers();
|
|
2798
|
+
setupWatchers(onChange);
|
|
2799
|
+
}
|
|
2800
|
+
function stopWatching() {
|
|
2801
|
+
closeAllWatchers();
|
|
2802
|
+
}
|
|
2803
|
+
function setupWatchers(onChange) {
|
|
2650
2804
|
const adapters = getActiveAdapters();
|
|
2651
2805
|
const watchedPaths = new Set;
|
|
2652
2806
|
for (const adapter of adapters) {
|
|
2653
2807
|
for (const watchPath of adapter.getWatchPaths()) {
|
|
2654
|
-
watchedPaths.add(
|
|
2808
|
+
watchedPaths.add(resolve4(watchPath));
|
|
2655
2809
|
watchDir(watchPath, (f) => adapter.matches(f), onChange);
|
|
2656
2810
|
}
|
|
2657
2811
|
}
|
|
2658
2812
|
const discovered = discoverProjectPlanDirs();
|
|
2659
2813
|
for (const { dir, agent } of discovered) {
|
|
2660
|
-
if (watchedPaths.has(
|
|
2814
|
+
if (watchedPaths.has(resolve4(dir)))
|
|
2661
2815
|
continue;
|
|
2662
2816
|
const adapter = adapters.find((a) => a.agent === agent);
|
|
2663
2817
|
if (!adapter)
|
|
2664
2818
|
continue;
|
|
2819
|
+
watchedPaths.add(resolve4(dir));
|
|
2665
2820
|
watchDir(dir, (f) => adapter.matches(f), onChange);
|
|
2666
2821
|
}
|
|
2822
|
+
const customDirs = getCustomPlanDirs();
|
|
2823
|
+
for (const dir of customDirs) {
|
|
2824
|
+
const resolvedCustom = resolve4(dir);
|
|
2825
|
+
let overlaps = false;
|
|
2826
|
+
for (const watched of watchedPaths) {
|
|
2827
|
+
if (pathsOverlapFilesystemTree(resolvedCustom, watched)) {
|
|
2828
|
+
overlaps = true;
|
|
2829
|
+
break;
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2832
|
+
if (overlaps)
|
|
2833
|
+
continue;
|
|
2834
|
+
watchedPaths.add(resolvedCustom);
|
|
2835
|
+
watchDir(dir, (f) => f.endsWith(".md"), onChange);
|
|
2836
|
+
}
|
|
2667
2837
|
}
|
|
2668
2838
|
// src/api.ts
|
|
2669
2839
|
import { request as httpRequest } from "node:http";
|
|
@@ -2853,7 +3023,7 @@ function requestText(urlString, options) {
|
|
|
2853
3023
|
if (options.body) {
|
|
2854
3024
|
headers["Content-Length"] = String(Buffer.byteLength(options.body));
|
|
2855
3025
|
}
|
|
2856
|
-
return new Promise((
|
|
3026
|
+
return new Promise((resolve5, reject) => {
|
|
2857
3027
|
const req = request(url, {
|
|
2858
3028
|
agent: false,
|
|
2859
3029
|
headers,
|
|
@@ -2865,7 +3035,7 @@ function requestText(urlString, options) {
|
|
|
2865
3035
|
body += chunk;
|
|
2866
3036
|
});
|
|
2867
3037
|
res.on("end", () => {
|
|
2868
|
-
|
|
3038
|
+
resolve5({
|
|
2869
3039
|
status: res.statusCode ?? 0,
|
|
2870
3040
|
body
|
|
2871
3041
|
});
|
|
@@ -2979,7 +3149,8 @@ async function login(siteUrlOverride) {
|
|
|
2979
3149
|
token: existing?.token,
|
|
2980
3150
|
cloudToken: callback.token,
|
|
2981
3151
|
convexUrl: callback.convexUrl,
|
|
2982
|
-
enabledAdapters: existing?.enabledAdapters ?? []
|
|
3152
|
+
enabledAdapters: existing?.enabledAdapters ?? [],
|
|
3153
|
+
customPlanDirs: existing?.customPlanDirs ?? []
|
|
2983
3154
|
};
|
|
2984
3155
|
saveConfig(config);
|
|
2985
3156
|
console.log(`[agendex] Logged in successfully!`);
|
|
@@ -2996,7 +3167,8 @@ function logout() {
|
|
|
2996
3167
|
token: existing.token,
|
|
2997
3168
|
cloudToken: undefined,
|
|
2998
3169
|
convexUrl: undefined,
|
|
2999
|
-
enabledAdapters: existing.enabledAdapters
|
|
3170
|
+
enabledAdapters: existing.enabledAdapters,
|
|
3171
|
+
customPlanDirs: existing.customPlanDirs
|
|
3000
3172
|
};
|
|
3001
3173
|
saveConfig(config);
|
|
3002
3174
|
console.log("[agendex] Logged out. Cloud token removed.");
|
|
@@ -3007,14 +3179,14 @@ async function startCallbackServer() {
|
|
|
3007
3179
|
let timeout;
|
|
3008
3180
|
let settle;
|
|
3009
3181
|
let fail;
|
|
3010
|
-
const result = new Promise((
|
|
3011
|
-
settle =
|
|
3182
|
+
const result = new Promise((resolve5, reject) => {
|
|
3183
|
+
settle = resolve5;
|
|
3012
3184
|
fail = reject;
|
|
3013
3185
|
});
|
|
3014
3186
|
const finish = (value) => {
|
|
3015
3187
|
if (!settle || !fail)
|
|
3016
3188
|
return;
|
|
3017
|
-
const
|
|
3189
|
+
const resolve5 = settle;
|
|
3018
3190
|
const reject = fail;
|
|
3019
3191
|
settle = undefined;
|
|
3020
3192
|
fail = undefined;
|
|
@@ -3031,7 +3203,7 @@ async function startCallbackServer() {
|
|
|
3031
3203
|
reject(value);
|
|
3032
3204
|
return;
|
|
3033
3205
|
}
|
|
3034
|
-
|
|
3206
|
+
resolve5(value);
|
|
3035
3207
|
};
|
|
3036
3208
|
server.on("request", (req, res) => {
|
|
3037
3209
|
const requestUrl = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
@@ -3065,8 +3237,8 @@ async function startCallbackServer() {
|
|
|
3065
3237
|
sockets.delete(socket);
|
|
3066
3238
|
});
|
|
3067
3239
|
});
|
|
3068
|
-
await new Promise((
|
|
3069
|
-
server.listen(0, "127.0.0.1", () =>
|
|
3240
|
+
await new Promise((resolve5, reject) => {
|
|
3241
|
+
server.listen(0, "127.0.0.1", () => resolve5());
|
|
3070
3242
|
server.once("error", reject);
|
|
3071
3243
|
});
|
|
3072
3244
|
server.once("error", (error) => {
|
|
@@ -3146,7 +3318,7 @@ function spawnBrowser(command, args, options = {}) {
|
|
|
3146
3318
|
|
|
3147
3319
|
// src/daemon.ts
|
|
3148
3320
|
import { spawn as spawn2 } from "node:child_process";
|
|
3149
|
-
import { resolve as
|
|
3321
|
+
import { resolve as resolve5 } from "node:path";
|
|
3150
3322
|
import { fileURLToPath } from "node:url";
|
|
3151
3323
|
|
|
3152
3324
|
// src/sync-cache.ts
|
|
@@ -3309,6 +3481,7 @@ async function runWorker() {
|
|
|
3309
3481
|
});
|
|
3310
3482
|
console.log(`[agendex] daemon running. Watching for file changes...`);
|
|
3311
3483
|
async function gracefulShutdown() {
|
|
3484
|
+
stopWatching();
|
|
3312
3485
|
await sendShutdown();
|
|
3313
3486
|
process.exit(0);
|
|
3314
3487
|
}
|
|
@@ -3332,17 +3505,17 @@ async function startSupervisor() {
|
|
|
3332
3505
|
};
|
|
3333
3506
|
process.on("SIGTERM", shutdown);
|
|
3334
3507
|
process.on("SIGINT", shutdown);
|
|
3335
|
-
const scriptPath =
|
|
3508
|
+
const scriptPath = resolve5(process.argv[1] ?? fileURLToPath(new URL("./cli.ts", import.meta.url)));
|
|
3336
3509
|
const restartTimes = [];
|
|
3337
3510
|
while (!stopping) {
|
|
3338
3511
|
workerProc = spawn2(process.execPath, [scriptPath, "start", "--worker"], {
|
|
3339
3512
|
stdio: ["ignore", "inherit", "inherit"]
|
|
3340
3513
|
});
|
|
3341
|
-
const exitCode = await new Promise((
|
|
3342
|
-
workerProc?.once("exit", (code) =>
|
|
3514
|
+
const exitCode = await new Promise((resolve6) => {
|
|
3515
|
+
workerProc?.once("exit", (code) => resolve6(code));
|
|
3343
3516
|
workerProc?.once("error", (error) => {
|
|
3344
3517
|
console.error("[agendex] failed to spawn worker:", error);
|
|
3345
|
-
|
|
3518
|
+
resolve6(1);
|
|
3346
3519
|
});
|
|
3347
3520
|
});
|
|
3348
3521
|
workerProc = null;
|
|
@@ -3421,7 +3594,7 @@ import { join as join12 } from "node:path";
|
|
|
3421
3594
|
// package.json
|
|
3422
3595
|
var package_default = {
|
|
3423
3596
|
name: "agendex-cli",
|
|
3424
|
-
version: "0.
|
|
3597
|
+
version: "0.13.0",
|
|
3425
3598
|
description: "Agendex CLI for login, sync, and daemon workflows",
|
|
3426
3599
|
homepage: "https://github.com/Tyru5/Agendex#readme",
|
|
3427
3600
|
repository: {
|
|
@@ -3568,7 +3741,7 @@ function firstCommandToken(argv) {
|
|
|
3568
3741
|
return;
|
|
3569
3742
|
}
|
|
3570
3743
|
var command = firstCommandToken(args) ?? "start";
|
|
3571
|
-
var cliEntry =
|
|
3744
|
+
var cliEntry = resolve6(process.argv[1] ?? fileURLToPath2(import.meta.url));
|
|
3572
3745
|
async function main() {
|
|
3573
3746
|
const isInternal = args.includes("--daemon") || args.includes("--worker");
|
|
3574
3747
|
if (command === "--version" || command === "-v") {
|
|
@@ -3583,6 +3756,9 @@ async function main() {
|
|
|
3583
3756
|
"open",
|
|
3584
3757
|
"view",
|
|
3585
3758
|
"cleanup",
|
|
3759
|
+
"add-dir",
|
|
3760
|
+
"remove-dir",
|
|
3761
|
+
"list-dirs",
|
|
3586
3762
|
"help",
|
|
3587
3763
|
"--help",
|
|
3588
3764
|
"-h"
|
|
@@ -3758,6 +3934,112 @@ async function main() {
|
|
|
3758
3934
|
}
|
|
3759
3935
|
return 0;
|
|
3760
3936
|
}
|
|
3937
|
+
case "add-dir": {
|
|
3938
|
+
const dirPath = args.find((a) => a !== "add-dir" && a !== "--dev" && !a.startsWith("--"));
|
|
3939
|
+
if (!dirPath || !dirPath.trim()) {
|
|
3940
|
+
writeStderr("[agendex] usage: agendex add-dir <path>");
|
|
3941
|
+
return 1;
|
|
3942
|
+
}
|
|
3943
|
+
const resolved = resolveCustomPlanDirPath(dirPath);
|
|
3944
|
+
if (!existsSync9(resolved)) {
|
|
3945
|
+
writeStderr(`[agendex] path does not exist: ${resolved}`);
|
|
3946
|
+
return 1;
|
|
3947
|
+
}
|
|
3948
|
+
if (!statSync2(resolved).isDirectory()) {
|
|
3949
|
+
writeStderr(`[agendex] path is not a directory: ${resolved}`);
|
|
3950
|
+
return 1;
|
|
3951
|
+
}
|
|
3952
|
+
if (args.includes("--live")) {
|
|
3953
|
+
const cfg = loadConfig();
|
|
3954
|
+
const token = cfg?.token;
|
|
3955
|
+
if (!token) {
|
|
3956
|
+
writeStderr("[agendex] no local token found in config — is the server running?");
|
|
3957
|
+
return 1;
|
|
3958
|
+
}
|
|
3959
|
+
const port = process.env.PORT ?? "4890";
|
|
3960
|
+
const { request } = await import("node:http");
|
|
3961
|
+
const body = JSON.stringify({ path: resolved });
|
|
3962
|
+
try {
|
|
3963
|
+
const res = await new Promise((resolve7, reject) => {
|
|
3964
|
+
const req = request(`http://localhost:${port}/api/v1/plan-sources`, {
|
|
3965
|
+
method: "POST",
|
|
3966
|
+
headers: {
|
|
3967
|
+
Authorization: `Bearer ${token}`,
|
|
3968
|
+
"Content-Type": "application/json",
|
|
3969
|
+
"Content-Length": String(Buffer.byteLength(body))
|
|
3970
|
+
}
|
|
3971
|
+
}, (res2) => {
|
|
3972
|
+
let data = "";
|
|
3973
|
+
res2.setEncoding("utf8");
|
|
3974
|
+
res2.on("data", (chunk) => {
|
|
3975
|
+
data += chunk;
|
|
3976
|
+
});
|
|
3977
|
+
res2.on("end", () => resolve7({ status: res2.statusCode ?? 0, body: data }));
|
|
3978
|
+
res2.on("error", reject);
|
|
3979
|
+
});
|
|
3980
|
+
req.on("error", reject);
|
|
3981
|
+
req.write(body);
|
|
3982
|
+
req.end();
|
|
3983
|
+
});
|
|
3984
|
+
if (res.status >= 200 && res.status < 300) {
|
|
3985
|
+
writeStdout(`[agendex] added custom plan dir: ${resolved}`);
|
|
3986
|
+
writeStdout(`[agendex] server notified — scanning + watching now`);
|
|
3987
|
+
} else {
|
|
3988
|
+
writeStderr(`[agendex] server returned ${res.status}: ${res.body}`);
|
|
3989
|
+
return 1;
|
|
3990
|
+
}
|
|
3991
|
+
} catch (err) {
|
|
3992
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3993
|
+
writeStderr(`[agendex] could not reach local server on port ${port}: ${msg}`);
|
|
3994
|
+
return 1;
|
|
3995
|
+
}
|
|
3996
|
+
} else {
|
|
3997
|
+
const cfg = loadConfig();
|
|
3998
|
+
const currentDirs = cfg?.customPlanDirs ?? [];
|
|
3999
|
+
const updated = normalizeCustomPlanDirs([...currentDirs, resolved]);
|
|
4000
|
+
saveConfig({
|
|
4001
|
+
...cfg ?? { configVersion: 3, enabledAdapters: [] },
|
|
4002
|
+
customPlanDirs: updated
|
|
4003
|
+
});
|
|
4004
|
+
writeStdout(`[agendex] added custom plan dir: ${resolved}`);
|
|
4005
|
+
writeStdout(`[agendex] daemon will pick up the change automatically`);
|
|
4006
|
+
}
|
|
4007
|
+
return 0;
|
|
4008
|
+
}
|
|
4009
|
+
case "remove-dir": {
|
|
4010
|
+
const dirPath = args.find((a) => a !== "remove-dir" && a !== "--dev" && !a.startsWith("--"));
|
|
4011
|
+
if (!dirPath || !dirPath.trim()) {
|
|
4012
|
+
writeStderr("[agendex] usage: agendex remove-dir <path>");
|
|
4013
|
+
return 1;
|
|
4014
|
+
}
|
|
4015
|
+
const resolved = resolveCustomPlanDirPath(dirPath);
|
|
4016
|
+
const cfg = loadConfig();
|
|
4017
|
+
const currentDirs = cfg?.customPlanDirs ?? [];
|
|
4018
|
+
const updated = currentDirs.filter((d2) => d2 !== resolved);
|
|
4019
|
+
if (updated.length === currentDirs.length) {
|
|
4020
|
+
writeStderr(`[agendex] directory not in custom plan dirs: ${resolved}`);
|
|
4021
|
+
return 1;
|
|
4022
|
+
}
|
|
4023
|
+
saveConfig({
|
|
4024
|
+
...cfg ?? { configVersion: 3, enabledAdapters: [] },
|
|
4025
|
+
customPlanDirs: updated
|
|
4026
|
+
});
|
|
4027
|
+
writeStdout(`[agendex] removed custom plan dir: ${resolved}`);
|
|
4028
|
+
return 0;
|
|
4029
|
+
}
|
|
4030
|
+
case "list-dirs": {
|
|
4031
|
+
const cfg = loadConfig();
|
|
4032
|
+
const dirs = cfg?.customPlanDirs ?? [];
|
|
4033
|
+
if (dirs.length === 0) {
|
|
4034
|
+
writeStdout("[agendex] no custom plan directories configured");
|
|
4035
|
+
} else {
|
|
4036
|
+
writeStdout(`[agendex] custom plan directories (${dirs.length}):`);
|
|
4037
|
+
for (const dir of dirs) {
|
|
4038
|
+
writeStdout(` - ${dir}`);
|
|
4039
|
+
}
|
|
4040
|
+
}
|
|
4041
|
+
return 0;
|
|
4042
|
+
}
|
|
3761
4043
|
case "status": {
|
|
3762
4044
|
const config = loadConfig();
|
|
3763
4045
|
const pidInfo = readPidInfo();
|
|
@@ -3768,6 +4050,11 @@ async function main() {
|
|
|
3768
4050
|
writeStdout(`[agendex] Cloud token: ${config?.cloudToken ? "set" : "not set"}`);
|
|
3769
4051
|
writeStdout(`[agendex] Convex URL: ${config?.convexUrl ?? "not set"}`);
|
|
3770
4052
|
writeStdout(`[agendex] Enabled adapters: ${config?.enabledAdapters.join(", ") || "none"}`);
|
|
4053
|
+
const customDirs = config?.customPlanDirs ?? [];
|
|
4054
|
+
writeStdout(`[agendex] Custom plan dirs: ${customDirs.length > 0 ? customDirs.length : "none"}`);
|
|
4055
|
+
for (const dir of customDirs) {
|
|
4056
|
+
writeStdout(` - ${dir}`);
|
|
4057
|
+
}
|
|
3771
4058
|
writeStdout(`[agendex] Daemon: ${running ? `running (PID ${pid})` : "not running"}`);
|
|
3772
4059
|
if (running && pidInfo?.startedAtMs) {
|
|
3773
4060
|
writeStdout(`[agendex] Uptime: ${formatDuration(Date.now() - pidInfo.startedAtMs)}`);
|
|
@@ -3832,6 +4119,10 @@ Usage:
|
|
|
3832
4119
|
agendex view <url> Open a shared plan URL in your browser
|
|
3833
4120
|
agendex logout Clear stored cloud token
|
|
3834
4121
|
agendex configure Select which agents/adapters to index
|
|
4122
|
+
agendex add-dir <path> Add a custom directory to scan for plans
|
|
4123
|
+
agendex add-dir <path> --live Add dir and notify running server immediately
|
|
4124
|
+
agendex remove-dir <path> Remove a custom directory
|
|
4125
|
+
agendex list-dirs List custom plan directories
|
|
3835
4126
|
agendex sync One-shot scan + sync to cloud (skips unchanged plans)
|
|
3836
4127
|
agendex sync --force Re-sync all plans, ignoring cache
|
|
3837
4128
|
agendex cleanup Interactively remove cloud daemons
|
|
@@ -3865,13 +4156,13 @@ function flushStream(stream) {
|
|
|
3865
4156
|
if (stream.destroyed || !stream.writable) {
|
|
3866
4157
|
return Promise.resolve();
|
|
3867
4158
|
}
|
|
3868
|
-
return new Promise((
|
|
4159
|
+
return new Promise((resolve7, reject) => {
|
|
3869
4160
|
stream.write("", (error) => {
|
|
3870
4161
|
if (error) {
|
|
3871
4162
|
reject(error);
|
|
3872
4163
|
return;
|
|
3873
4164
|
}
|
|
3874
|
-
|
|
4165
|
+
resolve7();
|
|
3875
4166
|
});
|
|
3876
4167
|
});
|
|
3877
4168
|
}
|