coding-agent-adapters 0.14.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,155 +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: /choose\s+the\s+text\s+style\s+that\s+looks\s+best\s+with\s+your\s+terminal|syntax\s+theme:/i,
668
+ pattern: /allow collection of anonymous analytics/i,
594
669
  type: "config",
595
- response: "",
596
- responseType: "keys",
597
- keys: ["enter"],
598
- description: "Accept Claude first-run theme/style prompt",
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: /trust.*(?:folder|directory)|safety.?check|project.you.created|(?:Yes|Allow).*(?:No|Deny).*(?:Enter|Return)/i,
604
- type: "permission",
605
- response: "",
606
- responseType: "keys",
607
- keys: ["enter"],
608
- description: "Accept trust prompt for working directory",
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: /wants? (?:your )?permission|needs your permission|(?:Allow|Approve)\s[\s\S]{0,50}(?:Deny|Don't allow)/i,
614
- type: "permission",
615
- response: "",
616
- responseType: "keys",
617
- keys: ["enter"],
618
- description: "Auto-approve tool permission prompts (file access, MCP tools, etc.)",
619
- safe: true,
620
- once: true
686
+ pattern: /open a github issue pre-filled/i,
687
+ type: "config",
688
+ response: "n",
689
+ responseType: "text",
690
+ description: "Decline automatic bug report",
691
+ safe: true
621
692
  },
622
693
  {
623
- pattern: /update available.*\[y\/n\]/i,
624
- type: "update",
694
+ pattern: /open documentation url for more info\?/i,
695
+ type: "config",
625
696
  response: "n",
626
697
  responseType: "text",
627
- description: "Decline Claude Code update to continue execution",
698
+ description: "Decline opening Aider documentation for model warnings",
628
699
  safe: true
629
700
  },
701
+ // ── File / edit operations ──────────────────────────────────────────
630
702
  {
631
- pattern: /new version.*available.*\[y\/n\]/i,
632
- type: "update",
633
- response: "n",
703
+ pattern: /add .+ to the chat\?/i,
704
+ type: "permission",
705
+ response: "y",
634
706
  responseType: "text",
635
- description: "Decline version upgrade prompt",
707
+ description: "Allow Aider to add files to chat context",
636
708
  safe: true
637
709
  },
638
710
  {
639
- pattern: /would you like to enable.*telemetry.*\[y\/n\]/i,
640
- type: "config",
641
- response: "n",
711
+ pattern: /add url to the chat\?/i,
712
+ type: "permission",
713
+ response: "y",
642
714
  responseType: "text",
643
- description: "Decline telemetry prompt",
715
+ description: "Allow Aider to add URL content to chat",
644
716
  safe: true
645
717
  },
646
718
  {
647
- pattern: /send anonymous usage data.*\[y\/n\]/i,
648
- type: "config",
649
- response: "n",
719
+ pattern: /create new file\?/i,
720
+ type: "permission",
721
+ response: "y",
650
722
  responseType: "text",
651
- description: "Decline anonymous usage data",
723
+ description: "Allow Aider to create new files",
652
724
  safe: true
653
725
  },
654
726
  {
655
- 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,
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,
656
770
  type: "config",
657
- response: "0",
771
+ response: "y",
658
772
  responseType: "text",
659
- description: "Dismiss optional Claude session survey",
773
+ description: "Create git repo for change tracking",
660
774
  safe: true,
661
775
  once: true
662
776
  },
663
777
  {
664
- pattern: /continue without.*\[y\/n\]/i,
778
+ pattern: /add .+ to \.gitignore/i,
665
779
  type: "config",
666
780
  response: "y",
667
781
  responseType: "text",
668
- 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",
669
817
  safe: true
670
818
  }
671
819
  ];
672
820
  getWorkspaceFiles() {
673
821
  return [
674
822
  {
675
- relativePath: "CLAUDE.md",
676
- description: "Project-level instructions read automatically on startup",
823
+ relativePath: ".aider.conventions.md",
824
+ description: "Project conventions and instructions read on startup (--read flag)",
677
825
  autoLoaded: true,
678
826
  type: "memory",
679
827
  format: "markdown"
680
828
  },
681
829
  {
682
- relativePath: ".claude/settings.json",
683
- description: "Project-scoped settings (allowed tools, permissions)",
830
+ relativePath: ".aider.conf.yml",
831
+ description: "Project-scoped Aider configuration (model, flags, options)",
684
832
  autoLoaded: true,
685
833
  type: "config",
686
- format: "json"
834
+ format: "yaml"
687
835
  },
688
836
  {
689
- relativePath: ".claude/commands",
690
- description: "Custom slash commands directory",
691
- autoLoaded: false,
692
- type: "config",
693
- 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"
694
842
  }
695
843
  ];
696
844
  }
697
- 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
+ }
698
864
  return {
699
- powerful: "claude-sonnet-4-20250514",
700
- fast: "claude-haiku-4-5-20251001"
865
+ powerful: "anthropic/claude-sonnet-4-20250514",
866
+ fast: "anthropic/claude-haiku-4-5-20251001"
701
867
  };
702
868
  }
703
869
  getCommand() {
704
- return "claude";
870
+ return "aider";
705
871
  }
