charon-hooks 0.2.3 → 0.2.4

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/server/index.js +480 -42
  2. package/package.json +2 -1
@@ -2,7 +2,7 @@
2
2
  import { Hono as Hono8 } from "hono";
3
3
  import { dirname as dirname3, resolve as resolve4 } from "path";
4
4
  import { fileURLToPath as fileURLToPath2 } from "url";
5
- import { existsSync as existsSync5 } from "fs";
5
+ import { existsSync as existsSync7 } from "fs";
6
6
 
7
7
  // src/server/middleware/logger.ts
8
8
  import { createMiddleware } from "hono/factory";
@@ -106,7 +106,7 @@ function serveStatic({ root, path: fallbackPath }) {
106
106
  import { Hono } from "hono";
107
107
 
108
108
  // src/lib/config/loader.ts
109
- import { readFileSync as readFileSync2, writeFileSync, mkdirSync as mkdirSync2, watch, existsSync as existsSync3 } from "fs";
109
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, watch, existsSync as existsSync5 } from "fs";
110
110
  import { dirname as dirname2 } from "path";
111
111
 
112
112
  // src/lib/config/schema.ts
@@ -158,9 +158,9 @@ function parseConfig(yamlContent) {
158
158
  var createDatabase;
159
159
  var isBun = typeof globalThis.Bun !== "undefined";
160
160
  if (isBun) {
161
- const { Database } = await import("bun:sqlite");
161
+ const { Database: Database2 } = await import("bun:sqlite");
162
162
  createDatabase = (path) => {
163
- const db2 = new Database(path);
163
+ const db2 = new Database2(path);
164
164
  return {
165
165
  exec: (sql) => db2.run(sql),
166
166
  prepare: (sql) => {
@@ -424,6 +424,7 @@ function listEvents(db2, filter) {
424
424
  // src/lib/pipeline/sanitizer.ts
425
425
  import { resolve as resolve3 } from "path";
426
426
  import { pathToFileURL } from "url";
427
+ import { createJiti } from "jiti";
427
428
 
428
429
  // src/lib/data-dir.ts
429
430
  import { existsSync as existsSync2, mkdirSync, copyFileSync, readdirSync } from "fs";
@@ -498,20 +499,28 @@ function copyDefaultFile(src, dest) {
498
499
 
499
500
  // src/lib/pipeline/sanitizer.ts
500
501
  var sanitizerCache = /* @__PURE__ */ new Map();
502
+ var jiti = createJiti(import.meta.url);
501
503
  async function loadSanitizer(name) {
502
504
  if (sanitizerCache.has(name)) {
503
505
  return sanitizerCache.get(name);
504
506
  }
507
+ const sanitizerPath = resolve3(sanitizersDir, `${name}.ts`);
505
508
  try {
506
- const sanitizerPath = resolve3(sanitizersDir, `${name}.ts`);
507
509
  const sanitizerUrl = pathToFileURL(sanitizerPath).href;
508
510
  const mod = await import(sanitizerUrl);
509
511
  const fn = mod.default;
510
512
  sanitizerCache.set(name, fn);
511
513
  return fn;
512
514
  } catch {
513
- sanitizerCache.set(name, null);
514
- return null;
515
+ try {
516
+ const mod = await jiti.import(sanitizerPath);
517
+ const fn = mod.default;
518
+ sanitizerCache.set(name, fn);
519
+ return fn;
520
+ } catch {
521
+ sanitizerCache.set(name, null);
522
+ return null;
523
+ }
515
524
  }
516
525
  }
517
526
  async function executeSanitizer(sanitizer, payload, headers, trigger) {
@@ -522,25 +531,108 @@ async function executeSanitizer(sanitizer, payload, headers, trigger) {
522
531
  return result;
523
532
  }
524
533
 
525
- // src/lib/egress/loader.ts
526
- var egressCache = /* @__PURE__ */ new Map();
527
- async function loadEgress(name) {
528
- if (egressCache.has(name)) {
529
- return egressCache.get(name);
534
+ // src/egress/console.ts
535
+ var consoleEgress = (task) => {
536
+ if (isDevMode) {
537
+ console.log("[egress:console]", JSON.stringify(task, null, 2));
530
538
  }
531
- try {
532
- const mod = await import(`@/egress/${name}`);
533
- const fn = mod.default;
534
- egressCache.set(name, fn);
535
- return fn;
536
- } catch {
537
- egressCache.set(name, null);
538
- return null;
539
+ };
540
+ var console_default = consoleEgress;
541
+
542
+ // src/egress/cli.ts
543
+ import { spawn } from "child_process";
544
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync } from "fs";
545
+ import { join as join2 } from "path";
546
+ function ensureMcpConfig() {
547
+ const mcpDir = join2(process.cwd(), "mcp");
548
+ const mcpConfigPath = join2(mcpDir, "charon-mcp.json");
549
+ const finishTaskPath = join2(mcpDir, "finish-task.ts");
550
+ if (!existsSync3(mcpConfigPath)) {
551
+ if (!existsSync3(mcpDir)) {
552
+ mkdirSync2(mcpDir, { recursive: true });
553
+ }
554
+ const config = {
555
+ mcpServers: {
556
+ "finish-task": {
557
+ type: "stdio",
558
+ command: "bun",
559
+ args: [finishTaskPath],
560
+ env: {
561
+ CHARON_API_URL: process.env.CHARON_API_URL || "http://localhost:3000"
562
+ }
563
+ }
564
+ }
565
+ };
566
+ writeFileSync(mcpConfigPath, JSON.stringify(config, null, 2) + "\n");
567
+ console.log("[egress:cli] Created MCP config:", mcpConfigPath);
539
568
  }
569
+ return mcpConfigPath;
540
570
  }
541
- async function executeEgress(egress, task, trigger) {
542
- await egress(task, trigger);
571
+ function shellEscape(str) {
572
+ return str.replace(/'/g, "'\\''").replace(/\n/g, "\\n");
573
+ }
574
+ function composeCommand(template, context) {
575
+ return template.replace(/\{(\w+)\}/g, (match, key) => {
576
+ if (key in context) {
577
+ const value = context[key];
578
+ const str = typeof value === "object" ? JSON.stringify(value) : String(value);
579
+ return shellEscape(str);
580
+ }
581
+ return match;
582
+ });
583
+ }
584
+ function composeWorkingDir(workingDir, context) {
585
+ if (!workingDir) return void 0;
586
+ return composeCommand(workingDir, context);
543
587
  }
588
+ var cliEgress = (task, trigger) => {
589
+ const cliTemplate = trigger.context?.cli_template;
590
+ if (!cliTemplate || typeof cliTemplate !== "string") {
591
+ console.error("[egress:cli] Missing cli_template in trigger context");
592
+ return;
593
+ }
594
+ const mcpConfig = cliTemplate.includes("{mcp_config}") ? ensureMcpConfig() : void 0;
595
+ const context = {
596
+ ...trigger.context,
597
+ ...task.task.context,
598
+ description: task.task.description,
599
+ trigger_id: task.trigger_id,
600
+ run_id: task.run_id,
601
+ ...mcpConfig && { mcp_config: mcpConfig }
602
+ };
603
+ const command = composeCommand(cliTemplate, context);
604
+ const workingDir = composeWorkingDir(
605
+ trigger.context?.working_dir,
606
+ context
607
+ );
608
+ console.log("[egress:cli] Executing:", command);
609
+ if (workingDir) {
610
+ console.log("[egress:cli] Working directory:", workingDir);
611
+ }
612
+ const child = spawn(command, [], {
613
+ shell: true,
614
+ cwd: workingDir,
615
+ detached: true,
616
+ stdio: ["ignore", "pipe", "pipe"]
617
+ });
618
+ child.stdout?.on("data", (data) => {
619
+ console.log("[egress:cli] stdout:", data.toString().trim());
620
+ });
621
+ child.stderr?.on("data", (data) => {
622
+ console.error("[egress:cli] stderr:", data.toString().trim());
623
+ });
624
+ child.on("error", (err) => {
625
+ console.error("[egress:cli] spawn error:", err.message);
626
+ });
627
+ child.on("exit", (code, signal) => {
628
+ if (code !== 0) {
629
+ console.error("[egress:cli] exited with code:", code, "signal:", signal);
630
+ }
631
+ });
632
+ child.unref();
633
+ console.log("[egress:cli] Spawned process PID:", child.pid);
634
+ };
635
+ var cli_default = cliEgress;
544
636
 
545
637
  // src/lib/pipeline/composer.ts
546
638
  function compose(template, context) {
@@ -553,6 +645,352 @@ function compose(template, context) {
553
645
  });
554
646
  }
555
647
 
648
+ // src/egress/kanban.ts
649
+ import Database from "better-sqlite3";
650
+ import { homedir as homedir2 } from "os";
651
+ import { join as join3 } from "path";
652
+ function uuidToBlob(uuid) {
653
+ const hex = uuid.replace(/-/g, "");
654
+ const bytes = new Uint8Array(16);
655
+ for (let i = 0; i < 16; i++) {
656
+ bytes[i] = parseInt(hex.substr(i * 2, 2), 16);
657
+ }
658
+ return bytes;
659
+ }
660
+ function blobToUUID(blob) {
661
+ const hex = Array.from(blob).map((b) => b.toString(16).padStart(2, "0")).join("");
662
+ return hex.slice(0, 8) + "-" + hex.slice(8, 12) + "-" + hex.slice(12, 16) + "-" + hex.slice(16, 20) + "-" + hex.slice(20);
663
+ }
664
+ function lookupRepoForProject(projectId) {
665
+ let db2 = null;
666
+ try {
667
+ const dbPath2 = join3(homedir2(), ".local/share/vibe-kanban/db.sqlite");
668
+ db2 = new Database(dbPath2, { readonly: true, timeout: 5e3 });
669
+ const projectBlob = uuidToBlob(projectId);
670
+ const result = db2.prepare("SELECT repo_id FROM project_repos WHERE project_id = ? LIMIT 1").get(projectBlob);
671
+ if (result?.repo_id) {
672
+ return blobToUUID(new Uint8Array(result.repo_id));
673
+ }
674
+ return null;
675
+ } catch (error) {
676
+ if (error instanceof Error && error.message.includes("locked")) {
677
+ console.error(
678
+ "[egress:kanban] Vibe Kanban database is locked. Please provide repo_id in config or try again later."
679
+ );
680
+ } else {
681
+ console.error("[egress:kanban] Failed to lookup repo:", error);
682
+ }
683
+ return null;
684
+ } finally {
685
+ db2?.close();
686
+ }
687
+ }
688
+ function extractTitle(description) {
689
+ const firstLine = description.split("\n")[0].trim();
690
+ return firstLine.length > 200 ? firstLine.slice(0, 197) + "..." : firstLine;
691
+ }
692
+ function composeTitle(template, context, fallbackDescription) {
693
+ const composed = compose(template, context);
694
+ const title = composed.length > 200 ? composed.slice(0, 197) + "..." : composed;
695
+ if (!title.trim() || title.includes("{")) {
696
+ return extractTitle(fallbackDescription);
697
+ }
698
+ return title;
699
+ }
700
+ async function startTaskAttempt(apiUrl, taskId, repoId, executor, variant, baseBranch) {
701
+ try {
702
+ const response = await fetch(`${apiUrl}/api/task-attempts`, {
703
+ method: "POST",
704
+ headers: {
705
+ "Content-Type": "application/json"
706
+ },
707
+ body: JSON.stringify({
708
+ task_id: taskId,
709
+ executor_profile_id: {
710
+ executor,
711
+ variant
712
+ },
713
+ repos: [
714
+ {
715
+ repo_id: repoId,
716
+ base_branch: baseBranch,
717
+ target_branch: baseBranch
718
+ }
719
+ ]
720
+ })
721
+ });
722
+ if (!response.ok) {
723
+ const errorText = await response.text();
724
+ console.error(
725
+ "[egress:kanban] Failed to start task:",
726
+ response.status,
727
+ response.statusText,
728
+ errorText
729
+ );
730
+ return false;
731
+ }
732
+ const result = await response.json();
733
+ if (!result.success) {
734
+ console.error("[egress:kanban] Start task error:", result.message);
735
+ return false;
736
+ }
737
+ console.log("[egress:kanban] Task started successfully");
738
+ console.log("[egress:kanban] Attempt ID:", result.data?.id);
739
+ console.log("[egress:kanban] Branch:", result.data?.branch);
740
+ return true;
741
+ } catch (error) {
742
+ if (error instanceof Error) {
743
+ console.error("[egress:kanban] Failed to start task:", error.message);
744
+ } else {
745
+ console.error("[egress:kanban] Failed to start task:", error);
746
+ }
747
+ return false;
748
+ }
749
+ }
750
+ var kanbanEgress = async (task, trigger) => {
751
+ const apiUrl = trigger.context?.api_url;
752
+ const projectId = trigger.context?.project_id;
753
+ const titleTemplate = trigger.context?.title_template;
754
+ const autoStart = trigger.context?.auto_start === true;
755
+ const repoId = trigger.context?.repo_id;
756
+ const executor = trigger.context?.executor || "CLAUDE_CODE";
757
+ const variant = trigger.context?.variant || "DEFAULT";
758
+ const baseBranch = trigger.context?.base_branch || "main";
759
+ if (!apiUrl || typeof apiUrl !== "string") {
760
+ console.error("[egress:kanban] Missing required config: api_url");
761
+ return;
762
+ }
763
+ if (!projectId || typeof projectId !== "string") {
764
+ console.error("[egress:kanban] Missing required config: project_id");
765
+ return;
766
+ }
767
+ let resolvedRepoId = repoId;
768
+ if (autoStart && !resolvedRepoId) {
769
+ console.log("[egress:kanban] No repo_id provided, looking up from Vibe Kanban database...");
770
+ resolvedRepoId = lookupRepoForProject(projectId) ?? void 0;
771
+ if (resolvedRepoId) {
772
+ console.log("[egress:kanban] Found repo_id:", resolvedRepoId);
773
+ } else {
774
+ console.error(
775
+ "[egress:kanban] Could not find repo for project. Either provide repo_id in config or ensure the project has a linked repository in Vibe Kanban."
776
+ );
777
+ return;
778
+ }
779
+ }
780
+ const taskContext = task.task.context || {};
781
+ const title = titleTemplate ? composeTitle(titleTemplate, taskContext, task.task.description) : extractTitle(task.task.description);
782
+ const description = task.task.description;
783
+ console.log("[egress:kanban] Creating task:", title);
784
+ console.log("[egress:kanban] Project ID:", projectId);
785
+ console.log("[egress:kanban] API URL:", apiUrl);
786
+ if (autoStart) {
787
+ console.log("[egress:kanban] Auto-start enabled with executor:", executor);
788
+ }
789
+ try {
790
+ const response = await fetch(`${apiUrl}/api/tasks`, {
791
+ method: "POST",
792
+ headers: {
793
+ "Content-Type": "application/json"
794
+ },
795
+ body: JSON.stringify({
796
+ project_id: projectId,
797
+ title,
798
+ description
799
+ })
800
+ });
801
+ if (!response.ok) {
802
+ const errorText = await response.text();
803
+ console.error(
804
+ "[egress:kanban] API error:",
805
+ response.status,
806
+ response.statusText,
807
+ errorText
808
+ );
809
+ return;
810
+ }
811
+ const result = await response.json();
812
+ if (!result.success || !result.data) {
813
+ console.error("[egress:kanban] API returned error:", result.message);
814
+ return;
815
+ }
816
+ const taskId = result.data.id;
817
+ console.log("[egress:kanban] Task created successfully:", taskId);
818
+ console.log("[egress:kanban] Task status:", result.data.status);
819
+ if (autoStart && resolvedRepoId) {
820
+ console.log("[egress:kanban] Starting task with", executor, variant);
821
+ await startTaskAttempt(apiUrl, taskId, resolvedRepoId, executor, variant, baseBranch);
822
+ }
823
+ } catch (error) {
824
+ if (error instanceof Error) {
825
+ console.error("[egress:kanban] Connection failed:", error.message);
826
+ } else {
827
+ console.error("[egress:kanban] Connection failed:", error);
828
+ }
829
+ }
830
+ };
831
+ var kanban_default = kanbanEgress;
832
+
833
+ // src/egress/auto-claude.ts
834
+ import { spawn as spawn2, spawnSync } from "child_process";
835
+ import { existsSync as existsSync4 } from "fs";
836
+ import { join as join4 } from "path";
837
+ import { homedir as homedir3 } from "os";
838
+ var DEB_BACKEND_PATH = "/opt/Auto-Claude/resources/backend";
839
+ var DEB_PYTHON_PATH = "/opt/Auto-Claude/resources/python/bin/python3";
840
+ var DEB_SITE_PACKAGES = "/opt/Auto-Claude/resources/python-site-packages";
841
+ function parseSpecId(output) {
842
+ const patterns = [
843
+ /Created spec:\s*(\d{3}[\w-]*)/i,
844
+ /Spec\s+(\d{3}[\w-]*)/i,
845
+ /specs\/(\d{3}[\w-]*)/,
846
+ /(\d{3}-[\w-]+)/
847
+ ];
848
+ for (const pattern of patterns) {
849
+ const match = output.match(pattern);
850
+ if (match) {
851
+ return match[1];
852
+ }
853
+ }
854
+ return null;
855
+ }
856
+ function resolvePath(path) {
857
+ return path.replace(/^~/, homedir3());
858
+ }
859
+ var autoClaudeEgress = async (task, trigger) => {
860
+ const ctx = trigger.context || {};
861
+ const projectDir = ctx.project_dir;
862
+ if (!projectDir) {
863
+ console.error("[egress:auto-claude] Missing project_dir in trigger context");
864
+ console.error("[egress:auto-claude] This is the directory where Auto-Claude will create specs");
865
+ return;
866
+ }
867
+ const resolvedProjectDir = resolvePath(projectDir);
868
+ if (!existsSync4(resolvedProjectDir)) {
869
+ console.error("[egress:auto-claude] Project directory does not exist:", resolvedProjectDir);
870
+ return;
871
+ }
872
+ const backendPath = resolvePath(ctx.backend_path || DEB_BACKEND_PATH);
873
+ const specRunnerPath = join4(backendPath, "runners", "spec_runner.py");
874
+ const runPath = join4(backendPath, "run.py");
875
+ if (!existsSync4(specRunnerPath)) {
876
+ console.error("[egress:auto-claude] spec_runner.py not found at:", specRunnerPath);
877
+ console.error("[egress:auto-claude] Backend path:", backendPath);
878
+ return;
879
+ }
880
+ const pythonPath = resolvePath(ctx.python_path || DEB_PYTHON_PATH);
881
+ if (!existsSync4(pythonPath)) {
882
+ console.error("[egress:auto-claude] Python not found at:", pythonPath);
883
+ return;
884
+ }
885
+ const complexity = ctx.complexity;
886
+ const autoRun = ctx.auto_run !== false;
887
+ const maxIterations = ctx.max_iterations;
888
+ const skipQa = ctx.skip_qa === true;
889
+ const autoApprove = ctx.auto_approve !== false;
890
+ const description = task.task.description;
891
+ console.log("[egress:auto-claude] Creating spec for task:", description.substring(0, 100) + "...");
892
+ console.log("[egress:auto-claude] Project directory:", resolvedProjectDir);
893
+ console.log("[egress:auto-claude] Backend path:", backendPath);
894
+ const specArgs = [
895
+ specRunnerPath,
896
+ "--task",
897
+ description,
898
+ "--project-dir",
899
+ resolvedProjectDir
900
+ ];
901
+ if (complexity) {
902
+ specArgs.push("--complexity", complexity);
903
+ }
904
+ if (autoApprove) {
905
+ specArgs.push("--auto-approve");
906
+ }
907
+ if (!autoRun) {
908
+ specArgs.push("--no-build");
909
+ }
910
+ console.log("[egress:auto-claude] Running:", pythonPath, specArgs.join(" "));
911
+ const env = {
912
+ ...process.env,
913
+ PYTHONPATH: DEB_SITE_PACKAGES
914
+ };
915
+ const specResult = spawnSync(pythonPath, specArgs, {
916
+ cwd: backendPath,
917
+ encoding: "utf-8",
918
+ timeout: 12e4,
919
+ // 2 minutes for spec creation
920
+ env
921
+ });
922
+ if (specResult.error) {
923
+ console.error("[egress:auto-claude] Failed to create spec:", specResult.error.message);
924
+ return;
925
+ }
926
+ const specOutput = (specResult.stdout || "") + (specResult.stderr || "");
927
+ console.log("[egress:auto-claude] spec_runner.py output:", specOutput);
928
+ if (specResult.status !== 0) {
929
+ console.error("[egress:auto-claude] spec_runner.py failed with status:", specResult.status);
930
+ return;
931
+ }
932
+ const specId = parseSpecId(specOutput);
933
+ if (specId) {
934
+ console.log("[egress:auto-claude] Created spec:", specId);
935
+ } else {
936
+ console.log("[egress:auto-claude] Spec created (could not parse ID from output)");
937
+ }
938
+ if (!autoRun) {
939
+ console.log("[egress:auto-claude] auto_run is disabled, spec created but not started");
940
+ } else if (specId && existsSync4(runPath)) {
941
+ if (maxIterations || skipQa) {
942
+ const runArgs = [
943
+ runPath,
944
+ "--spec",
945
+ specId,
946
+ "--project-dir",
947
+ resolvedProjectDir,
948
+ "--auto-continue"
949
+ ];
950
+ if (maxIterations) {
951
+ runArgs.push("--max-iterations", String(maxIterations));
952
+ }
953
+ if (skipQa) {
954
+ runArgs.push("--skip-qa");
955
+ }
956
+ console.log("[egress:auto-claude] Running spec with options:", pythonPath, runArgs.join(" "));
957
+ const runChild = spawn2(pythonPath, runArgs, {
958
+ cwd: backendPath,
959
+ detached: true,
960
+ stdio: ["ignore", "pipe", "pipe"],
961
+ env
962
+ });
963
+ runChild.stdout?.on("data", (data) => {
964
+ console.log("[egress:auto-claude] run.py stdout:", data.toString().trim());
965
+ });
966
+ runChild.stderr?.on("data", (data) => {
967
+ console.error("[egress:auto-claude] run.py stderr:", data.toString().trim());
968
+ });
969
+ runChild.on("error", (err) => {
970
+ console.error("[egress:auto-claude] run.py spawn error:", err.message);
971
+ });
972
+ runChild.unref();
973
+ console.log("[egress:auto-claude] Spawned run.py PID:", runChild.pid);
974
+ }
975
+ }
976
+ console.log("[egress:auto-claude] Task queued successfully");
977
+ };
978
+ var auto_claude_default = autoClaudeEgress;
979
+
980
+ // src/lib/egress/loader.ts
981
+ var egressRegistry = {
982
+ console: console_default,
983
+ cli: cli_default,
984
+ kanban: kanban_default,
985
+ "auto-claude": auto_claude_default
986
+ };
987
+ async function loadEgress(name) {
988
+ return egressRegistry[name] || null;
989
+ }
990
+ async function executeEgress(egress, task, trigger) {
991
+ await egress(task, trigger);
992
+ }
993
+
556
994
  // src/lib/pipeline/processor.ts
557
995
  function generateTaskId() {
558
996
  return "task_" + Math.random().toString(36).slice(2, 10);
@@ -910,9 +1348,9 @@ var DEFAULT_CONFIG = `# Charon trigger configuration
910
1348
  triggers: []
911
1349
  `;
912
1350
  async function loadConfig(path = triggersPath) {
913
- if (!existsSync3(path)) {
914
- mkdirSync2(dirname2(path), { recursive: true });
915
- writeFileSync(path, DEFAULT_CONFIG, "utf-8");
1351
+ if (!existsSync5(path)) {
1352
+ mkdirSync3(dirname2(path), { recursive: true });
1353
+ writeFileSync2(path, DEFAULT_CONFIG, "utf-8");
916
1354
  console.log(`[config] Created default config at ${path}`);
917
1355
  }
918
1356
  const content = readFileSync2(path, "utf-8");
@@ -949,7 +1387,7 @@ function getTrigger(id) {
949
1387
  return config.triggers.find((t) => t.id === id);
950
1388
  }
951
1389
  async function deleteTrigger(id, configPath2 = triggersPath) {
952
- if (!existsSync3(configPath2)) {
1390
+ if (!existsSync5(configPath2)) {
953
1391
  return false;
954
1392
  }
955
1393
  const content = readFileSync2(configPath2, "utf-8");
@@ -967,7 +1405,7 @@ async function deleteTrigger(id, configPath2 = triggersPath) {
967
1405
  config.triggers.splice(triggerIndex, 1);
968
1406
  const yaml = await import("yaml");
969
1407
  const newContent = yaml.stringify(config);
970
- writeFileSync(configPath2, newContent, "utf-8");
1408
+ writeFileSync2(configPath2, newContent, "utf-8");
971
1409
  console.log(`[config] Deleted trigger '${id}'`);
972
1410
  cachedConfig = config;
973
1411
  return true;
@@ -977,7 +1415,7 @@ function startConfigWatcher(configPath2 = triggersPath) {
977
1415
  return;
978
1416
  }
979
1417
  stopConfigWatcher();
980
- if (!existsSync3(configPath2)) {
1418
+ if (!existsSync5(configPath2)) {
981
1419
  console.warn(`[config] Config file not found: ${configPath2}, skipping watcher`);
982
1420
  return;
983
1421
  }
@@ -1024,7 +1462,7 @@ async function reloadConfig(configPath2) {
1024
1462
  }
1025
1463
 
1026
1464
  // src/lib/config/writer.ts
1027
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
1465
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
1028
1466
  import { parse as parseYaml2, stringify as stringifyYaml } from "yaml";
1029
1467
  var DEFAULT_CONFIG_PATH = "config/triggers.yaml";
1030
1468
  async function writeTrigger(trigger, configPath2 = DEFAULT_CONFIG_PATH) {
@@ -1068,7 +1506,7 @@ async function writeTrigger(trigger, configPath2 = DEFAULT_CONFIG_PATH) {
1068
1506
  defaultStringType: "PLAIN",
1069
1507
  defaultKeyType: "PLAIN"
1070
1508
  });
1071
- writeFileSync2(configPath2, yamlOutput);
1509
+ writeFileSync3(configPath2, yamlOutput);
1072
1510
  return { success: true };
1073
1511
  } catch (err) {
1074
1512
  return { success: false, error: `Write failed: ${err instanceof Error ? err.message : String(err)}` };
@@ -1091,7 +1529,7 @@ async function deleteTrigger2(id, configPath2 = DEFAULT_CONFIG_PATH) {
1091
1529
  defaultStringType: "PLAIN",
1092
1530
  defaultKeyType: "PLAIN"
1093
1531
  });
1094
- writeFileSync2(configPath2, yamlOutput);
1532
+ writeFileSync3(configPath2, yamlOutput);
1095
1533
  return { success: true };
1096
1534
  } catch (err) {
1097
1535
  return { success: false, error: `Delete failed: ${err instanceof Error ? err.message : String(err)}` };
@@ -1130,7 +1568,7 @@ async function writeTunnelConfig(tunnel, configPath2 = DEFAULT_CONFIG_PATH) {
1130
1568
  defaultStringType: "PLAIN",
1131
1569
  defaultKeyType: "PLAIN"
1132
1570
  });
1133
- writeFileSync2(configPath2, yamlOutput);
1571
+ writeFileSync3(configPath2, yamlOutput);
1134
1572
  return { success: true };
1135
1573
  } catch (err) {
1136
1574
  return { success: false, error: `Write failed: ${err instanceof Error ? err.message : String(err)}` };
@@ -1432,8 +1870,8 @@ runsRoutes.get("/:id", async (c) => {
1432
1870
 
1433
1871
  // src/server/routes/sanitizers.ts
1434
1872
  import { Hono as Hono3 } from "hono";
1435
- import { readdirSync as readdirSync2, existsSync as existsSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
1436
- import { join as join2 } from "path";
1873
+ import { readdirSync as readdirSync2, existsSync as existsSync6, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4 } from "fs";
1874
+ import { join as join5 } from "path";
1437
1875
  var sanitizersRoutes = new Hono3();
1438
1876
  var BOILERPLATE = `/**
1439
1877
  * Sanitizer function for processing webhook payloads.
@@ -1455,13 +1893,13 @@ function sanitizeName(name) {
1455
1893
  return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").replace(/-+/g, "-");
1456
1894
  }
1457
1895
  function listSanitizersInternal(dir = sanitizersDir) {
1458
- if (!existsSync4(dir)) {
1896
+ if (!existsSync6(dir)) {
1459
1897
  return [];
1460
1898
  }
1461
1899
  const files = readdirSync2(dir);
1462
1900
  return files.filter((f) => f.endsWith(".ts")).map((f) => ({
1463
1901
  name: f.replace(".ts", ""),
1464
- path: join2(dir, f)
1902
+ path: join5(dir, f)
1465
1903
  }));
1466
1904
  }
1467
1905
  function createSanitizerInternal(rawName, dir = sanitizersDir) {
@@ -1472,14 +1910,14 @@ function createSanitizerInternal(rawName, dir = sanitizersDir) {
1472
1910
  if (!name) {
1473
1911
  return { success: false, error: "Invalid name", status: 400 };
1474
1912
  }
1475
- const filePath = join2(dir, `${name}.ts`);
1476
- if (existsSync4(filePath)) {
1913
+ const filePath = join5(dir, `${name}.ts`);
1914
+ if (existsSync6(filePath)) {
1477
1915
  return { success: false, error: `Sanitizer '${name}' already exists`, status: 409 };
1478
1916
  }
1479
- if (!existsSync4(dir)) {
1480
- mkdirSync3(dir, { recursive: true });
1917
+ if (!existsSync6(dir)) {
1918
+ mkdirSync4(dir, { recursive: true });
1481
1919
  }
1482
- writeFileSync3(filePath, BOILERPLATE);
1920
+ writeFileSync4(filePath, BOILERPLATE);
1483
1921
  return { success: true, name, path: filePath, status: 201 };
1484
1922
  }
1485
1923
  sanitizersRoutes.get("/", async (c) => {
@@ -1785,7 +2223,7 @@ async function tunnelProxyMiddleware(c, next) {
1785
2223
  var __dirname2 = dirname3(fileURLToPath2(import.meta.url));
1786
2224
  var prodClientDir = resolve4(__dirname2, "../client");
1787
2225
  var devClientDir = resolve4(process.cwd(), "dist/client");
1788
- var clientDir = existsSync5(devClientDir) ? devClientDir : prodClientDir;
2226
+ var clientDir = existsSync7(devClientDir) ? devClientDir : prodClientDir;
1789
2227
  var app = new Hono8();
1790
2228
  app.use("*", quietLogger);
1791
2229
  app.use("*", tunnelProxyMiddleware);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "charon-hooks",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "Autonomous task triggering service - webhooks and cron to AI agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -36,6 +36,7 @@
36
36
  "clsx": "^2.1.1",
37
37
  "date-fns": "^4.1.0",
38
38
  "hono": "^4.7.0",
39
+ "jiti": "^2.6.1",
39
40
  "lucide-react": "^0.562.0",
40
41
  "node-cron": "^4.2.1",
41
42
  "radix-ui": "^1.4.3",