agendex-cli 0.12.0 → 0.14.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.
Files changed (2) hide show
  1. package/dist/cli.js +489 -44
  2. 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) => {
@@ -2254,6 +2256,16 @@ async function promptForAdapterSelection(options = {}) {
2254
2256
 
2255
2257
  // ../shared/src/config.ts
2256
2258
  var devModeOverride;
2259
+ function getHomeDir() {
2260
+ if (process.env.HOME)
2261
+ return process.env.HOME;
2262
+ if (process.env.USERPROFILE)
2263
+ return process.env.USERPROFILE;
2264
+ if (process.env.HOMEDRIVE && process.env.HOMEPATH) {
2265
+ return `${process.env.HOMEDRIVE}${process.env.HOMEPATH}`;
2266
+ }
2267
+ return homedir7();
2268
+ }
2257
2269
  function setDevMode(dev) {
2258
2270
  devModeOverride = dev;
2259
2271
  }
@@ -2263,7 +2275,7 @@ function isDevMode() {
2263
2275
  return process.env.AGENDEX_DEV === "1";
2264
2276
  }
2265
2277
  function getConfigDir() {
2266
- return join7(homedir7(), isDevMode() ? ".agendex-dev" : ".agendex");
2278
+ return join7(getHomeDir(), isDevMode() ? ".agendex-dev" : ".agendex");
2267
2279
  }
2268
2280
  function ensureConfigDir() {
2269
2281
  const dir = getConfigDir();
@@ -2290,7 +2302,7 @@ function normalizeAdapterIds(input) {
2290
2302
  }
2291
2303
  function expandHomePath(p) {
2292
2304
  if (p.startsWith("~/") || p === "~")
2293
- return join7(homedir7(), p.slice(1));
2305
+ return join7(getHomeDir(), p.slice(1));
2294
2306
  return p;
2295
2307
  }
2296
2308
  function resolveCustomPlanDirPath(userPath) {
@@ -2438,8 +2450,335 @@ var CLI_DAEMON_STALE_AFTER_MS = CLI_DAEMON_HEARTBEAT_INTERVAL_MS * 5;
2438
2450
  // ../shared/src/services/plan-service.ts
2439
2451
  import { existsSync as existsSync4, readdirSync as readdirSync3, statSync } from "node:fs";
2440
2452
  import { lstat, mkdir, readdir as readdir2, readFile as readFile6, stat as stat6, writeFile as writeFile3 } from "node:fs/promises";
2441
- import { homedir as homedir8 } from "node:os";
2442
2453
  import { join as join8, resolve as resolve3, sep as sep2 } from "node:path";
2454
+
2455
+ // ../shared/src/services/plan-value.ts
2456
+ function isLowValuePlan(plan) {
2457
+ const metadata = plan.metadata;
2458
+ return typeof metadata === "object" && metadata !== null && !Array.isArray(metadata) && metadata.lowValue === true;
2459
+ }
2460
+ function isIndexablePlan(plan) {
2461
+ return !isLowValuePlan(plan);
2462
+ }
2463
+ var PROPOSED_PLAN_TAG_REGEX2 = /<\s*\/?\s*proposed_plan\s*>/gi;
2464
+ var ESCAPED_PROPOSED_PLAN_TAG_REGEX2 = /&lt;\s*\/?\s*proposed_plan\s*&gt;/gi;
2465
+ var VISIBLE_TEXT_REGEX = /[\p{L}\p{N}]/u;
2466
+ var LOW_VALUE_METADATA_KEYS = ["lowValue", "lowValueReasons", "lowValueSignals"];
2467
+ function normalizeLineEndings2(text) {
2468
+ return text.replace(/\r\n?/g, `
2469
+ `);
2470
+ }
2471
+ function stripBoundaryHtmlComments(text) {
2472
+ let next = text;
2473
+ let previous = "";
2474
+ while (next !== previous) {
2475
+ previous = next;
2476
+ next = next.replace(/^\s*<!--[\s\S]*?-->\s*/, "").replace(/\s*<!--[\s\S]*?-->\s*$/, "");
2477
+ }
2478
+ return next;
2479
+ }
2480
+ function normalizePlanContent(content) {
2481
+ return stripBoundaryHtmlComments(normalizeLineEndings2(content).replace(/^\uFEFF/, "").replace(/^---\s*\n[\s\S]*?\n---\s*\n?/, "").replace(ESCAPED_PROPOSED_PLAN_TAG_REGEX2, "").replace(PROPOSED_PLAN_TAG_REGEX2, "")).trim();
2482
+ }
2483
+ function withoutLowValueMetadata(metadata) {
2484
+ const next = { ...metadata ?? {} };
2485
+ for (const key of LOW_VALUE_METADATA_KEYS)
2486
+ delete next[key];
2487
+ return next;
2488
+ }
2489
+ function unique(items) {
2490
+ return Array.from(new Set(items));
2491
+ }
2492
+ function visibleText(text) {
2493
+ return text.replace(/<!--[\s\S]*?-->/g, "").replace(/^---\s*\n[\s\S]*?\n---\s*\n?/, "").replace(ESCAPED_PROPOSED_PLAN_TAG_REGEX2, "").replace(PROPOSED_PLAN_TAG_REGEX2, "").replace(/[`*_#[\](){}<>:|~\-+=.]/g, " ").trim();
2494
+ }
2495
+ function isSeparatorLine(line) {
2496
+ return /^(?:-{3,}|_{3,}|\*{3,})$/.test(line.trim());
2497
+ }
2498
+ function isFenceLine(line) {
2499
+ return /^(?:`{3,}|~{3,})/.test(line.trim());
2500
+ }
2501
+ function meaningfulLines(text) {
2502
+ return text.split(`
2503
+ `).map((line) => line.trim()).filter((line) => line && !isSeparatorLine(line) && !isFenceLine(line));
2504
+ }
2505
+ function cleanMarkdownLine(line) {
2506
+ return line.trim().replace(/^>+\s*/, "").replace(/^#{1,6}\s+/, "").replace(/^[-*+]\s+/, "").replace(/^\d+[.)]\s+/, "").replace(/^\[[ xX]\]\s+/, "").replace(/^\*\*|\*\*$/g, "").replace(/^`|`$/g, "").trim();
2507
+ }
2508
+ function isHeadingLine(line) {
2509
+ return /^#{1,6}\s+\S/.test(line.trim());
2510
+ }
2511
+ function isChecklistLine(line) {
2512
+ return /^[-*+]\s+\[[ xX]\]\s+\S/.test(line.trim());
2513
+ }
2514
+ function isOrderedListLine(line) {
2515
+ return /^\d+[.)]\s+\S/.test(line.trim());
2516
+ }
2517
+ function isHeadingOnly(lines) {
2518
+ return lines.length > 0 && lines.some(isHeadingLine) && lines.every(isHeadingLine);
2519
+ }
2520
+ function metadataHasPlanBlocks(metadata) {
2521
+ const planBlocks = metadata?.planBlocks;
2522
+ return typeof planBlocks === "number" && planBlocks > 0;
2523
+ }
2524
+ function sectionName(line) {
2525
+ return cleanMarkdownLine(line).replace(/:$/, "").toLowerCase().replace(/\s+/g, " ");
2526
+ }
2527
+ var SECTION_LABELS = new Set([
2528
+ "context",
2529
+ "background",
2530
+ "problem",
2531
+ "goal",
2532
+ "goals",
2533
+ "scope",
2534
+ "approach",
2535
+ "strategy",
2536
+ "design",
2537
+ "implementation plan",
2538
+ "implementation",
2539
+ "plan",
2540
+ "files to modify",
2541
+ "files changed",
2542
+ "affected files",
2543
+ "steps",
2544
+ "step",
2545
+ "tasks",
2546
+ "task",
2547
+ "checklist",
2548
+ "todo",
2549
+ "todos",
2550
+ "verification",
2551
+ "testing",
2552
+ "tests",
2553
+ "test",
2554
+ "validation",
2555
+ "reuse",
2556
+ "existing utilities",
2557
+ "existing code",
2558
+ "acceptance criteria",
2559
+ "success criteria"
2560
+ ]);
2561
+ function hasSectionSyntax(line, section) {
2562
+ const cleaned = cleanMarkdownLine(line);
2563
+ return isHeadingLine(line) || /^[a-z][\w\s/&+-]{1,48}:/i.test(cleaned) || SECTION_LABELS.has(section);
2564
+ }
2565
+ function collectPositiveSignals(normalized, lines, metadata) {
2566
+ const signals = [];
2567
+ if (metadataHasPlanBlocks(metadata))
2568
+ signals.push("metadata:proposed-plan-block");
2569
+ for (const line of lines) {
2570
+ const section = sectionName(line);
2571
+ if (!hasSectionSyntax(line, section))
2572
+ continue;
2573
+ if (/^(context|background|problem|goal|goals|scope)\b/.test(section)) {
2574
+ signals.push("section:context");
2575
+ continue;
2576
+ }
2577
+ if (/^(approach|strategy|design)\b/.test(section)) {
2578
+ signals.push("section:approach");
2579
+ continue;
2580
+ }
2581
+ if (/^(implementation plan|implementation|plan)\b/.test(section)) {
2582
+ signals.push("section:implementation-plan");
2583
+ continue;
2584
+ }
2585
+ if (/^(files? to modify|files? changed|affected files?)\b/.test(section)) {
2586
+ signals.push("section:files-to-modify");
2587
+ continue;
2588
+ }
2589
+ if (/^(steps?|tasks?|checklist|todo|todos)\b/.test(section)) {
2590
+ signals.push("section:steps");
2591
+ continue;
2592
+ }
2593
+ if (/^(verification|testing|tests?|validation)\b/.test(section)) {
2594
+ signals.push("section:verification");
2595
+ continue;
2596
+ }
2597
+ if (/^(reuse|existing utilities|existing code)\b/.test(section)) {
2598
+ signals.push("section:reuse");
2599
+ continue;
2600
+ }
2601
+ if (/^(acceptance criteria|success criteria)\b/.test(section)) {
2602
+ signals.push("section:acceptance-criteria");
2603
+ }
2604
+ }
2605
+ if (lines.some(isChecklistLine))
2606
+ signals.push("checklist");
2607
+ const orderedStepCount = lines.filter(isOrderedListLine).length;
2608
+ if (orderedStepCount >= 2)
2609
+ signals.push("ordered-steps");
2610
+ const actionBulletCount = lines.filter((line) => {
2611
+ const trimmed = line.trim();
2612
+ if (!/^(?:[-*+]|\d+[.)])\s+/.test(trimmed))
2613
+ return false;
2614
+ const cleaned = cleanMarkdownLine(trimmed);
2615
+ return /^(?:add|implement|update|modify|create|remove|delete|refactor|test|verify|run|wire|persist|handle|ensure|document|rename|move|extract|reuse|validate)\b/i.test(cleaned);
2616
+ }).length;
2617
+ if (actionBulletCount >= 2)
2618
+ signals.push("action-bullets");
2619
+ if (lines.length >= 2 && /\b(?:will|should|need to|needs to|plan to|planned|approach is to|implementation will)\b/i.test(normalized)) {
2620
+ signals.push("future-plan-language");
2621
+ }
2622
+ return unique(signals);
2623
+ }
2624
+ function isStrongPositiveSignal(signal) {
2625
+ return signal === "metadata:proposed-plan-block" || signal === "checklist" || signal === "ordered-steps" || signal === "action-bullets" || signal === "section:approach" || signal === "section:implementation-plan" || signal === "section:files-to-modify" || signal === "section:steps" || signal === "section:acceptance-criteria";
2626
+ }
2627
+ function hasStrongPlanSignal(positiveSignals) {
2628
+ const sectionCount = positiveSignals.filter((signal) => signal.startsWith("section:")).length;
2629
+ return positiveSignals.some(isStrongPositiveSignal) || sectionCount >= 2;
2630
+ }
2631
+ function isPromptLikeOneLiner(line) {
2632
+ if (isHeadingLine(line) || isChecklistLine(line) || isOrderedListLine(line))
2633
+ return false;
2634
+ const cleaned = cleanMarkdownLine(line).toLowerCase();
2635
+ if (!cleaned)
2636
+ return false;
2637
+ return [
2638
+ /^(?:important:\s*)?work in\b/,
2639
+ /^(?:please|pls)\b/,
2640
+ /^(?:can|could|would|will)\s+you\b/,
2641
+ /^(?:i|we)\s+(?:need|want|would like|have to)\b/,
2642
+ /^(?:help|fix|implement|create|add|update|remove|delete|refactor|write|review|investigate|debug|plan)\b/,
2643
+ /\?$/,
2644
+ /\b(?:repository|repo|existing branch|worktree|pull request|pr)\b/
2645
+ ].some((pattern) => pattern.test(cleaned));
2646
+ }
2647
+ function normalizedTitle(title) {
2648
+ return cleanMarkdownLine(title ?? "").toLowerCase();
2649
+ }
2650
+ function looksLikeWrapperTitle(title) {
2651
+ return /^<user_(?:action|instructions|prompt)>$/i.test((title ?? "").trim());
2652
+ }
2653
+ function looksLikePromptTitle(title) {
2654
+ const cleaned = normalizedTitle(title);
2655
+ if (!cleaned)
2656
+ return false;
2657
+ return [
2658
+ /^(?:important:\s*)?work in\b/,
2659
+ /^review the code changes against\b/,
2660
+ /^perform a .*review\b/,
2661
+ /^(?:please|pls)\b/,
2662
+ /^(?:can|could|would|will)\s+you\b/,
2663
+ /^(?:i|we)\s+(?:need|want|would like|have to)\b/,
2664
+ /^(?:help|fix|implement|create|add|update|remove|delete|refactor|write|review|investigate|debug|plan)\b/,
2665
+ /\?$/,
2666
+ /\b(?:repository|repo|existing branch|worktree|pull request|pr)\b/
2667
+ ].some((pattern) => pattern.test(cleaned));
2668
+ }
2669
+ function looksLikeReviewOutput(normalized, title) {
2670
+ const cleanedTitle = normalizedTitle(title);
2671
+ const lower = normalized.toLowerCase();
2672
+ return /^review the code changes against\b/.test(cleanedTitle) || /^perform a .*review\b/.test(cleanedTitle) || /"findings"\s*:\s*\[/.test(normalized) || /"overall_correctness"\s*:/.test(normalized) || /\bfull review comments\s*:/i.test(normalized) || /\bthe patch (?:currently )?(?:breaks|introduces|regresses)\b/i.test(normalized) || /\bshould not be considered correct\b/i.test(lower);
2673
+ }
2674
+ function looksLikeSystemContext(normalized, lines) {
2675
+ const lower = normalized.toLowerCase();
2676
+ if (lower.startsWith("# agents.md instructions") || lower.startsWith("<environment_context>") || lower.startsWith("<system-reminder>") || lower.startsWith("<thinking>")) {
2677
+ return true;
2678
+ }
2679
+ const wrapperMatches = normalized.match(/<\/?(?:environment_context|system-reminder|thinking|analysis|reasoning|tool_call|tool_result)\b[^>]*>/gi);
2680
+ if (wrapperMatches && wrapperMatches.length >= 2)
2681
+ return true;
2682
+ const wrapperLineCount = lines.filter((line) => /^(?:analysis|reasoning|thought|assistant thought|system|developer):\b/i.test(line)).length;
2683
+ return wrapperLineCount > 0 && wrapperLineCount / Math.max(lines.length, 1) >= 0.4;
2684
+ }
2685
+ function looksLikeToolLog(normalized) {
2686
+ return /::[a-z0-9_-]+(?:\{|\[|\s*$)/i.test(normalized) || /<\/?(?:tool_call|tool_result)\b[^>]*>/i.test(normalized) || /\b(?:function_call|tool_calls|tool_result)\b/i.test(normalized);
2687
+ }
2688
+ function looksLikeExecutionReport(normalized) {
2689
+ const hasPastCompletion = /\b(?:fixed|pushed|committed|completed|done|implemented|updated|changed|patched|merged|deployed|passed|failed|resolved|reverted)\b/i.test(normalized);
2690
+ const hasReportSection = /^\s*(?:summary|result|results|changes|verification|status)\s*:/im.test(normalized);
2691
+ const hasReviewReportMarker = /\b(?:review findings?|review issues?|review comments?)\b/i.test(normalized);
2692
+ const hasCommandMarker = /::[a-z0-9_-]+(?:\{|\[|\s*$)/im.test(normalized) || /`[^`]*(?:bun|npm|pnpm|yarn|git|tsc|biome)[^`]*`/i.test(normalized) || /\b(?:git\s+(?:stage|commit|push|status)|bunx?\s+|npm\s+|pnpm\s+|yarn\s+)\b/i.test(normalized);
2693
+ return hasPastCompletion && (hasReportSection || hasCommandMarker || hasReviewReportMarker);
2694
+ }
2695
+ function lowValueAssessment(reasons, signals) {
2696
+ return {
2697
+ lowValue: reasons.length > 0,
2698
+ reasons: unique(reasons),
2699
+ signals: unique(signals).slice(0, 20)
2700
+ };
2701
+ }
2702
+ function assessPlanValue(input) {
2703
+ const metadata = withoutLowValueMetadata(input.metadata);
2704
+ const normalized = normalizePlanContent(input.content);
2705
+ const lines = meaningfulLines(normalized);
2706
+ const positiveSignals = collectPositiveSignals(normalized, lines, metadata);
2707
+ const signals = [...positiveSignals];
2708
+ const reasons = [];
2709
+ if (!VISIBLE_TEXT_REGEX.test(visibleText(normalized))) {
2710
+ return lowValueAssessment(["empty-content"], ["negative:empty-content"]);
2711
+ }
2712
+ if (isHeadingOnly(lines)) {
2713
+ return lowValueAssessment(["heading-only"], [...signals, "negative:heading-only"]);
2714
+ }
2715
+ const explicitPlanBlock = metadataHasPlanBlocks(metadata);
2716
+ const strongPositive = hasStrongPlanSignal(positiveSignals);
2717
+ const systemContext = looksLikeSystemContext(normalized, lines);
2718
+ const toolLog = looksLikeToolLog(normalized);
2719
+ const executionReport = looksLikeExecutionReport(normalized);
2720
+ const wrapperTitle = looksLikeWrapperTitle(input.title);
2721
+ const promptTitle = looksLikePromptTitle(input.title);
2722
+ const reviewOutput = looksLikeReviewOutput(normalized, input.title);
2723
+ if (systemContext)
2724
+ signals.push("negative:system-context");
2725
+ if (toolLog)
2726
+ signals.push("negative:tool-log");
2727
+ if (executionReport)
2728
+ signals.push("negative:execution-report");
2729
+ if (wrapperTitle)
2730
+ signals.push("negative:wrapper-title");
2731
+ if (promptTitle)
2732
+ signals.push("negative:prompt-title");
2733
+ if (reviewOutput)
2734
+ signals.push("negative:review-output");
2735
+ if (lines.length === 1)
2736
+ signals.push("shape:single-line");
2737
+ if (lines.length === 1 && positiveSignals.length === 0) {
2738
+ reasons.push(isPromptLikeOneLiner(lines[0] ?? "") ? "prompt-like" : "no-plan-signals");
2739
+ }
2740
+ if (systemContext && !strongPositive)
2741
+ reasons.push("system-context");
2742
+ if (executionReport && !strongPositive)
2743
+ reasons.push("execution-report");
2744
+ if (wrapperTitle && !explicitPlanBlock)
2745
+ reasons.push("wrapper-title");
2746
+ if (reviewOutput && !explicitPlanBlock)
2747
+ reasons.push("review-output");
2748
+ if (promptTitle && !strongPositive && positiveSignals.length === 0)
2749
+ reasons.push("prompt-like");
2750
+ if (toolLog && !strongPositive && positiveSignals.length === 0)
2751
+ reasons.push("no-plan-signals");
2752
+ if (reasons.length === 0 && positiveSignals.length === 0 && lines.length <= 3) {
2753
+ reasons.push("no-plan-signals");
2754
+ }
2755
+ return lowValueAssessment(reasons, signals);
2756
+ }
2757
+ function annotatePlanValueMetadata(plan) {
2758
+ const baseMetadata = withoutLowValueMetadata(plan.metadata);
2759
+ const assessment = assessPlanValue({
2760
+ content: plan.content,
2761
+ title: plan.title,
2762
+ metadata: baseMetadata
2763
+ });
2764
+ if (!assessment.lowValue) {
2765
+ return {
2766
+ ...plan,
2767
+ metadata: baseMetadata
2768
+ };
2769
+ }
2770
+ return {
2771
+ ...plan,
2772
+ metadata: {
2773
+ ...baseMetadata,
2774
+ lowValue: true,
2775
+ lowValueReasons: assessment.reasons,
2776
+ lowValueSignals: assessment.signals
2777
+ }
2778
+ };
2779
+ }
2780
+
2781
+ // ../shared/src/services/plan-service.ts
2443
2782
  function getUserPlansDir() {
2444
2783
  return join8(getConfigDir(), "plans");
2445
2784
  }
@@ -2480,7 +2819,7 @@ var SKIP_DIRS = new Set([
2480
2819
  "iCloud Drive"
2481
2820
  ]);
2482
2821
  function discoverProjectPlanDirs() {
2483
- const home2 = homedir8();
2822
+ const home2 = getHomeDir();
2484
2823
  const results = [];
2485
2824
  function walk(dir, depth) {
2486
2825
  if (depth > DISCOVERY_MAX_DEPTH)
@@ -2552,7 +2891,7 @@ async function parseGenericMarkdownPlan(filePath, extraMetadata) {
2552
2891
  try {
2553
2892
  const content = await readFile6(filePath, "utf-8");
2554
2893
  const stats = await stat6(filePath);
2555
- let agent = "unknown";
2894
+ let agent = (typeof extraMetadata.agentHint === "string" ? extraMetadata.agentHint : "") || "unknown";
2556
2895
  const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n/);
2557
2896
  if (fmMatch) {
2558
2897
  const agentLine = fmMatch[1]?.match(/^agent:\s*(.+)$/m);
@@ -2630,13 +2969,15 @@ async function scanCustomPlanDirs(coveredPaths, into) {
2630
2969
  continue;
2631
2970
  }
2632
2971
  const files = await walkDir(dir);
2972
+ const dirBasename = dir.replace(/[\\/]+$/, "").split(/[\\/]/).pop() ?? "custom";
2633
2973
  let count = 0;
2634
2974
  for (const file of files) {
2635
2975
  if (!file.endsWith(".md"))
2636
2976
  continue;
2637
2977
  const plan = await parseGenericMarkdownPlan(file, {
2638
2978
  source: "custom-dir",
2639
- customDir: dir
2979
+ customDir: dir,
2980
+ agentHint: dirBasename
2640
2981
  });
2641
2982
  if (plan) {
2642
2983
  into.set(plan.id, plan);
@@ -2659,7 +3000,8 @@ async function scan() {
2659
3000
  continue;
2660
3001
  const plans = await adapter.parse(file);
2661
3002
  for (const plan of plans) {
2662
- next.set(plan.id, plan);
3003
+ const annotated = annotatePlanValueMetadata(plan);
3004
+ next.set(annotated.id, annotated);
2663
3005
  }
2664
3006
  }
2665
3007
  }
@@ -2678,7 +3020,8 @@ async function scan() {
2678
3020
  continue;
2679
3021
  const plans = await adapter.parse(file);
2680
3022
  for (const plan of plans) {
2681
- next.set(plan.id, plan);
3023
+ const annotated = annotatePlanValueMetadata(plan);
3024
+ next.set(annotated.id, annotated);
2682
3025
  }
2683
3026
  }
2684
3027
  coveredPaths.add(resolvedDir);
@@ -2688,11 +3031,17 @@ async function scan() {
2688
3031
  await scanCustomPlanDirs(coveredPaths, next);
2689
3032
  store = next;
2690
3033
  notifyPlansChanged();
2691
- console.log(`[agendex] indexed ${store.size} plans from ${adapters.length} adapters`);
3034
+ const indexableCount = getIndexablePlans().length;
3035
+ const hiddenCount = store.size - indexableCount;
3036
+ const hiddenSuffix = hiddenCount > 0 ? ` (${hiddenCount} hidden as low-value)` : "";
3037
+ console.log(`[agendex] indexed ${indexableCount} plans${hiddenSuffix} from ${adapters.length} adapters`);
2692
3038
  }
2693
3039
  function getAll() {
2694
3040
  return Array.from(store.values());
2695
3041
  }
3042
+ function getIndexablePlans() {
3043
+ return getAll().filter(isIndexablePlan);
3044
+ }
2696
3045
  async function rescanFile(filePath) {
2697
3046
  const adapters = getActiveAdapters();
2698
3047
  const normalized = resolve3(filePath);
@@ -2707,7 +3056,7 @@ async function rescanFile(filePath) {
2707
3056
  const isInSearchPath = allSearchPaths.some((sp) => normalized.startsWith(sp + sep2) || normalized === sp);
2708
3057
  if (!isInSearchPath)
2709
3058
  continue;
2710
- const plans = await adapter.parse(filePath);
3059
+ const plans = (await adapter.parse(filePath)).map(annotatePlanValueMetadata);
2711
3060
  for (const plan of plans) {
2712
3061
  store.set(plan.id, plan);
2713
3062
  }
@@ -2905,6 +3254,22 @@ function getCloudConfig() {
2905
3254
  convexUrl: config.convexUrl
2906
3255
  };
2907
3256
  }
3257
+ function parseSyncSuccess(body) {
3258
+ try {
3259
+ const parsed = JSON.parse(body);
3260
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
3261
+ return { ok: true };
3262
+ }
3263
+ const result = parsed;
3264
+ return {
3265
+ ok: true,
3266
+ skippedLowValue: result.skippedLowValue === true,
3267
+ deleted: result.deleted === true
3268
+ };
3269
+ } catch {
3270
+ return { ok: true };
3271
+ }
3272
+ }
2908
3273
  async function syncPlan(plan) {
2909
3274
  const { token, convexUrl } = getCloudConfig();
2910
3275
  const url = `${convexUrl}/api/cli/sync`;
@@ -2936,7 +3301,7 @@ async function syncPlan(plan) {
2936
3301
  if (res.status < 200 || res.status >= 300) {
2937
3302
  return { ok: false, error: `${res.status}: ${res.body}` };
2938
3303
  }
2939
- return { ok: true };
3304
+ return parseSyncSuccess(res.body);
2940
3305
  }
2941
3306
  async function refreshStoredToken(currentToken, convexUrl) {
2942
3307
  const refreshed = await refreshToken(currentToken, convexUrl);
@@ -2991,7 +3356,6 @@ async function sendHeartbeat() {
2991
3356
  }
2992
3357
  async function sendShutdown() {
2993
3358
  try {
2994
- const { token, convexUrl } = getCloudConfig();
2995
3359
  cachedDeviceId ??= loadOrCreateDeviceId();
2996
3360
  await deleteDaemons([cachedDeviceId]);
2997
3361
  } catch {}
@@ -3409,6 +3773,8 @@ async function runWorker() {
3409
3773
  syncing = true;
3410
3774
  const batch = syncQueue.splice(0);
3411
3775
  let syncedCount = 0;
3776
+ let lowValueSkippedCount = 0;
3777
+ let lowValueDeletedCount = 0;
3412
3778
  let failedCount = 0;
3413
3779
  try {
3414
3780
  for (const payload of batch) {
@@ -3429,19 +3795,26 @@ async function runWorker() {
3429
3795
  failedCount++;
3430
3796
  console.error(`[agendex] sync failed for "${payload.title}": ${result.error}`);
3431
3797
  } else {
3432
- syncedCount++;
3798
+ if (result.skippedLowValue) {
3799
+ lowValueSkippedCount++;
3800
+ if (result.deleted)
3801
+ lowValueDeletedCount++;
3802
+ } else {
3803
+ syncedCount++;
3804
+ }
3433
3805
  syncCache[payload.localPlanId] = computePayloadHash(payload);
3434
3806
  }
3435
3807
  }
3436
3808
  } catch (err) {
3437
3809
  console.error("[agendex] sync error:", err);
3438
- syncQueue.unshift(...batch.slice(syncedCount + failedCount));
3810
+ syncQueue.unshift(...batch.slice(syncedCount + lowValueSkippedCount + failedCount));
3439
3811
  } finally {
3440
3812
  syncing = false;
3441
3813
  }
3442
- if (syncedCount > 0 || failedCount > 0) {
3814
+ if (syncedCount > 0 || lowValueSkippedCount > 0 || failedCount > 0) {
3443
3815
  saveSyncCache(syncCache);
3444
- console.log(`[agendex] sync complete: ${syncedCount} synced, ${failedCount} failed`);
3816
+ const lowValueSuffix2 = lowValueSkippedCount > 0 ? `, ${lowValueSkippedCount} low-value skipped/pruned${lowValueDeletedCount > 0 ? ` (${lowValueDeletedCount} deleted)` : ""}` : "";
3817
+ console.log(`[agendex] sync complete: ${syncedCount} synced${lowValueSuffix2}, ${failedCount} failed`);
3445
3818
  }
3446
3819
  if (syncQueue.length > 0)
3447
3820
  processSyncQueue();
@@ -3450,7 +3823,11 @@ async function runWorker() {
3450
3823
  console.log(`[agendex] initial scan...`);
3451
3824
  await scan();
3452
3825
  const plans = getAll();
3826
+ const syncablePlanCount = plans.filter(isIndexablePlan).length;
3827
+ const lowValuePlanCount = plans.length - syncablePlanCount;
3453
3828
  let initialSkipped = 0;
3829
+ let initialQueuedSyncable = 0;
3830
+ let initialQueuedLowValue = 0;
3454
3831
  for (const plan of plans) {
3455
3832
  const payload = planToPayload(plan);
3456
3833
  const hash = computePayloadHash(payload);
@@ -3458,6 +3835,11 @@ async function runWorker() {
3458
3835
  initialSkipped++;
3459
3836
  continue;
3460
3837
  }
3838
+ if (isLowValuePlan(plan)) {
3839
+ initialQueuedLowValue++;
3840
+ } else {
3841
+ initialQueuedSyncable++;
3842
+ }
3461
3843
  syncQueue.push(payload);
3462
3844
  }
3463
3845
  const activePlanIds = new Set(plans.map((plan) => plan.id));
@@ -3466,7 +3848,8 @@ async function runWorker() {
3466
3848
  delete syncCache[id];
3467
3849
  }
3468
3850
  saveSyncCache(syncCache, { replace: true });
3469
- console.log(`[agendex] syncing ${syncQueue.length} plans (${initialSkipped} unchanged)...`);
3851
+ const lowValueSuffix = lowValuePlanCount > 0 ? `, ${lowValuePlanCount} low-value hidden/pruned` : "";
3852
+ console.log(`[agendex] syncing ${initialQueuedSyncable} plans${lowValueSuffix} (${initialQueuedLowValue} low-value queued, ${initialSkipped} unchanged)...`);
3470
3853
  await processSyncQueue();
3471
3854
  setInterval(() => void sendHeartbeat(), CLI_DAEMON_HEARTBEAT_INTERVAL_MS);
3472
3855
  startWatching((changedPlans) => {
@@ -3534,33 +3917,41 @@ async function startSupervisor() {
3534
3917
  }
3535
3918
 
3536
3919
  // src/sync.ts
3920
+ function planToPayload2(plan) {
3921
+ return {
3922
+ localPlanId: plan.id,
3923
+ agent: plan.agent,
3924
+ title: plan.title,
3925
+ content: plan.content,
3926
+ format: plan.format,
3927
+ filePath: plan.filePath,
3928
+ workspace: plan.workspace,
3929
+ metadata: plan.metadata,
3930
+ createdAt: plan.createdAt.getTime(),
3931
+ updatedAt: plan.updatedAt.getTime()
3932
+ };
3933
+ }
3537
3934
  async function syncAll(force = false) {
3538
3935
  const config = await loadOrInitConfig();
3539
3936
  const adapters = resolveAdapters(config.enabledAdapters);
3540
3937
  setActiveAdapters(adapters);
3541
3938
  console.log(`[agendex] Scanning local plans...`);
3542
3939
  await scan();
3543
- const plans = getAll();
3544
- console.log(`[agendex] Found ${plans.length} plans. Syncing to cloud...`);
3940
+ const allPlans = getAll();
3941
+ const syncablePlans = allPlans.filter(isIndexablePlan);
3942
+ const lowValuePlans = allPlans.filter(isLowValuePlan);
3943
+ const hiddenSuffix = lowValuePlans.length > 0 ? ` (${lowValuePlans.length} low-value hidden/pruned)` : "";
3944
+ console.log(`[agendex] Found ${syncablePlans.length} syncable plans${hiddenSuffix}. Syncing to cloud...`);
3545
3945
  const cache = force ? {} : loadSyncCache();
3546
3946
  const activePlanIds = new Set;
3547
3947
  let synced = 0;
3948
+ let lowValueSkipped = 0;
3949
+ let lowValueDeleted = 0;
3548
3950
  let skipped = 0;
3549
3951
  let failed = 0;
3550
- for (const plan of plans) {
3952
+ for (const plan of [...syncablePlans, ...lowValuePlans]) {
3551
3953
  activePlanIds.add(plan.id);
3552
- const payload = {
3553
- localPlanId: plan.id,
3554
- agent: plan.agent,
3555
- title: plan.title,
3556
- content: plan.content,
3557
- format: plan.format,
3558
- filePath: plan.filePath,
3559
- workspace: plan.workspace,
3560
- metadata: plan.metadata,
3561
- createdAt: plan.createdAt.getTime(),
3562
- updatedAt: plan.updatedAt.getTime()
3563
- };
3954
+ const payload = planToPayload2(plan);
3564
3955
  const hash = computePayloadHash(payload);
3565
3956
  if (!force && cache[plan.id] === hash) {
3566
3957
  skipped++;
@@ -3568,7 +3959,13 @@ async function syncAll(force = false) {
3568
3959
  }
3569
3960
  const result = await syncPlan(payload);
3570
3961
  if (result.ok) {
3571
- synced++;
3962
+ if (result.skippedLowValue) {
3963
+ lowValueSkipped++;
3964
+ if (result.deleted)
3965
+ lowValueDeleted++;
3966
+ } else {
3967
+ synced++;
3968
+ }
3572
3969
  cache[plan.id] = hash;
3573
3970
  } else {
3574
3971
  failed++;
@@ -3580,7 +3977,8 @@ async function syncAll(force = false) {
3580
3977
  delete cache[id];
3581
3978
  }
3582
3979
  saveSyncCache(cache, { replace: true });
3583
- console.log(`[agendex] Sync complete: ${synced} synced, ${skipped} unchanged, ${failed} failed`);
3980
+ const lowValueSuffix = lowValueSkipped > 0 ? `, ${lowValueSkipped} low-value skipped/pruned${lowValueDeleted > 0 ? ` (${lowValueDeleted} deleted)` : ""}` : "";
3981
+ console.log(`[agendex] Sync complete: ${synced} synced${lowValueSuffix}, ${skipped} unchanged, ${failed} failed`);
3584
3982
  }
3585
3983
 
3586
3984
  // src/version.ts
@@ -3590,7 +3988,7 @@ import { join as join12 } from "node:path";
3590
3988
  // package.json
3591
3989
  var package_default = {
3592
3990
  name: "agendex-cli",
3593
- version: "0.12.0",
3991
+ version: "0.14.0",
3594
3992
  description: "Agendex CLI for login, sync, and daemon workflows",
3595
3993
  homepage: "https://github.com/Tyru5/Agendex#readme",
3596
3994
  repository: {
@@ -3945,15 +4343,61 @@ async function main() {
3945
4343
  writeStderr(`[agendex] path is not a directory: ${resolved}`);
3946
4344
  return 1;
3947
4345
  }
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`);
4346
+ if (args.includes("--live")) {
4347
+ const cfg = loadConfig();
4348
+ const token = cfg?.token;
4349
+ if (!token) {
4350
+ writeStderr("[agendex] no local token found in config — is the server running?");
4351
+ return 1;
4352
+ }
4353
+ const port = process.env.PORT ?? "4890";
4354
+ const { request } = await import("node:http");
4355
+ const body = JSON.stringify({ path: resolved });
4356
+ try {
4357
+ const res = await new Promise((resolve7, reject) => {
4358
+ const req = request(`http://localhost:${port}/api/v1/plan-sources`, {
4359
+ method: "POST",
4360
+ headers: {
4361
+ Authorization: `Bearer ${token}`,
4362
+ "Content-Type": "application/json",
4363
+ "Content-Length": String(Buffer.byteLength(body))
4364
+ }
4365
+ }, (res2) => {
4366
+ let data = "";
4367
+ res2.setEncoding("utf8");
4368
+ res2.on("data", (chunk) => {
4369
+ data += chunk;
4370
+ });
4371
+ res2.on("end", () => resolve7({ status: res2.statusCode ?? 0, body: data }));
4372
+ res2.on("error", reject);
4373
+ });
4374
+ req.on("error", reject);
4375
+ req.write(body);
4376
+ req.end();
4377
+ });
4378
+ if (res.status >= 200 && res.status < 300) {
4379
+ writeStdout(`[agendex] added custom plan dir: ${resolved}`);
4380
+ writeStdout(`[agendex] server notified — scanning + watching now`);
4381
+ } else {
4382
+ writeStderr(`[agendex] server returned ${res.status}: ${res.body}`);
4383
+ return 1;
4384
+ }
4385
+ } catch (err) {
4386
+ const msg = err instanceof Error ? err.message : String(err);
4387
+ writeStderr(`[agendex] could not reach local server on port ${port}: ${msg}`);
4388
+ return 1;
4389
+ }
4390
+ } else {
4391
+ const cfg = loadConfig();
4392
+ const currentDirs = cfg?.customPlanDirs ?? [];
4393
+ const updated = normalizeCustomPlanDirs([...currentDirs, resolved]);
4394
+ saveConfig({
4395
+ ...cfg ?? { configVersion: 3, enabledAdapters: [] },
4396
+ customPlanDirs: updated
4397
+ });
4398
+ writeStdout(`[agendex] added custom plan dir: ${resolved}`);
4399
+ writeStdout(`[agendex] daemon will pick up the change automatically`);
4400
+ }
3957
4401
  return 0;
3958
4402
  }
3959
4403
  case "remove-dir": {
@@ -4070,6 +4514,7 @@ Usage:
4070
4514
  agendex logout Clear stored cloud token
4071
4515
  agendex configure Select which agents/adapters to index
4072
4516
  agendex add-dir <path> Add a custom directory to scan for plans
4517
+ agendex add-dir <path> --live Add dir and notify running server immediately
4073
4518
  agendex remove-dir <path> Remove a custom directory
4074
4519
  agendex list-dirs List custom plan directories
4075
4520
  agendex sync One-shot scan + sync to cloud (skips unchanged plans)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agendex-cli",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "description": "Agendex CLI for login, sync, and daemon workflows",
5
5
  "homepage": "https://github.com/Tyru5/Agendex#readme",
6
6
  "repository": {