706
872
  getArgs(config) {
707
873
  const args = [];
708
- const adapterConfig = config.adapterConfig;
709
- if (!this.isInteractive(config)) {
710
- args.push("--print");
711
- if (config.workdir) {
712
- args.push("--cwd", config.workdir);
713
- }
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");
714
879
  }
715
- if (adapterConfig?.resume) {
716
- args.push("--resume", adapterConfig.resume);
717
- } else if (adapterConfig?.continue) {
718
- 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}`);
719
906
  }
720
907
  const approvalConfig = this.getApprovalConfig(config);
721
908
  if (approvalConfig) {
@@ -725,154 +912,56 @@ var ClaudeAdapter = class extends BaseCodingAdapter {
725
912
  }
726
913
  getEnv(config) {
727
914
  const env = {};
728
- const credentials = this.getCredentials(config);
729
- const adapterConfig = config.adapterConfig;
730
- if (credentials.anthropicKey) {
731
- env.ANTHROPIC_API_KEY = credentials.anthropicKey;
732
- }
733
- if (config.env?.ANTHROPIC_MODEL) {
734
- env.ANTHROPIC_MODEL = config.env.ANTHROPIC_MODEL;
735
- }
736
915
  if (!this.isInteractive(config)) {
737
- env.CLAUDE_CODE_DISABLE_INTERACTIVE = "true";
916
+ env.NO_COLOR = "1";
738
917
  }
739
- if (adapterConfig?.claudeHookTelemetry) {
740
- env.PARALLAX_CLAUDE_HOOK_TELEMETRY = "1";
741
- 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";
742
920
  }
743
921
  return env;
744
922
  }
745
- getHookTelemetryProtocol(options) {
746
- if (options?.httpUrl) {
747
- const httpHookBase = {
748
- type: "http",
749
- url: options.httpUrl,
750
- timeout: 5
751
- };
752
- if (options.sessionId) {
753
- httpHookBase.headers = { "X-Parallax-Session-Id": options.sessionId };
754
- }
755
- const hookEntry2 = [{ matcher: "", hooks: [{ ...httpHookBase }] }];
756
- const hookEntryNoMatcher = [{ hooks: [{ ...httpHookBase }] }];
757
- const settingsHooks2 = {
758
- PermissionRequest: hookEntryNoMatcher,
759
- PreToolUse: hookEntry2,
760
- Stop: hookEntryNoMatcher,
761
- Notification: hookEntry2,
762
- TaskCompleted: hookEntryNoMatcher
763
- };
764
- return {
765
- markerPrefix: "",
766
- scriptPath: "",
767
- scriptContent: "",
768
- settingsHooks: settingsHooks2
769
- };
770
- }
771
- const markerPrefix = options?.markerPrefix || CLAUDE_HOOK_MARKER_PREFIX;
772
- const scriptPath = options?.scriptPath || ".claude/hooks/parallax-hook-telemetry.sh";
773
- const scriptCommand = `"${"$"}CLAUDE_PROJECT_DIR"/${scriptPath}`;
774
- const hookEntry = [{ matcher: "", hooks: [{ type: "command", command: scriptCommand }] }];
775
- const settingsHooks = {
776
- Notification: hookEntry,
777
- PreToolUse: hookEntry,
778
- TaskCompleted: hookEntry,
779
- SessionEnd: hookEntry
780
- };
781
- const scriptContent = `#!/usr/bin/env bash
782
- set -euo pipefail
783
-
784
- INPUT="$(cat)"
785
- [ -z "${"$"}INPUT" ] && exit 0
786
-
787
- if ! command -v jq >/dev/null 2>&1; then
788
- exit 0
789
- fi
790
-
791
- EVENT="$(printf '%s' "${"$"}INPUT" | jq -r '.hook_event_name // empty')"
792
- [ -z "${"$"}EVENT" ] && exit 0
793
-
794
- NOTIFICATION_TYPE="$(printf '%s' "${"$"}INPUT" | jq -r '.notification_type // empty')"
795
- TOOL_NAME="$(printf '%s' "${"$"}INPUT" | jq -r '.tool_name // empty')"
796
- MESSAGE="$(printf '%s' "${"$"}INPUT" | jq -r '.message // empty')"
797
-
798
- printf '%s ' '${markerPrefix}'
799
- jq -nc --arg event "${"$"}EVENT" --arg notification_type "${"$"}NOTIFICATION_TYPE" --arg tool_name "${"$"}TOOL_NAME" --arg message "${"$"}MESSAGE" '({event: $event}
800
- + (if $notification_type != "" then {notification_type: $notification_type} else {} end)
801
- + (if $tool_name != "" then {tool_name: $tool_name} else {} end)
802
- + (if $message != "" then {message: $message} else {} end))'
803
- `;
804
- return {
805
- markerPrefix,
806
- scriptPath,
807
- scriptContent,
808
- settingsHooks
809
- };
810
- }
811
- getHookMarkers(output) {
812
- const markers = [];
813
- const markerRegex = /(?:^|\n)\s*([A-Z0-9_]+)\s+(\{[^\n\r]+\})/g;
814
- let match;
815
- while ((match = markerRegex.exec(output)) !== null) {
816
- const markerToken = match[1];
817
- if (!markerToken.includes("CLAUDE_HOOK")) {
818
- continue;
819
- }
820
- const payload = match[2];
821
- try {
822
- const parsed = JSON.parse(payload);
823
- const event = typeof parsed.event === "string" ? parsed.event : void 0;
824
- if (!event) continue;
825
- markers.push({
826
- event,
827
- notification_type: typeof parsed.notification_type === "string" ? parsed.notification_type : void 0,
828
- tool_name: typeof parsed.tool_name === "string" ? parsed.tool_name : void 0,
829
- message: typeof parsed.message === "string" ? parsed.message : void 0
830
- });
831
- } catch {
832
- }
833
- }
834
- return markers;
835
- }
836
- getLatestHookMarker(output) {
837
- const markers = this.getHookMarkers(output);
838
- return markers.length > 0 ? markers[markers.length - 1] : null;
839
- }
840
- stripHookMarkers(output) {
841
- return output.replace(/(?:^|\n)\s*[A-Z0-9_]*CLAUDE_HOOK[A-Z0-9_]*\s+\{[^\n\r]+\}\s*/g, "\n");
842
- }
843
923
  detectLogin(output) {
844
924
  const stripped = this.stripAnsi(output);
845
- 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")) {
846
926
  return {
847
927
  required: true,
848
- type: "cli_auth",
849
- 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"
850
930
  };
851
931
  }
852
- 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")) {
853
933
  return {
854
934
  required: true,
855
935
  type: "api_key",
856
- instructions: "Set ANTHROPIC_API_KEY environment variable or provide credentials in adapterConfig"
936
+ instructions: "API key is invalid - please check your credentials"
857
937
  };
858
938
  }
859
- 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)) {
860
949
  const urlMatch = stripped.match(/https?:\/\/[^\s]+/);
861
950
  return {
862
951
  required: true,
863
952
  type: "browser",
864
953
  url: urlMatch ? urlMatch[0] : void 0,
865
- instructions: "Browser authentication required"
954
+ instructions: "Complete OpenRouter authentication in browser"
866
955
  };
867
956
  }
868
957
  return { required: false };
869
958
  }
870
959
  /**
871
- * 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
872
962
  */
873
963
  detectBlockingPrompt(output) {
874
964
  const stripped = this.stripAnsi(output);
875
- const marker = this.getLatestHookMarker(stripped);
876
965
  const loginDetection = this.detectLogin(output);
877
966
  if (loginDetection.required) {
878
967
  return {
@@ -884,242 +973,116 @@ jq -nc --arg event "${"$"}EVENT" --arg notification_type "${"$"}NOTIFICATION
884
973
  instructions: loginDetection.instructions
885
974
  };
886
975
  }
887
- if (marker?.event === "Notification") {
888
- if (marker.notification_type === "permission_prompt") {
889
- return {
890
- detected: true,
891
- type: "permission",
892
- prompt: marker.message || "Claude permission prompt",
893
- suggestedResponse: "keys:enter",
894
- canAutoRespond: true,
895
- instructions: "Claude is waiting for permission approval"
896
- };
897
- }
898
- if (marker.notification_type === "elicitation_dialog") {
899
- return {
900
- detected: true,
901
- type: "tool_wait",
902
- prompt: marker.message || "Claude elicitation dialog",
903
- canAutoRespond: false,
904
- instructions: "Claude is waiting for required user input"
905
- };
906
- }
907
- }
908
- 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)) {
909
- return {
910
- detected: true,
911
- type: "config",
912
- prompt: "Claude session survey",
913
- options: ["1", "2", "3", "0"],
914
- suggestedResponse: "0",
915
- canAutoRespond: true,
916
- instructions: "Optional survey prompt; reply 0 to dismiss"
917
- };
918
- }
919
- if (/enter\/tab\/space to toggle.*esc to cancel|enter to confirm.*esc to cancel|esc to close/i.test(stripped)) {
920
- return {
921
- detected: true,
922
- type: "config",
923
- prompt: "Claude dialog awaiting navigation",
924
- options: ["keys:enter", "keys:esc", "keys:down,enter"],
925
- suggestedResponse: "keys:esc",
926
- canAutoRespond: false,
927
- instructions: "Use Enter/Esc or arrow keys to navigate this dialog"
928
- };
929
- }
930
- 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)) {
931
- return {
932
- detected: true,
933
- type: "config",
934
- prompt: "Claude menu navigation required",
935
- options: ["keys:esc", "keys:enter", "keys:down,enter"],
936
- suggestedResponse: "keys:esc",
937
- canAutoRespond: false,
938
- instructions: "Claude is showing an interactive menu; use arrow keys + Enter or Esc"
939
- };
940
- }
941
- if (/Do you want to|wants? (your )?permission|needs your permission/i.test(stripped)) {
942
- return {
943
- detected: true,
944
- type: "permission",
945
- prompt: "Claude tool permission",
946
- suggestedResponse: "keys:enter",
947
- canAutoRespond: true,
948
- instructions: "Claude is asking permission to use a tool"
949
- };
950
- }
951
- 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)) {
952
977
  return {
953
978
  detected: true,
954
979
  type: "model_select",
955
- prompt: "Claude model selection",
980
+ prompt: "Model selection required",
956
981
  canAutoRespond: false,
957
- instructions: "Please select a Claude model or set ANTHROPIC_MODEL env var"
982
+ instructions: "Please select a model or set AIDER_MODEL env var"
958
983
  };
959
984
  }
960
- if (/which.*tier|select.*plan|api.*tier/i.test(stripped)) {
985
+ if (/please answer with one of:/i.test(stripped)) {
961
986
  return {
962
987
  detected: true,
963
- type: "config",
964
- prompt: "API tier selection",
988
+ type: "unknown",
989
+ prompt: "Invalid confirmation input",
965
990
  canAutoRespond: false,
966
- instructions: "Please select an API tier"
991
+ instructions: "Aider received an invalid response to a confirmation prompt"
967
992
  };
968
993
  }
969
- if (/welcome to claude|first time setup|initial configuration/i.test(stripped)) {
994
+ if (/delete|remove|overwrite/i.test(stripped) && (/\[y\/n\]/i.test(stripped) || /\(Y\)es\/\(N\)o/i.test(stripped))) {
970
995
  return {
971
996
  detected: true,
972
- type: "config",
973
- prompt: "First-time setup",
997
+ type: "permission",
998
+ prompt: "Destructive operation confirmation",
999
+ options: ["y", "n"],
974
1000
  canAutoRespond: false,
975
- instructions: "Claude Code requires initial configuration"
1001
+ instructions: "Aider is asking to perform a potentially destructive operation"
976
1002
  };
977
1003
  }
978
- if (/allow.*access|grant.*permission|access to .* files/i.test(stripped) && /\[y\/n\]/i.test(stripped)) {
979
- return {
980
- detected: true,
981
- type: "permission",
982
- prompt: "File/directory access permission",
983
- options: ["y", "n"],
984
- suggestedResponse: "y",
985
- canAutoRespond: true,
986
- instructions: "Claude Code requesting file access permission"
987
- };
988
- }
989
- if (this.detectReady(output) || this.detectTaskComplete(output)) {
990
- return { detected: false };
991
- }
992
- if (/❯/.test(stripped.slice(-300))) {
993
- return { detected: false };
994
- }
995
1004
  return super.detectBlockingPrompt(output);
996
1005
  }
997
1006
  /**
998
- * Detect if Claude Code is actively loading/processing.
1007
+ * Detect if Aider is actively loading/processing.
999
1008
  *
1000
1009
  * Patterns from: AGENT_LOADING_STATUS_PATTERNS.json
1001
- * - claude_active_reading_files: "Reading N files…"
1002
- * - 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 ..."
1003
1013
  */
1004
1014
  detectLoading(output) {
1005
1015
  const stripped = this.stripAnsi(output);
1006
- const marker = this.getLatestHookMarker(stripped);
1007
1016
  const tail = stripped.slice(-500);
1008
- if (marker?.event === "PreToolUse") {
1009
- return true;
1010
- }
1011
- if (/esc\s+to\s+interrupt/i.test(tail)) {
1017
+ if (/Waiting\s+for\s+(?:LLM|[A-Za-z0-9_./:@-]+)/i.test(tail)) {
1012
1018
  return true;
1013
1019
  }
1014
- if (/Reading\s+\d+\s+files/i.test(tail)) {
1020
+ if (/Generating\s+commit\s+message\s+with\s+/i.test(tail)) {
1015
1021
  return true;
1016
1022
  }
1017
1023
  return false;
1018
1024
  }
1019
1025
  /**
1020
- * Detect if an external tool/process is running within the Claude session.
1021
- *
1022
- * Claude Code can launch external tools (browser, bash, Node, Python, etc.)
1023
- * that show status lines like "Claude in Chrome[javascript_tool]" or
1024
- * "[bash_tool]", "[python_tool]", etc.
1025
- *
1026
- * When detected, stall detection is suppressed and the UI can display
1027
- * which tool is active.
1028
- */
1029
- detectToolRunning(output) {
1030
- const stripped = this.stripAnsi(output);
1031
- const marker = this.getLatestHookMarker(stripped);
1032
- const tail = stripped.slice(-500);
1033
- if (marker?.event === "PreToolUse" && marker.tool_name) {
1034
- return {
1035
- toolName: marker.tool_name.toLowerCase(),
1036
- description: `${marker.tool_name} (hook)`
1037
- };
1038
- }
1039
- const contextualMatch = tail.match(/Claude\s+in\s+([A-Za-z0-9._-]+)\s*\[(\w+_tool)\]/i);
1040
- if (contextualMatch) {
1041
- const appName = contextualMatch[1];
1042
- const toolType = contextualMatch[2].toLowerCase();
1043
- const friendlyName = toolType.replace(/_tool$/i, "");
1044
- return { toolName: friendlyName, description: `${appName} (${toolType})` };
1045
- }
1046
- const toolMatch = tail.match(/\[(\w+_tool)\]/i);
1047
- if (toolMatch) {
1048
- const toolType = toolMatch[1].toLowerCase();
1049
- const friendlyName = toolType.replace(/_tool$/i, "");
1050
- return { toolName: friendlyName, description: toolType };
1051
- }
1052
- return null;
1053
- }
1054
- /**
1055
- * Detect task completion for Claude Code.
1026
+ * Detect task completion for Aider.
1056
1027
  *
1057
- * High-confidence pattern: turn duration summary + idle prompt.
1058
- * Claude Code shows "<Verb> for Xm Ys" (e.g. "Cooked for 3m 12s")
1059
- * 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
1060
1031
  *
1061
1032
  * Patterns from: AGENT_LOADING_STATUS_PATTERNS.json
1062
- * - claude_completed_turn_duration
1063
- * - claude_completed_turn_duration_custom_verb
1033
+ * - aider_completed_llm_response_ready
1064
1034
  */
1065
1035
  detectTaskComplete(output) {
1066
1036
  const stripped = this.stripAnsi(output);
1067
- const marker = this.getLatestHookMarker(stripped);
1068
- if (!stripped.trim()) return false;
1069
- if (marker?.event === "TaskCompleted") {
1037
+ if (/Aider\s+is\s+waiting\s+for\s+your\s+input/.test(stripped)) {
1070
1038
  return true;
1071
1039
  }
1072
- if (marker?.event === "Notification" && marker.notification_type === "idle_prompt") {
1073
- 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
+ }
1074
1049
  }
1075
- 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)) {
1076
1055
  return false;
1077
1056
  }
1078
- 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);
1079
- const tail = stripped.slice(-300);
1080
- const hasIdlePrompt = /❯/.test(tail);
1081
- if (hasDuration && hasIdlePrompt) {
1057
+ if (/(?:ask|code|architect|help)(?:\s+multi)?>\s*$/m.test(stripped) || /^multi>\s*$/m.test(stripped)) {
1082
1058
  return true;
1083
1059
  }
1084
- if (hasIdlePrompt && stripped.includes("for shortcuts")) {
1060
+ if (/^Aider v\d+/m.test(stripped)) {
1085
1061
  return true;
1086
1062
  }
1087
- return false;
1088
- }
1089
- detectReady(output) {
1090
- const stripped = this.stripAnsi(output);
1091
- const marker = this.getLatestHookMarker(stripped);
1092
- if (!stripped.trim()) return false;
1093
- if (marker?.event === "Notification") {
1094
- if (marker.notification_type === "permission_prompt" || marker.notification_type === "elicitation_dialog") {
1095
- return false;
1096
- }
1097
- if (marker.notification_type === "idle_prompt") {
1098
- return true;
1099
- }
1100
- }
1101
- if (/trust.*directory|do you want to|needs? your permission/i.test(stripped)) {
1102
- return false;
1063
+ if (/^(?:Readonly|Editable):/m.test(stripped)) {
1064
+ return true;
1103
1065
  }
1104
- const tail = stripped.slice(-300);
1105
- const hasConversationalReadyText = stripped.includes("How can I help") || stripped.includes("What would you like");
1106
- const hasLegacyPrompt = /claude>/i.test(tail);
1107
- const hasShortcutsHint = stripped.includes("for shortcuts");
1108
- 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));
1109
- return hasConversationalReadyText || hasLegacyPrompt || hasShortcutsHint || hasInteractivePromptBar;
1066
+ return (
1067
+ // Legacy prompt patterns
1068
+ stripped.includes("aider>") || /Added.*to the chat/i.test(stripped) || />\s*$/.test(stripped)
1069
+ );
1110
1070
  }
1111
1071
  parseOutput(output) {
1112
- const withoutHookMarkers = this.stripHookMarkers(output);
1113
- const stripped = this.stripAnsi(withoutHookMarkers);
1072
+ const stripped = this.stripAnsi(output);
1114
1073
  const isComplete = this.isResponseComplete(stripped);
1115
1074
  if (!isComplete) {
1116
1075
  return null;
1117
1076
  }
1118
1077
  const isQuestion = this.containsQuestion(stripped);
1119
- 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
+ );
1120
1083
  return {
1121
1084
  type: isQuestion ? "question" : "response",
1122
- content,
1085
+ content: content.trim(),
1123
1086
  isComplete: true,
1124
1087
  isQuestion,
1125
1088
  metadata: {
@@ -1127,90 +1090,154 @@ jq -nc --arg event "${"$"}EVENT" --arg notification_type "${"$"}NOTIFICATION
1127
1090
  }
1128
1091
  };
1129
1092
  }
1130
- getPromptPattern() {
1131
- return /claude>\s*$/i;
1132
- }
1133
- getHealthCheckCommand() {
1134
- return "claude --version";
1135
- }
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
+ */
1136
1097
  detectExit(output) {
1137
1098
  const stripped = this.stripAnsi(output);
1138
- const marker = this.getLatestHookMarker(stripped);
1139
- if (marker?.event === "SessionEnd") {
1140
- 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
+ };
1141
1108
  }
1142
1109
  return super.detectExit(output);
1143
1110
  }
1111
+ getPromptPattern() {
1112
+ return /(?:ask|code|architect|help|aider|multi)(?:\s+multi)?>\s*$/i;
1113
+ }
1114
+ getHealthCheckCommand() {
1115
+ return "aider --version";
1116
+ }
1144
1117
  };
1145
1118
 
1146
- // src/gemini-adapter.ts
1147
- var GEMINI_HOOK_MARKER_PREFIX = "PARALLAX_GEMINI_HOOK";
1148
- var GeminiAdapter = class extends BaseCodingAdapter {
1149
- adapterType = "gemini";
1150
- 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;
1151
1127
  installation = {
1152
- command: "npm install -g @google/gemini-cli",
1128
+ command: "npm install -g @anthropic-ai/claude-code",
1153
1129
  alternatives: [
1154
- "See documentation for latest installation method"
1130
+ "npx @anthropic-ai/claude-code (run without installing)",
1131
+ "brew install claude-code (macOS with Homebrew)"
1155
1132
  ],
1156
- docsUrl: "https://github.com/google-gemini/gemini-cli#installation"
1133
+ docsUrl: "https://docs.anthropic.com/en/docs/claude-code",
1134
+ minVersion: "1.0.0"
1157
1135
  };
1158
1136
  /**
1159
- * Auto-response rules for Gemini CLI.
1160
- * Gemini uses Ink/React TUI with arrow-key radio menus.
1161
- * 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.
1162
1140
  */
1163
1141
  autoResponseRules = [
1164
1142
  {
1165
- pattern: /do.?you.?trust.?this.?folder|trust.?folder|trust.?parent.?folder/i,
1166
- 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",
1167
1145
  response: "",
1168
1146
  responseType: "keys",
1169
1147
  keys: ["enter"],
1170
- description: "Trust current folder (default selection in radio menu)",
1148
+ description: "Accept Claude first-run theme/style prompt",
1171
1149
  safe: true,
1172
1150
  once: true
1173
1151
  },
1174
1152
  {
1175
- 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,
1176
1154
  type: "permission",
1177
1155
  response: "",
1178
1156
  responseType: "keys",
1179
1157
  keys: ["enter"],
1180
- description: "Trust multiple folders being added to workspace",
1158
+ description: "Accept trust prompt for working directory",
1181
1159
  safe: true,
1182
1160
  once: true
1183
1161
  },
1184
1162
  {
1185
- pattern: /allow.?google.?to.?use.?this.?data/i,
1186
- 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",
1187
1165
  response: "",
1188
1166
  responseType: "keys",
1189
- keys: ["down", "enter"],
1190
- 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",
1191
1210
  safe: true,
1192
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
1193
1220
  }
1194
1221
  ];
1195
1222
  getWorkspaceFiles() {
1196
1223
  return [
1197
1224
  {
1198
- relativePath: "GEMINI.md",
1225
+ relativePath: "CLAUDE.md",
1199
1226
  description: "Project-level instructions read automatically on startup",
1200
1227
  autoLoaded: true,
1201
1228
  type: "memory",
1202
1229
  format: "markdown"
1203
1230
  },
1204
1231
  {
1205
- relativePath: ".gemini/settings.json",
1206
- description: "Project-scoped settings (tool permissions, sandbox config)",
1207
- autoLoaded: true,
1232
+ relativePath: ".claude/settings.json",
1233
+ description: "Project-scoped settings (allowed tools, permissions)",
1234
+ autoLoaded: true,
1208
1235
  type: "config",
1209
1236
  format: "json"
1210
1237
  },
1211
1238
  {
1212
- relativePath: ".gemini/styles",
1213
- description: "Custom style/persona definitions directory",
1239
+ relativePath: ".claude/commands",
1240
+ description: "Custom slash commands directory",
1214
1241
  autoLoaded: false,
1215
1242
  type: "config",
1216
1243
  format: "markdown"
@@ -1219,22 +1246,27 @@ var GeminiAdapter = class extends BaseCodingAdapter {
1219
1246
  }
1220
1247
  getRecommendedModels(_credentials) {
1221
1248
  return {
1222
- powerful: "gemini-3-pro",
1223
- fast: "gemini-3-flash"
1249
+ powerful: "claude-sonnet-4-20250514",
1250
+ fast: "claude-haiku-4-5-20251001"
1224
1251
  };
1225
1252
  }
1226
1253
  getCommand() {
1227
- return "gemini";
1254
+ return "claude";
1228
1255
  }
1229
1256
  getArgs(config) {
1230
1257
  const args = [];
1258
+ const adapterConfig = config.adapterConfig;
1231
1259
  if (!this.isInteractive(config)) {
1232
- args.push("--non-interactive");
1233
- args.push("--output-format", "text");
1260
+ args.push("--print");
1234
1261
  if (config.workdir) {
1235
1262
  args.push("--cwd", config.workdir);
1236
1263
  }
1237
1264
  }
1265
+ if (adapterConfig?.resume) {
1266
+ args.push("--resume", adapterConfig.resume);
1267
+ } else if (adapterConfig?.continue) {
1268
+ args.push("--continue");
1269
+ }
1238
1270
  const approvalConfig = this.getApprovalConfig(config);
1239
1271
  if (approvalConfig) {
1240
1272
  args.push(...approvalConfig.cliFlags);
@@ -1245,34 +1277,39 @@ var GeminiAdapter = class extends BaseCodingAdapter {
1245
1277
  const env = {};
1246
1278
  const credentials = this.getCredentials(config);
1247
1279
  const adapterConfig = config.adapterConfig;
1248
- if (credentials.googleKey) {
1249
- env.GOOGLE_API_KEY = credentials.googleKey;
1250
- env.GEMINI_API_KEY = credentials.googleKey;
1280
+ if (credentials.anthropicKey) {
1281
+ env.ANTHROPIC_API_KEY = credentials.anthropicKey;
1251
1282
  }
1252
- if (config.env?.GEMINI_MODEL) {
1253
- env.GEMINI_MODEL = config.env.GEMINI_MODEL;
1283
+ if (config.env?.ANTHROPIC_MODEL) {
1284
+ env.ANTHROPIC_MODEL = config.env.ANTHROPIC_MODEL;
1254
1285
  }
1255
1286
  if (!this.isInteractive(config)) {
1256
- env.NO_COLOR = "1";
1287
+ env.CLAUDE_CODE_DISABLE_INTERACTIVE = "true";
1257
1288
  }
1258
- if (adapterConfig?.geminiHookTelemetry) {
1259
- env.PARALLAX_GEMINI_HOOK_TELEMETRY = "1";
1260
- 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;
1261
1292
  }
1262
1293
  return env;
1263
1294
  }
1264
1295
  getHookTelemetryProtocol(options) {
1265
1296
  if (options?.httpUrl) {
1266
- const sessionHeader = options.sessionId ? ` -H 'X-Parallax-Session-Id: ${options.sessionId}'` : "";
1267
- 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}"'`;
1268
- const hookEntry2 = [{ matcher: "", hooks: [{ type: "command", command: curlCommand, timeout: 5e3 }] }];
1269
- 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 }] }];
1270
1307
  const settingsHooks2 = {
1271
- BeforeTool: hookEntry2,
1272
- AfterTool: hookEntry2,
1273
- AfterAgent: hookEntryNoMatcher,
1274
- SessionEnd: hookEntryNoMatcher,
1275
- Notification: hookEntry2
1308
+ PermissionRequest: hookEntryNoMatcher,
1309
+ PreToolUse: hookEntry2,
1310
+ Stop: hookEntryNoMatcher,
1311
+ Notification: hookEntry2,
1312
+ TaskCompleted: hookEntryNoMatcher
1276
1313
  };
