coding-agent-adapters 0.13.0 → 0.15.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 CHANGED
@@ -1,20 +1,46 @@
1
1
  'use strict';
2
2
 
3
+ var pino = require('pino');
3
4
  var promises = require('fs/promises');
4
5
  var path = require('path');
5
6
  var adapterTypes = require('adapter-types');
6
7
 
7
- // src/base-coding-adapter.ts
8
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
9
+
10
+ var pino__default = /*#__PURE__*/_interopDefault(pino);
11
+
12
+ // src/index.ts
8
13
 
9
14
  // src/approval-presets.ts
15
+ var _autonomousSandboxWarningLogged = false;
10
16
  var TOOL_CATEGORIES = [
11
- { category: "file_read", risk: "low", description: "Read files, search, list directories" },
12
- { category: "file_write", risk: "medium", description: "Write, edit, and create files" },
17
+ {
18
+ category: "file_read",
19
+ risk: "low",
20
+ description: "Read files, search, list directories"
21
+ },
22
+ {
23
+ category: "file_write",
24
+ risk: "medium",
25
+ description: "Write, edit, and create files"
26
+ },
13
27
  { category: "shell", risk: "high", description: "Execute shell commands" },
14
28
  { category: "web", risk: "medium", description: "Web search and fetch" },
15
- { category: "agent", risk: "medium", description: "Spawn sub-agents, skills, MCP tools" },
16
- { category: "planning", risk: "low", description: "Task planning and todo management" },
17
- { category: "user_interaction", risk: "low", description: "Ask user questions" }
29
+ {
30
+ category: "agent",
31
+ risk: "medium",
32
+ description: "Spawn sub-agents, skills, MCP tools"
33
+ },
34
+ {
35
+ category: "planning",
36
+ risk: "low",
37
+ description: "Task planning and todo management"
38
+ },
39
+ {
40
+ category: "user_interaction",
41
+ risk: "low",
42
+ description: "Ask user questions"
43
+ }
18
44
  ];
