coding-agent-adapters 0.11.1 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +568 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +103 -5
- package/dist/index.d.ts +103 -5
- package/dist/index.js +567 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -337,6 +337,16 @@ function generateAiderApprovalConfig(preset) {
|
|
|
337
337
|
summary: `Aider: ${def.description}`
|
|
338
338
|
};
|
|
339
339
|
}
|
|
340
|
+
function generateHermesApprovalConfig(preset) {
|
|
341
|
+
const def = getPresetDefinition(preset);
|
|
342
|
+
return {
|
|
343
|
+
preset,
|
|
344
|
+
cliFlags: [],
|
|
345
|
+
workspaceFiles: [],
|
|
346
|
+
envVars: {},
|
|
347
|
+
summary: `Hermes Agent: ${def.description}`
|
|
348
|
+
};
|
|
349
|
+
}
|
|
340
350
|
function generateApprovalConfig(adapterType, preset) {
|
|
341
351
|
switch (adapterType) {
|
|
342
352
|
case "claude":
|
|
@@ -347,6 +357,8 @@ function generateApprovalConfig(adapterType, preset) {
|
|
|
347
357
|
return generateCodexApprovalConfig(preset);
|
|
348
358
|
case "aider":
|
|
349
359
|
return generateAiderApprovalConfig(preset);
|
|
360
|
+
case "hermes":
|
|
361
|
+
return generateHermesApprovalConfig(preset);
|
|
350
362
|
default:
|
|
351
363
|
throw new Error(`Unknown adapter type: ${adapterType}`);
|
|
352
364
|
}
|
|
@@ -424,6 +436,13 @@ var BaseCodingAdapter = class extends ptyManager.BaseCLIAdapter {
|
|
|
424
436
|
result = result.replace(/ {2,}/g, " ");
|
|
425
437
|
return result;
|
|
426
438
|
}
|
|
439
|
+
/**
|
|
440
|
+
* Generate hook telemetry protocol configuration.
|
|
441
|
+
* Returns null by default — only Claude adapter supports hooks.
|
|
442
|
+
*/
|
|
443
|
+
getHookTelemetryProtocol(_options) {
|
|
444
|
+
return null;
|
|
445
|
+
}
|
|
427
446
|
/**
|
|
428
447
|
* Override detectExit to include installation instructions
|
|
429
448
|
*/
|
|
@@ -549,6 +568,7 @@ Docs: ${this.installation.docsUrl}`
|
|
|
549
568
|
};
|
|
550
569
|
|
|
551
570
|
// src/claude-adapter.ts
|
|
571
|
+
var CLAUDE_HOOK_MARKER_PREFIX = "PARALLAX_CLAUDE_HOOK";
|
|
552
572
|
var ClaudeAdapter = class extends BaseCodingAdapter {
|
|
553
573
|
adapterType = "claude";
|
|
554
574
|
displayName = "Claude Code";
|
|
@@ -586,7 +606,8 @@ var ClaudeAdapter = class extends BaseCodingAdapter {
|
|
|
586
606
|
responseType: "keys",
|
|
587
607
|
keys: ["enter"],
|
|
588
608
|
description: "Auto-approve tool permission prompts (file access, MCP tools, etc.)",
|
|
589
|
-
safe: true
|
|
609
|
+
safe: true,
|
|
610
|
+
once: true
|
|
590
611
|
},
|
|
591
612
|
{
|
|
592
613
|
pattern: /update available.*\[y\/n\]/i,
|
|
@@ -695,6 +716,7 @@ var ClaudeAdapter = class extends BaseCodingAdapter {
|
|
|
695
716
|
getEnv(config) {
|
|
696
717
|
const env = {};
|
|
697
718
|
const credentials = this.getCredentials(config);
|
|
719
|
+
const adapterConfig = config.adapterConfig;
|
|
698
720
|
if (credentials.anthropicKey) {
|
|
699
721
|
env.ANTHROPIC_API_KEY = credentials.anthropicKey;
|
|
700
722
|
}
|
|
@@ -704,8 +726,110 @@ var ClaudeAdapter = class extends BaseCodingAdapter {
|
|
|
704
726
|
if (!this.isInteractive(config)) {
|
|
705
727
|
env.CLAUDE_CODE_DISABLE_INTERACTIVE = "true";
|
|
706
728
|
}
|
|
729
|
+
if (adapterConfig?.claudeHookTelemetry) {
|
|
730
|
+
env.PARALLAX_CLAUDE_HOOK_TELEMETRY = "1";
|
|
731
|
+
env.PARALLAX_CLAUDE_HOOK_MARKER_PREFIX = adapterConfig.claudeHookMarkerPrefix || CLAUDE_HOOK_MARKER_PREFIX;
|
|
732
|
+
}
|
|
707
733
|
return env;
|
|
708
734
|
}
|
|
735
|
+
getHookTelemetryProtocol(options) {
|
|
736
|
+
if (options?.httpUrl) {
|
|
737
|
+
const httpHookBase = {
|
|
738
|
+
type: "http",
|
|
739
|
+
url: options.httpUrl,
|
|
740
|
+
timeout: 5
|
|
741
|
+
};
|
|
742
|
+
if (options.sessionId) {
|
|
743
|
+
httpHookBase.headers = { "X-Parallax-Session-Id": options.sessionId };
|
|
744
|
+
}
|
|
745
|
+
const hookEntry2 = [{ matcher: "", hooks: [{ ...httpHookBase }] }];
|
|
746
|
+
const hookEntryNoMatcher = [{ hooks: [{ ...httpHookBase }] }];
|
|
747
|
+
const settingsHooks2 = {
|
|
748
|
+
PermissionRequest: hookEntryNoMatcher,
|
|
749
|
+
PreToolUse: hookEntry2,
|
|
750
|
+
Stop: hookEntryNoMatcher,
|
|
751
|
+
Notification: hookEntry2,
|
|
752
|
+
TaskCompleted: hookEntryNoMatcher
|
|
753
|
+
};
|
|
754
|
+
return {
|
|
755
|
+
markerPrefix: "",
|
|
756
|
+
scriptPath: "",
|
|
757
|
+
scriptContent: "",
|
|
758
|
+
settingsHooks: settingsHooks2
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
const markerPrefix = options?.markerPrefix || CLAUDE_HOOK_MARKER_PREFIX;
|
|
762
|
+
const scriptPath = options?.scriptPath || ".claude/hooks/parallax-hook-telemetry.sh";
|
|
763
|
+
const scriptCommand = `"${"$"}CLAUDE_PROJECT_DIR"/${scriptPath}`;
|
|
764
|
+
const hookEntry = [{ matcher: "", hooks: [{ type: "command", command: scriptCommand }] }];
|
|
765
|
+
const settingsHooks = {
|
|
766
|
+
Notification: hookEntry,
|
|
767
|
+
PreToolUse: hookEntry,
|
|
768
|
+
TaskCompleted: hookEntry,
|
|
769
|
+
SessionEnd: hookEntry
|
|
770
|
+
};
|
|
771
|
+
const scriptContent = `#!/usr/bin/env bash
|
|
772
|
+
set -euo pipefail
|
|
773
|
+
|
|
774
|
+
INPUT="$(cat)"
|
|
775
|
+
[ -z "${"$"}INPUT" ] && exit 0
|
|
776
|
+
|
|
777
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
778
|
+
exit 0
|
|
779
|
+
fi
|
|
780
|
+
|
|
781
|
+
EVENT="$(printf '%s' "${"$"}INPUT" | jq -r '.hook_event_name // empty')"
|
|
782
|
+
[ -z "${"$"}EVENT" ] && exit 0
|
|
783
|
+
|
|
784
|
+
NOTIFICATION_TYPE="$(printf '%s' "${"$"}INPUT" | jq -r '.notification_type // empty')"
|
|
785
|
+
TOOL_NAME="$(printf '%s' "${"$"}INPUT" | jq -r '.tool_name // empty')"
|
|
786
|
+
MESSAGE="$(printf '%s' "${"$"}INPUT" | jq -r '.message // empty')"
|
|
787
|
+
|
|
788
|
+
printf '%s ' '${markerPrefix}'
|
|
789
|
+
jq -nc --arg event "${"$"}EVENT" --arg notification_type "${"$"}NOTIFICATION_TYPE" --arg tool_name "${"$"}TOOL_NAME" --arg message "${"$"}MESSAGE" '({event: $event}
|
|
790
|
+
+ (if $notification_type != "" then {notification_type: $notification_type} else {} end)
|
|
791
|
+
+ (if $tool_name != "" then {tool_name: $tool_name} else {} end)
|
|
792
|
+
+ (if $message != "" then {message: $message} else {} end))'
|
|
793
|
+
`;
|
|
794
|
+
return {
|
|
795
|
+
markerPrefix,
|
|
796
|
+
scriptPath,
|
|
797
|
+
scriptContent,
|
|
798
|
+
settingsHooks
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
getHookMarkers(output) {
|
|
802
|
+
const markers = [];
|
|
803
|
+
const markerRegex = /(?:^|\n)\s*([A-Z0-9_]+)\s+(\{[^\n\r]+\})/g;
|
|
804
|
+
let match;
|
|
805
|
+
while ((match = markerRegex.exec(output)) !== null) {
|
|
806
|
+
const markerToken = match[1];
|
|
807
|
+
if (!markerToken.includes("CLAUDE_HOOK")) {
|
|
808
|
+
continue;
|
|
809
|
+
}
|
|
810
|
+
const payload = match[2];
|
|
811
|
+
try {
|
|
812
|
+
const parsed = JSON.parse(payload);
|
|
813
|
+
const event = typeof parsed.event === "string" ? parsed.event : void 0;
|
|
814
|
+
if (!event) continue;
|
|
815
|
+
markers.push({
|
|
816
|
+
event,
|
|
817
|
+
notification_type: typeof parsed.notification_type === "string" ? parsed.notification_type : void 0,
|
|
818
|
+
tool_name: typeof parsed.tool_name === "string" ? parsed.tool_name : void 0,
|
|
819
|
+
message: typeof parsed.message === "string" ? parsed.message : void 0
|
|
820
|
+
});
|
|
821
|
+
} catch {
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
return markers;
|
|
825
|
+
}
|
|
826
|
+
getLatestHookMarker(output) {
|
|
827
|
+
const markers = this.getHookMarkers(output);
|
|
828
|
+
return markers.length > 0 ? markers[markers.length - 1] : null;
|
|
829
|
+
}
|
|
830
|
+
stripHookMarkers(output) {
|
|
831
|
+
return output.replace(/(?:^|\n)\s*[A-Z0-9_]*CLAUDE_HOOK[A-Z0-9_]*\s+\{[^\n\r]+\}\s*/g, "\n");
|
|
832
|
+
}
|
|
709
833
|
detectLogin(output) {
|
|
710
834
|
const stripped = this.stripAnsi(output);
|
|
711
835
|
if (stripped.includes("Not logged in") || stripped.includes("Please run /login") || stripped.includes("please log in") || stripped.includes("run /login")) {
|
|
@@ -738,6 +862,7 @@ var ClaudeAdapter = class extends BaseCodingAdapter {
|
|
|
738
862
|
*/
|
|
739
863
|
detectBlockingPrompt(output) {
|
|
740
864
|
const stripped = this.stripAnsi(output);
|
|
865
|
+
const marker = this.getLatestHookMarker(stripped);
|
|
741
866
|
const loginDetection = this.detectLogin(output);
|
|
742
867
|
if (loginDetection.required) {
|
|
743
868
|
return {
|
|
@@ -749,6 +874,27 @@ var ClaudeAdapter = class extends BaseCodingAdapter {
|
|
|
749
874
|
instructions: loginDetection.instructions
|
|
750
875
|
};
|
|
751
876
|
}
|
|
877
|
+
if (marker?.event === "Notification") {
|
|
878
|
+
if (marker.notification_type === "permission_prompt") {
|
|
879
|
+
return {
|
|
880
|
+
detected: true,
|
|
881
|
+
type: "permission",
|
|
882
|
+
prompt: marker.message || "Claude permission prompt",
|
|
883
|
+
suggestedResponse: "keys:enter",
|
|
884
|
+
canAutoRespond: true,
|
|
885
|
+
instructions: "Claude is waiting for permission approval"
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
if (marker.notification_type === "elicitation_dialog") {
|
|
889
|
+
return {
|
|
890
|
+
detected: true,
|
|
891
|
+
type: "tool_wait",
|
|
892
|
+
prompt: marker.message || "Claude elicitation dialog",
|
|
893
|
+
canAutoRespond: false,
|
|
894
|
+
instructions: "Claude is waiting for required user input"
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
}
|
|
752
898
|
if (/how is claude doing this session\?\s*\(optional\)|1:\s*bad\s+2:\s*fine\s+3:\s*good\s+0:\s*dismiss/i.test(stripped)) {
|
|
753
899
|
return {
|
|
754
900
|
detected: true,
|
|
@@ -847,7 +993,11 @@ var ClaudeAdapter = class extends BaseCodingAdapter {
|
|
|
847
993
|
*/
|
|
848
994
|
detectLoading(output) {
|
|
849
995
|
const stripped = this.stripAnsi(output);
|
|
996
|
+
const marker = this.getLatestHookMarker(stripped);
|
|
850
997
|
const tail = stripped.slice(-500);
|
|
998
|
+
if (marker?.event === "PreToolUse") {
|
|
999
|
+
return true;
|
|
1000
|
+
}
|
|
851
1001
|
if (/esc\s+to\s+interrupt/i.test(tail)) {
|
|
852
1002
|
return true;
|
|
853
1003
|
}
|
|
@@ -868,7 +1018,14 @@ var ClaudeAdapter = class extends BaseCodingAdapter {
|
|
|
868
1018
|
*/
|
|
869
1019
|
detectToolRunning(output) {
|
|
870
1020
|
const stripped = this.stripAnsi(output);
|
|
1021
|
+
const marker = this.getLatestHookMarker(stripped);
|
|
871
1022
|
const tail = stripped.slice(-500);
|
|
1023
|
+
if (marker?.event === "PreToolUse" && marker.tool_name) {
|
|
1024
|
+
return {
|
|
1025
|
+
toolName: marker.tool_name.toLowerCase(),
|
|
1026
|
+
description: `${marker.tool_name} (hook)`
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
872
1029
|
const contextualMatch = tail.match(/Claude\s+in\s+([A-Za-z0-9._-]+)\s*\[(\w+_tool)\]/i);
|
|
873
1030
|
if (contextualMatch) {
|
|
874
1031
|
const appName = contextualMatch[1];
|
|
@@ -897,7 +1054,14 @@ var ClaudeAdapter = class extends BaseCodingAdapter {
|
|
|
897
1054
|
*/
|
|
898
1055
|
detectTaskComplete(output) {
|
|
899
1056
|
const stripped = this.stripAnsi(output);
|
|
1057
|
+
const marker = this.getLatestHookMarker(stripped);
|
|
900
1058
|
if (!stripped.trim()) return false;
|
|
1059
|
+
if (marker?.event === "TaskCompleted") {
|
|
1060
|
+
return true;
|
|
1061
|
+
}
|
|
1062
|
+
if (marker?.event === "Notification" && marker.notification_type === "idle_prompt") {
|
|
1063
|
+
return true;
|
|
1064
|
+
}
|
|
901
1065
|
if (/trust.*directory|do you want to|needs? your permission/i.test(stripped)) {
|
|
902
1066
|
return false;
|
|
903
1067
|
}
|
|
@@ -914,7 +1078,16 @@ var ClaudeAdapter = class extends BaseCodingAdapter {
|
|
|
914
1078
|
}
|
|
915
1079
|
detectReady(output) {
|
|
916
1080
|
const stripped = this.stripAnsi(output);
|
|
1081
|
+
const marker = this.getLatestHookMarker(stripped);
|
|
917
1082
|
if (!stripped.trim()) return false;
|
|
1083
|
+
if (marker?.event === "Notification") {
|
|
1084
|
+
if (marker.notification_type === "permission_prompt" || marker.notification_type === "elicitation_dialog") {
|
|
1085
|
+
return false;
|
|
1086
|
+
}
|
|
1087
|
+
if (marker.notification_type === "idle_prompt") {
|
|
1088
|
+
return true;
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
918
1091
|
if (/trust.*directory|do you want to|needs? your permission/i.test(stripped)) {
|
|
919
1092
|
return false;
|
|
920
1093
|
}
|
|
@@ -925,7 +1098,8 @@ var ClaudeAdapter = class extends BaseCodingAdapter {
|
|
|
925
1098
|
return hasConversationalReadyText || hasLegacyPrompt || hasShortcutsHint;
|
|
926
1099
|
}
|
|
927
1100
|
parseOutput(output) {
|
|
928
|
-
const
|
|
1101
|
+
const withoutHookMarkers = this.stripHookMarkers(output);
|
|
1102
|
+
const stripped = this.stripAnsi(withoutHookMarkers);
|
|
929
1103
|
const isComplete = this.isResponseComplete(stripped);
|
|
930
1104
|
if (!isComplete) {
|
|
931
1105
|
return null;
|
|
@@ -948,9 +1122,18 @@ var ClaudeAdapter = class extends BaseCodingAdapter {
|
|
|
948
1122
|
getHealthCheckCommand() {
|
|
949
1123
|
return "claude --version";
|
|
950
1124
|
}
|
|
1125
|
+
detectExit(output) {
|
|
1126
|
+
const stripped = this.stripAnsi(output);
|
|
1127
|
+
const marker = this.getLatestHookMarker(stripped);
|
|
1128
|
+
if (marker?.event === "SessionEnd") {
|
|
1129
|
+
return { exited: true, code: 0 };
|
|
1130
|
+
}
|
|
1131
|
+
return super.detectExit(output);
|
|
1132
|
+
}
|
|
951
1133
|
};
|
|
952
1134
|
|
|
953
1135
|
// src/gemini-adapter.ts
|
|
1136
|
+
var GEMINI_HOOK_MARKER_PREFIX = "PARALLAX_GEMINI_HOOK";
|
|
954
1137
|
var GeminiAdapter = class extends BaseCodingAdapter {
|
|
955
1138
|
adapterType = "gemini";
|
|
956
1139
|
displayName = "Google Gemini";
|
|
@@ -1050,6 +1233,7 @@ var GeminiAdapter = class extends BaseCodingAdapter {
|
|
|
1050
1233
|
getEnv(config) {
|
|
1051
1234
|
const env = {};
|
|
1052
1235
|
const credentials = this.getCredentials(config);
|
|
1236
|
+
const adapterConfig = config.adapterConfig;
|
|
1053
1237
|
if (credentials.googleKey) {
|
|
1054
1238
|
env.GOOGLE_API_KEY = credentials.googleKey;
|
|
1055
1239
|
env.GEMINI_API_KEY = credentials.googleKey;
|
|
@@ -1060,8 +1244,96 @@ var GeminiAdapter = class extends BaseCodingAdapter {
|
|
|
1060
1244
|
if (!this.isInteractive(config)) {
|
|
1061
1245
|
env.NO_COLOR = "1";
|
|
1062
1246
|
}
|
|
1247
|
+
if (adapterConfig?.geminiHookTelemetry) {
|
|
1248
|
+
env.PARALLAX_GEMINI_HOOK_TELEMETRY = "1";
|
|
1249
|
+
env.PARALLAX_GEMINI_HOOK_MARKER_PREFIX = adapterConfig.geminiHookMarkerPrefix || GEMINI_HOOK_MARKER_PREFIX;
|
|
1250
|
+
}
|
|
1063
1251
|
return env;
|
|
1064
1252
|
}
|
|
1253
|
+
getHookTelemetryProtocol(options) {
|
|
1254
|
+
const markerPrefix = options?.markerPrefix || GEMINI_HOOK_MARKER_PREFIX;
|
|
1255
|
+
const scriptPath = options?.scriptPath || ".gemini/hooks/parallax-hook-telemetry.sh";
|
|
1256
|
+
const scriptCommand = `"${"$"}GEMINI_PROJECT_ROOT"/${scriptPath}`;
|
|
1257
|
+
const hookEntry = [{ matcher: "", hooks: [{ type: "command", command: scriptCommand }] }];
|
|
1258
|
+
const settingsHooks = {
|
|
1259
|
+
Notification: hookEntry,
|
|
1260
|
+
BeforeTool: hookEntry,
|
|
1261
|
+
AfterAgent: hookEntry,
|
|
1262
|
+
SessionEnd: hookEntry
|
|
1263
|
+
};
|
|
1264
|
+
const scriptContent = `#!/usr/bin/env bash
|
|
1265
|
+
set -euo pipefail
|
|
1266
|
+
|
|
1267
|
+
INPUT="$(cat)"
|
|
1268
|
+
[ -z "${"$"}INPUT" ] && exit 0
|
|
1269
|
+
|
|
1270
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
1271
|
+
# Valid no-op response
|
|
1272
|
+
printf '%s
|
|
1273
|
+
' '{"continue":true}'
|
|
1274
|
+
exit 0
|
|
1275
|
+
fi
|
|
1276
|
+
|
|
1277
|
+
EVENT="$(printf '%s' "${"$"}INPUT" | jq -r '.hookEventName // .hook_event_name // empty')"
|
|
1278
|
+
[ -z "${"$"}EVENT" ] && { printf '%s
|
|
1279
|
+
' '{"continue":true}'; exit 0; }
|
|
1280
|
+
|
|
1281
|
+
NOTIFICATION_TYPE="$(printf '%s' "${"$"}INPUT" | jq -r '.notificationType // .notification_type // empty')"
|
|
1282
|
+
TOOL_NAME="$(printf '%s' "${"$"}INPUT" | jq -r '.toolName // .tool_name // empty')"
|
|
1283
|
+
MESSAGE="$(printf '%s' "${"$"}INPUT" | jq -r '.message // empty')"
|
|
1284
|
+
|
|
1285
|
+
PAYLOAD="$(jq -nc \\
|
|
1286
|
+
--arg event "${"$"}EVENT" \\
|
|
1287
|
+
--arg notification_type "${"$"}NOTIFICATION_TYPE" \\
|
|
1288
|
+
--arg tool_name "${"$"}TOOL_NAME" \\
|
|
1289
|
+
--arg message "${"$"}MESSAGE" \\
|
|
1290
|
+
'({event: $event}
|
|
1291
|
+
+ (if $notification_type != "" then {notification_type: $notification_type} else {} end)
|
|
1292
|
+
+ (if $tool_name != "" then {tool_name: $tool_name} else {} end)
|
|
1293
|
+
+ (if $message != "" then {message: $message} else {} end))')"
|
|
1294
|
+
|
|
1295
|
+
MARKER="${markerPrefix} ${"$"}PAYLOAD"
|
|
1296
|
+
jq -nc --arg m "${"$"}MARKER" '{continue: true, suppressOutput: true, systemMessage: $m}'
|
|
1297
|
+
`;
|
|
1298
|
+
return {
|
|
1299
|
+
markerPrefix,
|
|
1300
|
+
scriptPath,
|
|
1301
|
+
scriptContent,
|
|
1302
|
+
settingsHooks
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
getHookMarkers(output) {
|
|
1306
|
+
const markers = [];
|
|
1307
|
+
const markerRegex = /(?:^|\n)\s*([A-Z0-9_]+)\s+(\{[^\n\r]+\})/g;
|
|
1308
|
+
let match;
|
|
1309
|
+
while ((match = markerRegex.exec(output)) !== null) {
|
|
1310
|
+
const markerToken = match[1];
|
|
1311
|
+
if (!markerToken.includes("GEMINI_HOOK")) {
|
|
1312
|
+
continue;
|
|
1313
|
+
}
|
|
1314
|
+
const payload = match[2];
|
|
1315
|
+
try {
|
|
1316
|
+
const parsed = JSON.parse(payload);
|
|
1317
|
+
const event = typeof parsed.event === "string" ? parsed.event : void 0;
|
|
1318
|
+
if (!event) continue;
|
|
1319
|
+
markers.push({
|
|
1320
|
+
event,
|
|
1321
|
+
notification_type: typeof parsed.notification_type === "string" ? parsed.notification_type : void 0,
|
|
1322
|
+
tool_name: typeof parsed.tool_name === "string" ? parsed.tool_name : void 0,
|
|
1323
|
+
message: typeof parsed.message === "string" ? parsed.message : void 0
|
|
1324
|
+
});
|
|
1325
|
+
} catch {
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
return markers;
|
|
1329
|
+
}
|
|
1330
|
+
getLatestHookMarker(output) {
|
|
1331
|
+
const markers = this.getHookMarkers(output);
|
|
1332
|
+
return markers.length > 0 ? markers[markers.length - 1] : null;
|
|
1333
|
+
}
|
|
1334
|
+
stripHookMarkers(output) {
|
|
1335
|
+
return output.replace(/(?:^|\n)\s*[A-Z0-9_]*GEMINI_HOOK[A-Z0-9_]*\s+\{[^\n\r]+\}\s*/g, "\n");
|
|
1336
|
+
}
|
|
1065
1337
|
detectLogin(output) {
|
|
1066
1338
|
const stripped = this.stripAnsi(output);
|
|
1067
1339
|
if (stripped.includes("API key not found") || /set (?:GOOGLE_API_KEY|GEMINI_API_KEY)/i.test(stripped) || stripped.includes("authentication required") || stripped.includes("Invalid API key") || stripped.includes("API key is not valid")) {
|
|
@@ -1112,6 +1384,17 @@ var GeminiAdapter = class extends BaseCodingAdapter {
|
|
|
1112
1384
|
}
|
|
1113
1385
|
detectBlockingPrompt(output) {
|
|
1114
1386
|
const stripped = this.stripAnsi(output);
|
|
1387
|
+
const marker = this.getLatestHookMarker(stripped);
|
|
1388
|
+
if (marker?.event === "Notification" && marker.notification_type === "ToolPermission") {
|
|
1389
|
+
return {
|
|
1390
|
+
detected: true,
|
|
1391
|
+
type: "permission",
|
|
1392
|
+
prompt: marker.message || "Gemini tool permission",
|
|
1393
|
+
suggestedResponse: "keys:enter",
|
|
1394
|
+
canAutoRespond: true,
|
|
1395
|
+
instructions: "Gemini is asking to allow a tool action"
|
|
1396
|
+
};
|
|
1397
|
+
}
|
|
1115
1398
|
if (/apply.?this.?change\??/i.test(stripped) || /allow.?execution.?of/i.test(stripped) || /do.?you.?want.?to.?proceed\??/i.test(stripped) || /waiting.?for.?user.?confirmation/i.test(stripped)) {
|
|
1116
1399
|
return {
|
|
1117
1400
|
detected: true,
|
|
@@ -1207,7 +1490,11 @@ var GeminiAdapter = class extends BaseCodingAdapter {
|
|
|
1207
1490
|
*/
|
|
1208
1491
|
detectLoading(output) {
|
|
1209
1492
|
const stripped = this.stripAnsi(output);
|
|
1493
|
+
const marker = this.getLatestHookMarker(stripped);
|
|
1210
1494
|
const tail = stripped.slice(-500);
|
|
1495
|
+
if (marker?.event === "BeforeTool") {
|
|
1496
|
+
return true;
|
|
1497
|
+
}
|
|
1211
1498
|
if (/esc\s+to\s+cancel/i.test(tail)) {
|
|
1212
1499
|
return true;
|
|
1213
1500
|
}
|
|
@@ -1216,6 +1503,17 @@ var GeminiAdapter = class extends BaseCodingAdapter {
|
|
|
1216
1503
|
}
|
|
1217
1504
|
return false;
|
|
1218
1505
|
}
|
|
1506
|
+
detectToolRunning(output) {
|
|
1507
|
+
const stripped = this.stripAnsi(output);
|
|
1508
|
+
const marker = this.getLatestHookMarker(stripped);
|
|
1509
|
+
if (marker?.event === "BeforeTool" && marker.tool_name) {
|
|
1510
|
+
return {
|
|
1511
|
+
toolName: marker.tool_name.toLowerCase(),
|
|
1512
|
+
description: `${marker.tool_name} (hook)`
|
|
1513
|
+
};
|
|
1514
|
+
}
|
|
1515
|
+
return null;
|
|
1516
|
+
}
|
|
1219
1517
|
/**
|
|
1220
1518
|
* Detect task completion for Gemini CLI.
|
|
1221
1519
|
*
|
|
@@ -1228,6 +1526,10 @@ var GeminiAdapter = class extends BaseCodingAdapter {
|
|
|
1228
1526
|
*/
|
|
1229
1527
|
detectTaskComplete(output) {
|
|
1230
1528
|
const stripped = this.stripAnsi(output);
|
|
1529
|
+
const marker = this.getLatestHookMarker(stripped);
|
|
1530
|
+
if (marker?.event === "AfterAgent") {
|
|
1531
|
+
return true;
|
|
1532
|
+
}
|
|
1231
1533
|
if (/◇\s+Ready/.test(stripped)) {
|
|
1232
1534
|
return true;
|
|
1233
1535
|
}
|
|
@@ -1238,6 +1540,13 @@ var GeminiAdapter = class extends BaseCodingAdapter {
|
|
|
1238
1540
|
}
|
|
1239
1541
|
detectReady(output) {
|
|
1240
1542
|
const stripped = this.stripAnsi(output);
|
|
1543
|
+
const marker = this.getLatestHookMarker(stripped);
|
|
1544
|
+
if (marker?.event === "Notification" && marker.notification_type === "ToolPermission") {
|
|
1545
|
+
return false;
|
|
1546
|
+
}
|
|
1547
|
+
if (marker?.event === "AfterAgent") {
|
|
1548
|
+
return true;
|
|
1549
|
+
}
|
|
1241
1550
|
const hasActiveOverlay = /interactive\s+shell\s+awaiting\s+input|press\s+tab\s+to\s+focus\s+shell/i.test(stripped) || /waiting\s+for\s+user\s+confirmation|apply.?this.?change|allow.?execution|do.?you.?want.?to.?proceed/i.test(stripped) || /do.?you.?want.?to.?continue\s*\([yY]\/[nN]\)\??|are.?you.?sure\??\s*\([yY]\/[nN]\)\??/i.test(stripped) || /enable.?checkpointing.?to.?recover.?your.?session.?after.?a.?crash/i.test(stripped) || /esc\s+to\s+cancel|esc\s+to\s+interrupt/i.test(stripped);
|
|
1242
1551
|
if (hasActiveOverlay) {
|
|
1243
1552
|
return false;
|
|
@@ -1255,7 +1564,8 @@ var GeminiAdapter = class extends BaseCodingAdapter {
|
|
|
1255
1564
|
/gemini>\s*$/i.test(stripped);
|
|
1256
1565
|
}
|
|
1257
1566
|
parseOutput(output) {
|
|
1258
|
-
const
|
|
1567
|
+
const withoutHookMarkers = this.stripHookMarkers(output);
|
|
1568
|
+
const stripped = this.stripAnsi(withoutHookMarkers);
|
|
1259
1569
|
const isComplete = this.isResponseComplete(stripped);
|
|
1260
1570
|
if (!isComplete) {
|
|
1261
1571
|
return null;
|
|
@@ -1279,6 +1589,13 @@ var GeminiAdapter = class extends BaseCodingAdapter {
|
|
|
1279
1589
|
*/
|
|
1280
1590
|
detectExit(output) {
|
|
1281
1591
|
const stripped = this.stripAnsi(output);
|
|
1592
|
+
const marker = this.getLatestHookMarker(stripped);
|
|
1593
|
+
if (marker?.event === "SessionEnd") {
|
|
1594
|
+
return {
|
|
1595
|
+
exited: true,
|
|
1596
|
+
code: 0
|
|
1597
|
+
};
|
|
1598
|
+
}
|
|
1282
1599
|
if (/folder.?trust.?level.?must.?be.?selected.*exiting/i.test(stripped)) {
|
|
1283
1600
|
return {
|
|
1284
1601
|
exited: true,
|
|
@@ -2128,6 +2445,213 @@ var AiderAdapter = class extends BaseCodingAdapter {
|
|
|
2128
2445
|
}
|
|
2129
2446
|
};
|
|
2130
2447
|
|
|
2448
|
+
// src/hermes-adapter.ts
|
|
2449
|
+
var HermesAdapter = class extends BaseCodingAdapter {
|
|
2450
|
+
adapterType = "hermes";
|
|
2451
|
+
displayName = "Hermes Agent";
|
|
2452
|
+
/** Prompt-toolkit TUI + spinner rendering needs a slightly longer settle. */
|
|
2453
|
+
readySettleMs = 400;
|
|
2454
|
+
installation = {
|
|
2455
|
+
command: 'pip install "hermes-agent[cli]"',
|
|
2456
|
+
alternatives: [
|
|
2457
|
+
'pipx install "hermes-agent[cli]"',
|
|
2458
|
+
'uv tool install "hermes-agent[cli]"'
|
|
2459
|
+
],
|
|
2460
|
+
docsUrl: "https://github.com/NousResearch/hermes-agent"
|
|
2461
|
+
};
|
|
2462
|
+
getWorkspaceFiles() {
|
|
2463
|
+
return [
|
|
2464
|
+
{
|
|
2465
|
+
relativePath: "AGENTS.md",
|
|
2466
|
+
description: "Project instructions and architecture notes loaded by Hermes context files",
|
|
2467
|
+
autoLoaded: true,
|
|
2468
|
+
type: "memory",
|
|
2469
|
+
format: "markdown"
|
|
2470
|
+
},
|
|
2471
|
+
{
|
|
2472
|
+
relativePath: "SOUL.md",
|
|
2473
|
+
description: "Optional persona/context file auto-injected into Hermes system prompt",
|
|
2474
|
+
autoLoaded: true,
|
|
2475
|
+
type: "memory",
|
|
2476
|
+
format: "markdown"
|
|
2477
|
+
},
|
|
2478
|
+
{
|
|
2479
|
+
relativePath: "cli-config.yaml",
|
|
2480
|
+
description: "Legacy/local Hermes CLI configuration file",
|
|
2481
|
+
autoLoaded: true,
|
|
2482
|
+
type: "config",
|
|
2483
|
+
format: "yaml"
|
|
2484
|
+
}
|
|
2485
|
+
];
|
|
2486
|
+
}
|
|
2487
|
+
getRecommendedModels(_credentials) {
|
|
2488
|
+
return {
|
|
2489
|
+
powerful: "anthropic/claude-opus-4.6",
|
|
2490
|
+
fast: "google/gemini-3-flash-preview"
|
|
2491
|
+
};
|
|
2492
|
+
}
|
|
2493
|
+
getCommand() {
|
|
2494
|
+
return "hermes";
|
|
2495
|
+
}
|
|
2496
|
+
getArgs(_config) {
|
|
2497
|
+
return ["chat"];
|
|
2498
|
+
}
|
|
2499
|
+
getEnv(config) {
|
|
2500
|
+
const env = {};
|
|
2501
|
+
const credentials = this.getCredentials(config);
|
|
2502
|
+
if (credentials.openaiKey) {
|
|
2503
|
+
env.OPENROUTER_API_KEY = credentials.openaiKey;
|
|
2504
|
+
env.OPENAI_API_KEY = credentials.openaiKey;
|
|
2505
|
+
}
|
|
2506
|
+
if (credentials.anthropicKey) {
|
|
2507
|
+
env.ANTHROPIC_API_KEY = credentials.anthropicKey;
|
|
2508
|
+
}
|
|
2509
|
+
if (credentials.googleKey) {
|
|
2510
|
+
env.GOOGLE_API_KEY = credentials.googleKey;
|
|
2511
|
+
env.GEMINI_API_KEY = credentials.googleKey;
|
|
2512
|
+
}
|
|
2513
|
+
if (!this.isInteractive(config)) {
|
|
2514
|
+
env.HERMES_QUIET = "1";
|
|
2515
|
+
}
|
|
2516
|
+
return env;
|
|
2517
|
+
}
|
|
2518
|
+
detectLogin(output) {
|
|
2519
|
+
const stripped = this.stripAnsi(output);
|
|
2520
|
+
if (/isn.?t configured yet/i.test(stripped) || /no api keys or providers found/i.test(stripped) || /run setup now\?\s*\[y\/n\]/i.test(stripped)) {
|
|
2521
|
+
return {
|
|
2522
|
+
required: true,
|
|
2523
|
+
type: "api_key",
|
|
2524
|
+
instructions: "Hermes requires provider credentials. Run: hermes setup"
|
|
2525
|
+
};
|
|
2526
|
+
}
|
|
2527
|
+
return { required: false };
|
|
2528
|
+
}
|
|
2529
|
+
detectBlockingPrompt(output) {
|
|
2530
|
+
const stripped = this.stripAnsi(output);
|
|
2531
|
+
const loginDetection = this.detectLogin(output);
|
|
2532
|
+
if (loginDetection.required) {
|
|
2533
|
+
return {
|
|
2534
|
+
detected: true,
|
|
2535
|
+
type: "login",
|
|
2536
|
+
prompt: loginDetection.instructions,
|
|
2537
|
+
canAutoRespond: false,
|
|
2538
|
+
instructions: loginDetection.instructions
|
|
2539
|
+
};
|
|
2540
|
+
}
|
|
2541
|
+
if (/Hermes needs your input|Other \(type your answer\)/i.test(stripped)) {
|
|
2542
|
+
return {
|
|
2543
|
+
detected: true,
|
|
2544
|
+
type: "tool_wait",
|
|
2545
|
+
prompt: "Hermes clarify prompt",
|
|
2546
|
+
canAutoRespond: false,
|
|
2547
|
+
instructions: "Hermes is waiting for clarify input (arrow keys + Enter or free text)."
|
|
2548
|
+
};
|
|
2549
|
+
}
|
|
2550
|
+
if (/Sudo Password Required|password hidden|Password \(hidden\):/i.test(stripped)) {
|
|
2551
|
+
return {
|
|
2552
|
+
detected: true,
|
|
2553
|
+
type: "tool_wait",
|
|
2554
|
+
prompt: "Hermes sudo password prompt",
|
|
2555
|
+
canAutoRespond: false,
|
|
2556
|
+
instructions: "Hermes terminal tool is waiting for a sudo password or skip."
|
|
2557
|
+
};
|
|
2558
|
+
}
|
|
2559
|
+
if (/Dangerous Command|Allow once|Allow for this session|permanent allowlist|\bDeny\b/i.test(stripped)) {
|
|
2560
|
+
return {
|
|
2561
|
+
detected: true,
|
|
2562
|
+
type: "permission",
|
|
2563
|
+
prompt: "Hermes dangerous command approval",
|
|
2564
|
+
canAutoRespond: false,
|
|
2565
|
+
instructions: "Choose approval policy (once/session/always/deny)."
|
|
2566
|
+
};
|
|
2567
|
+
}
|
|
2568
|
+
return super.detectBlockingPrompt(output);
|
|
2569
|
+
}
|
|
2570
|
+
detectLoading(output) {
|
|
2571
|
+
const stripped = this.stripAnsi(output);
|
|
2572
|
+
const tail = stripped.slice(-1200);
|
|
2573
|
+
if (/(?:pondering|contemplating|musing|cogitating|ruminating|deliberating|mulling|reflecting|processing|reasoning|analyzing|computing|synthesizing|formulating|brainstorming)\.\.\.\s*\(\d+\.\d+s\)/i.test(tail)) {
|
|
2574
|
+
return true;
|
|
2575
|
+
}
|
|
2576
|
+
if (/\(\d+\.\d+s\)\s*$/.test(tail) && /(?:🔍|📄|💻|⚙️|📖|✍️|🔧|🌐|👆|⌨️|📋|🧠|📚|🎨|🐍|🔀|⚡|💬)/.test(tail)) {
|
|
2577
|
+
return true;
|
|
2578
|
+
}
|
|
2579
|
+
if (/⚕\s*❯\s*$/.test(tail)) {
|
|
2580
|
+
return true;
|
|
2581
|
+
}
|
|
2582
|
+
return false;
|
|
2583
|
+
}
|
|
2584
|
+
detectTaskComplete(output) {
|
|
2585
|
+
if (/╭─\s*⚕\s*Hermes/i.test(output)) {
|
|
2586
|
+
return true;
|
|
2587
|
+
}
|
|
2588
|
+
const stripped = this.stripAnsi(output);
|
|
2589
|
+
if (!stripped.trim()) return false;
|
|
2590
|
+
if (this.detectLoading(stripped)) {
|
|
2591
|
+
return false;
|
|
2592
|
+
}
|
|
2593
|
+
const hasIdlePrompt = /(?:^|\n)\s*❯\s*$/m.test(stripped);
|
|
2594
|
+
const hasToolFeed = /(?:^|\n)\s*┊\s+\S+/m.test(stripped);
|
|
2595
|
+
if (hasIdlePrompt && hasToolFeed) {
|
|
2596
|
+
return true;
|
|
2597
|
+
}
|
|
2598
|
+
return false;
|
|
2599
|
+
}
|
|
2600
|
+
detectReady(output) {
|
|
2601
|
+
const stripped = this.stripAnsi(output);
|
|
2602
|
+
const tail = stripped.slice(-800);
|
|
2603
|
+
if (!tail.trim()) return false;
|
|
2604
|
+
if (this.detectLoading(tail)) {
|
|
2605
|
+
return false;
|
|
2606
|
+
}
|
|
2607
|
+
if (/Hermes needs your input|Sudo Password Required|Dangerous Command/i.test(tail)) {
|
|
2608
|
+
return false;
|
|
2609
|
+
}
|
|
2610
|
+
if (/(?:⚕|⚠|🔐|\?|✎)\s*❯\s*$/.test(tail)) {
|
|
2611
|
+
return false;
|
|
2612
|
+
}
|
|
2613
|
+
return /(?:^|\n)\s*❯\s*$/m.test(tail);
|
|
2614
|
+
}
|
|
2615
|
+
parseOutput(output) {
|
|
2616
|
+
const raw = output;
|
|
2617
|
+
const stripped = this.stripAnsi(output);
|
|
2618
|
+
const complete = this.detectTaskComplete(raw) || this.detectReady(stripped);
|
|
2619
|
+
if (!complete) {
|
|
2620
|
+
return null;
|
|
2621
|
+
}
|
|
2622
|
+
let content = "";
|
|
2623
|
+
const boxMatch = raw.match(/╭─\s*⚕\s*Hermes[^\n]*\n([\s\S]*?)\n\s*╰/i);
|
|
2624
|
+
if (boxMatch?.[1]) {
|
|
2625
|
+
content = boxMatch[1].trim();
|
|
2626
|
+
}
|
|
2627
|
+
if (!content) {
|
|
2628
|
+
content = this.extractContent(stripped, /(?:^|\n)\s*(?:⚕|⚠|🔐|\?|✎)?\s*❯\s*$/gim);
|
|
2629
|
+
}
|
|
2630
|
+
return {
|
|
2631
|
+
type: this.containsQuestion(content) ? "question" : "response",
|
|
2632
|
+
content,
|
|
2633
|
+
isComplete: true,
|
|
2634
|
+
isQuestion: this.containsQuestion(content),
|
|
2635
|
+
metadata: {
|
|
2636
|
+
raw: output
|
|
2637
|
+
}
|
|
2638
|
+
};
|
|
2639
|
+
}
|
|
2640
|
+
getPromptPattern() {
|
|
2641
|
+
return /(?:^|\n)\s*(?:⚕|⚠|🔐|\?|✎)?\s*❯\s*$/i;
|
|
2642
|
+
}
|
|
2643
|
+
detectExit(output) {
|
|
2644
|
+
const stripped = this.stripAnsi(output);
|
|
2645
|
+
if (/Goodbye!\s*⚕/i.test(stripped)) {
|
|
2646
|
+
return { exited: true, code: 0 };
|
|
2647
|
+
}
|
|
2648
|
+
return super.detectExit(output);
|
|
2649
|
+
}
|
|
2650
|
+
getHealthCheckCommand() {
|
|
2651
|
+
return "hermes version";
|
|
2652
|
+
}
|
|
2653
|
+
};
|
|
2654
|
+
|
|
2131
2655
|
// src/pattern-loader.ts
|
|
2132
2656
|
var BASELINE_PATTERNS = {
|
|
2133
2657
|
claude: {
|
|
@@ -2251,6 +2775,41 @@ var BASELINE_PATTERNS = {
|
|
|
2251
2775
|
toolWait: [],
|
|
2252
2776
|
exit: [],
|
|
2253
2777
|
source: "baseline"
|
|
2778
|
+
},
|
|
2779
|
+
hermes: {
|
|
2780
|
+
ready: [
|
|
2781
|
+
"\u276F",
|
|
2782
|
+
"\u2695 Hermes",
|
|
2783
|
+
"Welcome to Hermes Agent"
|
|
2784
|
+
],
|
|
2785
|
+
auth: [
|
|
2786
|
+
"isn't configured yet",
|
|
2787
|
+
"no API keys or providers found",
|
|
2788
|
+
"Run setup now? [Y/n]"
|
|
2789
|
+
],
|
|
2790
|
+
blocking: [
|
|
2791
|
+
"Hermes needs your input",
|
|
2792
|
+
"Sudo Password Required",
|
|
2793
|
+
"Dangerous Command"
|
|
2794
|
+
],
|
|
2795
|
+
loading: [
|
|
2796
|
+
"deliberating...",
|
|
2797
|
+
"(0.0s)",
|
|
2798
|
+
"\u2695 \u276F"
|
|
2799
|
+
],
|
|
2800
|
+
turnComplete: [
|
|
2801
|
+
"\u256D\u2500 \u2695 Hermes",
|
|
2802
|
+
"\u276F"
|
|
2803
|
+
],
|
|
2804
|
+
toolWait: [
|
|
2805
|
+
"Hermes needs your input",
|
|
2806
|
+
"Sudo Password Required",
|
|
2807
|
+
"Dangerous Command"
|
|
2808
|
+
],
|
|
2809
|
+
exit: [
|
|
2810
|
+
"Goodbye! \u2695"
|
|
2811
|
+
],
|
|
2812
|
+
source: "baseline"
|
|
2254
2813
|
}
|
|
2255
2814
|
};
|
|
2256
2815
|
var patternCache = /* @__PURE__ */ new Map();
|
|
@@ -2321,14 +2880,16 @@ function createAllAdapters() {
|
|
|
2321
2880
|
new ClaudeAdapter(),
|
|
2322
2881
|
new GeminiAdapter(),
|
|
2323
2882
|
new CodexAdapter(),
|
|
2324
|
-
new AiderAdapter()
|
|
2883
|
+
new AiderAdapter(),
|
|
2884
|
+
new HermesAdapter()
|
|
2325
2885
|
];
|
|
2326
2886
|
}
|
|
2327
2887
|
var ADAPTER_TYPES = {
|
|
2328
2888
|
claude: ClaudeAdapter,
|
|
2329
2889
|
gemini: GeminiAdapter,
|
|
2330
2890
|
codex: CodexAdapter,
|
|
2331
|
-
aider: AiderAdapter
|
|
2891
|
+
aider: AiderAdapter,
|
|
2892
|
+
hermes: HermesAdapter
|
|
2332
2893
|
};
|
|
2333
2894
|
function createAdapter(type) {
|
|
2334
2895
|
const AdapterClass = ADAPTER_TYPES[type];
|
|
@@ -2385,6 +2946,7 @@ exports.ClaudeAdapter = ClaudeAdapter;
|
|
|
2385
2946
|
exports.CodexAdapter = CodexAdapter;
|
|
2386
2947
|
exports.GEMINI_TOOL_CATEGORIES = GEMINI_TOOL_CATEGORIES;
|
|
2387
2948
|
exports.GeminiAdapter = GeminiAdapter;
|
|
2949
|
+
exports.HermesAdapter = HermesAdapter;
|
|
2388
2950
|
exports.PRESET_DEFINITIONS = PRESET_DEFINITIONS;
|
|
2389
2951
|
exports.TOOL_CATEGORIES = TOOL_CATEGORIES;
|
|
2390
2952
|
exports.checkAdapters = checkAdapters;
|
|
@@ -2397,6 +2959,7 @@ exports.generateApprovalConfig = generateApprovalConfig;
|
|
|
2397
2959
|
exports.generateClaudeApprovalConfig = generateClaudeApprovalConfig;
|
|
2398
2960
|
exports.generateCodexApprovalConfig = generateCodexApprovalConfig;
|
|
2399
2961
|
exports.generateGeminiApprovalConfig = generateGeminiApprovalConfig;
|
|
2962
|
+
exports.generateHermesApprovalConfig = generateHermesApprovalConfig;
|
|
2400
2963
|
exports.getBaselinePatterns = getBaselinePatterns;
|
|
2401
2964
|
exports.getPresetDefinition = getPresetDefinition;
|
|
2402
2965
|
exports.hasDynamicPatterns = hasDynamicPatterns;
|