1277
1314
  return {
1278
1315
  markerPrefix: "",
@@ -1281,14 +1318,16 @@ var GeminiAdapter = class extends BaseCodingAdapter {
1281
1318
  settingsHooks: settingsHooks2
1282
1319
  };
1283
1320
  }
1284
- const markerPrefix = options?.markerPrefix || GEMINI_HOOK_MARKER_PREFIX;
1285
- const scriptPath = options?.scriptPath || ".gemini/hooks/parallax-hook-telemetry.sh";
1286
- const scriptCommand = `"${"$"}GEMINI_PROJECT_ROOT"/${scriptPath}`;
1287
- 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
+ ];
1288
1327
  const settingsHooks = {
1289
1328
  Notification: hookEntry,
1290
- BeforeTool: hookEntry,
1291
- AfterAgent: hookEntry,
1329
+ PreToolUse: hookEntry,
1330
+ TaskCompleted: hookEntry,
1292
1331
  SessionEnd: hookEntry
1293
1332
  };
1294
1333
  const scriptContent = `#!/usr/bin/env bash
@@ -1298,32 +1337,21 @@ INPUT="$(cat)"
1298
1337
  [ -z "${"$"}INPUT" ] && exit 0
1299
1338
 
1300
1339
  if ! command -v jq >/dev/null 2>&1; then
1301
- # Valid no-op response
1302
- printf '%s
1303
- ' '{"continue":true}'
1304
1340
  exit 0
1305
1341
  fi
1306
1342
 
1307
- EVENT="$(printf '%s' "${"$"}INPUT" | jq -r '.hookEventName // .hook_event_name // empty')"
1308
- [ -z "${"$"}EVENT" ] && { printf '%s
1309
- ' '{"continue":true}'; exit 0; }
1343
+ EVENT="$(printf '%s' "${"$"}INPUT" | jq -r '.hook_event_name // empty')"
1344
+ [ -z "${"$"}EVENT" ] && exit 0
1310
1345
 
1311
- NOTIFICATION_TYPE="$(printf '%s' "${"$"}INPUT" | jq -r '.notificationType // .notification_type // empty')"
1312
- 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')"
1313
1348
  MESSAGE="$(printf '%s' "${"$"}INPUT" | jq -r '.message // empty')"
1314
1349
 
1315
- PAYLOAD="$(jq -nc \\
1316
- --arg event "${"$"}EVENT" \\
1317
- --arg notification_type "${"$"}NOTIFICATION_TYPE" \\
1318
- --arg tool_name "${"$"}TOOL_NAME" \\
1319
- --arg message "${"$"}MESSAGE" \\
1320
- '({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}
1321
1352
  + (if $notification_type != "" then {notification_type: $notification_type} else {} end)
1322
1353
  + (if $tool_name != "" then {tool_name: $tool_name} else {} end)
1323
- + (if $message != "" then {message: $message} else {} end))')"
1324
-
1325
- MARKER="${markerPrefix} ${"$"}PAYLOAD"
1326
- jq -nc --arg m "${"$"}MARKER" '{continue: true, suppressOutput: true, systemMessage: $m}'
1354
+ + (if $message != "" then {message: $message} else {} end))'
1327
1355
  `;