19
45
  var PRESET_DEFINITIONS = [
20
46
  {
@@ -34,14 +60,29 @@ var PRESET_DEFINITIONS = [
34
60
  {
35
61
  preset: "permissive",
36
62
  description: "File ops auto-approved, shell still prompts.",
37
- autoApprove: ["file_read", "file_write", "planning", "user_interaction", "web", "agent"],
63
+ autoApprove: [
64
+ "file_read",
65
+ "file_write",
66
+ "planning",
67
+ "user_interaction",
68
+ "web",
69
+ "agent"
70
+ ],
38
71
  requireApproval: ["shell"],
39
72
  blocked: []
40
73
  },
41
74
  {
42
75
  preset: "autonomous",
43
76
  description: "Everything auto-approved. Use with sandbox.",
44
- autoApprove: ["file_read", "file_write", "shell", "web", "agent", "planning", "user_interaction"],
77
+ autoApprove: [
78
+ "file_read",
79
+ "file_write",
80
+ "shell",
81
+ "web",
82
+ "agent",
83
+ "planning",
84
+ "user_interaction"
85
+ ],
45
86
  requireApproval: [],
46
87
  blocked: []
47
88
  }
@@ -162,7 +203,10 @@ function getToolsForCategories(mapping, categories) {
162
203
  }
163
204
  function generateClaudeApprovalConfig(preset) {
164
205
  const def = getPresetDefinition(preset);
165
- const allowTools = getToolsForCategories(CLAUDE_TOOL_CATEGORIES, def.autoApprove);
206
+ const allowTools = getToolsForCategories(
207
+ CLAUDE_TOOL_CATEGORIES,
208
+ def.autoApprove
209
+ );
166
210
  const denyTools = getToolsForCategories(CLAUDE_TOOL_CATEGORIES, def.blocked);
167
211
  const settings = {
168
212
  permissions: {}
@@ -182,6 +226,13 @@ function generateClaudeApprovalConfig(preset) {
182
226
  }
183
227
  const cliFlags = [];
184
228
  if (preset === "autonomous") {
229
+ cliFlags.push("--dangerously-skip-permissions");
230
+ if (!_autonomousSandboxWarningLogged) {
231
+ console.warn(
232
+ "Autonomous preset uses --dangerously-skip-permissions. Ensure agents run in a sandboxed environment."
233
+ );
234
+ _autonomousSandboxWarningLogged = true;
235
+ }
185
236
  const allTools = Object.keys(CLAUDE_TOOL_CATEGORIES);
186
237
  cliFlags.push("--tools", allTools.join(","));
187
238
  }
@@ -202,8 +253,14 @@ function generateClaudeApprovalConfig(preset) {
202
253
  function generateGeminiApprovalConfig(preset) {
203
254
  const def = getPresetDefinition(preset);
204
255
  const cliFlags = [];
205
- const allowedTools = getToolsForCategories(GEMINI_TOOL_CATEGORIES, def.autoApprove);
206
- const excludeTools = getToolsForCategories(GEMINI_TOOL_CATEGORIES, def.blocked);
256
+ const allowedTools = getToolsForCategories(
257
+ GEMINI_TOOL_CATEGORIES,
258
+ def.autoApprove
259
+ );
260
+ const excludeTools = getToolsForCategories(
261
+ GEMINI_TOOL_CATEGORIES,
262
+ def.blocked
263
+ );
207
264
  let approvalMode;
208
265
  switch (preset) {
209
266
  case "readonly":
@@ -329,7 +386,8 @@ function generateAiderApprovalConfig(preset) {
329
386
  workspaceFiles: [
330
387
  {
331
388
  relativePath: ".aider.conf.yml",
332
- content: lines.join("\n") + "\n",
389
+ content: `${lines.join("\n")}
390
+ `,
333
391
  format: "yaml"
334
392
  }
335
393
  ],
@@ -391,7 +449,9 @@ var BaseCodingAdapter = class extends adapterTypes.BaseCLIAdapter {
391
449
  * Returns the relativePath of the first 'memory' type file from getWorkspaceFiles().
392
450
  */
393
451
  get memoryFilePath() {
394
- const memoryFile = this.getWorkspaceFiles().find((f) => f.type === "memory");
452
+ const memoryFile = this.getWorkspaceFiles().find(
453
+ (f) => f.type === "memory"
454
+ );
395
455
  if (!memoryFile) {
396
456
  throw new Error(`${this.displayName} adapter has no memory file defined`);
397
457
  }
@@ -432,7 +492,10 @@ var BaseCodingAdapter = class extends adapterTypes.BaseCLIAdapter {
432
492
  result = super.stripAnsi(result);
433
493
  result = result.replace(/[\x00-\x08\x0b-\x0d\x0e-\x1f\x7f]/g, "");
434
494
  result = result.replace(/\xa0/g, " ");
435
- result = result.replace(/[│╭╰╮╯─═╌║╔╗╚╝╠╣╦╩╬┌┐└┘├┤┬┴┼●○❮▶◀⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏⣾⣽⣻⢿⡿⣟⣯⣷✻✶✳✢⏺←→↑↓⬆⬇◆▪▫■□▲△▼▽◈⟨⟩⌘⏎⏏⌫⌦⇧⇪⌥]/g, " ");
495
+ result = result.replace(
496
+ /[│╭╰╮╯─═╌║╔╗╚╝╠╣╦╩╬┌┐└┘├┤┬┴┼●○❮▶◀⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏⣾⣽⣻⢿⡿⣟⣯⣷✻✶✳✢⏺←→↑↓⬆⬇◆▪▫■□▲△▼▽◈⟨⟩⌘⏎⏏⌫⌦⇧⇪⌥]/g,
497
+ " "
498
+ );
436
499
  result = result.replace(/ {2,}/g, " ");
437
500
  return result;
438
501
  }
@@ -507,7 +570,10 @@ Docs: ${this.installation.docsUrl}`
507
570
  extractContent(output, promptPattern) {
508
571
  let content = output;
509
572
  content = content.replace(promptPattern, "");
510
- content = content.replace(/^(Thinking|Working|Reading|Writing|Processing|Generating)\.+$/gm, "");
573
+ content = content.replace(
574
+ /^(Thinking|Working|Reading|Writing|Processing|Generating)\.+$/gm,
575
+ ""
576
+ );
511
577
  content = content.trim();
512
578
  return content;
513
579
  }
@@ -527,7 +593,10 @@ Docs: ${this.installation.docsUrl}`
527
593
  getApprovalConfig(config) {
528
594
  const preset = this.getApprovalPreset(config);
529
595
  if (!preset) return null;
530
- return generateApprovalConfig(this.adapterType, preset);
596
+ return generateApprovalConfig(
597
+ this.adapterType,
598
+ preset
599
+ );
531
600
  }
532
601
  /**
533
602
  * Write approval config files to a workspace directory.
@@ -567,145 +636,273 @@ Docs: ${this.installation.docsUrl}`
567
636
  }
568
637
  };
569
638
 
570
- // src/claude-adapter.ts
571
- var CLAUDE_HOOK_MARKER_PREFIX = "PARALLAX_CLAUDE_HOOK";
572
- var ClaudeAdapter = class extends BaseCodingAdapter {
573
- adapterType = "claude";
574
- displayName = "Claude Code";
575
- /** Heaviest TUI — status bar, shortcuts, update notices, /ide suggestions */
576
- readySettleMs = 500;
639
+ // src/aider-adapter.ts
640
+ var AiderAdapter = class extends BaseCodingAdapter {
641
+ adapterType = "aider";
642
+ displayName = "Aider";
643
+ /** Minimal TUI, mostly text output — shorter settle delay */
644
+ readySettleMs = 200;
645
+ /**
646
+ * Aider uses plain text [y/n] prompts, NOT TUI arrow-key menus.
647
+ */
648
+ usesTuiMenus = false;
577
649
  installation = {
578
- command: "npm install -g @anthropic-ai/claude-code",
650
+ command: "pip install aider-chat",
579
651
  alternatives: [
580
- "npx @anthropic-ai/claude-code (run without installing)",
581
- "brew install claude-code (macOS with Homebrew)"
652
+ "pipx install aider-chat (isolated install)",
653
+ "brew install aider (macOS with Homebrew)"
582
654
  ],
583
- docsUrl: "https://docs.anthropic.com/en/docs/claude-code",
584
- minVersion: "1.0.0"
655
+ docsUrl: "https://aider.chat/docs/install.html",
656
+ minVersion: "0.50.0"
585
657
  };
586
658
  /**
587
- * Auto-response rules for Claude Code CLI.
588
- * These handle common text-based [y/n] prompts that can be safely auto-responded.
589
- * Explicit responseType: 'text' prevents the usesTuiMenus default from kicking in.
659
+ * Auto-response rules for Aider CLI.
660
+ * Aider uses plain text prompts via io.py:832 with (Y)es/(N)o format.
661
+ * All rules are responseType: 'text' Aider never uses TUI menus.
662
+ *
663
+ * Decline rules come first to override the generic accept patterns.
590
664
  */
591
665
  autoResponseRules = [
666
+ // ── Decline rules (specific, checked first) ────────────────────────
592
667
  {
593
- pattern: /trust.*(?:folder|directory)|safety.?check|project.you.created|(?:Yes|Allow).*(?:No|Deny).*(?:Enter|Return)/i,
594
- type: "permission",
595
- response: "",
596
- responseType: "keys",
597
- keys: ["enter"],
598
- description: "Accept trust prompt for working directory",
668
+ pattern: /allow collection of anonymous analytics/i,
669
+ type: "config",
670
+ response: "n",
671
+ responseType: "text",
672
+ description: "Decline Aider telemetry opt-in",
599
673
  safe: true,
600
674
  once: true
601
675
  },
602
676
  {
603
- pattern: /wants? (?:your )?permission|needs your permission|(?:Allow|Approve)\s[\s\S]{0,50}(?:Deny|Don't allow)/i,
604
- type: "permission",
605
- response: "",
606
- responseType: "keys",
607
- keys: ["enter"],
608
- description: "Auto-approve tool permission prompts (file access, MCP tools, etc.)",
677
+ pattern: /would you like to see what.?s new in this version/i,
678
+ type: "config",
679
+ response: "n",
680
+ responseType: "text",
681
+ description: "Decline release notes offer",
609
682
  safe: true,
610
683
  once: true
611
684
  },
612
685
  {
613
- pattern: /update available.*\[y\/n\]/i,
614
- type: "update",
686
+ pattern: /open a github issue pre-filled/i,
687
+ type: "config",
615
688
  response: "n",
616
689
  responseType: "text",
617
- description: "Decline Claude Code update to continue execution",
690
+ description: "Decline automatic bug report",
618
691
  safe: true
619
692
  },
620
693
  {
621
- pattern: /new version.*available.*\[y\/n\]/i,
622
- type: "update",
694
+ pattern: /open documentation url for more info\?/i,
695
+ type: "config",
623
696
  response: "n",
624
697
  responseType: "text",
625
- description: "Decline version upgrade prompt",
698
+ description: "Decline opening Aider documentation for model warnings",
626
699
  safe: true
627
700
  },
701
+ // ── File / edit operations ──────────────────────────────────────────
628
702
  {
629
- pattern: /would you like to enable.*telemetry.*\[y\/n\]/i,
630
- type: "config",
631
- response: "n",
703
+ pattern: /add .+ to the chat\?/i,
704
+ type: "permission",
705
+ response: "y",
632
706
  responseType: "text",
633
- description: "Decline telemetry prompt",
707
+ description: "Allow Aider to add files to chat context",
634
708
  safe: true
635
709
  },
636
710
  {
637
- pattern: /send anonymous usage data.*\[y\/n\]/i,
638
- type: "config",
639
- response: "n",
711
+ pattern: /add url to the chat\?/i,
712
+ type: "permission",
713
+ response: "y",
640
714
  responseType: "text",
641
- description: "Decline anonymous usage data",
715
+ description: "Allow Aider to add URL content to chat",
642
716
  safe: true
643
717
  },
644
718
  {
645
- pattern: /how is claude doing this session\?\s*\(optional\)|1:\s*bad\s+2:\s*fine\s+3:\s*good\s+0:\s*dismiss/i,
719
+ pattern: /create new file\?/i,
720
+ type: "permission",
721
+ response: "y",
722
+ responseType: "text",
723
+ description: "Allow Aider to create new files",
724
+ safe: true
725
+ },
726
+ {
727
+ pattern: /allow edits to file/i,
728
+ type: "permission",
729
+ response: "y",
730
+ responseType: "text",
731
+ description: "Allow edits to file not yet in chat",
732
+ safe: true
733
+ },
734
+ {
735
+ pattern: /edit the files\?/i,
736
+ type: "permission",
737
+ response: "y",
738
+ responseType: "text",
739
+ description: "Accept architect mode edits",
740
+ safe: true
741
+ },
742
+ // ── Shell operations ────────────────────────────────────────────────
743
+ {
744
+ pattern: /run shell commands?\?/i,
745
+ type: "permission",
746
+ response: "y",
747
+ responseType: "text",
748
+ description: "Allow Aider to run shell commands",
749
+ safe: true
750
+ },
751
+ {
752
+ pattern: /add command output to the chat\?/i,
753
+ type: "permission",
754
+ response: "y",
755
+ responseType: "text",
756
+ description: "Add shell command output to chat context",
757
+ safe: true
758
+ },
759
+ {
760
+ pattern: /add \d+.*tokens of command output to the chat\?/i,
761
+ type: "permission",
762
+ response: "y",
763
+ responseType: "text",
764
+ description: "Add /run command output to chat context",
765
+ safe: true
766
+ },
767
+ // ── Setup / maintenance ─────────────────────────────────────────────
768
+ {
769
+ pattern: /no git repo found.*create one/i,
646
770
  type: "config",
647
- response: "0",
771
+ response: "y",
648
772
  responseType: "text",
649
- description: "Dismiss optional Claude session survey",
773
+ description: "Create git repo for change tracking",
650
774
  safe: true,
651
775
  once: true
652
776
  },
653
777
  {
654
- pattern: /continue without.*\[y\/n\]/i,
778
+ pattern: /add .+ to \.gitignore/i,
655
779
  type: "config",
656
780
  response: "y",
657
781
  responseType: "text",
658
- description: "Continue without optional feature",
782
+ description: "Update .gitignore with Aider patterns",
783
+ safe: true,
784
+ once: true
785
+ },
786
+ {
787
+ pattern: /run pip install\?/i,
788
+ type: "config",
789
+ response: "y",
790
+ responseType: "text",
791
+ description: "Install missing Python dependencies",
792
+ safe: true
793
+ },
794
+ {
795
+ pattern: /install playwright\?/i,
796
+ type: "config",
797
+ response: "y",
798
+ responseType: "text",
799
+ description: "Install Playwright for web scraping",
800
+ safe: true
801
+ },
802
+ // ── Other safe confirmations ────────────────────────────────────────
803
+ {
804
+ pattern: /fix lint errors in/i,
805
+ type: "permission",
806
+ response: "y",
807
+ responseType: "text",
808
+ description: "Accept lint error fix suggestion",
809
+ safe: true
810
+ },
811
+ {
812
+ pattern: /try to proceed anyway\?/i,
813
+ type: "config",
814
+ response: "y",
815
+ responseType: "text",
816
+ description: "Continue despite context limit warning",
659
817
  safe: true
660
818
  }
661
819
  ];
662
820
  getWorkspaceFiles() {
663
821
  return [
664
822
  {
665
- relativePath: "CLAUDE.md",
666
- description: "Project-level instructions read automatically on startup",
823
+ relativePath: ".aider.conventions.md",
824
+ description: "Project conventions and instructions read on startup (--read flag)",
667
825
  autoLoaded: true,
668
826
  type: "memory",
669
827
  format: "markdown"
670
828
  },
671
829
  {
672
- relativePath: ".claude/settings.json",
673
- description: "Project-scoped settings (allowed tools, permissions)",
830
+ relativePath: ".aider.conf.yml",
831
+ description: "Project-scoped Aider configuration (model, flags, options)",
674
832
  autoLoaded: true,
675
833
  type: "config",
676
- format: "json"
834
+ format: "yaml"
677
835
  },
678
836
  {
679
- relativePath: ".claude/commands",
680
- description: "Custom slash commands directory",
681
- autoLoaded: false,
682
- type: "config",
683
- format: "markdown"
837
+ relativePath: ".aiderignore",
838
+ description: "Gitignore-style file listing paths Aider should not edit",
839
+ autoLoaded: true,
840
+ type: "rules",
841
+ format: "text"
684
842
  }
685
843
  ];
686
844
  }
687
- getRecommendedModels(_credentials) {
845
+ getRecommendedModels(credentials) {
846
+ if (credentials?.anthropicKey) {
847
+ return {
848
+ powerful: "anthropic/claude-sonnet-4-20250514",
849
+ fast: "anthropic/claude-haiku-4-5-20251001"
850
+ };
851
+ }
852
+ if (credentials?.openaiKey) {
853
+ return {
854
+ powerful: "openai/o3",
855
+ fast: "openai/gpt-4o-mini"
856
+ };
857
+ }
858
+ if (credentials?.googleKey) {
859
+ return {
860
+ powerful: "gemini/gemini-3-pro",
861
+ fast: "gemini/gemini-3-flash"
862
+ };
863
+ }
688
864
  return {
689
- powerful: "claude-sonnet-4-20250514",
690
- fast: "claude-haiku-4-5-20251001"
865
+ powerful: "anthropic/claude-sonnet-4-20250514",
866
+ fast: "anthropic/claude-haiku-4-5-20251001"
691
867
  };
692
868
  }
693
869
  getCommand() {
694
- return "claude";
870
+ return "aider";
695
871
  }
696
872
  getArgs(config) {
697
873
  const args = [];
698
- const adapterConfig = config.adapterConfig;
699
- if (!this.isInteractive(config)) {
700
- args.push("--print");
701
- if (config.workdir) {
702
- args.push("--cwd", config.workdir);
703
- }
874
+ const interactive = this.isInteractive(config);
875
+ if (!interactive) {
876
+ args.push("--auto-commits");
877
+ args.push("--no-pretty");
878
+ args.push("--no-show-diffs");
704
879
  }
705
- if (adapterConfig?.resume) {
706
- args.push("--resume", adapterConfig.resume);
707
- } else if (adapterConfig?.continue) {
708
- args.push("--continue");
880
+ const provider = config.adapterConfig?.provider;
881
+ const credentials = this.getCredentials(config);
882
+ if (config.env?.AIDER_MODEL) {
883
+ args.push("--model", config.env.AIDER_MODEL);
884
+ } else if (interactive && (credentials.googleKey || process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY)) {
885
+ args.push("--model", "gemini");
886
+ } else if (!interactive && provider === "anthropic") {
887
+ args.push("--model", "sonnet");
888
+ } else if (!interactive && provider === "openai") {
889
+ args.push("--model", "4o");
890
+ } else if (!interactive && provider === "google") {
891
+ args.push("--model", "gemini");
892
+ } else if (!interactive && credentials.anthropicKey) {
893
+ args.push("--model", "sonnet");
894
+ } else if (!interactive && credentials.openaiKey) {
895
+ args.push("--model", "4o");
896
+ } else if (!interactive && credentials.googleKey) {
897
+ args.push("--model", "gemini");
898
+ }
899
+ if (!interactive) {
900
+ if (credentials.anthropicKey)
901
+ args.push("--api-key", `anthropic=${credentials.anthropicKey}`);
902
+ if (credentials.openaiKey)
903
+ args.push("--api-key", `openai=${credentials.openaiKey}`);
904
+ if (credentials.googleKey)
905
+ args.push("--api-key", `gemini=${credentials.googleKey}`);
709
906
  }
710
907
  const approvalConfig = this.getApprovalConfig(config);
711
908
  if (approvalConfig) {
@@ -715,154 +912,56 @@ var ClaudeAdapter = class extends BaseCodingAdapter {
715
912
  }
716
913
  getEnv(config) {
717
914
  const env = {};
718
- const credentials = this.getCredentials(config);
719
- const adapterConfig = config.adapterConfig;
720
- if (credentials.anthropicKey) {
721
- env.ANTHROPIC_API_KEY = credentials.anthropicKey;
722
- }
723
- if (config.env?.ANTHROPIC_MODEL) {
724
- env.ANTHROPIC_MODEL = config.env.ANTHROPIC_MODEL;
725
- }
726
915
  if (!this.isInteractive(config)) {
727
- env.CLAUDE_CODE_DISABLE_INTERACTIVE = "true";
916
+ env.NO_COLOR = "1";
728
917
  }
729
- if (adapterConfig?.claudeHookTelemetry) {
730
- env.PARALLAX_CLAUDE_HOOK_TELEMETRY = "1";
731
- env.PARALLAX_CLAUDE_HOOK_MARKER_PREFIX = adapterConfig.claudeHookMarkerPrefix || CLAUDE_HOOK_MARKER_PREFIX;
918
+ if (config.env?.AIDER_NO_GIT === "true") {
919
+ env.AIDER_NO_GIT = "true";
732
920
  }
733
921
  return env;
734
922
  }
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
- }
833
923
  detectLogin(output) {
834
924
  const stripped = this.stripAnsi(output);
835
- if (stripped.includes("Not logged in") || stripped.includes("Please run /login") || stripped.includes("please log in") || stripped.includes("run /login")) {
925
+ if (stripped.includes("No API key") || stripped.includes("API key not found") || stripped.includes("ANTHROPIC_API_KEY") || stripped.includes("OPENAI_API_KEY") || stripped.includes("Missing API key")) {
836
926
  return {
837
927
  required: true,
838
- type: "cli_auth",
839
- instructions: 'Claude Code requires authentication. Run "claude login" in your terminal.'
928
+ type: "api_key",
929
+ instructions: "Set ANTHROPIC_API_KEY or OPENAI_API_KEY environment variable"
840
930
  };
841
931
  }
842
- if (stripped.includes("API key not found") || stripped.includes("ANTHROPIC_API_KEY") || stripped.includes("authentication required") || stripped.includes("Please sign in") || stripped.includes("Invalid API key")) {
932
+ if (stripped.includes("Invalid API key") || stripped.includes("Authentication failed") || stripped.includes("Unauthorized")) {
843
933
  return {
844
934
  required: true,
845
935
  type: "api_key",
846
- instructions: "Set ANTHROPIC_API_KEY environment variable or provide credentials in adapterConfig"
936
+ instructions: "API key is invalid - please check your credentials"
847
937
  };
848
938
  }
849
- if (stripped.includes("Open this URL") || stripped.includes("browser to authenticate")) {
939
+ if (/login to openrouter or create a free account/i.test(stripped)) {
940
+ return {
941
+ required: true,
942
+ type: "oauth",
943
+ instructions: "Aider offering OpenRouter OAuth login \u2014 provide API keys to skip"
944
+ };
945
+ }
946
+ if (/please open this url in your browser to connect aider with openrouter/i.test(
947
+ stripped
948
+ ) || /waiting up to 5 minutes for you to finish in the browser/i.test(stripped)) {
850
949
  const urlMatch = stripped.match(/https?:\/\/[^\s]+/);
851
950
  return {
852
951
  required: true,
853
952
  type: "browser",
854
953
  url: urlMatch ? urlMatch[0] : void 0,
855
- instructions: "Browser authentication required"
954
+ instructions: "Complete OpenRouter authentication in browser"
856
955
  };
857
956
  }
858
957
  return { required: false };
859
958
  }
860
959
  /**
861
- * Detect blocking prompts specific to Claude Code CLI
960
+ * Detect blocking prompts specific to Aider CLI.
961
+ * Source: io.py, onboarding.py, base_coder.py, report.py
862
962
  */
863
963
  detectBlockingPrompt(output) {
864
964
  const stripped = this.stripAnsi(output);
865
- const marker = this.getLatestHookMarker(stripped);
866
965
  const loginDetection = this.detectLogin(output);
867
966
  if (loginDetection.required) {
868
967
  return {
@@ -874,241 +973,116 @@ jq -nc --arg event "${"$"}EVENT" --arg notification_type "${"$"}NOTIFICATION
874
973
  instructions: loginDetection.instructions
875
974
  };
876
975
  }
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
- }
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)) {
899
- return {
900
- detected: true,
901
- type: "config",
902
- prompt: "Claude session survey",
903
- options: ["1", "2", "3", "0"],
904
- suggestedResponse: "0",
905
- canAutoRespond: true,
906
- instructions: "Optional survey prompt; reply 0 to dismiss"
907
- };
908
- }
909
- if (/enter\/tab\/space to toggle.*esc to cancel|enter to confirm.*esc to cancel|esc to close/i.test(stripped)) {
910
- return {
911
- detected: true,
912
- type: "config",
913
- prompt: "Claude dialog awaiting navigation",
914
- options: ["keys:enter", "keys:esc", "keys:down,enter"],
915
- suggestedResponse: "keys:esc",
916
- canAutoRespond: false,
917
- instructions: "Use Enter/Esc or arrow keys to navigate this dialog"
918
- };
919
- }
920
- if (/press .* to navigate .* enter .* esc|use (?:arrow|↑↓) keys|enter to select|esc to (?:go back|close|cancel)/i.test(stripped) || /(?:^|\n)\s*(?:❯|>)\s*\/(?:agents|chrome|config|tasks|skills|remote-env)\b/im.test(stripped)) {
921
- return {
922
- detected: true,
923
- type: "config",
924
- prompt: "Claude menu navigation required",
925
- options: ["keys:esc", "keys:enter", "keys:down,enter"],
926
- suggestedResponse: "keys:esc",
927
- canAutoRespond: false,
928
- instructions: "Claude is showing an interactive menu; use arrow keys + Enter or Esc"
929
- };
930
- }
931
- if (/Do you want to|wants? (your )?permission|needs your permission/i.test(stripped)) {
932
- return {
933
- detected: true,
934
- type: "permission",
935
- prompt: "Claude tool permission",
936
- suggestedResponse: "keys:enter",
937
- canAutoRespond: true,
938
- instructions: "Claude is asking permission to use a tool"
939
- };
940
- }
941
- if (/choose.*model|select.*model|available models/i.test(stripped) && /\d+\)|claude-/i.test(stripped)) {
976
+ if (/select.*model|choose.*model|which model/i.test(stripped)) {
942
977
  return {
943
978
  detected: true,
944
979
  type: "model_select",
945
- prompt: "Claude model selection",
946
- canAutoRespond: false,
947
- instructions: "Please select a Claude model or set ANTHROPIC_MODEL env var"
948
- };
949
- }
950
- if (/which.*tier|select.*plan|api.*tier/i.test(stripped)) {
951
- return {
952
- detected: true,
953
- type: "config",
954
- prompt: "API tier selection",
980
+ prompt: "Model selection required",
955
981
  canAutoRespond: false,
956
- instructions: "Please select an API tier"
982
+ instructions: "Please select a model or set AIDER_MODEL env var"
957
983
  };
958
984
  }
959
- if (/welcome to claude|first time setup|initial configuration/i.test(stripped)) {
985
+ if (/please answer with one of:/i.test(stripped)) {
960
986
  return {
961
987
  detected: true,
962
- type: "config",
963
- prompt: "First-time setup",
988
+ type: "unknown",
989
+ prompt: "Invalid confirmation input",
964
990
  canAutoRespond: false,
965
- instructions: "Claude Code requires initial configuration"
991
+ instructions: "Aider received an invalid response to a confirmation prompt"
966
992
  };
967
993
  }
968
- if (/allow.*access|grant.*permission|access to .* files/i.test(stripped) && /\[y\/n\]/i.test(stripped)) {
994
+ if (/delete|remove|overwrite/i.test(stripped) && (/\[y\/n\]/i.test(stripped) || /\(Y\)es\/\(N\)o/i.test(stripped))) {
969
995
  return {
970
996
  detected: true,
971
997
  type: "permission",
972
- prompt: "File/directory access permission",
998
+ prompt: "Destructive operation confirmation",
973
999
  options: ["y", "n"],
974
- suggestedResponse: "y",
975
- canAutoRespond: true,
976
- instructions: "Claude Code requesting file access permission"
1000
+ canAutoRespond: false,
1001
+ instructions: "Aider is asking to perform a potentially destructive operation"
977
1002
  };
978
1003
  }
979
- if (this.detectReady(output) || this.detectTaskComplete(output)) {
980
- return { detected: false };
981
- }
982
- if (/❯/.test(stripped.slice(-300))) {
983
- return { detected: false };
984
- }
985
1004
  return super.detectBlockingPrompt(output);
986
1005
  }
987
1006
  /**
988
- * Detect if Claude Code is actively loading/processing.
1007
+ * Detect if Aider is actively loading/processing.
989
1008
  *
990
1009
  * Patterns from: AGENT_LOADING_STATUS_PATTERNS.json
991
- * - claude_active_reading_files: "Reading N files…"
992
- * - General: "esc to interrupt" spinner status line
1010
+ * - aider_active_waiting_model: "Waiting for <model>"
1011
+ * - aider_active_waiting_llm_default: "Waiting for LLM"
1012
+ * - aider_active_generating_commit_message: "Generating commit message with ..."
993
1013
  */
994
1014
  detectLoading(output) {
995
1015
  const stripped = this.stripAnsi(output);
996
- const marker = this.getLatestHookMarker(stripped);
997
1016
  const tail = stripped.slice(-500);
998
- if (marker?.event === "PreToolUse") {
999
- return true;
1000
- }
1001
- if (/esc\s+to\s+interrupt/i.test(tail)) {
1017
+ if (/Waiting\s+for\s+(?:LLM|[A-Za-z0-9_./:@-]+)/i.test(tail)) {
1002
1018
  return true;
1003
1019
  }
1004
- if (/Reading\s+\d+\s+files/i.test(tail)) {
1020
+ if (/Generating\s+commit\s+message\s+with\s+/i.test(tail)) {
1005
1021
  return true;
1006
1022
  }
1007
1023
  return false;
1008
1024
  }
1009
1025
  /**
1010
- * Detect if an external tool/process is running within the Claude session.
1011
- *
1012
- * Claude Code can launch external tools (browser, bash, Node, Python, etc.)
1013
- * that show status lines like "Claude in Chrome[javascript_tool]" or
1014
- * "[bash_tool]", "[python_tool]", etc.
1015
- *
1016
- * When detected, stall detection is suppressed and the UI can display
1017
- * which tool is active.
1018
- */
1019
- detectToolRunning(output) {
1020
- const stripped = this.stripAnsi(output);
1021
- const marker = this.getLatestHookMarker(stripped);
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
- }
1029
- const contextualMatch = tail.match(/Claude\s+in\s+([A-Za-z0-9._-]+)\s*\[(\w+_tool)\]/i);
1030
- if (contextualMatch) {
1031
- const appName = contextualMatch[1];
1032
- const toolType = contextualMatch[2].toLowerCase();
1033
- const friendlyName = toolType.replace(/_tool$/i, "");
1034
- return { toolName: friendlyName, description: `${appName} (${toolType})` };
1035
- }
1036
- const toolMatch = tail.match(/\[(\w+_tool)\]/i);
1037
- if (toolMatch) {
1038
- const toolType = toolMatch[1].toLowerCase();
1039
- const friendlyName = toolType.replace(/_tool$/i, "");
1040
- return { toolName: friendlyName, description: toolType };
1041
- }
1042
- return null;
1043
- }
1044
- /**
1045
- * Detect task completion for Claude Code.
1026
+ * Detect task completion for Aider.
1046
1027
  *
1047
- * High-confidence pattern: turn duration summary + idle prompt.
1048
- * Claude Code shows "<Verb> for Xm Ys" (e.g. "Cooked for 3m 12s")
1049
- * when a turn completes, followed by the ❯ input prompt.
1028
+ * High-confidence patterns:
1029
+ * - "Aider is waiting for your input" notification (bell message)
1030
+ * - Edit-format mode prompts (ask>, code>, architect>) after output
1050
1031
  *
1051
1032
  * Patterns from: AGENT_LOADING_STATUS_PATTERNS.json
1052
- * - claude_completed_turn_duration
1053
- * - claude_completed_turn_duration_custom_verb
1033
+ * - aider_completed_llm_response_ready
1054
1034
  */
1055
1035
  detectTaskComplete(output) {
1056
1036
  const stripped = this.stripAnsi(output);
1057
- const marker = this.getLatestHookMarker(stripped);
1058
- if (!stripped.trim()) return false;
1059
- if (marker?.event === "TaskCompleted") {
1037
+ if (/Aider\s+is\s+waiting\s+for\s+your\s+input/.test(stripped)) {
1060
1038
  return true;
1061
1039
  }
1062
- if (marker?.event === "Notification" && marker.notification_type === "idle_prompt") {
1063
- return true;
1040
+ const hasPrompt = /(?:(?:ask|code|architect)(?:\s+multi)?)?>\s*$/m.test(
1041
+ stripped
1042
+ );
1043
+ if (hasPrompt) {
1044
+ const hasEditMarkers = /Applied edit to|Commit [a-f0-9]+|wrote to|Updated/i.test(stripped);
1045
+ const hasTokenUsage = /Tokens:|Cost:/i.test(stripped);
1046
+ if (hasEditMarkers || hasTokenUsage) {
1047
+ return true;
1048
+ }
1064
1049
  }
1065
- if (/trust.*directory|do you want to|needs? your permission/i.test(stripped)) {
1050
+ return false;
1051
+ }
1052
+ detectReady(output) {
1053
+ const stripped = this.stripAnsi(output);
1054
+ if (/login to openrouter/i.test(stripped) || /open this url in your browser/i.test(stripped) || /waiting up to 5 minutes/i.test(stripped)) {
1066
1055
  return false;
1067
1056
  }
1068
- const hasDuration = /[A-Z][A-Za-z' -]{2,40}\s+for\s+\d+(?:h\s+\d{1,2}m\s+\d{1,2}s|m\s+\d{1,2}s|s)/.test(stripped);
1069
- const tail = stripped.slice(-300);
1070
- const hasIdlePrompt = /❯/.test(tail);
1071
- if (hasDuration && hasIdlePrompt) {
1057
+ if (/(?:ask|code|architect|help)(?:\s+multi)?>\s*$/m.test(stripped) || /^multi>\s*$/m.test(stripped)) {
1072
1058
  return true;
1073
1059
  }
1074
- if (hasIdlePrompt && stripped.includes("for shortcuts")) {
1060
+ if (/^Aider v\d+/m.test(stripped)) {
1075
1061
  return true;
1076
1062
  }
1077
- return false;
1078
- }
1079
- detectReady(output) {
1080
- const stripped = this.stripAnsi(output);
1081
- const marker = this.getLatestHookMarker(stripped);
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
- }
1091
- if (/trust.*directory|do you want to|needs? your permission/i.test(stripped)) {
1092
- return false;
1063
+ if (/^(?:Readonly|Editable):/m.test(stripped)) {
1064
+ return true;
1093
1065
  }
1094
- const tail = stripped.slice(-300);
1095
- const hasConversationalReadyText = stripped.includes("How can I help") || stripped.includes("What would you like");
1096
- const hasLegacyPrompt = /claude>/i.test(tail);
1097
- const hasShortcutsHint = stripped.includes("for shortcuts");
1098
- return hasConversationalReadyText || hasLegacyPrompt || hasShortcutsHint;
1066
+ return (
1067
+ // Legacy prompt patterns
1068
+ stripped.includes("aider>") || /Added.*to the chat/i.test(stripped) || />\s*$/.test(stripped)
1069
+ );
1099
1070
  }
1100
1071
  parseOutput(output) {
1101
- const withoutHookMarkers = this.stripHookMarkers(output);
1102
- const stripped = this.stripAnsi(withoutHookMarkers);
1072
+ const stripped = this.stripAnsi(output);
1103
1073
  const isComplete = this.isResponseComplete(stripped);
1104
1074
  if (!isComplete) {
1105
1075
  return null;
1106
1076
  }
1107
1077
  const isQuestion = this.containsQuestion(stripped);
1108
- const content = this.extractContent(stripped, /^.*>\s*/gm);
1078
+ let content = this.extractContent(stripped, /^.*aider>\s*/gim);
1079
+ content = content.replace(
1080
+ /^(Added|Removed|Created|Updated) .+ (to|from) the chat\.?$/gm,
1081
+ ""
1082
+ );
1109
1083
  return {
1110
1084
  type: isQuestion ? "question" : "response",
1111
- content,
1085
+ content: content.trim(),
1112
1086
  isComplete: true,
1113
1087
  isQuestion,
1114
1088
  metadata: {
@@ -1116,90 +1090,154 @@ jq -nc --arg event "${"$"}EVENT" --arg notification_type "${"$"}NOTIFICATION
1116
1090
  }
1117
1091
  };
1118
1092
  }
1119
- getPromptPattern() {
1120
- return /claude>\s*$/i;
1121
- }
1122
- getHealthCheckCommand() {
1123
- return "claude --version";
1124
- }
1093
+ /**
1094
+ * Detect exit conditions specific to Aider.
1095
+ * Source: base_coder.py:994, base_coder.py:998, report.py:77, versioncheck.py:58
1096
+ */
1125
1097
  detectExit(output) {
1126
1098
  const stripped = this.stripAnsi(output);
1127
- const marker = this.getLatestHookMarker(stripped);
1128
- if (marker?.event === "SessionEnd") {
1129
- return { exited: true, code: 0 };
1099
+ if (/\^C again to exit/i.test(stripped) || /\^C KeyboardInterrupt/i.test(stripped)) {
1100
+ return { exited: true, code: 130 };
1101
+ }
1102
+ if (/re-run aider to use new version/i.test(stripped)) {
1103
+ return {
1104
+ exited: true,
1105
+ code: 0,
1106
+ error: "Aider updated \u2014 restart required"
1107
+ };
1130
1108
  }
1131
1109
  return super.detectExit(output);
1132
1110
  }
1111
+ getPromptPattern() {
1112
+ return /(?:ask|code|architect|help|aider|multi)(?:\s+multi)?>\s*$/i;
1113
+ }
1114
+ getHealthCheckCommand() {
1115
+ return "aider --version";
1116
+ }
1133
1117
  };
1134
1118
 
1135
- // src/gemini-adapter.ts
1136
- var GEMINI_HOOK_MARKER_PREFIX = "PARALLAX_GEMINI_HOOK";
1137
- var GeminiAdapter = class extends BaseCodingAdapter {
1138
- adapterType = "gemini";
1139
- displayName = "Google Gemini";
1119
+ // src/claude-adapter.ts
1120
+ var CLAUDE_HOOK_MARKER_PREFIX = "PARALLAX_CLAUDE_HOOK";
1121
+ var ClaudeAdapter = class extends BaseCodingAdapter {
1122
+ adapterType = "claude";
1123
+ displayName = "Claude Code";
1124
+ /** Heaviest TUI — status bar, shortcuts, update notices, /ide suggestions.
1125
+ * 3000ms needed because detectReady fires early during boot rendering. */
1126
+ readySettleMs = 3e3;
1140
1127
  installation = {
1141
- command: "npm install -g @google/gemini-cli",
1128
+ command: "npm install -g @anthropic-ai/claude-code",
1142
1129
  alternatives: [
1143
- "See documentation for latest installation method"
1130
+ "npx @anthropic-ai/claude-code (run without installing)",
1131
+ "brew install claude-code (macOS with Homebrew)"
1144
1132
  ],
1145
- docsUrl: "https://github.com/google-gemini/gemini-cli#installation"
1133
+ docsUrl: "https://docs.anthropic.com/en/docs/claude-code",
1134
+ minVersion: "1.0.0"
1146
1135
  };
1147
1136
  /**
1148
- * Auto-response rules for Gemini CLI.
1149
- * Gemini uses Ink/React TUI with arrow-key radio menus.
1150
- * Source: FolderTrustDialog.tsx, MultiFolderTrustDialog.tsx, CloudFreePrivacyNotice.tsx
1137
+ * Auto-response rules for Claude Code CLI.
1138
+ * These handle common text-based [y/n] prompts that can be safely auto-responded.
1139
+ * Explicit responseType: 'text' prevents the usesTuiMenus default from kicking in.
1151
1140
  */
1152
1141
  autoResponseRules = [
1153
1142
  {
1154
- pattern: /do.?you.?trust.?this.?folder|trust.?folder|trust.?parent.?folder/i,
1155
- type: "permission",
1143
+ pattern: /choose\s+the\s+text\s+style\s+that\s+looks\s+best\s+with\s+your\s+terminal|syntax\s+theme:/i,
1144
+ type: "config",
1156
1145
  response: "",
1157
1146
  responseType: "keys",
1158
1147
  keys: ["enter"],
1159
- description: "Trust current folder (default selection in radio menu)",
1148
+ description: "Accept Claude first-run theme/style prompt",
1160
1149
  safe: true,
1161
1150
  once: true
1162
1151
  },
1163
1152
  {
1164
- pattern: /trust.?the.?following.?folders.*(added|workspace)/i,
1153
+ pattern: /trust.*(?:folder|directory)|safety.?check|project.you.created|(?:Yes|Allow).*(?:No|Deny).*(?:Enter|Return)/i,
1165
1154
  type: "permission",
1166
1155
  response: "",
1167
1156
  responseType: "keys",
1168
1157
  keys: ["enter"],
1169
- description: "Trust multiple folders being added to workspace",
1158
+ description: "Accept trust prompt for working directory",
1170
1159
  safe: true,
1171
1160
  once: true
1172
1161
  },
1173
1162
  {
1174
- pattern: /allow.?google.?to.?use.?this.?data/i,
1175
- type: "config",
1163
+ pattern: /wants? (?:your )?permission|needs your permission|(?:Allow|Approve)\s[\s\S]{0,50}(?:Deny|Don't allow)/i,
1164
+ type: "permission",
1176
1165
  response: "",
1177
1166
  responseType: "keys",
1178
- keys: ["down", "enter"],
1179
- description: 'Decline Google data collection (select "No")',
1167
+ keys: ["enter"],
1168
+ description: "Auto-approve tool permission prompts (file access, MCP tools, etc.)",
1169
+ safe: true,
1170
+ once: true
1171
+ },
1172
+ {
1173
+ pattern: /update available.*\[y\/n\]/i,
1174
+ type: "update",
1175
+ response: "n",
1176
+ responseType: "text",
1177
+ description: "Decline Claude Code update to continue execution",
1178
+ safe: true
1179
+ },
1180
+ {
1181
+ pattern: /new version.*available.*\[y\/n\]/i,
1182
+ type: "update",
1183
+ response: "n",
1184
+ responseType: "text",
1185
+ description: "Decline version upgrade prompt",
1186
+ safe: true
1187
+ },
1188
+ {
1189
+ pattern: /would you like to enable.*telemetry.*\[y\/n\]/i,
1190
+ type: "config",
1191
+ response: "n",
1192
+ responseType: "text",
1193
+ description: "Decline telemetry prompt",
1194
+ safe: true
1195
+ },
1196
+ {
1197
+ pattern: /send anonymous usage data.*\[y\/n\]/i,
1198
+ type: "config",
1199
+ response: "n",
1200
+ responseType: "text",
1201
+ description: "Decline anonymous usage data",
1202
+ safe: true
1203
+ },
1204
+ {
1205
+ pattern: /how is claude doing this session\?\s*\(optional\)|1:\s*bad\s+2:\s*fine\s+3:\s*good\s+0:\s*dismiss/i,
1206
+ type: "config",
1207
+ response: "0",
1208
+ responseType: "text",
1209
+ description: "Dismiss optional Claude session survey",
1180
1210
  safe: true,
1181
1211
  once: true
1212
+ },
1213
+ {
1214
+ pattern: /continue without.*\[y\/n\]/i,
1215
+ type: "config",
1216
+ response: "y",
1217
+ responseType: "text",
1218
+ description: "Continue without optional feature",
1219
+ safe: true
1182
1220
  }
1183
1221
  ];
1184
1222
  getWorkspaceFiles() {
1185
1223
  return [
1186
1224
  {
1187
- relativePath: "GEMINI.md",
1225
+ relativePath: "CLAUDE.md",
1188
1226
  description: "Project-level instructions read automatically on startup",
1189
1227
  autoLoaded: true,
1190
1228
  type: "memory",
1191
1229
  format: "markdown"
1192
1230
  },
1193
1231
  {
1194
- relativePath: ".gemini/settings.json",
1195
- description: "Project-scoped settings (tool permissions, sandbox config)",
1232
+ relativePath: ".claude/settings.json",
1233
+ description: "Project-scoped settings (allowed tools, permissions)",
1196
1234
  autoLoaded: true,
1197
1235
  type: "config",
1198
1236
  format: "json"
1199
1237
  },
1200
1238
  {
1201
- relativePath: ".gemini/styles",
1202
- description: "Custom style/persona definitions directory",
1239
+ relativePath: ".claude/commands",
1240
+ description: "Custom slash commands directory",
1203
1241
  autoLoaded: false,
1204
1242
  type: "config",
1205
1243
  format: "markdown"
@@ -1208,22 +1246,27 @@ var GeminiAdapter = class extends BaseCodingAdapter {
1208
1246
  }
1209
1247
  getRecommendedModels(_credentials) {
1210
1248
  return {
1211
- powerful: "gemini-3-pro",
1212
- fast: "gemini-3-flash"
1249
+ powerful: "claude-sonnet-4-20250514",
1250
+ fast: "claude-haiku-4-5-20251001"
1213
1251
  };
1214
1252
  }
1215
1253
  getCommand() {
1216
- return "gemini";
1254
+ return "claude";
1217
1255
  }
1218
1256
  getArgs(config) {
1219
1257
  const args = [];
1258
+ const adapterConfig = config.adapterConfig;
1220
1259
  if (!this.isInteractive(config)) {
1221
- args.push("--non-interactive");
1222
- args.push("--output-format", "text");
1260
+ args.push("--print");
1223
1261
  if (config.workdir) {
1224
1262
  args.push("--cwd", config.workdir);
1225
1263
  }
1226
1264
  }
1265
+ if (adapterConfig?.resume) {
1266
+ args.push("--resume", adapterConfig.resume);
1267
+ } else if (adapterConfig?.continue) {
1268
+ args.push("--continue");
1269
+ }
1227
1270
  const approvalConfig = this.getApprovalConfig(config);
1228
1271
  if (approvalConfig) {
1229
1272
  args.push(...approvalConfig.cliFlags);
@@ -1234,34 +1277,39 @@ var GeminiAdapter = class extends BaseCodingAdapter {
1234
1277
  const env = {};
1235
1278
  const credentials = this.getCredentials(config);
1236
1279
  const adapterConfig = config.adapterConfig;
1237
- if (credentials.googleKey) {
1238
- env.GOOGLE_API_KEY = credentials.googleKey;
1239
- env.GEMINI_API_KEY = credentials.googleKey;
1280
+ if (credentials.anthropicKey) {
1281
+ env.ANTHROPIC_API_KEY = credentials.anthropicKey;
1240
1282
  }
1241
- if (config.env?.GEMINI_MODEL) {
1242
- env.GEMINI_MODEL = config.env.GEMINI_MODEL;
1283
+ if (config.env?.ANTHROPIC_MODEL) {
1284
+ env.ANTHROPIC_MODEL = config.env.ANTHROPIC_MODEL;
1243
1285
  }
1244
1286
  if (!this.isInteractive(config)) {
1245
- env.NO_COLOR = "1";
1287
+ env.CLAUDE_CODE_DISABLE_INTERACTIVE = "true";
1246
1288
  }
1247
- if (adapterConfig?.geminiHookTelemetry) {
1248
- env.PARALLAX_GEMINI_HOOK_TELEMETRY = "1";
1249
- env.PARALLAX_GEMINI_HOOK_MARKER_PREFIX = adapterConfig.geminiHookMarkerPrefix || GEMINI_HOOK_MARKER_PREFIX;
1289
+ if (adapterConfig?.claudeHookTelemetry) {
1290
+ env.PARALLAX_CLAUDE_HOOK_TELEMETRY = "1";
1291
+ env.PARALLAX_CLAUDE_HOOK_MARKER_PREFIX = adapterConfig.claudeHookMarkerPrefix || CLAUDE_HOOK_MARKER_PREFIX;
1250
1292
  }
1251
1293
  return env;
1252
1294
  }
1253
1295
  getHookTelemetryProtocol(options) {
1254
1296
  if (options?.httpUrl) {
1255
- const sessionHeader = options.sessionId ? ` -H 'X-Parallax-Session-Id: ${options.sessionId}'` : "";
1256
- const curlCommand = `bash -c 'curl -sf -X POST "${options.httpUrl}" -H "Content-Type: application/json"${sessionHeader} -d @- --max-time 4 2>/dev/null || echo "{\\"continue\\":true}"'`;
1257
- const hookEntry2 = [{ matcher: "", hooks: [{ type: "command", command: curlCommand, timeout: 5e3 }] }];
1258
- const hookEntryNoMatcher = [{ hooks: [{ type: "command", command: curlCommand, timeout: 5e3 }] }];
1297
+ const httpHookBase = {
1298
+ type: "http",
1299
+ url: options.httpUrl,
1300
+ timeout: 5
1301
+ };
1302
+ if (options.sessionId) {
1303
+ httpHookBase.headers = { "X-Parallax-Session-Id": options.sessionId };
1304
+ }
1305
+ const hookEntry2 = [{ matcher: "", hooks: [{ ...httpHookBase }] }];
1306
+ const hookEntryNoMatcher = [{ hooks: [{ ...httpHookBase }] }];
1259
1307
  const settingsHooks2 = {
1260
- BeforeTool: hookEntry2,
1261
- AfterTool: hookEntry2,
1262
- AfterAgent: hookEntryNoMatcher,
1263
- SessionEnd: hookEntryNoMatcher,
1264
- Notification: hookEntry2
1308
+ PermissionRequest: hookEntryNoMatcher,
1309
+ PreToolUse: hookEntry2,
1310
+ Stop: hookEntryNoMatcher,
1311
+ Notification: hookEntry2,
1312
+ TaskCompleted: hookEntryNoMatcher
1265
1313
  };
1266
1314
  return {
1267
1315
  markerPrefix: "",
@@ -1270,14 +1318,16 @@ var GeminiAdapter = class extends BaseCodingAdapter {
1270
1318
  settingsHooks: settingsHooks2
1271
1319
  };
1272
1320
  }
1273
- const markerPrefix = options?.markerPrefix || GEMINI_HOOK_MARKER_PREFIX;
1274
- const scriptPath = options?.scriptPath || ".gemini/hooks/parallax-hook-telemetry.sh";
1275
- const scriptCommand = `"${"$"}GEMINI_PROJECT_ROOT"/${scriptPath}`;
1276
- const hookEntry = [{ matcher: "", hooks: [{ type: "command", command: scriptCommand }] }];
1321
+ const markerPrefix = options?.markerPrefix || CLAUDE_HOOK_MARKER_PREFIX;
1322
+ const scriptPath = options?.scriptPath || ".claude/hooks/parallax-hook-telemetry.sh";
1323
+ const scriptCommand = `"${"$"}CLAUDE_PROJECT_DIR"/${scriptPath}`;
1324
+ const hookEntry = [
1325
+ { matcher: "", hooks: [{ type: "command", command: scriptCommand }] }
1326
+ ];
1277
1327
  const settingsHooks = {
1278
1328
  Notification: hookEntry,
1279
- BeforeTool: hookEntry,
1280
- AfterAgent: hookEntry,
1329
+ PreToolUse: hookEntry,
1330
+ TaskCompleted: hookEntry,
1281
1331
  SessionEnd: hookEntry
1282
1332
  };
1283
1333
  const scriptContent = `#!/usr/bin/env bash
@@ -1287,32 +1337,21 @@ INPUT="$(cat)"
1287
1337
  [ -z "${"$"}INPUT" ] && exit 0
1288
1338
 
1289
1339
  if ! command -v jq >/dev/null 2>&1; then
1290
- # Valid no-op response
1291
- printf '%s
1292
- ' '{"continue":true}'
1293
1340
  exit 0
1294
1341
  fi
1295
1342
 
1296
- EVENT="$(printf '%s' "${"$"}INPUT" | jq -r '.hookEventName // .hook_event_name // empty')"
1297
- [ -z "${"$"}EVENT" ] && { printf '%s
1298
- ' '{"continue":true}'; exit 0; }
1343
+ EVENT="$(printf '%s' "${"$"}INPUT" | jq -r '.hook_event_name // empty')"
1344
+ [ -z "${"$"}EVENT" ] && exit 0
1299
1345
 
1300
- NOTIFICATION_TYPE="$(printf '%s' "${"$"}INPUT" | jq -r '.notificationType // .notification_type // empty')"
1301
- TOOL_NAME="$(printf '%s' "${"$"}INPUT" | jq -r '.toolName // .tool_name // empty')"
1346
+ NOTIFICATION_TYPE="$(printf '%s' "${"$"}INPUT" | jq -r '.notification_type // empty')"
1347
+ TOOL_NAME="$(printf '%s' "${"$"}INPUT" | jq -r '.tool_name // empty')"
1302
1348
  MESSAGE="$(printf '%s' "${"$"}INPUT" | jq -r '.message // empty')"
1303
1349
 
1304
- PAYLOAD="$(jq -nc \\
1305
- --arg event "${"$"}EVENT" \\
1306
- --arg notification_type "${"$"}NOTIFICATION_TYPE" \\
1307
- --arg tool_name "${"$"}TOOL_NAME" \\
1308
- --arg message "${"$"}MESSAGE" \\
1309
- '({event: $event}
1350
+ printf '%s ' '${markerPrefix}'
1351
+ jq -nc --arg event "${"$"}EVENT" --arg notification_type "${"$"}NOTIFICATION_TYPE" --arg tool_name "${"$"}TOOL_NAME" --arg message "${"$"}MESSAGE" '({event: $event}
1310
1352
  + (if $notification_type != "" then {notification_type: $notification_type} else {} end)
1311
1353
  + (if $tool_name != "" then {tool_name: $tool_name} else {} end)
1312
- + (if $message != "" then {message: $message} else {} end))')"
1313
-
1314
- MARKER="${markerPrefix} ${"$"}PAYLOAD"
1315
- jq -nc --arg m "${"$"}MARKER" '{continue: true, suppressOutput: true, systemMessage: $m}'
1354
+ + (if $message != "" then {message: $message} else {} end))'
1316
1355
  `;
1317
1356
  return {
1318
1357
  markerPrefix,
@@ -1327,7 +1366,7 @@ jq -nc --arg m "${"$"}MARKER" '{continue: true, suppressOutput: true, systemMess
1327
1366
  let match;
1328
1367
  while ((match = markerRegex.exec(output)) !== null) {
1329
1368
  const markerToken = match[1];
1330
- if (!markerToken.includes("GEMINI_HOOK")) {
1369
+ if (!markerToken.includes("CLAUDE_HOOK")) {
1331
1370
  continue;
1332
1371
  }
1333
1372
  const payload = match[2];
@@ -1351,208 +1390,288 @@ jq -nc --arg m "${"$"}MARKER" '{continue: true, suppressOutput: true, systemMess
1351
1390
  return markers.length > 0 ? markers[markers.length - 1] : null;
1352
1391
  }
1353
1392
  stripHookMarkers(output) {
1354
- return output.replace(/(?:^|\n)\s*[A-Z0-9_]*GEMINI_HOOK[A-Z0-9_]*\s+\{[^\n\r]+\}\s*/g, "\n");
1393
+ return output.replace(
1394
+ /(?:^|\n)\s*[A-Z0-9_]*CLAUDE_HOOK[A-Z0-9_]*\s+\{[^\n\r]+\}\s*/g,
1395
+ "\n"
1396
+ );
1355
1397
  }
1356
1398
  detectLogin(output) {
1357
1399
  const stripped = this.stripAnsi(output);
1358
- 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")) {
1400
+ if (stripped.includes("Not logged in") || stripped.includes("Please run /login") || stripped.includes("please log in") || stripped.includes("run /login")) {
1359
1401
  return {
1360
1402
  required: true,
1361
- type: "api_key",
1362
- instructions: "Set GOOGLE_API_KEY or GEMINI_API_KEY environment variable"
1403
+ type: "cli_auth",
1404
+ instructions: 'Claude Code requires authentication. Run "claude login" in your terminal.'
1363
1405
  };
1364
1406
  }
1365
- if (/enter.?gemini.?api.?key/i.test(stripped)) {
1407
+ if (stripped.includes("API key not found") || stripped.includes("ANTHROPIC_API_KEY") || stripped.includes("authentication required") || stripped.includes("Please sign in") || stripped.includes("Invalid API key")) {
1366
1408
  return {
1367
1409
  required: true,
1368
1410
  type: "api_key",
1369
- instructions: "Enter a Gemini API key or set GEMINI_API_KEY environment variable"
1370
- };
1371
- }
1372
- if (/how.?would.?you.?like.?to.?authenticate/i.test(stripped) || /get.?started/i.test(stripped) && /login.?with.?google|use.?gemini.?api.?key|vertex/i.test(stripped)) {
1373
- return {
1374
- required: true,
1375
- type: "oauth",
1376
- instructions: "Gemini CLI authentication required \u2014 select an auth method"
1377
- };
1378
- }
1379
- if (/waiting.?for.?auth/i.test(stripped)) {
1380
- return {
1381
- required: true,
1382
- type: "oauth",
1383
- instructions: "Waiting for browser authentication to complete"
1411
+ instructions: "Set ANTHROPIC_API_KEY environment variable or provide credentials in adapterConfig"
1384
1412
  };
1385
1413
  }
1386
- if (stripped.includes("Sign in with Google") || stripped.includes("OAuth") || stripped.includes("accounts.google.com")) {
1414
+ if (stripped.includes("Open this URL") || stripped.includes("browser to authenticate")) {
1387
1415
  const urlMatch = stripped.match(/https?:\/\/[^\s]+/);
1388
- return {
1389
- required: true,
1390
- type: "oauth",
1391
- url: urlMatch ? urlMatch[0] : "https://accounts.google.com",
1392
- instructions: "Google OAuth authentication required"
1393
- };
1394
- }
1395
- if (stripped.includes("Application Default Credentials") || stripped.includes("gcloud auth")) {
1396
1416
  return {
1397
1417
  required: true,
1398
1418
  type: "browser",
1399
- instructions: "Run: gcloud auth application-default login"
1419
+ url: urlMatch ? urlMatch[0] : void 0,
1420
+ instructions: "Browser authentication required"
1400
1421
  };
1401
1422
  }
1402
1423
  return { required: false };
1403
1424
  }
1425
+ /**
1426
+ * Detect blocking prompts specific to Claude Code CLI
1427
+ */
1404
1428
  detectBlockingPrompt(output) {
1405
1429
  const stripped = this.stripAnsi(output);
1406
1430
  const marker = this.getLatestHookMarker(stripped);
1407
- if (marker?.event === "Notification" && marker.notification_type === "ToolPermission") {
1431
+ if (this.detectLoading(output)) {
1432
+ return { detected: false };
1433
+ }
1434
+ const trimmedTail = stripped.slice(-200).trim();
1435
+ if (/^[a-zA-Z]{1,30}(?:…|\.{3})\s*$/.test(trimmedTail)) {
1436
+ return { detected: false };
1437
+ }
1438
+ const loginDetection = this.detectLogin(output);
1439
+ if (loginDetection.required) {
1408
1440
  return {
1409
1441
  detected: true,
1410
- type: "permission",
1411
- prompt: marker.message || "Gemini tool permission",
1412
- suggestedResponse: "keys:enter",
1413
- canAutoRespond: true,
1414
- instructions: "Gemini is asking to allow a tool action"
1442
+ type: "login",
1443
+ prompt: loginDetection.instructions,
1444
+ url: loginDetection.url,
1445
+ canAutoRespond: false,
1446
+ instructions: loginDetection.instructions
1415
1447
  };
1416
1448
  }
1417
- 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)) {
1449
+ if (marker?.event === "Notification") {
1450
+ if (marker.notification_type === "permission_prompt") {
1451
+ return {
1452
+ detected: true,
1453
+ type: "permission",
1454
+ prompt: marker.message || "Claude permission prompt",
1455
+ suggestedResponse: "keys:enter",
1456
+ canAutoRespond: true,
1457
+ instructions: "Claude is waiting for permission approval"
1458
+ };
1459
+ }
1460
+ if (marker.notification_type === "elicitation_dialog") {
1461
+ return {
1462
+ detected: true,
1463
+ type: "tool_wait",
1464
+ prompt: marker.message || "Claude elicitation dialog",
1465
+ canAutoRespond: false,
1466
+ instructions: "Claude is waiting for required user input"
1467
+ };
1468
+ }
1469
+ }
1470
+ if (/Bypass Permissions mode.*accept all responsibility/is.test(stripped) && /❯\s*1\.\s*No.*exit/i.test(stripped) && /2\.\s*Yes.*I accept/i.test(stripped)) {
1418
1471
  return {
1419
1472
  detected: true,
1420
1473
  type: "permission",
1421
- prompt: "Gemini tool execution confirmation",
1422
- suggestedResponse: "keys:enter",
1474
+ prompt: "Bypass Permissions confirmation",
1475
+ options: ["1", "2"],
1476
+ suggestedResponse: "2",
1423
1477
  canAutoRespond: true,
1424
- instructions: "Gemini is asking to apply a change (file write, shell command, etc.)"
1478
+ instructions: "Claude is asking to confirm bypass permissions mode; reply 2 to accept"
1425
1479
  };
1426
1480
  }
1427
- if (/do.?you.?want.?to.?continue\s*\([yY]\/[nN]\)\??/i.test(stripped) || /continue\??\s*\([yY]\/[nN]\)\??/i.test(stripped) || /are.?you.?sure\??\s*\([yY]\/[nN]\)\??/i.test(stripped)) {
1481
+ 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(
1482
+ stripped
1483
+ )) {
1428
1484
  return {
1429
1485
  detected: true,
1430
- type: "tool_wait",
1431
- prompt: "Interactive shell confirmation required (y/n)",
1432
- canAutoRespond: false,
1433
- instructions: "Focus shell input (Tab) and answer the y/n confirmation prompt"
1486
+ type: "config",
1487
+ prompt: "Claude session survey",
1488
+ options: ["1", "2", "3", "0"],
1489
+ suggestedResponse: "0",
1490
+ canAutoRespond: true,
1491
+ instructions: "Optional survey prompt; reply 0 to dismiss"
1434
1492
  };
1435
1493
  }
1436
- if (/Interactive\s+shell\s+awaiting\s+input/i.test(stripped)) {
1494
+ if (/enter\/tab\/space to toggle.*esc to cancel|enter to confirm.*esc to cancel|esc to close/i.test(
1495
+ stripped
1496
+ )) {
1437
1497
  return {
1438
1498
  detected: true,
1439
- type: "tool_wait",
1440
- prompt: "Gemini interactive shell needs user focus",
1499
+ type: "config",
1500
+ prompt: "Claude dialog awaiting navigation",
1501
+ options: ["keys:enter", "keys:esc", "keys:down,enter"],
1502
+ suggestedResponse: "keys:esc",
1441
1503
  canAutoRespond: false,
1442
- instructions: "Press Tab to focus the interactive shell, or wait for it to complete"
1504
+ instructions: "Use Enter/Esc or arrow keys to navigate this dialog"
1443
1505
  };
1444
1506
  }
1445
- if (/enable.?checkpointing.?to.?recover.?your.?session.?after.?a.?crash/i.test(stripped)) {
1507
+ if (/press .* to navigate .* enter .* esc|use (?:arrow|↑↓) keys|enter to select|esc to (?:go back|close|cancel)/i.test(
1508
+ stripped
1509
+ ) || /(?:^|\n)\s*(?:❯|>)\s*\/(?:agents|chrome|config|tasks|skills|remote-env)\b/im.test(
1510
+ stripped
1511
+ )) {
1446
1512
  return {
1447
1513
  detected: true,
1448
1514
  type: "config",
1449
- prompt: "Gemini checkpoint setup prompt",
1515
+ prompt: "Claude menu navigation required",
1516
+ options: ["keys:esc", "keys:enter", "keys:down,enter"],
1517
+ suggestedResponse: "keys:esc",
1450
1518
  canAutoRespond: false,
1451
- instructions: 'Respond to checkpoint setup prompt (for example: press "s" to configure or dismiss)'
1519
+ instructions: "Claude is showing an interactive menu; use arrow keys + Enter or Esc"
1452
1520
  };
1453
1521
  }
1454
- const loginDetection = this.detectLogin(output);
1455
- if (loginDetection.required) {
1522
+ if (/Do you want to|wants? (your )?permission|needs your permission/i.test(
1523
+ stripped
1524
+ )) {
1456
1525
  return {
1457
1526
  detected: true,
1458
- type: "login",
1459
- prompt: loginDetection.instructions,
1460
- url: loginDetection.url,
1461
- canAutoRespond: false,
1462
- instructions: loginDetection.instructions
1527
+ type: "permission",
1528
+ prompt: "Claude tool permission",
1529
+ suggestedResponse: "keys:enter",
1530
+ canAutoRespond: true,
1531
+ instructions: "Claude is asking permission to use a tool"
1463
1532
  };
1464
1533
  }
1465
- if (/further.?action.?is.?required/i.test(stripped) || /verify.?your.?account/i.test(stripped) || /waiting.?for.?verification/i.test(stripped)) {
1534
+ if (/choose.*model|select.*model|available models/i.test(stripped) && /\d+\)|claude-/i.test(stripped)) {
1466
1535
  return {
1467
1536
  detected: true,
1468
- type: "config",
1469
- prompt: "Account verification required",
1537
+ type: "model_select",
1538
+ prompt: "Claude model selection",
1470
1539
  canAutoRespond: false,
1471
- instructions: "Your Gemini account requires verification before continuing"
1540
+ instructions: "Please select a Claude model or set ANTHROPIC_MODEL env var"
1472
1541
  };
1473
1542
  }
1474
- if (/select.*model|choose.*model|gemini-/i.test(stripped) && /\d+\)/i.test(stripped)) {
1543
+ if (/which.*tier|select.*plan|api.*tier/i.test(stripped)) {
1475
1544
  return {
1476
1545
  detected: true,
1477
- type: "model_select",
1478
- prompt: "Gemini model selection",
1546
+ type: "config",
1547
+ prompt: "API tier selection",
1479
1548
  canAutoRespond: false,
1480
- instructions: "Please select a model or set GEMINI_MODEL env var"
1549
+ instructions: "Please select an API tier"
1481
1550
  };
1482
1551
  }
1483
- if (/select.*project|choose.*project|google cloud project/i.test(stripped)) {
1552
+ if (/welcome to claude|first time setup|initial configuration/i.test(stripped)) {
1484
1553
  return {
1485
1554
  detected: true,
1486
- type: "project_select",
1487
- prompt: "Google Cloud project selection",
1555
+ type: "config",
1556
+ prompt: "First-time setup",
1488
1557
  canAutoRespond: false,
1489
- instructions: "Please select a Google Cloud project"
1558
+ instructions: "Claude Code requires initial configuration"
1490
1559
  };
1491
1560
  }
1492
- if (/safety.*filter|content.*blocked|unsafe.*content/i.test(stripped)) {
1561
+ if (/allow.*access|grant.*permission|access to .* files/i.test(stripped) && /\[y\/n\]/i.test(stripped)) {
1493
1562
  return {
1494
1563
  detected: true,
1495
- type: "unknown",
1496
- prompt: "Safety filter triggered",
1497
- canAutoRespond: false,
1498
- instructions: "Content was blocked by safety filters"
1564
+ type: "permission",
1565
+ prompt: "File/directory access permission",
1566
+ options: ["y", "n"],
1567
+ suggestedResponse: "y",
1568
+ canAutoRespond: true,
1569
+ instructions: "Claude Code requesting file access permission"
1499
1570
  };
1500
1571
  }
1572
+ if (this.detectReady(output) || this.detectTaskComplete(output)) {
1573
+ return { detected: false };
1574
+ }
1575
+ if (/❯/.test(stripped.slice(-300))) {
1576
+ return { detected: false };
1577
+ }
1501
1578
  return super.detectBlockingPrompt(output);
1502
1579
  }
1503
1580
  /**
1504
- * Detect if Gemini CLI is actively loading/processing.
1581
+ * Detect if Claude Code is actively loading/processing.
1505
1582
  *
1506
1583
  * Patterns from: AGENT_LOADING_STATUS_PATTERNS.json
1507
- * - gemini_active_loading_line: "(esc to cancel, Xs)"
1508
- * - gemini_active_waiting_user_confirmation: "Waiting for user confirmation..."
1584
+ * - claude_active_reading_files: "Reading N files…"
1585
+ * - General: "esc to interrupt" spinner status line
1509
1586
  */
1510
1587
  detectLoading(output) {
1511
1588
  const stripped = this.stripAnsi(output);
1512
1589
  const marker = this.getLatestHookMarker(stripped);
1513
1590
  const tail = stripped.slice(-500);
1514
- if (marker?.event === "BeforeTool") {
1591
+ if (marker?.event === "PreToolUse") {
1515
1592
  return true;
1516
1593
  }
1517
- if (/esc\s+to\s+cancel/i.test(tail)) {
1594
+ if (/esc\s+to\s+interrupt/i.test(tail)) {
1518
1595
  return true;
1519
1596
  }
1520
- if (/Waiting\s+for\s+user\s+confirmation/i.test(tail)) {
1597
+ if (/Reading\s+\d+\s+files/i.test(tail)) {
1521
1598
  return true;
1522
1599
  }
1523
1600
  return false;
1524
1601
  }
1525
- detectToolRunning(output) {
1526
- const stripped = this.stripAnsi(output);
1527
- const marker = this.getLatestHookMarker(stripped);
1528
- if (marker?.event === "BeforeTool" && marker.tool_name) {
1529
- return {
1530
- toolName: marker.tool_name.toLowerCase(),
1531
- description: `${marker.tool_name} (hook)`
1532
- };
1533
- }
1602
+ /**
1603
+ * Detect if an external tool/process is running within the Claude session.
1604
+ *
1605
+ * Claude Code can launch external tools (browser, bash, Node, Python, etc.)
1606
+ * that show status lines like "Claude in Chrome[javascript_tool]" or
1607
+ * "[bash_tool]", "[python_tool]", etc.
1608
+ *
1609
+ * When detected, stall detection is suppressed and the UI can display
1610
+ * which tool is active.
1611
+ */
1612
+ detectToolRunning(output) {
1613
+ const stripped = this.stripAnsi(output);
1614
+ const marker = this.getLatestHookMarker(stripped);
1615
+ const tail = stripped.slice(-500);
1616
+ if (marker?.event === "PreToolUse" && marker.tool_name) {
1617
+ return {
1618
+ toolName: marker.tool_name.toLowerCase(),
1619
+ description: `${marker.tool_name} (hook)`
1620
+ };
1621
+ }
1622
+ const contextualMatch = tail.match(
1623
+ /Claude\s+in\s+([A-Za-z0-9._-]+)\s*\[(\w+_tool)\]/i
1624
+ );
1625
+ if (contextualMatch) {
1626
+ const appName = contextualMatch[1];
1627
+ const toolType = contextualMatch[2].toLowerCase();
1628
+ const friendlyName = toolType.replace(/_tool$/i, "");
1629
+ return {
1630
+ toolName: friendlyName,
1631
+ description: `${appName} (${toolType})`
1632
+ };
1633
+ }
1634
+ const toolMatch = tail.match(/\[(\w+_tool)\]/i);
1635
+ if (toolMatch) {
1636
+ const toolType = toolMatch[1].toLowerCase();
1637
+ const friendlyName = toolType.replace(/_tool$/i, "");
1638
+ return { toolName: friendlyName, description: toolType };
1639
+ }
1534
1640
  return null;
1535
1641
  }
1536
1642
  /**
1537
- * Detect task completion for Gemini CLI.
1643
+ * Detect task completion for Claude Code.
1538
1644
  *
1539
- * High-confidence patterns:
1540
- * - "◇ Ready" window title signal (OSC sequence, may survive ANSI stripping)
1541
- * - "Type your message" composer placeholder after agent output
1645
+ * High-confidence pattern: turn duration summary + idle prompt.
1646
+ * Claude Code shows "<Verb> for Xm Ys" (e.g. "Cooked for 3m 12s")
1647
+ * when a turn completes, followed by the ❯ input prompt.
1542
1648
  *
1543
1649
  * Patterns from: AGENT_LOADING_STATUS_PATTERNS.json
1544
- * - gemini_ready_title
1650
+ * - claude_completed_turn_duration
1651
+ * - claude_completed_turn_duration_custom_verb
1545
1652
  */
1546
1653
  detectTaskComplete(output) {
1547
1654
  const stripped = this.stripAnsi(output);
1548
1655
  const marker = this.getLatestHookMarker(stripped);
1549
- if (marker?.event === "AfterAgent") {
1656
+ if (!stripped.trim()) return false;
1657
+ if (marker?.event === "TaskCompleted") {
1550
1658
  return true;
1551
1659
  }
1552
- if (/◇\s+Ready/.test(stripped)) {
1660
+ if (marker?.event === "Notification" && marker.notification_type === "idle_prompt") {
1553
1661
  return true;
1554
1662
  }
1555
- if (/type.?your.?message/i.test(stripped)) {
1663
+ if (/trust.*directory|do you want to|needs? your permission/i.test(stripped)) {
1664
+ return false;
1665
+ }
1666
+ const hasDuration = /[A-Z][A-Za-z' -]{2,40}\s+for\s+\d+(?:h\s+\d{1,2}m\s+\d{1,2}s|m\s+\d{1,2}s|s)/.test(
1667
+ stripped
1668
+ );
1669
+ const tail = stripped.slice(-300);
1670
+ const hasIdlePrompt = /❯/.test(tail);
1671
+ if (hasDuration && hasIdlePrompt) {
1672
+ return true;
1673
+ }
1674
+ if (hasIdlePrompt && stripped.includes("for shortcuts")) {
1556
1675
  return true;
1557
1676
  }
1558
1677
  return false;
@@ -1560,27 +1679,24 @@ jq -nc --arg m "${"$"}MARKER" '{continue: true, suppressOutput: true, systemMess
1560
1679
  detectReady(output) {
1561
1680
  const stripped = this.stripAnsi(output);
1562
1681
  const marker = this.getLatestHookMarker(stripped);
1563
- if (marker?.event === "Notification" && marker.notification_type === "ToolPermission") {
1564
- return false;
1565
- }
1566
- if (marker?.event === "AfterAgent") {
1567
- return true;
1568
- }
1569
- 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);
1570
- if (hasActiveOverlay) {
1571
- return false;
1572
- }
1573
- if (/type.?your.?message/i.test(stripped)) {
1574
- return true;
1682
+ if (!stripped.trim()) return false;
1683
+ if (marker?.event === "Notification") {
1684
+ if (marker.notification_type === "permission_prompt" || marker.notification_type === "elicitation_dialog") {
1685
+ return false;
1686
+ }
1687
+ if (marker.notification_type === "idle_prompt") {
1688
+ return true;
1689
+ }
1575
1690
  }
1576
- if (/do.?you.?trust.?this.?folder/i.test(stripped) || /how.?would.?you.?like.?to.?authenticate/i.test(stripped) || /waiting.?for.?auth/i.test(stripped) || /allow.?google.?to.?use.?this.?data/i.test(stripped)) {
1691
+ if (/trust.*directory|do you want to|needs? your permission/i.test(stripped)) {
1577
1692
  return false;
1578
1693
  }
1579
- if (/^\s*[>!*]\s+/m.test(stripped) || /\(r:\)/.test(stripped)) {
1580
- return true;
1581
- }
1582
- return stripped.includes("How can I help") || stripped.includes("What would you like") || // Match "gemini> " prompt specifically, not bare ">"
1583
- /gemini>\s*$/i.test(stripped);
1694
+ const tail = stripped.slice(-300);
1695
+ const hasConversationalReadyText = stripped.includes("How can I help") || stripped.includes("What would you like");
1696
+ const hasLegacyPrompt = /claude>/i.test(tail);
1697
+ const hasShortcutsHint = stripped.includes("for shortcuts");
1698
+ const hasInteractivePromptBar = /❯\s+\S/.test(tail) && (/\/effort/i.test(stripped) || /Welcome back/i.test(stripped) || /Recent activity/i.test(stripped) || /What's new/i.test(stripped));
1699
+ return hasConversationalReadyText || hasLegacyPrompt || hasShortcutsHint || hasInteractivePromptBar;
1584
1700
  }
1585
1701
  parseOutput(output) {
1586
1702
  const withoutHookMarkers = this.stripHookMarkers(output);
@@ -1590,8 +1706,7 @@ jq -nc --arg m "${"$"}MARKER" '{continue: true, suppressOutput: true, systemMess
1590
1706
  return null;
1591
1707
  }
1592
1708
  const isQuestion = this.containsQuestion(stripped);
1593
- let content = this.extractContent(stripped, /^.*(?:gemini|>)\s*/gim);
1594
- content = content.replace(/^\[Safety[^\]]*\].*$/gm, "");
1709
+ const content = this.extractContent(stripped, /^.*>\s*/gm);
1595
1710
  return {
1596
1711
  type: isQuestion ? "question" : "response",
1597
1712
  content,
@@ -1602,57 +1717,30 @@ jq -nc --arg m "${"$"}MARKER" '{continue: true, suppressOutput: true, systemMess
1602
1717
  }
1603
1718
  };
1604
1719
  }
1605
- /**
1606
- * Detect exit conditions specific to Gemini CLI.
1607
- * Source: FolderTrustDialog.tsx:127, LogoutConfirmationDialog.tsx:64
1608
- */
1720
+ getPromptPattern() {
1721
+ return /claude>\s*$/i;
1722
+ }
1723
+ getHealthCheckCommand() {
1724
+ return "claude --version";
1725
+ }
1609
1726
  detectExit(output) {
1610
1727
  const stripped = this.stripAnsi(output);
1611
1728
  const marker = this.getLatestHookMarker(stripped);
1612
1729
  if (marker?.event === "SessionEnd") {
1613
- return {
1614
- exited: true,
1615
- code: 0
1616
- };
1617
- }
1618
- if (/folder.?trust.?level.?must.?be.?selected.*exiting/i.test(stripped)) {
1619
- return {
1620
- exited: true,
1621
- code: 1,
1622
- error: "Gemini CLI exited because no folder trust level was selected"
1623
- };
1624
- }
1625
- if (/you are now logged out/i.test(stripped)) {
1626
- return {
1627
- exited: true,
1628
- code: 0
1629
- };
1630
- }
1631
- if (/Agent\s+powering\s+down/i.test(stripped)) {
1632
- return {
1633
- exited: true,
1634
- code: 0
1635
- };
1730
+ return { exited: true, code: 0 };
1636
1731
  }
1637
1732
  return super.detectExit(output);
1638
1733
  }
1639
- getPromptPattern() {
1640
- return /gemini>\s*$/i;
1641
- }
1642
- getHealthCheckCommand() {
1643
- return "gemini --version";
1644
- }
1645
1734
  };
1646
1735
 
1647
1736
  // src/codex-adapter.ts
1648
1737
  var CodexAdapter = class extends BaseCodingAdapter {
1649
1738
  adapterType = "codex";
1650
1739
  displayName = "OpenAI Codex";
1740
+ readySettleMs = 2e3;
1651
1741
  installation = {
1652
1742
  command: "npm install -g @openai/codex",
1653
- alternatives: [
1654
- "pip install openai (Python SDK)"
1655
- ],
1743
+ alternatives: ["pip install openai (Python SDK)"],
1656
1744
  docsUrl: "https://github.com/openai/codex"
1657
1745
  };
1658
1746
  /**
@@ -1947,18 +2035,28 @@ var CodexAdapter = class extends BaseCodingAdapter {
1947
2035
  return false;
1948
2036
  }
1949
2037
  detectReady(output) {
2038
+ const rawTail = output.slice(-2e3);
2039
+ const hasRawComposerSignals = /OpenAI\s+Codex/i.test(rawTail) && (/Explain\s+this\s+codebase/i.test(rawTail) || /Summarize\s+recent\s+commits/i.test(rawTail) || /Ask\s+Codex\s+to\s+do\s+anything/i.test(rawTail) || /\?\s+for\s+shortcuts/i.test(rawTail) || /context\s+left/i.test(rawTail));
2040
+ if (hasRawComposerSignals) {
2041
+ return true;
2042
+ }
1950
2043
  const stripped = this.stripAnsi(output);
1951
2044
  if (!stripped.trim()) return false;
1952
2045
  const tail = stripped.slice(-1200);
1953
2046
  const hasComposerPrompt = /^\s*›\s*(?!\d+\.)\S.*$/m.test(tail) || /›\s+Ask\s+Codex\s+to\s+do\s+anything/.test(tail);
1954
2047
  const hasComposerFooter = /\?\s+for\s+shortcuts/i.test(tail) || /context\s+left/i.test(tail) || /tab\s+to\s+queue\s+message/i.test(tail) || /shift\s*\+\s*enter\s+for\s+newline/i.test(tail);
1955
- if (hasComposerPrompt || hasComposerFooter) {
2048
+ const hasStartupComposerHints = /Summarize\s+recent\s+commits/i.test(tail) || /Explain\s+this\s+codebase/i.test(tail) || /Ask\s+Codex\s+to\s+do\s+anything/i.test(tail);
2049
+ const hasCodexHeader = /OpenAI\s+Codex/i.test(tail) && /directory:\s+~?\/?.+/i.test(tail);
2050
+ const hasInteractiveStatusBar = /gpt-[\w.-]+\s+(?:high|medium|low)/i.test(tail) && /left\b/i.test(tail);
2051
+ if (hasComposerPrompt || hasComposerFooter || hasStartupComposerHints || hasCodexHeader && hasInteractiveStatusBar) {
1956
2052
  return true;
1957
2053
  }
1958
2054
  if (/do.?you.?trust.?the.?contents/i.test(stripped) || /sign.?in.?with.?chatgpt/i.test(stripped) || /update.?available/i.test(stripped) || /enable.?full.?access/i.test(stripped) || /choose.?working.?directory/i.test(stripped)) {
1959
2055
  return false;
1960
2056
  }
1961
- if (/explain this codebase|summarize recent commits|find and fix a bug/i.test(stripped)) {
2057
+ if (/explain this codebase|summarize recent commits|find and fix a bug/i.test(
2058
+ stripped
2059
+ )) {
1962
2060
  return true;
1963
2061
  }
1964
2062
  return stripped.includes("How can I help") || /(?:codex|>)\s*$/i.test(stripped);
@@ -2007,258 +2105,97 @@ var CodexAdapter = class extends BaseCodingAdapter {
2007
2105
  }
2008
2106
  };
2009
2107
 
2010
- // src/aider-adapter.ts
2011
- var AiderAdapter = class extends BaseCodingAdapter {
2012
- adapterType = "aider";
2013
- displayName = "Aider";
2014
- /** Minimal TUI, mostly text output — shorter settle delay */
2015
- readySettleMs = 200;
2016
- /**
2017
- * Aider uses plain text [y/n] prompts, NOT TUI arrow-key menus.
2018
- */
2019
- usesTuiMenus = false;
2108
+ // src/gemini-adapter.ts
2109
+ var GEMINI_HOOK_MARKER_PREFIX = "PARALLAX_GEMINI_HOOK";
2110
+ var GeminiAdapter = class extends BaseCodingAdapter {
2111
+ adapterType = "gemini";
2112
+ displayName = "Google Gemini";
2113
+ readySettleMs = 1500;
2020
2114
  installation = {
2021
- command: "pip install aider-chat",
2022
- alternatives: [
2023
- "pipx install aider-chat (isolated install)",
2024
- "brew install aider (macOS with Homebrew)"
2025
- ],
2026
- docsUrl: "https://aider.chat/docs/install.html",
2027
- minVersion: "0.50.0"
2115
+ command: "npm install -g @google/gemini-cli",
2116
+ alternatives: ["See documentation for latest installation method"],
2117
+ docsUrl: "https://github.com/google-gemini/gemini-cli#installation"
2028
2118
  };
2029
2119
  /**
2030
- * Auto-response rules for Aider CLI.
2031
- * Aider uses plain text prompts via io.py:832 with (Y)es/(N)o format.
2032
- * All rules are responseType: 'text' Aider never uses TUI menus.
2033
- *
2034
- * Decline rules come first to override the generic accept patterns.
2120
+ * Auto-response rules for Gemini CLI.
2121
+ * Gemini uses Ink/React TUI with arrow-key radio menus.
2122
+ * Source: FolderTrustDialog.tsx, MultiFolderTrustDialog.tsx, CloudFreePrivacyNotice.tsx
2035
2123
  */
2036
2124
  autoResponseRules = [
2037
- // ── Decline rules (specific, checked first) ────────────────────────
2038
2125
  {
2039
- pattern: /allow collection of anonymous analytics/i,
2040
- type: "config",
2041
- response: "n",
2042
- responseType: "text",
2043
- description: "Decline Aider telemetry opt-in",
2126
+ pattern: /do.?you.?trust.?this.?folder|trust.?folder|trust.?parent.?folder/i,
2127
+ type: "permission",
2128
+ response: "",
2129
+ responseType: "keys",
2130
+ keys: ["enter"],
2131
+ description: "Trust current folder (default selection in radio menu)",
2044
2132
  safe: true,
2045
2133
  once: true
2046
2134
  },
2047
2135
  {
2048
- pattern: /would you like to see what.?s new in this version/i,
2049
- type: "config",
2050
- response: "n",
2051
- responseType: "text",
2052
- description: "Decline release notes offer",
2136
+ pattern: /trust.?the.?following.?folders.*(added|workspace)/i,
2137
+ type: "permission",
2138
+ response: "",
2139
+ responseType: "keys",
2140
+ keys: ["enter"],
2141
+ description: "Trust multiple folders being added to workspace",
2053
2142
  safe: true,
2054
2143
  once: true
2055
2144
  },
2056
2145
  {
2057
- pattern: /open a github issue pre-filled/i,
2146
+ pattern: /allow.?google.?to.?use.?this.?data/i,
2058
2147
  type: "config",
2059
- response: "n",
2060
- responseType: "text",
2061
- description: "Decline automatic bug report",
2062
- safe: true
2063
- },
2064
- // ── File / edit operations ──────────────────────────────────────────
2065
- {
2066
- pattern: /add .+ to the chat\?/i,
2067
- type: "permission",
2068
- response: "y",
2069
- responseType: "text",
2070
- description: "Allow Aider to add files to chat context",
2071
- safe: true
2072
- },
2073
- {
2074
- pattern: /add url to the chat\?/i,
2075
- type: "permission",
2076
- response: "y",
2077
- responseType: "text",
2078
- description: "Allow Aider to add URL content to chat",
2079
- safe: true
2080
- },
2081
- {
2082
- pattern: /create new file\?/i,
2083
- type: "permission",
2084
- response: "y",
2085
- responseType: "text",
2086
- description: "Allow Aider to create new files",
2087
- safe: true
2088
- },
2089
- {
2090
- pattern: /allow edits to file/i,
2091
- type: "permission",
2092
- response: "y",
2093
- responseType: "text",
2094
- description: "Allow edits to file not yet in chat",
2095
- safe: true
2096
- },
2097
- {
2098
- pattern: /edit the files\?/i,
2099
- type: "permission",
2100
- response: "y",
2101
- responseType: "text",
2102
- description: "Accept architect mode edits",
2103
- safe: true
2104
- },
2105
- // ── Shell operations ────────────────────────────────────────────────
2106
- {
2107
- pattern: /run shell commands?\?/i,
2108
- type: "permission",
2109
- response: "y",
2110
- responseType: "text",
2111
- description: "Allow Aider to run shell commands",
2112
- safe: true
2113
- },
2114
- {
2115
- pattern: /add command output to the chat\?/i,
2116
- type: "permission",
2117
- response: "y",
2118
- responseType: "text",
2119
- description: "Add shell command output to chat context",
2120
- safe: true
2121
- },
2122
- {
2123
- pattern: /add \d+.*tokens of command output to the chat\?/i,
2124
- type: "permission",
2125
- response: "y",
2126
- responseType: "text",
2127
- description: "Add /run command output to chat context",
2128
- safe: true
2129
- },
2130
- // ── Setup / maintenance ─────────────────────────────────────────────
2131
- {
2132
- pattern: /no git repo found.*create one/i,
2133
- type: "config",
2134
- response: "y",
2135
- responseType: "text",
2136
- description: "Create git repo for change tracking",
2137
- safe: true,
2138
- once: true
2139
- },
2140
- {
2141
- pattern: /add .+ to \.gitignore/i,
2142
- type: "config",
2143
- response: "y",
2144
- responseType: "text",
2145
- description: "Update .gitignore with Aider patterns",
2148
+ response: "",
2149
+ responseType: "keys",
2150
+ keys: ["down", "enter"],
2151
+ description: 'Decline Google data collection (select "No")',
2146
2152
  safe: true,
2147
2153
  once: true
2148
- },
2149
- {
2150
- pattern: /run pip install\?/i,
2151
- type: "config",
2152
- response: "y",
2153
- responseType: "text",
2154
- description: "Install missing Python dependencies",
2155
- safe: true
2156
- },
2157
- {
2158
- pattern: /install playwright\?/i,
2159
- type: "config",
2160
- response: "y",
2161
- responseType: "text",
2162
- description: "Install Playwright for web scraping",
2163
- safe: true
2164
- },
2165
- // ── Other safe confirmations ────────────────────────────────────────
2166
- {
2167
- pattern: /fix lint errors in/i,
2168
- type: "permission",
2169
- response: "y",
2170
- responseType: "text",
2171
- description: "Accept lint error fix suggestion",
2172
- safe: true
2173
- },
2174
- {
2175
- pattern: /try to proceed anyway\?/i,
2176
- type: "config",
2177
- response: "y",
2178
- responseType: "text",
2179
- description: "Continue despite context limit warning",
2180
- safe: true
2181
2154
  }
2182
2155
  ];
2183
2156
  getWorkspaceFiles() {
2184
2157
  return [
2185
2158
  {
2186
- relativePath: ".aider.conventions.md",
2187
- description: "Project conventions and instructions read on startup (--read flag)",
2159
+ relativePath: "GEMINI.md",
2160
+ description: "Project-level instructions read automatically on startup",
2188
2161
  autoLoaded: true,
2189
2162
  type: "memory",
2190
2163
  format: "markdown"
2191
2164
  },
2192
2165
  {
2193
- relativePath: ".aider.conf.yml",
2194
- description: "Project-scoped Aider configuration (model, flags, options)",
2166
+ relativePath: ".gemini/settings.json",
2167
+ description: "Project-scoped settings (tool permissions, sandbox config)",
2195
2168
  autoLoaded: true,
2196
2169
  type: "config",
2197
- format: "yaml"
2170
+ format: "json"
2198
2171
  },
2199
2172
  {
2200
- relativePath: ".aiderignore",
2201
- description: "Gitignore-style file listing paths Aider should not edit",
2202
- autoLoaded: true,
2203
- type: "rules",
2204
- format: "text"
2173
+ relativePath: ".gemini/styles",
2174
+ description: "Custom style/persona definitions directory",
2175
+ autoLoaded: false,
2176
+ type: "config",
2177
+ format: "markdown"
2205
2178
  }
2206
2179
  ];
2207
2180
  }
2208
- getRecommendedModels(credentials) {
2209
- if (credentials?.anthropicKey) {
2210
- return {
2211
- powerful: "anthropic/claude-sonnet-4-20250514",
2212
- fast: "anthropic/claude-haiku-4-5-20251001"
2213
- };
2214
- }
2215
- if (credentials?.openaiKey) {
2216
- return {
2217
- powerful: "openai/o3",
2218
- fast: "openai/gpt-4o-mini"
2219
- };
2220
- }
2221
- if (credentials?.googleKey) {
2222
- return {
2223
- powerful: "gemini/gemini-3-pro",
2224
- fast: "gemini/gemini-3-flash"
2225
- };
2226
- }
2181
+ getRecommendedModels(_credentials) {
2227
2182
  return {
2228
- powerful: "anthropic/claude-sonnet-4-20250514",
2229
- fast: "anthropic/claude-haiku-4-5-20251001"
2183
+ powerful: "gemini-3-pro",
2184
+ fast: "gemini-3-flash"
2230
2185
  };
2231
2186
  }
2232
2187
  getCommand() {
2233
- return "aider";
2188
+ return "gemini";
2234
2189
  }
2235
2190
  getArgs(config) {
2236
2191
  const args = [];
2237
- args.push("--auto-commits");
2238
2192
  if (!this.isInteractive(config)) {
2239
- args.push("--no-pretty");
2240
- args.push("--no-show-diffs");
2241
- }
2242
- const provider = config.adapterConfig?.provider;
2243
- const credentials = this.getCredentials(config);
2244
- if (config.env?.AIDER_MODEL) {
2245
- args.push("--model", config.env.AIDER_MODEL);
2246
- } else if (provider === "anthropic") {
2247
- args.push("--model", "sonnet");
2248
- } else if (provider === "openai") {
2249
- args.push("--model", "4o");
2250
- } else if (provider === "google") {
2251
- args.push("--model", "gemini");
2252
- } else if (credentials.anthropicKey) {
2253
- args.push("--model", "sonnet");
2254
- } else if (credentials.openaiKey) {
2255
- args.push("--model", "4o");
2256
- } else if (credentials.googleKey) {
2257
- args.push("--model", "gemini");
2193
+ args.push("--non-interactive");
2194
+ args.push("--output-format", "text");
2195
+ if (config.workdir) {
2196
+ args.push("--cwd", config.workdir);
2197
+ }
2258
2198
  }
2259
- if (credentials.anthropicKey) args.push("--api-key", `anthropic=${credentials.anthropicKey}`);
2260
- if (credentials.openaiKey) args.push("--api-key", `openai=${credentials.openaiKey}`);
2261
- if (credentials.googleKey) args.push("--api-key", `gemini=${credentials.googleKey}`);
2262
2199
  const approvalConfig = this.getApprovalConfig(config);
2263
2200
  if (approvalConfig) {
2264
2201
  args.push(...approvalConfig.cliFlags);
@@ -2267,54 +2204,239 @@ var AiderAdapter = class extends BaseCodingAdapter {
2267
2204
  }
2268
2205
  getEnv(config) {
2269
2206
  const env = {};
2207
+ const credentials = this.getCredentials(config);
2208
+ const adapterConfig = config.adapterConfig;
2209
+ if (credentials.googleKey) {
2210
+ env.GOOGLE_API_KEY = credentials.googleKey;
2211
+ env.GEMINI_API_KEY = credentials.googleKey;
2212
+ }
2213
+ if (config.env?.GEMINI_MODEL) {
2214
+ env.GEMINI_MODEL = config.env.GEMINI_MODEL;
2215
+ }
2270
2216
  if (!this.isInteractive(config)) {
2271
2217
  env.NO_COLOR = "1";
2272
2218
  }
2273
- if (config.env?.AIDER_NO_GIT === "true") {
2274
- env.AIDER_NO_GIT = "true";
2219
+ if (adapterConfig?.geminiHookTelemetry) {
2220
+ env.PARALLAX_GEMINI_HOOK_TELEMETRY = "1";
2221
+ env.PARALLAX_GEMINI_HOOK_MARKER_PREFIX = adapterConfig.geminiHookMarkerPrefix || GEMINI_HOOK_MARKER_PREFIX;
2275
2222
  }
2276
2223
  return env;
2277
2224
  }
2225
+ getHookTelemetryProtocol(options) {
2226
+ if (options?.httpUrl) {
2227
+ const sessionHeader = options.sessionId ? ` -H 'X-Parallax-Session-Id: ${options.sessionId}'` : "";
2228
+ const curlCommand = `bash -c 'curl -sf -X POST "${options.httpUrl}" -H "Content-Type: application/json"${sessionHeader} -d @- --max-time 4 2>/dev/null || echo "{\\"continue\\":true}"'`;
2229
+ const hookEntry2 = [
2230
+ {
2231
+ matcher: "",
2232
+ hooks: [{ type: "command", command: curlCommand, timeout: 5e3 }]
2233
+ }
2234
+ ];
2235
+ const hookEntryNoMatcher = [
2236
+ { hooks: [{ type: "command", command: curlCommand, timeout: 5e3 }] }
2237
+ ];
2238
+ const settingsHooks2 = {
2239
+ BeforeTool: hookEntry2,
2240
+ AfterTool: hookEntry2,
2241
+ AfterAgent: hookEntryNoMatcher,
2242
+ SessionEnd: hookEntryNoMatcher,
2243
+ Notification: hookEntry2
2244
+ };
2245
+ return {
2246
+ markerPrefix: "",
2247
+ scriptPath: "",
2248
+ scriptContent: "",
2249
+ settingsHooks: settingsHooks2
2250
+ };
2251
+ }
2252
+ const markerPrefix = options?.markerPrefix || GEMINI_HOOK_MARKER_PREFIX;
2253
+ const scriptPath = options?.scriptPath || ".gemini/hooks/parallax-hook-telemetry.sh";
2254
+ const scriptCommand = `"${"$"}GEMINI_PROJECT_ROOT"/${scriptPath}`;
2255
+ const hookEntry = [
2256
+ { matcher: "", hooks: [{ type: "command", command: scriptCommand }] }
2257
+ ];
2258
+ const settingsHooks = {
2259
+ Notification: hookEntry,
2260
+ BeforeTool: hookEntry,
2261
+ AfterAgent: hookEntry,
2262
+ SessionEnd: hookEntry
2263
+ };
2264
+ const scriptContent = `#!/usr/bin/env bash
2265
+ set -euo pipefail
2266
+
2267
+ INPUT="$(cat)"
2268
+ [ -z "${"$"}INPUT" ] && exit 0
2269
+
2270
+ if ! command -v jq >/dev/null 2>&1; then
2271
+ # Valid no-op response
2272
+ printf '%s
2273
+ ' '{"continue":true}'
2274
+ exit 0
2275
+ fi
2276
+
2277
+ EVENT="$(printf '%s' "${"$"}INPUT" | jq -r '.hookEventName // .hook_event_name // empty')"
2278
+ [ -z "${"$"}EVENT" ] && { printf '%s
2279
+ ' '{"continue":true}'; exit 0; }
2280
+
2281
+ NOTIFICATION_TYPE="$(printf '%s' "${"$"}INPUT" | jq -r '.notificationType // .notification_type // empty')"
2282
+ TOOL_NAME="$(printf '%s' "${"$"}INPUT" | jq -r '.toolName // .tool_name // empty')"
2283
+ MESSAGE="$(printf '%s' "${"$"}INPUT" | jq -r '.message // empty')"
2284
+
2285
+ PAYLOAD="$(jq -nc \\
2286
+ --arg event "${"$"}EVENT" \\
2287
+ --arg notification_type "${"$"}NOTIFICATION_TYPE" \\
2288
+ --arg tool_name "${"$"}TOOL_NAME" \\
2289
+ --arg message "${"$"}MESSAGE" \\
2290
+ '({event: $event}
2291
+ + (if $notification_type != "" then {notification_type: $notification_type} else {} end)
2292
+ + (if $tool_name != "" then {tool_name: $tool_name} else {} end)
2293
+ + (if $message != "" then {message: $message} else {} end))')"
2294
+
2295
+ MARKER="${markerPrefix} ${"$"}PAYLOAD"
2296
+ jq -nc --arg m "${"$"}MARKER" '{continue: true, suppressOutput: true, systemMessage: $m}'
2297
+ `;
2298
+ return {
2299
+ markerPrefix,
2300
+ scriptPath,
2301
+ scriptContent,
2302
+ settingsHooks
2303
+ };
2304
+ }
2305
+ getHookMarkers(output) {
2306
+ const markers = [];
2307
+ const markerRegex = /(?:^|\n)\s*([A-Z0-9_]+)\s+(\{[^\n\r]+\})/g;
2308
+ let match;
2309
+ while ((match = markerRegex.exec(output)) !== null) {
2310
+ const markerToken = match[1];
2311
+ if (!markerToken.includes("GEMINI_HOOK")) {
2312
+ continue;
2313
+ }
2314
+ const payload = match[2];
2315
+ try {
2316
+ const parsed = JSON.parse(payload);
2317
+ const event = typeof parsed.event === "string" ? parsed.event : void 0;
2318
+ if (!event) continue;
2319
+ markers.push({
2320
+ event,
2321
+ notification_type: typeof parsed.notification_type === "string" ? parsed.notification_type : void 0,
2322
+ tool_name: typeof parsed.tool_name === "string" ? parsed.tool_name : void 0,
2323
+ message: typeof parsed.message === "string" ? parsed.message : void 0
2324
+ });
2325
+ } catch {
2326
+ }
2327
+ }
2328
+ return markers;
2329
+ }
2330
+ getLatestHookMarker(output) {
2331
+ const markers = this.getHookMarkers(output);
2332
+ return markers.length > 0 ? markers[markers.length - 1] : null;
2333
+ }
2334
+ stripHookMarkers(output) {
2335
+ return output.replace(
2336
+ /(?:^|\n)\s*[A-Z0-9_]*GEMINI_HOOK[A-Z0-9_]*\s+\{[^\n\r]+\}\s*/g,
2337
+ "\n"
2338
+ );
2339
+ }
2278
2340
  detectLogin(output) {
2279
2341
  const stripped = this.stripAnsi(output);
2280
- if (stripped.includes("No API key") || stripped.includes("API key not found") || stripped.includes("ANTHROPIC_API_KEY") || stripped.includes("OPENAI_API_KEY") || stripped.includes("Missing API key")) {
2342
+ 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")) {
2281
2343
  return {
2282
2344
  required: true,
2283
2345
  type: "api_key",
2284
- instructions: "Set ANTHROPIC_API_KEY or OPENAI_API_KEY environment variable"
2346
+ instructions: "Set GOOGLE_API_KEY or GEMINI_API_KEY environment variable"
2347
+ };
2348
+ }
2349
+ if (/enter.?gemini.?api.?key/i.test(stripped)) {
2350
+ return {
2351
+ required: true,
2352
+ type: "api_key",
2353
+ instructions: "Enter a Gemini API key or set GEMINI_API_KEY environment variable"
2354
+ };
2355
+ }
2356
+ if (/how.?would.?you.?like.?to.?authenticate/i.test(stripped) || /get.?started/i.test(stripped) && /login.?with.?google|use.?gemini.?api.?key|vertex/i.test(stripped)) {
2357
+ return {
2358
+ required: true,
2359
+ type: "oauth",
2360
+ instructions: "Gemini CLI authentication required \u2014 select an auth method"
2285
2361
  };
2286
2362
  }
2287
- if (stripped.includes("Invalid API key") || stripped.includes("Authentication failed") || stripped.includes("Unauthorized")) {
2363
+ if (/waiting.?for.?auth/i.test(stripped)) {
2288
2364
  return {
2289
2365
  required: true,
2290
- type: "api_key",
2291
- instructions: "API key is invalid - please check your credentials"
2366
+ type: "oauth",
2367
+ instructions: "Waiting for browser authentication to complete"
2292
2368
  };
2293
2369
  }
2294
- if (/login to openrouter or create a free account/i.test(stripped)) {
2370
+ if (stripped.includes("Sign in with Google") || stripped.includes("OAuth") || stripped.includes("accounts.google.com")) {
2371
+ const urlMatch = stripped.match(/https?:\/\/[^\s]+/);
2295
2372
  return {
2296
2373
  required: true,
2297
2374
  type: "oauth",
2298
- instructions: "Aider offering OpenRouter OAuth login \u2014 provide API keys to skip"
2375
+ url: urlMatch ? urlMatch[0] : "https://accounts.google.com",
2376
+ instructions: "Google OAuth authentication required"
2299
2377
  };
2300
2378
  }
2301
- if (/please open this url in your browser to connect aider with openrouter/i.test(stripped) || /waiting up to 5 minutes for you to finish in the browser/i.test(stripped)) {
2302
- const urlMatch = stripped.match(/https?:\/\/[^\s]+/);
2379
+ if (stripped.includes("Application Default Credentials") || stripped.includes("gcloud auth")) {
2303
2380
  return {
2304
2381
  required: true,
2305
2382
  type: "browser",
2306
- url: urlMatch ? urlMatch[0] : void 0,
2307
- instructions: "Complete OpenRouter authentication in browser"
2383
+ instructions: "Run: gcloud auth application-default login"
2308
2384
  };
2309
2385
  }
2310
2386
  return { required: false };
2311
2387
  }
2312
- /**
2313
- * Detect blocking prompts specific to Aider CLI.
2314
- * Source: io.py, onboarding.py, base_coder.py, report.py
2315
- */
2316
2388
  detectBlockingPrompt(output) {
2317
2389
  const stripped = this.stripAnsi(output);
2390
+ const marker = this.getLatestHookMarker(stripped);
2391
+ if (marker?.event === "Notification" && marker.notification_type === "ToolPermission") {
2392
+ return {
2393
+ detected: true,
2394
+ type: "permission",
2395
+ prompt: marker.message || "Gemini tool permission",
2396
+ suggestedResponse: "keys:enter",
2397
+ canAutoRespond: true,
2398
+ instructions: "Gemini is asking to allow a tool action"
2399
+ };
2400
+ }
2401
+ 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)) {
2402
+ return {
2403
+ detected: true,
2404
+ type: "permission",
2405
+ prompt: "Gemini tool execution confirmation",
2406
+ suggestedResponse: "keys:enter",
2407
+ canAutoRespond: true,
2408
+ instructions: "Gemini is asking to apply a change (file write, shell command, etc.)"
2409
+ };
2410
+ }
2411
+ if (/do.?you.?want.?to.?continue\s*\([yY]\/[nN]\)\??/i.test(stripped) || /continue\??\s*\([yY]\/[nN]\)\??/i.test(stripped) || /are.?you.?sure\??\s*\([yY]\/[nN]\)\??/i.test(stripped)) {
2412
+ return {
2413
+ detected: true,
2414
+ type: "tool_wait",
2415
+ prompt: "Interactive shell confirmation required (y/n)",
2416
+ canAutoRespond: false,
2417
+ instructions: "Focus shell input (Tab) and answer the y/n confirmation prompt"
2418
+ };
2419
+ }
2420
+ if (/Interactive\s+shell\s+awaiting\s+input/i.test(stripped)) {
2421
+ return {
2422
+ detected: true,
2423
+ type: "tool_wait",
2424
+ prompt: "Gemini interactive shell needs user focus",
2425
+ canAutoRespond: false,
2426
+ instructions: "Press Tab to focus the interactive shell, or wait for it to complete"
2427
+ };
2428
+ }
2429
+ if (/enable.?checkpointing.?to.?recover.?your.?session.?after.?a.?crash/i.test(
2430
+ stripped
2431
+ )) {
2432
+ return {
2433
+ detected: true,
2434
+ type: "config",
2435
+ prompt: "Gemini checkpoint setup prompt",
2436
+ canAutoRespond: false,
2437
+ instructions: 'Respond to checkpoint setup prompt (for example: press "s" to configure or dismiss)'
2438
+ };
2439
+ }
2318
2440
  const loginDetection = this.detectLogin(output);
2319
2441
  if (loginDetection.required) {
2320
2442
  return {
@@ -2326,111 +2448,147 @@ var AiderAdapter = class extends BaseCodingAdapter {
2326
2448
  instructions: loginDetection.instructions
2327
2449
  };
2328
2450
  }
2329
- if (/select.*model|choose.*model|which model/i.test(stripped)) {
2451
+ if (/further.?action.?is.?required/i.test(stripped) || /verify.?your.?account/i.test(stripped) || /waiting.?for.?verification/i.test(stripped)) {
2452
+ return {
2453
+ detected: true,
2454
+ type: "config",
2455
+ prompt: "Account verification required",
2456
+ canAutoRespond: false,
2457
+ instructions: "Your Gemini account requires verification before continuing"
2458
+ };
2459
+ }
2460
+ if (/select.*model|choose.*model|gemini-/i.test(stripped) && /\d+\)/i.test(stripped)) {
2330
2461
  return {
2331
2462
  detected: true,
2332
2463
  type: "model_select",
2333
- prompt: "Model selection required",
2464
+ prompt: "Gemini model selection",
2334
2465
  canAutoRespond: false,
2335
- instructions: "Please select a model or set AIDER_MODEL env var"
2466
+ instructions: "Please select a model or set GEMINI_MODEL env var"
2336
2467
  };
2337
2468
  }
2338
- if (/please answer with one of:/i.test(stripped)) {
2469
+ if (/select.*project|choose.*project|google cloud project/i.test(stripped)) {
2339
2470
  return {
2340
2471
  detected: true,
2341
- type: "unknown",
2342
- prompt: "Invalid confirmation input",
2472
+ type: "project_select",
2473
+ prompt: "Google Cloud project selection",
2343
2474
  canAutoRespond: false,
2344
- instructions: "Aider received an invalid response to a confirmation prompt"
2475
+ instructions: "Please select a Google Cloud project"
2345
2476
  };
2346
2477
  }
2347
- if (/delete|remove|overwrite/i.test(stripped) && (/\[y\/n\]/i.test(stripped) || /\(Y\)es\/\(N\)o/i.test(stripped))) {
2478
+ if (/safety.*filter|content.*blocked|unsafe.*content/i.test(stripped)) {
2348
2479
  return {
2349
2480
  detected: true,
2350
- type: "permission",
2351
- prompt: "Destructive operation confirmation",
2352
- options: ["y", "n"],
2481
+ type: "unknown",
2482
+ prompt: "Safety filter triggered",
2353
2483
  canAutoRespond: false,
2354
- instructions: "Aider is asking to perform a potentially destructive operation"
2484
+ instructions: "Content was blocked by safety filters"
2355
2485
  };
2356
2486
  }
2357
2487
  return super.detectBlockingPrompt(output);
2358
2488
  }
2359
2489
  /**
2360
- * Detect if Aider is actively loading/processing.
2490
+ * Detect if Gemini CLI is actively loading/processing.
2361
2491
  *
2362
2492
  * Patterns from: AGENT_LOADING_STATUS_PATTERNS.json
2363
- * - aider_active_waiting_model: "Waiting for <model>"
2364
- * - aider_active_waiting_llm_default: "Waiting for LLM"
2365
- * - aider_active_generating_commit_message: "Generating commit message with ..."
2493
+ * - gemini_active_loading_line: "(esc to cancel, Xs)"
2494
+ * - gemini_active_waiting_user_confirmation: "Waiting for user confirmation..."
2366
2495
  */
2367
2496
  detectLoading(output) {
2368
2497
  const stripped = this.stripAnsi(output);
2498
+ const marker = this.getLatestHookMarker(stripped);
2369
2499
  const tail = stripped.slice(-500);
2370
- if (/Waiting\s+for\s+(?:LLM|[A-Za-z0-9_./:@-]+)/i.test(tail)) {
2500
+ if (marker?.event === "BeforeTool") {
2371
2501
  return true;
2372
2502
  }
2373
- if (/Generating\s+commit\s+message\s+with\s+/i.test(tail)) {
2503
+ if (/esc\s+to\s+cancel/i.test(tail)) {
2504
+ return true;
2505
+ }
2506
+ if (/Waiting\s+for\s+user\s+confirmation/i.test(tail)) {
2374
2507
  return true;
2375
2508
  }
2376
2509
  return false;
2377
2510
  }
2511
+ detectToolRunning(output) {
2512
+ const stripped = this.stripAnsi(output);
2513
+ const marker = this.getLatestHookMarker(stripped);
2514
+ if (marker?.event === "BeforeTool" && marker.tool_name) {
2515
+ return {
2516
+ toolName: marker.tool_name.toLowerCase(),
2517
+ description: `${marker.tool_name} (hook)`
2518
+ };
2519
+ }
2520
+ return null;
2521
+ }
2378
2522
  /**
2379
- * Detect task completion for Aider.
2523
+ * Detect task completion for Gemini CLI.
2380
2524
  *
2381
2525
  * High-confidence patterns:
2382
- * - "Aider is waiting for your input" notification (bell message)
2383
- * - Edit-format mode prompts (ask>, code>, architect>) after output
2526
+ * - " Ready" window title signal (OSC sequence, may survive ANSI stripping)
2527
+ * - "Type your message" composer placeholder after agent output
2384
2528
  *
2385
2529
  * Patterns from: AGENT_LOADING_STATUS_PATTERNS.json
2386
- * - aider_completed_llm_response_ready
2530
+ * - gemini_ready_title
2387
2531
  */
2388
2532
  detectTaskComplete(output) {
2389
2533
  const stripped = this.stripAnsi(output);
2390
- if (/Aider\s+is\s+waiting\s+for\s+your\s+input/.test(stripped)) {
2534
+ const marker = this.getLatestHookMarker(stripped);
2535
+ if (marker?.event === "AfterAgent") {
2391
2536
  return true;
2392
2537
  }
2393
- const hasPrompt = /(?:(?:ask|code|architect)(?:\s+multi)?)?>\s*$/m.test(stripped);
2394
- if (hasPrompt) {
2395
- const hasEditMarkers = /Applied edit to|Commit [a-f0-9]+|wrote to|Updated/i.test(stripped);
2396
- const hasTokenUsage = /Tokens:|Cost:/i.test(stripped);
2397
- if (hasEditMarkers || hasTokenUsage) {
2398
- return true;
2399
- }
2538
+ if (/◇\s+Ready/.test(stripped)) {
2539
+ return true;
2540
+ }
2541
+ if (/type.?your.?message/i.test(stripped)) {
2542
+ return true;
2400
2543
  }
2401
2544
  return false;
2402
2545
  }
2403
2546
  detectReady(output) {
2404
2547
  const stripped = this.stripAnsi(output);
2405
- if (/login to openrouter/i.test(stripped) || /open this url in your browser/i.test(stripped) || /waiting up to 5 minutes/i.test(stripped)) {
2548
+ const marker = this.getLatestHookMarker(stripped);
2549
+ if (marker?.event === "Notification" && marker.notification_type === "ToolPermission") {
2406
2550
  return false;
2407
2551
  }
2408
- if (/(?:ask|code|architect|help)(?:\s+multi)?>\s*$/m.test(stripped) || /^multi>\s*$/m.test(stripped)) {
2552
+ if (marker?.event === "AfterAgent") {
2409
2553
  return true;
2410
2554
  }
2411
- if (/^Aider v\d+/m.test(stripped)) {
2555
+ const hasActiveOverlay = /interactive\s+shell\s+awaiting\s+input|press\s+tab\s+to\s+focus\s+shell/i.test(
2556
+ stripped
2557
+ ) || /waiting\s+for\s+user\s+confirmation|apply.?this.?change|allow.?execution|do.?you.?want.?to.?proceed/i.test(
2558
+ stripped
2559
+ ) || /do.?you.?want.?to.?continue\s*\([yY]\/[nN]\)\??|are.?you.?sure\??\s*\([yY]\/[nN]\)\??/i.test(
2560
+ stripped
2561
+ ) || /enable.?checkpointing.?to.?recover.?your.?session.?after.?a.?crash/i.test(
2562
+ stripped
2563
+ ) || /esc\s+to\s+cancel|esc\s+to\s+interrupt/i.test(stripped);
2564
+ if (hasActiveOverlay) {
2565
+ return false;
2566
+ }
2567
+ if (/type.?your.?message/i.test(stripped)) {
2412
2568
  return true;
2413
2569
  }
2414
- if (/^(?:Readonly|Editable):/m.test(stripped)) {
2570
+ if (/do.?you.?trust.?this.?folder/i.test(stripped) || /how.?would.?you.?like.?to.?authenticate/i.test(stripped) || /waiting.?for.?auth/i.test(stripped) || /allow.?google.?to.?use.?this.?data/i.test(stripped)) {
2571
+ return false;
2572
+ }
2573
+ if (/^\s*[>!*]\s+/m.test(stripped) || /\(r:\)/.test(stripped)) {
2415
2574
  return true;
2416
2575
  }
2417
- return (
2418
- // Legacy prompt patterns
2419
- stripped.includes("aider>") || /Added.*to the chat/i.test(stripped) || />\s*$/.test(stripped)
2420
- );
2576
+ return stripped.includes("How can I help") || stripped.includes("What would you like") || // Match "gemini> " prompt specifically, not bare ">"
2577
+ /gemini>\s*$/i.test(stripped);
2421
2578
  }
2422
2579
  parseOutput(output) {
2423
- const stripped = this.stripAnsi(output);
2580
+ const withoutHookMarkers = this.stripHookMarkers(output);
2581
+ const stripped = this.stripAnsi(withoutHookMarkers);
2424
2582
  const isComplete = this.isResponseComplete(stripped);
2425
2583
  if (!isComplete) {
2426
2584
  return null;
2427
2585
  }
2428
2586
  const isQuestion = this.containsQuestion(stripped);
2429
- let content = this.extractContent(stripped, /^.*aider>\s*/gim);
2430
- content = content.replace(/^(Added|Removed|Created|Updated) .+ (to|from) the chat\.?$/gm, "");
2587
+ let content = this.extractContent(stripped, /^.*(?:gemini|>)\s*/gim);
2588
+ content = content.replace(/^\[Safety[^\]]*\].*$/gm, "");
2431
2589
  return {
2432
2590
  type: isQuestion ? "question" : "response",
2433
- content: content.trim(),
2591
+ content,
2434
2592
  isComplete: true,
2435
2593
  isQuestion,
2436
2594
  metadata: {
@@ -2439,28 +2597,44 @@ var AiderAdapter = class extends BaseCodingAdapter {
2439
2597
  };
2440
2598
  }
2441
2599
  /**
2442
- * Detect exit conditions specific to Aider.
2443
- * Source: base_coder.py:994, base_coder.py:998, report.py:77, versioncheck.py:58
2600
+ * Detect exit conditions specific to Gemini CLI.
2601
+ * Source: FolderTrustDialog.tsx:127, LogoutConfirmationDialog.tsx:64
2444
2602
  */
2445
2603
  detectExit(output) {
2446
2604
  const stripped = this.stripAnsi(output);
2447
- if (/\^C again to exit/i.test(stripped) || /\^C KeyboardInterrupt/i.test(stripped)) {
2448
- return { exited: true, code: 130 };
2605
+ const marker = this.getLatestHookMarker(stripped);
2606
+ if (marker?.event === "SessionEnd") {
2607
+ return {
2608
+ exited: true,
2609
+ code: 0
2610
+ };
2449
2611
  }
2450
- if (/re-run aider to use new version/i.test(stripped)) {
2612
+ if (/folder.?trust.?level.?must.?be.?selected.*exiting/i.test(stripped)) {
2451
2613
  return {
2452
2614
  exited: true,
2453
- code: 0,
2454
- error: "Aider updated \u2014 restart required"
2615
+ code: 1,
2616
+ error: "Gemini CLI exited because no folder trust level was selected"
2617
+ };
2618
+ }
2619
+ if (/you are now logged out/i.test(stripped)) {
2620
+ return {
2621
+ exited: true,
2622
+ code: 0
2623
+ };
2624
+ }
2625
+ if (/Agent\s+powering\s+down/i.test(stripped)) {
2626
+ return {
2627
+ exited: true,
2628
+ code: 0
2455
2629
  };
2456
2630
  }
2457
2631
  return super.detectExit(output);
2458
2632
  }
2459
2633
  getPromptPattern() {
2460
- return /(?:ask|code|architect|help|aider|multi)(?:\s+multi)?>\s*$/i;
2634
+ return /gemini>\s*$/i;
2461
2635
  }
2462
2636
  getHealthCheckCommand() {
2463
- return "aider --version";
2637
+ return "gemini --version";
2464
2638
  }
2465
2639
  };
2466
2640
 
@@ -2566,7 +2740,9 @@ var HermesAdapter = class extends BaseCodingAdapter {
2566
2740
  instructions: "Hermes is waiting for clarify input (arrow keys + Enter or free text)."
2567
2741
  };
2568
2742
  }
2569
- if (/Sudo Password Required|password hidden|Password \(hidden\):/i.test(stripped)) {
2743
+ if (/Sudo Password Required|password hidden|Password \(hidden\):/i.test(
2744
+ stripped
2745
+ )) {
2570
2746
  return {
2571
2747
  detected: true,
2572
2748
  type: "tool_wait",
@@ -2575,7 +2751,9 @@ var HermesAdapter = class extends BaseCodingAdapter {
2575
2751
  instructions: "Hermes terminal tool is waiting for a sudo password or skip."
2576
2752
  };
2577
2753
  }
2578
- if (/Dangerous Command|Allow once|Allow for this session|permanent allowlist|\bDeny\b/i.test(stripped)) {
2754
+ if (/Dangerous Command|Allow once|Allow for this session|permanent allowlist|\bDeny\b/i.test(
2755
+ stripped
2756
+ )) {
2579
2757
  return {
2580
2758
  detected: true,
2581
2759
  type: "permission",
@@ -2589,7 +2767,9 @@ var HermesAdapter = class extends BaseCodingAdapter {
2589
2767
  detectLoading(output) {
2590
2768
  const stripped = this.stripAnsi(output);
2591
2769
  const tail = stripped.slice(-1200);
2592
- if (/(?:pondering|contemplating|musing|cogitating|ruminating|deliberating|mulling|reflecting|processing|reasoning|analyzing|computing|synthesizing|formulating|brainstorming)\.\.\.\s*\(\d+\.\d+s\)/i.test(tail)) {
2770
+ if (/(?:pondering|contemplating|musing|cogitating|ruminating|deliberating|mulling|reflecting|processing|reasoning|analyzing|computing|synthesizing|formulating|brainstorming)\.\.\.\s*\(\d+\.\d+s\)/i.test(
2771
+ tail
2772
+ )) {
2593
2773
  return true;
2594
2774
  }
2595
2775
  if (/\(\d+\.\d+s\)\s*$/.test(tail) && /(?:🔍|📄|💻|⚙️|📖|✍️|🔧|🌐|👆|⌨️|📋|🧠|📚|🎨|🐍|🔀|⚡|💬)/.test(tail)) {
@@ -2623,7 +2803,9 @@ var HermesAdapter = class extends BaseCodingAdapter {
2623
2803
  if (this.detectLoading(tail)) {
2624
2804
  return false;
2625
2805
  }
2626
- if (/Hermes needs your input|Sudo Password Required|Dangerous Command/i.test(tail)) {
2806
+ if (/Hermes needs your input|Sudo Password Required|Dangerous Command/i.test(
2807
+ tail
2808
+ )) {
2627
2809
  return false;
2628
2810
  }
2629
2811
  if (/(?:⚕|⚠|🔐|\?|✎)\s*❯\s*$/.test(tail)) {
@@ -2644,7 +2826,10 @@ var HermesAdapter = class extends BaseCodingAdapter {
2644
2826
  content = boxMatch[1].trim();
2645
2827
  }
2646
2828
  if (!content) {
2647
- content = this.extractContent(stripped, /(?:^|\n)\s*(?:⚕|⚠|🔐|\?|✎)?\s*❯\s*$/gim);
2829
+ content = this.extractContent(
2830
+ stripped,
2831
+ /(?:^|\n)\s*(?:⚕|⚠|🔐|\?|✎)?\s*❯\s*$/gim
2832
+ );
2648
2833
  }
2649
2834
  return {
2650
2835
  type: this.containsQuestion(content) ? "question" : "response",
@@ -2674,12 +2859,7 @@ var HermesAdapter = class extends BaseCodingAdapter {
2674
2859
  // src/pattern-loader.ts
2675
2860
  var BASELINE_PATTERNS = {
2676
2861
  claude: {
2677
- ready: [
2678
- "Claude Code",
2679
- "How can I help",
2680
- "What would you like",
2681
- "Ready"
2682
- ],
2862
+ ready: ["Claude Code", "How can I help", "What would you like", "Ready"],
2683
2863
  auth: [
2684
2864
  "ANTHROPIC_API_KEY",
2685
2865
  "API key not found",
@@ -2687,17 +2867,9 @@ var BASELINE_PATTERNS = {
2687
2867
  "Please sign in",
2688
2868
  "Invalid API key"
2689
2869
  ],
2690
- blocking: [
2691
- "update available",
2692
- "[y/n]"
2693
- ],
2694
- loading: [
2695
- "Reading X files\u2026"
2696
- ],
2697
- turnComplete: [
2698
- "Cooked for 1m 6s",
2699
- "<CustomVerb> for 4m 39s"
2700
- ],
2870
+ blocking: ["update available", "[y/n]"],
2871
+ loading: ["Reading X files\u2026"],
2872
+ turnComplete: ["Cooked for 1m 6s", "<CustomVerb> for 4m 39s"],
2701
2873
  toolWait: [],
2702
2874
  exit: [],
2703
2875
  source: "baseline"
@@ -2717,10 +2889,7 @@ var BASELINE_PATTERNS = {
2717
2889
  "gcloud auth",
2718
2890
  "Application Default Credentials"
2719
2891
  ],
2720
- blocking: [
2721
- "update available",
2722
- "[y/n]"
2723
- ],
2892
+ blocking: ["update available", "[y/n]"],
2724
2893
  loading: [
2725
2894
  "<phrase> (esc to cancel, 25s)",
2726
2895
  "Waiting for user confirmation...",
@@ -2729,37 +2898,21 @@ var BASELINE_PATTERNS = {
2729
2898
  "Warming up the AI hamsters"
2730
2899
  ],
2731
2900
  turnComplete: [],
2732
- toolWait: [
2733
- "Interactive shell awaiting input... press tab to focus shell"
2734
- ],
2735
- exit: [
2736
- "Agent powering down. Goodbye!"
2737
- ],
2901
+ toolWait: ["Interactive shell awaiting input... press tab to focus shell"],
2902
+ exit: ["Agent powering down. Goodbye!"],
2738
2903
  source: "baseline"
2739
2904
  },
2740
2905
  codex: {
2741
- ready: [
2742
- "Codex",
2743
- "How can I help",
2744
- "Ready"
2745
- ],
2906
+ ready: ["Codex", "How can I help", "Ready"],
2746
2907
  auth: [
2747
2908
  "OPENAI_API_KEY",
2748
2909
  "API key not found",
2749
2910
  "Unauthorized",
2750
2911
  "Invalid API key"
2751
2912
  ],
2752
- blocking: [
2753
- "update available",
2754
- "[y/n]"
2755
- ],
2756
- loading: [
2757
- "\u2022 Working (0s \u2022 esc to interrupt)",
2758
- "Booting MCP server: alpha"
2759
- ],
2760
- turnComplete: [
2761
- "Worked for 1m 05s"
2762
- ],
2913
+ blocking: ["update available", "[y/n]"],
2914
+ loading: ["\u2022 Working (0s \u2022 esc to interrupt)", "Booting MCP server: alpha"],
2915
+ turnComplete: ["Worked for 1m 05s"],
2763
2916
  toolWait: [
2764
2917
  "Waiting for background terminal \xB7 <command>",
2765
2918
  "Searching the web"
@@ -2768,39 +2921,21 @@ var BASELINE_PATTERNS = {
2768
2921
  source: "baseline"
2769
2922
  },
2770
2923
  aider: {
2771
- ready: [
2772
- "Aider",
2773
- "What would you like",
2774
- "Ready"
2775
- ],
2776
- auth: [
2777
- "API key",
2778
- "OPENAI_API_KEY",
2779
- "ANTHROPIC_API_KEY",
2780
- "No API key"
2781
- ],
2782
- blocking: [
2783
- "(Y)es/(N)o",
2784
- "[y/n]"
2785
- ],
2924
+ ready: ["Aider", "What would you like", "Ready"],
2925
+ auth: ["API key", "OPENAI_API_KEY", "ANTHROPIC_API_KEY", "No API key"],
2926
+ blocking: ["(Y)es/(N)o", "[y/n]"],
2786
2927
  loading: [
2787
2928
  "Waiting for <model>",
2788
2929
  "Waiting for LLM",
2789
2930
  "Generating commit message with <model>"
2790
2931
  ],
2791
- turnComplete: [
2792
- "Aider is waiting for your input"
2793
- ],
2932
+ turnComplete: ["Aider is waiting for your input"],
2794
2933
  toolWait: [],
2795
2934
  exit: [],
2796
2935
  source: "baseline"
2797
2936
  },
2798
2937
  hermes: {
2799
- ready: [
2800
- "\u276F",
2801
- "\u2695 Hermes",
2802
- "Welcome to Hermes Agent"
2803
- ],
2938
+ ready: ["\u276F", "\u2695 Hermes", "Welcome to Hermes Agent"],
2804
2939
  auth: [
2805
2940
  "isn't configured yet",
2806
2941
  "no API keys or providers found",
@@ -2811,23 +2946,14 @@ var BASELINE_PATTERNS = {
2811
2946
  "Sudo Password Required",
2812
2947
  "Dangerous Command"
2813
2948
  ],
2814
- loading: [
2815
- "deliberating...",
2816
- "(0.0s)",
2817
- "\u2695 \u276F"
2818
- ],
2819
- turnComplete: [
2820
- "\u256D\u2500 \u2695 Hermes",
2821
- "\u276F"
2822
- ],
2949
+ loading: ["deliberating...", "(0.0s)", "\u2695 \u276F"],
2950
+ turnComplete: ["\u256D\u2500 \u2695 Hermes", "\u276F"],
2823
2951
  toolWait: [
2824
2952
  "Hermes needs your input",
2825
2953
  "Sudo Password Required",
2826
2954
  "Dangerous Command"
2827
2955
  ],
2828
- exit: [
2829
- "Goodbye! \u2695"
2830
- ],
2956
+ exit: ["Goodbye! \u2695"],
2831
2957
  source: "baseline"
2832
2958
  }
2833
2959
  };
@@ -2894,6 +3020,7 @@ async function hasDynamicPatterns(adapter) {
2894
3020
  }
2895
3021
 
2896
3022
  // src/index.ts
3023
+ var logger = pino__default.default({ name: "coding-agent-adapters" });
2897
3024
  function createAllAdapters() {
2898
3025
  return [
2899
3026
  new ClaudeAdapter(),
@@ -2940,18 +3067,20 @@ async function printMissingAdapters(types) {
2940
3067
  const results = types ? await checkAdapters(types) : await checkAllAdapters();
2941
3068
  const missing = results.filter((r) => !r.installed);
2942
3069
  if (missing.length === 0) {
2943
- console.log("All CLI tools are installed!");
3070
+ logger.info("All CLI tools are installed");
2944
3071
  return;
2945
3072
  }
2946
- console.log("\nMissing CLI tools:\n");
3073
+ logger.info({ count: missing.length }, "Missing CLI tools detected");
2947
3074
  for (const m of missing) {
2948
- console.log(`${m.adapter}`);
2949
- console.log(` Install: ${m.installCommand}`);
2950
- console.log(` Docs: ${m.docsUrl}`);
2951
- if (m.error) {
2952
- console.log(` Error: ${m.error}`);
2953
- }
2954
- console.log();
3075
+ logger.warn(
3076
+ {
3077
+ adapter: m.adapter,
3078
+ installCommand: m.installCommand,
3079
+ docsUrl: m.docsUrl,
3080
+ ...m.error ? { error: m.error } : {}
3081
+ },
3082
+ "CLI tool not installed"
3083
+ );
2955
3084
  }
2956
3085
  }
2957
3086