1328
1356
  return {
1329
1357
  markerPrefix,
@@ -1338,7 +1366,7 @@ jq -nc --arg m "${"$"}MARKER" '{continue: true, suppressOutput: true, systemMess
1338
1366
  let match;
1339
1367
  while ((match = markerRegex.exec(output)) !== null) {
1340
1368
  const markerToken = match[1];
1341
- if (!markerToken.includes("GEMINI_HOOK")) {
1369
+ if (!markerToken.includes("CLAUDE_HOOK")) {
1342
1370
  continue;
1343
1371
  }
1344
1372
  const payload = match[2];
@@ -1362,208 +1390,288 @@ jq -nc --arg m "${"$"}MARKER" '{continue: true, suppressOutput: true, systemMess
1362
1390
  return markers.length > 0 ? markers[markers.length - 1] : null;
1363
1391
  }
1364
1392
  stripHookMarkers(output) {
1365
- 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
+ );
1366
1397
  }
1367
1398
  detectLogin(output) {
1368
1399
  const stripped = this.stripAnsi(output);
1369
- 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")) {
1370
1401
  return {
1371
1402
  required: true,
1372
- type: "api_key",
1373
- 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.'
1374
1405
  };
1375
1406
  }
1376
- 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")) {
1377
1408
  return {
1378
1409
  required: true,
1379
1410
  type: "api_key",
1380
- instructions: "Enter a Gemini API key or set GEMINI_API_KEY environment variable"
1381
- };
1382
- }
1383
- 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)) {
1384
- return {
1385
- required: true,
1386
- type: "oauth",
1387
- instructions: "Gemini CLI authentication required \u2014 select an auth method"
1388
- };
1389
- }
1390
- if (/waiting.?for.?auth/i.test(stripped)) {
1391
- return {
1392
- required: true,
1393
- type: "oauth",
1394
- instructions: "Waiting for browser authentication to complete"
1411
+ instructions: "Set ANTHROPIC_API_KEY environment variable or provide credentials in adapterConfig"
1395
1412
  };
1396
1413
  }
1397
- 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")) {
1398
1415
  const urlMatch = stripped.match(/https?:\/\/[^\s]+/);
1399
- return {
1400
- required: true,
1401
- type: "oauth",
1402
- url: urlMatch ? urlMatch[0] : "https://accounts.google.com",
1403
- instructions: "Google OAuth authentication required"
1404
- };
1405
- }
1406
- if (stripped.includes("Application Default Credentials") || stripped.includes("gcloud auth")) {
1407
1416
  return {
1408
1417
  required: true,
1409
1418
  type: "browser",
1410
- instructions: "Run: gcloud auth application-default login"
1419
+ url: urlMatch ? urlMatch[0] : void 0,
1420
+ instructions: "Browser authentication required"
1411
1421
  };
1412
1422
  }
1413
1423
  return { required: false };
1414
1424
  }
1425
+ /**
1426
+ * Detect blocking prompts specific to Claude Code CLI
1427
+ */
1415
1428
  detectBlockingPrompt(output) {
1416
1429
  const stripped = this.stripAnsi(output);
1417
1430
  const marker = this.getLatestHookMarker(stripped);
1418
- 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) {
1419
1440
  return {
1420
1441
  detected: true,
1421
- type: "permission",
1422
- prompt: marker.message || "Gemini tool permission",
1423
- suggestedResponse: "keys:enter",
1424
- canAutoRespond: true,
1425
- 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
1426
1447
  };
1427
1448
  }
1428
- 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)) {
1429
1471
  return {
1430
1472
  detected: true,
1431
1473
  type: "permission",
1432
- prompt: "Gemini tool execution confirmation",
1433
- suggestedResponse: "keys:enter",
1474
+ prompt: "Bypass Permissions confirmation",
1475
+ options: ["1", "2"],
1476
+ suggestedResponse: "2",
1434
1477
  canAutoRespond: true,
1435
- 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"
1436
1479
  };
1437
1480
  }
1438
- 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
+ )) {
1439
1484
  return {
1440
1485
  detected: true,
1441
- type: "tool_wait",
1442
- prompt: "Interactive shell confirmation required (y/n)",
1443
- canAutoRespond: false,
1444
- 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"
1445
1492
  };
1446
1493
  }
1447
- 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
+ )) {
1448
1497
  return {
1449
1498
  detected: true,
1450
- type: "tool_wait",
1451
- 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",
1452
1503
  canAutoRespond: false,
1453
- 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"
1454
1505
  };
1455
1506
  }
1456
- 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
+ )) {
1457
1512
  return {
1458
1513
  detected: true,
1459
1514
  type: "config",
1460
- prompt: "Gemini checkpoint setup prompt",
1515
+ prompt: "Claude menu navigation required",
1516
+ options: ["keys:esc", "keys:enter", "keys:down,enter"],
1517
+ suggestedResponse: "keys:esc",
1461
1518
  canAutoRespond: false,
1462
- 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"
1463
1520
  };
1464
1521
  }
1465
- const loginDetection = this.detectLogin(output);
1466
- if (loginDetection.required) {
1522
+ if (/Do you want to|wants? (your )?permission|needs your permission/i.test(
1523
+ stripped
1524
+ )) {
1467
1525
  return {
1468
1526
  detected: true,
1469
- type: "login",
1470
- prompt: loginDetection.instructions,
1471
- url: loginDetection.url,
1472
- canAutoRespond: false,
1473
- 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"
1474
1532
  };
1475
1533
  }
1476
- 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)) {
1477
1535
  return {
1478
1536
  detected: true,
1479
- type: "config",
1480
- prompt: "Account verification required",
1537
+ type: "model_select",
1538
+ prompt: "Claude model selection",
1481
1539
  canAutoRespond: false,
1482
- instructions: "Your Gemini account requires verification before continuing"
1540
+ instructions: "Please select a Claude model or set ANTHROPIC_MODEL env var"
1483
1541
  };
1484
1542
  }
1485
- if (/select.*model|choose.*model|gemini-/i.test(stripped) && /\d+\)/i.test(stripped)) {
1543
+ if (/which.*tier|select.*plan|api.*tier/i.test(stripped)) {
1486
1544
  return {
1487
1545
  detected: true,
1488
- type: "model_select",
1489
- prompt: "Gemini model selection",
1546
+ type: "config",
1547
+ prompt: "API tier selection",
1490
1548
  canAutoRespond: false,
1491
- instructions: "Please select a model or set GEMINI_MODEL env var"
1549
+ instructions: "Please select an API tier"
1492
1550
  };
1493
1551
  }
1494
- if (/select.*project|choose.*project|google cloud project/i.test(stripped)) {
1552
+ if (/welcome to claude|first time setup|initial configuration/i.test(stripped)) {
1495
1553
  return {
1496
1554
  detected: true,
1497
- type: "project_select",
1498
- prompt: "Google Cloud project selection",
1555
+ type: "config",
1556
+ prompt: "First-time setup",
1499
1557
  canAutoRespond: false,
1500
- instructions: "Please select a Google Cloud project"
1558
+ instructions: "Claude Code requires initial configuration"
1501
1559
  };
1502
1560
  }
1503
- 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)) {
1504
1562
  return {
1505
1563
  detected: true,
1506
- type: "unknown",
1507
- prompt: "Safety filter triggered",
1508
- canAutoRespond: false,
1509
- 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"
1510
1570
  };
1511
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
+ }
1512
1578
  return super.detectBlockingPrompt(output);
1513
1579
  }
1514
1580
  /**
1515
- * Detect if Gemini CLI is actively loading/processing.
1581
+ * Detect if Claude Code is actively loading/processing.
1516
1582
  *
1517
1583
  * Patterns from: AGENT_LOADING_STATUS_PATTERNS.json
1518
- * - gemini_active_loading_line: "(esc to cancel, Xs)"
1519
- * - 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
1520
1586
  */
1521
1587
  detectLoading(output) {
1522
1588
  const stripped = this.stripAnsi(output);
1523
1589
  const marker = this.getLatestHookMarker(stripped);
1524
1590
  const tail = stripped.slice(-500);
1525
- if (marker?.event === "BeforeTool") {
1591
+ if (marker?.event === "PreToolUse") {
1526
1592
  return true;
1527
1593
  }
1528
- if (/esc\s+to\s+cancel/i.test(tail)) {
1594
+ if (/esc\s+to\s+interrupt/i.test(tail)) {
1529
1595
  return true;
1530
1596
  }
1531
- if (/Waiting\s+for\s+user\s+confirmation/i.test(tail)) {
1597
+ if (/Reading\s+\d+\s+files/i.test(tail)) {
1532
1598
  return true;
1533
1599
  }
1534
1600
  return false;
1535
1601
  }
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
+ */
1536
1612
  detectToolRunning(output) {
1537
1613
  const stripped = this.stripAnsi(output);
1538
1614
  const marker = this.getLatestHookMarker(stripped);
1539
- if (marker?.event === "BeforeTool" && marker.tool_name) {
1615
+ const tail = stripped.slice(-500);
1616
+ if (marker?.event === "PreToolUse" && marker.tool_name) {
1540
1617
  return {
1541
1618
  toolName: marker.tool_name.toLowerCase(),
1542
1619
  description: `${marker.tool_name} (hook)`
1543
1620
  };
1544
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
+ }
1545
1640
  return null;
1546
1641
  }
1547
1642
  /**
1548
- * Detect task completion for Gemini CLI.
1643
+ * Detect task completion for Claude Code.
1549
1644
  *
1550
- * High-confidence patterns:
1551
- * - "◇ Ready" window title signal (OSC sequence, may survive ANSI stripping)
1552
- * - "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.
1553
1648
  *
1554
1649
  * Patterns from: AGENT_LOADING_STATUS_PATTERNS.json
1555
- * - gemini_ready_title
1650
+ * - claude_completed_turn_duration
1651
+ * - claude_completed_turn_duration_custom_verb
1556
1652
  */
1557
1653
  detectTaskComplete(output) {
1558
1654
  const stripped = this.stripAnsi(output);
1559
1655
  const marker = this.getLatestHookMarker(stripped);
1560
- if (marker?.event === "AfterAgent") {
1656
+ if (!stripped.trim()) return false;
1657
+ if (marker?.event === "TaskCompleted") {
1561
1658
  return true;
1562
1659
  }
1563
- if (/◇\s+Ready/.test(stripped)) {
1660
+ if (marker?.event === "Notification" && marker.notification_type === "idle_prompt") {
1564
1661
  return true;
1565
1662
  }
1566
- 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")) {
1567
1675
  return true;
1568
1676
  }
1569
1677
  return false;
@@ -1571,27 +1679,24 @@ jq -nc --arg m "${"$"}MARKER" '{continue: true, suppressOutput: true, systemMess
1571
1679
  detectReady(output) {
1572
1680
  const stripped = this.stripAnsi(output);
1573
1681
  const marker = this.getLatestHookMarker(stripped);
1574
- if (marker?.event === "Notification" && marker.notification_type === "ToolPermission") {
1575
- return false;
1576
- }
1577
- if (marker?.event === "AfterAgent") {
1578
- return true;
1579
- }
1580
- 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);
1581
- if (hasActiveOverlay) {
1582
- return false;
1583
- }
1584
- if (/type.?your.?message/i.test(stripped)) {
1585
- 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
+ }
1586
1690
  }
1587
- 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)) {
1588
1692
  return false;
1589
1693
  }
1590
- if (/^\s*[>!*]\s+/m.test(stripped) || /\(r:\)/.test(stripped)) {
1591
- return true;
1592
- }
1593
- return stripped.includes("How can I help") || stripped.includes("What would you like") || // Match "gemini> " prompt specifically, not bare ">"
1594
- /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;
1595
1700
  }
1596
1701
  parseOutput(output) {
1597
1702
  const withoutHookMarkers = this.stripHookMarkers(output);
@@ -1601,8 +1706,7 @@ jq -nc --arg m "${"$"}MARKER" '{continue: true, suppressOutput: true, systemMess
1601
1706
  return null;
1602
1707
  }
1603
1708
  const isQuestion = this.containsQuestion(stripped);
1604
- let content = this.extractContent(stripped, /^.*(?:gemini|>)\s*/gim);
1605
- content = content.replace(/^\[Safety[^\]]*\].*$/gm, "");
1709
+ const content = this.extractContent(stripped, /^.*>\s*/gm);
1606
1710
  return {
1607
1711
  type: isQuestion ? "question" : "response",
1608
1712
  content,
@@ -1613,57 +1717,30 @@ jq -nc --arg m "${"$"}MARKER" '{continue: true, suppressOutput: true, systemMess
1613
1717
  }
1614
1718
  };
1615
1719
  }
1616
- /**
1617
- * Detect exit conditions specific to Gemini CLI.
1618
- * Source: FolderTrustDialog.tsx:127, LogoutConfirmationDialog.tsx:64
1619
- */
1720
+ getPromptPattern() {
1721
+ return /claude>\s*$/i;
1722
+ }
1723
+ getHealthCheckCommand() {
1724
+ return "claude --version";
1725
+ }
1620
1726
  detectExit(output) {
1621
1727
  const stripped = this.stripAnsi(output);
1622
1728
  const marker = this.getLatestHookMarker(stripped);
1623
1729
  if (marker?.event === "SessionEnd") {
1624
- return {
1625
- exited: true,
1626
- code: 0
1627
- };
1628
- }
1629
- if (/folder.?trust.?level.?must.?be.?selected.*exiting/i.test(stripped)) {
1630
- return {
1631
- exited: true,
1632
- code: 1,
1633
- error: "Gemini CLI exited because no folder trust level was selected"
1634
- };
1635
- }
1636
- if (/you are now logged out/i.test(stripped)) {
1637
- return {
1638
- exited: true,
1639
- code: 0
1640
- };
1641
- }
1642
- if (/Agent\s+powering\s+down/i.test(stripped)) {
1643
- return {
1644
- exited: true,
1645
- code: 0
1646
- };
1730
+ return { exited: true, code: 0 };
1647
1731
  }
1648
1732
  return super.detectExit(output);
1649
1733
  }
1650
- getPromptPattern() {
1651
- return /gemini>\s*$/i;
1652
- }
1653
- getHealthCheckCommand() {
1654
- return "gemini --version";
1655
- }
1656
1734
  };
1657
1735
 
1658
1736
  // src/codex-adapter.ts
1659
1737
  var CodexAdapter = class extends BaseCodingAdapter {
1660
1738
  adapterType = "codex";
1661
1739
  displayName = "OpenAI Codex";
1740
+ readySettleMs = 2e3;
1662
1741
  installation = {
1663
1742
  command: "npm install -g @openai/codex",
1664
- alternatives: [
1665
- "pip install openai (Python SDK)"
1666
- ],
1743
+ alternatives: ["pip install openai (Python SDK)"],
1667
1744
  docsUrl: "https://github.com/openai/codex"
1668
1745
  };
1669
1746
  /**
@@ -1977,7 +2054,9 @@ var CodexAdapter = class extends BaseCodingAdapter {
1977
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)) {
1978
2055
  return false;
1979
2056
  }
1980
- 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
+ )) {
1981
2060
  return true;
1982
2061
  }
1983
2062
  return stripped.includes("How can I help") || /(?:codex|>)\s*$/i.test(stripped);
@@ -2026,270 +2105,96 @@ var CodexAdapter = class extends BaseCodingAdapter {
2026
2105
  }
2027
2106
  };
2028
2107
 
2029
- // src/aider-adapter.ts
2030
- var AiderAdapter = class extends BaseCodingAdapter {
2031
- adapterType = "aider";
2032
- displayName = "Aider";
2033
- /** Minimal TUI, mostly text output — shorter settle delay */
2034
- readySettleMs = 200;
2035
- /**
2036
- * Aider uses plain text [y/n] prompts, NOT TUI arrow-key menus.
2037
- */
2038
- 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;
2039
2114
  installation = {
2040
- command: "pip install aider-chat",
2041
- alternatives: [
2042
- "pipx install aider-chat (isolated install)",
2043
- "brew install aider (macOS with Homebrew)"
2044
- ],
2045
- docsUrl: "https://aider.chat/docs/install.html",
2046
- 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"
2047
2118
  };
2048
2119
  /**
2049
- * Auto-response rules for Aider CLI.
2050
- * Aider uses plain text prompts via io.py:832 with (Y)es/(N)o format.
2051
- * All rules are responseType: 'text' Aider never uses TUI menus.
2052
- *
2053
- * 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
2054
2123
  */
2055
2124
  autoResponseRules = [
2056
- // ── Decline rules (specific, checked first) ────────────────────────
2057
2125
  {
2058
- pattern: /allow collection of anonymous analytics/i,
2059
- type: "config",
2060
- response: "n",
2061
- responseType: "text",
2062
- 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)",
2063
2132
  safe: true,
2064
2133
  once: true
2065
2134
  },
2066
2135
  {
2067
- pattern: /would you like to see what.?s new in this version/i,
2068
- type: "config",
2069
- response: "n",
2070
- responseType: "text",
2071
- 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",
2072
2142
  safe: true,
2073
2143
  once: true
2074
2144
  },
2075
2145
  {
2076
- pattern: /open a github issue pre-filled/i,
2077
- type: "config",
2078
- response: "n",
2079
- responseType: "text",
2080
- description: "Decline automatic bug report",
2081
- safe: true
2082
- },
2083
- {
2084
- pattern: /open documentation url for more info\?/i,
2146
+ pattern: /allow.?google.?to.?use.?this.?data/i,
2085
2147
  type: "config",
2086
- response: "n",
2087
- responseType: "text",
2088
- description: "Decline opening Aider documentation for model warnings",
2089
- safe: true
2090
- },
2091
- // ── File / edit operations ──────────────────────────────────────────
2092
- {
2093
- pattern: /add .+ to the chat\?/i,
2094
- type: "permission",
2095
- response: "y",
2096
- responseType: "text",
2097
- description: "Allow Aider to add files to chat context",
2098
- safe: true
2099
- },
2100
- {
2101
- pattern: /add url to the chat\?/i,
2102
- type: "permission",
2103
- response: "y",
2104
- responseType: "text",
2105
- description: "Allow Aider to add URL content to chat",
2106
- safe: true
2107
- },
2108
- {
2109
- pattern: /create new file\?/i,
2110
- type: "permission",
2111
- response: "y",
2112
- responseType: "text",
2113
- description: "Allow Aider to create new files",
2114
- safe: true
2115
- },
2116
- {
2117
- pattern: /allow edits to file/i,
2118
- type: "permission",
2119
- response: "y",
2120
- responseType: "text",
2121
- description: "Allow edits to file not yet in chat",
2122
- safe: true
2123
- },
2124
- {
2125
- pattern: /edit the files\?/i,
2126
- type: "permission",
2127
- response: "y",
2128
- responseType: "text",
2129
- description: "Accept architect mode edits",
2130
- safe: true
2131
- },
2132
- // ── Shell operations ────────────────────────────────────────────────
2133
- {
2134
- pattern: /run shell commands?\?/i,
2135
- type: "permission",
2136
- response: "y",
2137
- responseType: "text",
2138
- description: "Allow Aider to run shell commands",
2139
- safe: true
2140
- },
2141
- {
2142
- pattern: /add command output to the chat\?/i,
2143
- type: "permission",
2144
- response: "y",
2145
- responseType: "text",
2146
- description: "Add shell command output to chat context",
2147
- safe: true
2148
- },
2149
- {
2150
- pattern: /add \d+.*tokens of command output to the chat\?/i,
2151
- type: "permission",
2152
- response: "y",
2153
- responseType: "text",
2154
- description: "Add /run command output to chat context",
2155
- safe: true
2156
- },
2157
- // ── Setup / maintenance ─────────────────────────────────────────────
2158
- {
2159
- pattern: /no git repo found.*create one/i,
2160
- type: "config",
2161
- response: "y",
2162
- responseType: "text",
2163
- description: "Create git repo for change tracking",
2164
- safe: true,
2165
- once: true
2166
- },
2167
- {
2168
- pattern: /add .+ to \.gitignore/i,
2169
- type: "config",
2170
- response: "y",
2171
- responseType: "text",
2172
- description: "Update .gitignore with Aider patterns",
2148
+ response: "",
2149
+ responseType: "keys",
2150
+ keys: ["down", "enter"],
2151
+ description: 'Decline Google data collection (select "No")',
2173
2152
  safe: true,
2174
2153
  once: true
2175
- },
2176
- {
2177
- pattern: /run pip install\?/i,
2178
- type: "config",
2179
- response: "y",
2180
- responseType: "text",
2181
- description: "Install missing Python dependencies",
2182
- safe: true
2183
- },
2184
- {
2185
- pattern: /install playwright\?/i,
2186
- type: "config",
2187
- response: "y",
2188
- responseType: "text",
2189
- description: "Install Playwright for web scraping",
2190
- safe: true
2191
- },
2192
- // ── Other safe confirmations ────────────────────────────────────────
2193
- {
2194
- pattern: /fix lint errors in/i,
2195
- type: "permission",
2196
- response: "y",
2197
- responseType: "text",
2198
- description: "Accept lint error fix suggestion",
2199
- safe: true
2200
- },
2201
- {
2202
- pattern: /try to proceed anyway\?/i,
2203
- type: "config",
2204
- response: "y",
2205
- responseType: "text",
2206
- description: "Continue despite context limit warning",
2207
- safe: true
2208
2154
  }
2209
2155
  ];
2210
2156
  getWorkspaceFiles() {
2211
2157
  return [
2212
2158
  {
2213
- relativePath: ".aider.conventions.md",
2214
- description: "Project conventions and instructions read on startup (--read flag)",
2159
+ relativePath: "GEMINI.md",
2160
+ description: "Project-level instructions read automatically on startup",
2215
2161
  autoLoaded: true,
2216
2162
  type: "memory",
2217
2163
  format: "markdown"
2218
2164
  },
2219
2165
  {
2220
- relativePath: ".aider.conf.yml",
2221
- description: "Project-scoped Aider configuration (model, flags, options)",
2166
+ relativePath: ".gemini/settings.json",
2167
+ description: "Project-scoped settings (tool permissions, sandbox config)",
2222
2168
  autoLoaded: true,
2223
2169
  type: "config",
2224
- format: "yaml"
2170
+ format: "json"
2225
2171
  },
2226
2172
  {
2227
- relativePath: ".aiderignore",
2228
- description: "Gitignore-style file listing paths Aider should not edit",
2229
- autoLoaded: true,
2230
- type: "rules",
2231
- format: "text"
2173
+ relativePath: ".gemini/styles",
2174
+ description: "Custom style/persona definitions directory",
2175
+ autoLoaded: false,
2176
+ type: "config",
2177
+ format: "markdown"
2232
2178
  }
2233
2179
  ];
2234
2180
  }
2235
- getRecommendedModels(credentials) {
2236
- if (credentials?.anthropicKey) {
2237
- return {
2238
- powerful: "anthropic/claude-sonnet-4-20250514",
2239
- fast: "anthropic/claude-haiku-4-5-20251001"
2240
- };
2241
- }
2242
- if (credentials?.openaiKey) {
2243
- return {
2244
- powerful: "openai/o3",
2245
- fast: "openai/gpt-4o-mini"
2246
- };
2247
- }
2248
- if (credentials?.googleKey) {
2249
- return {
2250
- powerful: "gemini/gemini-3-pro",
2251
- fast: "gemini/gemini-3-flash"
2252
- };
2253
- }
2181
+ getRecommendedModels(_credentials) {
2254
2182
  return {
2255
- powerful: "anthropic/claude-sonnet-4-20250514",
2256
- fast: "anthropic/claude-haiku-4-5-20251001"
2183
+ powerful: "gemini-3-pro",
2184
+ fast: "gemini-3-flash"
2257
2185
  };
2258
2186
  }
2259
2187
  getCommand() {
2260
- return "aider";
2188
+ return "gemini";
2261
2189
  }
2262
2190
  getArgs(config) {
2263
2191
  const args = [];
2264
- const interactive = this.isInteractive(config);
2265
- if (!interactive) {
2266
- args.push("--auto-commits");
2267
- args.push("--no-pretty");
2268
- args.push("--no-show-diffs");
2269
- }
2270
- const provider = config.adapterConfig?.provider;
2271
- const credentials = this.getCredentials(config);
2272
- if (config.env?.AIDER_MODEL) {
2273
- args.push("--model", config.env.AIDER_MODEL);
2274
- } else if (interactive && (credentials.googleKey || process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY)) {
2275
- args.push("--model", "gemini");
2276
- } else if (!interactive && provider === "anthropic") {
2277
- args.push("--model", "sonnet");
2278
- } else if (!interactive && provider === "openai") {
2279
- args.push("--model", "4o");
2280
- } else if (!interactive && provider === "google") {
2281
- args.push("--model", "gemini");
2282
- } else if (!interactive && credentials.anthropicKey) {
2283
- args.push("--model", "sonnet");
2284
- } else if (!interactive && credentials.openaiKey) {
2285
- args.push("--model", "4o");
2286
- } else if (!interactive && credentials.googleKey) {
2287
- args.push("--model", "gemini");
2288
- }
2289
- if (!interactive) {
2290
- if (credentials.anthropicKey) args.push("--api-key", `anthropic=${credentials.anthropicKey}`);
2291
- if (credentials.openaiKey) args.push("--api-key", `openai=${credentials.openaiKey}`);
2292
- if (credentials.googleKey) args.push("--api-key", `gemini=${credentials.googleKey}`);
2192
+ if (!this.isInteractive(config)) {
2193
+ args.push("--non-interactive");
2194
+ args.push("--output-format", "text");
2195
+ if (config.workdir) {
2196
+ args.push("--cwd", config.workdir);
2197
+ }
2293
2198
  }
2294
2199
  const approvalConfig = this.getApprovalConfig(config);
2295
2200
  if (approvalConfig) {
@@ -2299,54 +2204,239 @@ var AiderAdapter = class extends BaseCodingAdapter {
2299
2204
  }
2300
2205
  getEnv(config) {
2301
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
+ }
2302
2216
  if (!this.isInteractive(config)) {
2303
2217
  env.NO_COLOR = "1";
2304
2218
  }
2305
- if (config.env?.AIDER_NO_GIT === "true") {
2306
- 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;
2307
2222
  }
2308
2223
  return env;
2309
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
+ }
2310
2340
  detectLogin(output) {
2311
2341
  const stripped = this.stripAnsi(output);
2312
- 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")) {
2313
2343
  return {
2314
2344
  required: true,
2315
2345
  type: "api_key",
2316
- 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"
2317
2361
  };
2318
2362
  }
2319
- if (stripped.includes("Invalid API key") || stripped.includes("Authentication failed") || stripped.includes("Unauthorized")) {
2363
+ if (/waiting.?for.?auth/i.test(stripped)) {
2320
2364
  return {
2321
2365
  required: true,
2322
- type: "api_key",
2323
- instructions: "API key is invalid - please check your credentials"
2366
+ type: "oauth",
2367
+ instructions: "Waiting for browser authentication to complete"
2324
2368
  };
2325
2369
  }
2326
- 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]+/);
2327
2372
  return {
2328
2373
  required: true,
2329
2374
  type: "oauth",
2330
- 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"
2331
2377
  };
2332
2378
  }
2333
- 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)) {
2334
- const urlMatch = stripped.match(/https?:\/\/[^\s]+/);
2379
+ if (stripped.includes("Application Default Credentials") || stripped.includes("gcloud auth")) {
2335
2380
  return {
2336
2381
  required: true,
2337
2382
  type: "browser",
2338
- url: urlMatch ? urlMatch[0] : void 0,
2339
- instructions: "Complete OpenRouter authentication in browser"
2383
+ instructions: "Run: gcloud auth application-default login"
2340
2384
  };
2341
2385
  }
2342
2386
  return { required: false };
2343
2387
  }
2344
- /**
2345
- * Detect blocking prompts specific to Aider CLI.
2346
- * Source: io.py, onboarding.py, base_coder.py, report.py
2347
- */
2348
2388
  detectBlockingPrompt(output) {
2349
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
+ }
2350
2440
  const loginDetection = this.detectLogin(output);
2351
2441
  if (loginDetection.required) {
2352
2442
  return {
@@ -2358,111 +2448,147 @@ var AiderAdapter = class extends BaseCodingAdapter {
2358
2448
  instructions: loginDetection.instructions
2359
2449
  };
2360
2450
  }
2361
- 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)) {
2362
2461
  return {
2363
2462
  detected: true,
2364
2463
  type: "model_select",
2365
- prompt: "Model selection required",
2464
+ prompt: "Gemini model selection",
2366
2465
  canAutoRespond: false,
2367
- instructions: "Please select a model or set AIDER_MODEL env var"
2466
+ instructions: "Please select a model or set GEMINI_MODEL env var"
2368
2467
  };
2369
2468
  }
2370
- if (/please answer with one of:/i.test(stripped)) {
2469
+ if (/select.*project|choose.*project|google cloud project/i.test(stripped)) {
2371
2470
  return {
2372
2471
  detected: true,
2373
- type: "unknown",
2374
- prompt: "Invalid confirmation input",
2472
+ type: "project_select",
2473
+ prompt: "Google Cloud project selection",
2375
2474
  canAutoRespond: false,
2376
- instructions: "Aider received an invalid response to a confirmation prompt"
2475
+ instructions: "Please select a Google Cloud project"
2377
2476
  };
2378
2477
  }
2379
- 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)) {
2380
2479
  return {
2381
2480
  detected: true,
2382
- type: "permission",
2383
- prompt: "Destructive operation confirmation",
2384
- options: ["y", "n"],
2481
+ type: "unknown",
2482
+ prompt: "Safety filter triggered",
2385
2483
  canAutoRespond: false,
2386
- instructions: "Aider is asking to perform a potentially destructive operation"
2484
+ instructions: "Content was blocked by safety filters"
2387
2485
  };
2388
2486
  }
2389
2487
  return super.detectBlockingPrompt(output);
2390
2488
  }
2391
2489
  /**
2392
- * Detect if Aider is actively loading/processing.
2490
+ * Detect if Gemini CLI is actively loading/processing.
2393
2491
  *
2394
2492
  * Patterns from: AGENT_LOADING_STATUS_PATTERNS.json
2395
- * - aider_active_waiting_model: "Waiting for <model>"
2396
- * - aider_active_waiting_llm_default: "Waiting for LLM"
2397
- * - 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..."
2398
2495
  */
2399
2496
  detectLoading(output) {
2400
2497
  const stripped = this.stripAnsi(output);
2498
+ const marker = this.getLatestHookMarker(stripped);
2401
2499
  const tail = stripped.slice(-500);
2402
- if (/Waiting\s+for\s+(?:LLM|[A-Za-z0-9_./:@-]+)/i.test(tail)) {
2500
+ if (marker?.event === "BeforeTool") {
2403
2501
  return true;
2404
2502
  }
2405
- 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)) {
2406
2507
  return true;
2407
2508
  }
2408
2509
  return false;
2409
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
+ }
2410
2522
  /**
2411
- * Detect task completion for Aider.
2523
+ * Detect task completion for Gemini CLI.
2412
2524
  *
2413
2525
  * High-confidence patterns:
2414
- * - "Aider is waiting for your input" notification (bell message)
2415
- * - 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
2416
2528
  *
2417
2529
  * Patterns from: AGENT_LOADING_STATUS_PATTERNS.json
2418
- * - aider_completed_llm_response_ready
2530
+ * - gemini_ready_title
2419
2531
  */
2420
2532
  detectTaskComplete(output) {
2421
2533
  const stripped = this.stripAnsi(output);
2422
- 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") {
2423
2536
  return true;
2424
2537
  }
2425
- const hasPrompt = /(?:(?:ask|code|architect)(?:\s+multi)?)?>\s*$/m.test(stripped);
2426
- if (hasPrompt) {
2427
- const hasEditMarkers = /Applied edit to|Commit [a-f0-9]+|wrote to|Updated/i.test(stripped);
2428
- const hasTokenUsage = /Tokens:|Cost:/i.test(stripped);
2429
- if (hasEditMarkers || hasTokenUsage) {
2430
- return true;
2431
- }
2538
+ if (/◇\s+Ready/.test(stripped)) {
2539
+ return true;
2540
+ }
2541
+ if (/type.?your.?message/i.test(stripped)) {
2542
+ return true;
2432
2543
  }
2433
2544
  return false;
2434
2545
  }
2435
2546
  detectReady(output) {
2436
2547
  const stripped = this.stripAnsi(output);
2437
- 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") {
2438
2550
  return false;
2439
2551
  }
2440
- if (/(?:ask|code|architect|help)(?:\s+multi)?>\s*$/m.test(stripped) || /^multi>\s*$/m.test(stripped)) {
2552
+ if (marker?.event === "AfterAgent") {
2441
2553
  return true;
2442
2554
  }
2443
- 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)) {
2444
2568
  return true;
2445
2569
  }
2446
- 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)) {
2447
2574
  return true;
2448
2575
  }
2449
- return (
2450
- // Legacy prompt patterns
2451
- stripped.includes("aider>") || /Added.*to the chat/i.test(stripped) || />\s*$/.test(stripped)
2452
- );
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);
2453
2578
  }
2454
2579
  parseOutput(output) {
2455
- const stripped = this.stripAnsi(output);
2580
+ const withoutHookMarkers = this.stripHookMarkers(output);
2581
+ const stripped = this.stripAnsi(withoutHookMarkers);
2456
2582
  const isComplete = this.isResponseComplete(stripped);
2457
2583
  if (!isComplete) {
2458
2584
  return null;
2459
2585
  }
2460
2586
  const isQuestion = this.containsQuestion(stripped);
2461
- let content = this.extractContent(stripped, /^.*aider>\s*/gim);
2462
- 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, "");
2463
2589
  return {
2464
2590
  type: isQuestion ? "question" : "response",
2465
- content: content.trim(),
2591
+ content,
2466
2592
  isComplete: true,
2467
2593
  isQuestion,
2468
2594
  metadata: {
@@ -2471,28 +2597,44 @@ var AiderAdapter = class extends BaseCodingAdapter {
2471
2597
  };
2472
2598
  }
2473
2599
  /**
2474
- * Detect exit conditions specific to Aider.
2475
- * 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
2476
2602
  */
2477
2603
  detectExit(output) {
2478
2604
  const stripped = this.stripAnsi(output);
2479
- if (/\^C again to exit/i.test(stripped) || /\^C KeyboardInterrupt/i.test(stripped)) {
2480
- 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
+ };
2481
2611
  }
2482
- if (/re-run aider to use new version/i.test(stripped)) {
2612
+ if (/folder.?trust.?level.?must.?be.?selected.*exiting/i.test(stripped)) {
2483
2613
  return {
2484
2614
  exited: true,
2485
- code: 0,
2486
- 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
2487
2629
  };
2488
2630
  }
2489
2631
  return super.detectExit(output);
2490
2632
  }
2491
2633
  getPromptPattern() {
2492
- return /(?:ask|code|architect|help|aider|multi)(?:\s+multi)?>\s*$/i;
2634
+ return /gemini>\s*$/i;
2493
2635
  }
2494
2636
  getHealthCheckCommand() {
2495
- return "aider --version";
2637
+ return "gemini --version";
2496
2638
  }
2497
2639
  };
2498
2640
 
@@ -2598,7 +2740,9 @@ var HermesAdapter = class extends BaseCodingAdapter {
2598
2740
  instructions: "Hermes is waiting for clarify input (arrow keys + Enter or free text)."
2599
2741
  };
2600
2742
  }
2601
- 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
+ )) {
2602
2746
  return {
2603
2747
  detected: true,
2604
2748
  type: "tool_wait",
@@ -2607,7 +2751,9 @@ var HermesAdapter = class extends BaseCodingAdapter {
2607
2751
  instructions: "Hermes terminal tool is waiting for a sudo password or skip."
2608
2752
  };
2609
2753
  }
2610
- 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
+ )) {
2611
2757
  return {
2612
2758
  detected: true,
2613
2759
  type: "permission",
@@ -2621,7 +2767,9 @@ var HermesAdapter = class extends BaseCodingAdapter {
2621
2767
  detectLoading(output) {
2622
2768
  const stripped = this.stripAnsi(output);
2623
2769
  const tail = stripped.slice(-1200);
2624
- 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
+ )) {
2625
2773
  return true;
2626
2774
  }
2627
2775
  if (/\(\d+\.\d+s\)\s*$/.test(tail) && /(?:🔍|📄|💻|⚙️|📖|✍️|🔧|🌐|👆|⌨️|📋|🧠|📚|🎨|🐍|🔀|⚡|💬)/.test(tail)) {
@@ -2655,7 +2803,9 @@ var HermesAdapter = class extends BaseCodingAdapter {
2655
2803
  if (this.detectLoading(tail)) {
2656
2804
  return false;
2657
2805
  }
2658
- 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
+ )) {
2659
2809
  return false;
2660
2810
  }
2661
2811
  if (/(?:⚕|⚠|🔐|\?|✎)\s*❯\s*$/.test(tail)) {
@@ -2676,7 +2826,10 @@ var HermesAdapter = class extends BaseCodingAdapter {
2676
2826
  content = boxMatch[1].trim();
2677
2827
  }
2678
2828
  if (!content) {
2679
- content = this.extractContent(stripped, /(?:^|\n)\s*(?:⚕|⚠|🔐|\?|✎)?\s*❯\s*$/gim);
2829
+ content = this.extractContent(
2830
+ stripped,
2831
+ /(?:^|\n)\s*(?:⚕|⚠|🔐|\?|✎)?\s*❯\s*$/gim
2832
+ );
2680
2833
  }
2681
2834
  return {
2682
2835
  type: this.containsQuestion(content) ? "question" : "response",
@@ -2706,12 +2859,7 @@ var HermesAdapter = class extends BaseCodingAdapter {
2706
2859
  // src/pattern-loader.ts
2707
2860
  var BASELINE_PATTERNS = {
2708
2861
  claude: {
2709
- ready: [
2710
- "Claude Code",
2711
- "How can I help",
2712
- "What would you like",
2713
- "Ready"
2714
- ],
2862
+ ready: ["Claude Code", "How can I help", "What would you like", "Ready"],
2715
2863
  auth: [
2716
2864
  "ANTHROPIC_API_KEY",
2717
2865
  "API key not found",
@@ -2719,17 +2867,9 @@ var BASELINE_PATTERNS = {
2719
2867
  "Please sign in",
2720
2868
  "Invalid API key"
2721
2869
  ],
2722
- blocking: [
2723
- "update available",
2724
- "[y/n]"
2725
- ],
2726
- loading: [
2727
- "Reading X files\u2026"
2728
- ],
2729
- turnComplete: [
2730
- "Cooked for 1m 6s",
2731
- "<CustomVerb> for 4m 39s"
2732
- ],
2870
+ blocking: ["update available", "[y/n]"],
2871
+ loading: ["Reading X files\u2026"],
2872
+ turnComplete: ["Cooked for 1m 6s", "<CustomVerb> for 4m 39s"],
2733
2873
  toolWait: [],
2734
2874
  exit: [],
2735
2875
  source: "baseline"
@@ -2749,10 +2889,7 @@ var BASELINE_PATTERNS = {
2749
2889
  "gcloud auth",
2750
2890
  "Application Default Credentials"
2751
2891
  ],
2752
- blocking: [
2753
- "update available",
2754
- "[y/n]"
2755
- ],
2892
+ blocking: ["update available", "[y/n]"],
2756
2893
  loading: [
2757
2894
  "<phrase> (esc to cancel, 25s)",
2758
2895
  "Waiting for user confirmation...",
@@ -2761,37 +2898,21 @@ var BASELINE_PATTERNS = {
2761
2898
  "Warming up the AI hamsters"
2762
2899
  ],
2763
2900
  turnComplete: [],
2764
- toolWait: [
2765
- "Interactive shell awaiting input... press tab to focus shell"
2766
- ],
2767
- exit: [
2768
- "Agent powering down. Goodbye!"
2769
- ],
2901
+ toolWait: ["Interactive shell awaiting input... press tab to focus shell"],
2902
+ exit: ["Agent powering down. Goodbye!"],
2770
2903
  source: "baseline"
2771
2904
  },
2772
2905
  codex: {
2773
- ready: [
2774
- "Codex",
2775
- "How can I help",
2776
- "Ready"
2777
- ],
2906
+ ready: ["Codex", "How can I help", "Ready"],
2778
2907
  auth: [
2779
2908
  "OPENAI_API_KEY",
2780
2909
  "API key not found",
2781
2910
  "Unauthorized",
2782
2911
  "Invalid API key"
2783
2912
  ],
2784
- blocking: [
2785
- "update available",
2786
- "[y/n]"
2787
- ],
2788
- loading: [
2789
- "\u2022 Working (0s \u2022 esc to interrupt)",
2790
- "Booting MCP server: alpha"
2791
- ],
2792
- turnComplete: [
2793
- "Worked for 1m 05s"
2794
- ],
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"],
2795
2916
  toolWait: [
2796
2917
  "Waiting for background terminal \xB7 <command>",
2797
2918
  "Searching the web"
@@ -2800,39 +2921,21 @@ var BASELINE_PATTERNS = {
2800
2921
  source: "baseline"
2801
2922
  },
2802
2923
  aider: {
2803
- ready: [
2804
- "Aider",
2805
- "What would you like",
2806
- "Ready"
2807
- ],
2808
- auth: [
2809
- "API key",
2810
- "OPENAI_API_KEY",
2811
- "ANTHROPIC_API_KEY",
2812
- "No API key"
2813
- ],
2814
- blocking: [
2815
- "(Y)es/(N)o",
2816
- "[y/n]"
2817
- ],
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]"],
2818
2927
  loading: [
2819
2928
  "Waiting for <model>",
2820
2929
  "Waiting for LLM",
2821
2930
  "Generating commit message with <model>"
2822
2931
  ],
2823
- turnComplete: [
2824
- "Aider is waiting for your input"
2825
- ],
2932
+ turnComplete: ["Aider is waiting for your input"],
2826
2933
  toolWait: [],
2827
2934
  exit: [],
2828
2935
  source: "baseline"
2829
2936
  },
2830
2937
  hermes: {
2831
- ready: [
2832
- "\u276F",
2833
- "\u2695 Hermes",
2834
- "Welcome to Hermes Agent"
2835
- ],
2938
+ ready: ["\u276F", "\u2695 Hermes", "Welcome to Hermes Agent"],
2836
2939
  auth: [
2837
2940
  "isn't configured yet",
2838
2941
  "no API keys or providers found",
@@ -2843,23 +2946,14 @@ var BASELINE_PATTERNS = {
2843
2946
  "Sudo Password Required",
2844
2947
  "Dangerous Command"
2845
2948
  ],
2846
- loading: [
2847
- "deliberating...",
2848
- "(0.0s)",
2849
- "\u2695 \u276F"
2850
- ],
2851
- turnComplete: [
2852
- "\u256D\u2500 \u2695 Hermes",
2853
- "\u276F"
2854
- ],
2949
+ loading: ["deliberating...", "(0.0s)", "\u2695 \u276F"],
2950
+ turnComplete: ["\u256D\u2500 \u2695 Hermes", "\u276F"],
2855
2951
  toolWait: [
2856
2952
  "Hermes needs your input",
2857
2953
  "Sudo Password Required",
2858
2954
  "Dangerous Command"
2859
2955
  ],
2860
- exit: [
2861
- "Goodbye! \u2695"
2862
- ],
2956
+ exit: ["Goodbye! \u2695"],
2863
2957
  source: "baseline"
2864
2958
  }
2865
2959
  };
@@ -2926,6 +3020,7 @@ async function hasDynamicPatterns(adapter) {
2926
3020
  }
2927
3021
 
2928
3022
  // src/index.ts
3023
+ var logger = pino__default.default({ name: "coding-agent-adapters" });
2929
3024
  function createAllAdapters() {
2930
3025
  return [
2931
3026
  new ClaudeAdapter(),
@@ -2972,18 +3067,20 @@ async function printMissingAdapters(types) {
2972
3067
  const results = types ? await checkAdapters(types) : await checkAllAdapters();
2973
3068
  const missing = results.filter((r) => !r.installed);
2974
3069
  if (missing.length === 0) {
2975
- console.log("All CLI tools are installed!");
3070
+ logger.info("All CLI tools are installed");
2976
3071
  return;
2977
3072
  }
2978
- console.log("\nMissing CLI tools:\n");
3073
+ logger.info({ count: missing.length }, "Missing CLI tools detected");
2979
3074
  for (const m of missing) {
2980
- console.log(`${m.adapter}`);
2981
- console.log(` Install: ${m.installCommand}`);
2982
- console.log(` Docs: ${m.docsUrl}`);
2983
- if (m.error) {
2984
- console.log(` Error: ${m.error}`);
2985
- }
2986
- 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
+ );
2987
3084
  }
2988
3085
  }
2989
3086