@ai-devkit/agent-manager 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/.swcrc +27 -0
  2. package/dist/AgentManager.js +72 -74
  3. package/dist/AgentManager.js.map +1 -1
  4. package/dist/__tests__/AgentManager.test.js +564 -0
  5. package/dist/__tests__/AgentManager.test.js.map +1 -0
  6. package/dist/__tests__/adapters/ClaudeCodeAdapter.test.js +2157 -0
  7. package/dist/__tests__/adapters/ClaudeCodeAdapter.test.js.map +1 -0
  8. package/dist/__tests__/adapters/CodexAdapter.test.js +1354 -0
  9. package/dist/__tests__/adapters/CodexAdapter.test.js.map +1 -0
  10. package/dist/__tests__/adapters/CopilotAdapter.test.js +500 -0
  11. package/dist/__tests__/adapters/CopilotAdapter.test.js.map +1 -0
  12. package/dist/__tests__/adapters/GeminiCliAdapter.test.js +1229 -0
  13. package/dist/__tests__/adapters/GeminiCliAdapter.test.js.map +1 -0
  14. package/dist/__tests__/adapters/OpenCodeAdapter.test.js +626 -0
  15. package/dist/__tests__/adapters/OpenCodeAdapter.test.js.map +1 -0
  16. package/dist/__tests__/terminal/TmuxManager.test.js +159 -0
  17. package/dist/__tests__/terminal/TmuxManager.test.js.map +1 -0
  18. package/dist/__tests__/terminal/TtyWriter.test.js +162 -0
  19. package/dist/__tests__/terminal/TtyWriter.test.js.map +1 -0
  20. package/dist/__tests__/utils/AgentRegistry.test.js +282 -0
  21. package/dist/__tests__/utils/AgentRegistry.test.js.map +1 -0
  22. package/dist/__tests__/utils/ClaudeSessionParser.test.js +312 -0
  23. package/dist/__tests__/utils/ClaudeSessionParser.test.js.map +1 -0
  24. package/dist/__tests__/utils/matching.test.js +324 -0
  25. package/dist/__tests__/utils/matching.test.js.map +1 -0
  26. package/dist/__tests__/utils/process.test.js +177 -0
  27. package/dist/__tests__/utils/process.test.js.map +1 -0
  28. package/dist/__tests__/utils/session.test.js +158 -0
  29. package/dist/__tests__/utils/session.test.js.map +1 -0
  30. package/dist/adapters/AgentAdapter.d.ts +1 -1
  31. package/dist/adapters/AgentAdapter.d.ts.map +1 -1
  32. package/dist/adapters/AgentAdapter.js +8 -7
  33. package/dist/adapters/AgentAdapter.js.map +1 -1
  34. package/dist/adapters/ClaudeCodeAdapter.js +66 -77
  35. package/dist/adapters/ClaudeCodeAdapter.js.map +1 -1
  36. package/dist/adapters/CodexAdapter.d.ts +9 -0
  37. package/dist/adapters/CodexAdapter.d.ts.map +1 -1
  38. package/dist/adapters/CodexAdapter.js +188 -141
  39. package/dist/adapters/CodexAdapter.js.map +1 -1
  40. package/dist/adapters/CopilotAdapter.d.ts +44 -0
  41. package/dist/adapters/CopilotAdapter.d.ts.map +1 -0
  42. package/dist/adapters/CopilotAdapter.js +314 -0
  43. package/dist/adapters/CopilotAdapter.js.map +1 -0
  44. package/dist/adapters/GeminiCliAdapter.d.ts +2 -0
  45. package/dist/adapters/GeminiCliAdapter.d.ts.map +1 -1
  46. package/dist/adapters/GeminiCliAdapter.js +138 -156
  47. package/dist/adapters/GeminiCliAdapter.js.map +1 -1
  48. package/dist/adapters/OpenCodeAdapter.js +60 -69
  49. package/dist/adapters/OpenCodeAdapter.js.map +1 -1
  50. package/dist/adapters/index.d.ts +1 -0
  51. package/dist/adapters/index.d.ts.map +1 -1
  52. package/dist/adapters/index.js +2 -0
  53. package/dist/adapters/index.js.map +1 -1
  54. package/dist/index.d.ts +1 -0
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +2 -0
  57. package/dist/index.js.map +1 -1
  58. package/dist/terminal/TerminalFocusManager.js +62 -49
  59. package/dist/terminal/TerminalFocusManager.js.map +1 -1
  60. package/dist/terminal/TmuxManager.js +59 -34
  61. package/dist/terminal/TmuxManager.js.map +1 -1
  62. package/dist/terminal/TtyWriter.js +37 -16
  63. package/dist/terminal/TtyWriter.js.map +1 -1
  64. package/dist/terminal/index.js +1 -0
  65. package/dist/terminal/index.js.map +1 -1
  66. package/dist/utils/AgentRegistry.js +41 -32
  67. package/dist/utils/AgentRegistry.js.map +1 -1
  68. package/dist/utils/ClaudeSessionParser.js +46 -87
  69. package/dist/utils/ClaudeSessionParser.js.map +1 -1
  70. package/dist/utils/agents.d.ts +1 -1
  71. package/dist/utils/agents.d.ts.map +1 -1
  72. package/dist/utils/agents.js +22 -11
  73. package/dist/utils/agents.js.map +1 -1
  74. package/dist/utils/applescript.js +3 -6
  75. package/dist/utils/applescript.js.map +1 -1
  76. package/dist/utils/index.js +1 -0
  77. package/dist/utils/index.js.map +1 -1
  78. package/dist/utils/matching.js +21 -29
  79. package/dist/utils/matching.js.map +1 -1
  80. package/dist/utils/process.js +77 -56
  81. package/dist/utils/process.js.map +1 -1
  82. package/dist/utils/session.js +20 -34
  83. package/dist/utils/session.js.map +1 -1
  84. package/dist/utils/sortAgents.js +6 -8
  85. package/dist/utils/sortAgents.js.map +1 -1
  86. package/package.json +11 -7
  87. package/src/__tests__/adapters/ClaudeCodeAdapter.test.ts +2 -0
  88. package/src/__tests__/adapters/CodexAdapter.test.ts +94 -0
  89. package/src/__tests__/adapters/CopilotAdapter.test.ts +411 -0
  90. package/src/__tests__/adapters/GeminiCliAdapter.test.ts +74 -1
  91. package/src/adapters/AgentAdapter.ts +1 -1
  92. package/src/adapters/CodexAdapter.ts +114 -6
  93. package/src/adapters/CopilotAdapter.ts +453 -0
  94. package/src/adapters/GeminiCliAdapter.ts +42 -3
  95. package/src/adapters/index.ts +1 -0
  96. package/src/index.ts +1 -0
  97. package/src/utils/agents.ts +1 -1
@@ -3,18 +3,17 @@ import { promisify } from 'util';
3
3
  import { getProcessTty } from '../utils/process.js';
4
4
  import { escapeAppleScript } from '../utils/applescript.js';
5
5
  const execFileAsync = promisify(execFile);
6
- export var TerminalType;
7
- (function (TerminalType) {
6
+ export var TerminalType = /*#__PURE__*/ function(TerminalType) {
8
7
  TerminalType["TMUX"] = "tmux";
9
8
  TerminalType["ITERM2"] = "iterm2";
10
9
  TerminalType["TERMINAL_APP"] = "terminal-app";
11
10
  TerminalType["UNKNOWN"] = "unknown";
12
- })(TerminalType || (TerminalType = {}));
11
+ return TerminalType;
12
+ }({});
13
13
  export class TerminalFocusManager {
14
14
  /**
15
15
  * Find the terminal location (emulator info) for a given process ID
16
- */
17
- async findTerminal(pid) {
16
+ */ async findTerminal(pid) {
18
17
  const ttyShort = getProcessTty(pid);
19
18
  // If no TTY or invalid, we can't find the terminal
20
19
  if (!ttyShort || ttyShort === '?') {
@@ -23,73 +22,71 @@ export class TerminalFocusManager {
23
22
  const fullTty = `/dev/${ttyShort}`;
24
23
  // 1. Check tmux (most specific if running inside it)
25
24
  const tmuxLocation = await this.findTmuxPane(fullTty);
26
- if (tmuxLocation)
27
- return tmuxLocation;
25
+ if (tmuxLocation) return tmuxLocation;
28
26
  // 2. Check iTerm2
29
27
  const itermLocation = await this.findITerm2Session(fullTty);
30
- if (itermLocation)
31
- return itermLocation;
28
+ if (itermLocation) return itermLocation;
32
29
  // 3. Check Terminal.app
33
30
  const terminalAppLocation = await this.findTerminalAppWindow(fullTty);
34
- if (terminalAppLocation)
35
- return terminalAppLocation;
31
+ if (terminalAppLocation) return terminalAppLocation;
36
32
  // 4. Fallback: we know the TTY but not the emulator wrapper
37
33
  return {
38
- type: TerminalType.UNKNOWN,
34
+ type: "unknown",
39
35
  identifier: '',
40
36
  tty: fullTty
41
37
  };
42
38
  }
43
39
  /**
44
40
  * Focus the terminal identified by the location
45
- */
46
- async focusTerminal(location) {
41
+ */ async focusTerminal(location) {
47
42
  try {
48
- switch (location.type) {
49
- case TerminalType.TMUX:
43
+ switch(location.type){
44
+ case "tmux":
50
45
  return await this.focusTmuxPane(location.identifier);
51
- case TerminalType.ITERM2:
46
+ case "iterm2":
52
47
  return await this.focusITerm2Session(location.tty);
53
- case TerminalType.TERMINAL_APP:
48
+ case "terminal-app":
54
49
  return await this.focusTerminalAppWindow(location.tty);
55
50
  default:
56
51
  return false;
57
52
  }
58
- }
59
- catch {
53
+ } catch {
60
54
  return false;
61
55
  }
62
56
  }
63
57
  async findTmuxPane(tty) {
64
58
  try {
65
59
  const { stdout } = await execFileAsync('tmux', [
66
- 'list-panes', '-a', '-F', '#{pane_tty}|#{session_name}:#{window_index}.#{pane_index}'
60
+ 'list-panes',
61
+ '-a',
62
+ '-F',
63
+ '#{pane_tty}|#{session_name}:#{window_index}.#{pane_index}'
67
64
  ]);
68
65
  const lines = stdout.trim().split('\n');
69
- for (const line of lines) {
70
- if (!line.trim())
71
- continue;
66
+ for (const line of lines){
67
+ if (!line.trim()) continue;
72
68
  const [paneTty, identifier] = line.split('|');
73
69
  if (paneTty === tty && identifier) {
74
70
  return {
75
- type: TerminalType.TMUX,
71
+ type: "tmux",
76
72
  identifier,
77
73
  tty
78
74
  };
79
75
  }
80
76
  }
81
- }
82
- catch {
83
- // tmux might not be installed or running
77
+ } catch {
78
+ // tmux might not be installed or running
84
79
  }
85
80
  return null;
86
81
  }
87
82
  async findITerm2Session(tty) {
88
83
  try {
89
84
  // Check if iTerm2 is running first to avoid launching it
90
- await execFileAsync('pgrep', ['-x', 'iTerm2']);
91
- }
92
- catch {
85
+ await execFileAsync('pgrep', [
86
+ '-x',
87
+ 'iTerm2'
88
+ ]);
89
+ } catch {
93
90
  return null;
94
91
  }
95
92
  try {
@@ -107,26 +104,30 @@ export class TerminalFocusManager {
107
104
  end repeat
108
105
  end tell
109
106
  `;
110
- const { stdout } = await execFileAsync('osascript', ['-e', script]);
107
+ const { stdout } = await execFileAsync('osascript', [
108
+ '-e',
109
+ script
110
+ ]);
111
111
  if (stdout.trim() === "found") {
112
112
  return {
113
- type: TerminalType.ITERM2,
113
+ type: "iterm2",
114
114
  identifier: tty,
115
115
  tty
116
116
  };
117
117
  }
118
- }
119
- catch {
120
- // iTerm2 script failed
118
+ } catch {
119
+ // iTerm2 script failed
121
120
  }
122
121
  return null;
123
122
  }
124
123
  async findTerminalAppWindow(tty) {
125
124
  try {
126
125
  // Check if Terminal.app is running
127
- await execFileAsync('pgrep', ['-x', 'Terminal']);
128
- }
129
- catch {
126
+ await execFileAsync('pgrep', [
127
+ '-x',
128
+ 'Terminal'
129
+ ]);
130
+ } catch {
130
131
  return null;
131
132
  }
132
133
  try {
@@ -142,26 +143,31 @@ export class TerminalFocusManager {
142
143
  end repeat
143
144
  end tell
144
145
  `;
145
- const { stdout } = await execFileAsync('osascript', ['-e', script]);
146
+ const { stdout } = await execFileAsync('osascript', [
147
+ '-e',
148
+ script
149
+ ]);
146
150
  if (stdout.trim() === "found") {
147
151
  return {
148
- type: TerminalType.TERMINAL_APP,
152
+ type: "terminal-app",
149
153
  identifier: tty,
150
154
  tty
151
155
  };
152
156
  }
153
- }
154
- catch {
155
- // Terminal.app script failed
157
+ } catch {
158
+ // Terminal.app script failed
156
159
  }
157
160
  return null;
158
161
  }
159
162
  async focusTmuxPane(identifier) {
160
163
  try {
161
- await execFileAsync('tmux', ['switch-client', '-t', identifier]);
164
+ await execFileAsync('tmux', [
165
+ 'switch-client',
166
+ '-t',
167
+ identifier
168
+ ]);
162
169
  return true;
163
- }
164
- catch {
170
+ } catch {
165
171
  return false;
166
172
  }
167
173
  }
@@ -182,7 +188,10 @@ export class TerminalFocusManager {
182
188
  end repeat
183
189
  end tell
184
190
  `;
185
- const { stdout } = await execFileAsync('osascript', ['-e', script]);
191
+ const { stdout } = await execFileAsync('osascript', [
192
+ '-e',
193
+ script
194
+ ]);
186
195
  return stdout.trim() === "true";
187
196
  }
188
197
  async focusTerminalAppWindow(tty) {
@@ -201,8 +210,12 @@ export class TerminalFocusManager {
201
210
  end repeat
202
211
  end tell
203
212
  `;
204
- const { stdout } = await execFileAsync('osascript', ['-e', script]);
213
+ const { stdout } = await execFileAsync('osascript', [
214
+ '-e',
215
+ script
216
+ ]);
205
217
  return stdout.trim() === "true";
206
218
  }
207
219
  }
220
+
208
221
  //# sourceMappingURL=TerminalFocusManager.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"TerminalFocusManager.js","sourceRoot":"","sources":["../../src/terminal/TerminalFocusManager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAE5D,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAE1C,MAAM,CAAN,IAAY,YAKX;AALD,WAAY,YAAY;IACpB,6BAAa,CAAA;IACb,iCAAiB,CAAA;IACjB,6CAA6B,CAAA;IAC7B,mCAAmB,CAAA;AACvB,CAAC,EALW,YAAY,KAAZ,YAAY,QAKvB;AAQD,MAAM,OAAO,oBAAoB;IAC7B;;OAEG;IACH,KAAK,CAAC,YAAY,CAAC,GAAW;QAC1B,MAAM,QAAQ,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;QAEpC,mDAAmD;QACnD,IAAI,CAAC,QAAQ,IAAI,QAAQ,KAAK,GAAG,EAAE,CAAC;YAChC,OAAO,IAAI,CAAC;QAChB,CAAC;QAED,MAAM,OAAO,GAAG,QAAQ,QAAQ,EAAE,CAAC;QAEnC,qDAAqD;QACrD,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;QACtD,IAAI,YAAY;YAAE,OAAO,YAAY,CAAC;QAEtC,kBAAkB;QAClB,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC;QAC5D,IAAI,aAAa;YAAE,OAAO,aAAa,CAAC;QAExC,wBAAwB;QACxB,MAAM,mBAAmB,GAAG,MAAM,IAAI,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC;QACtE,IAAI,mBAAmB;YAAE,OAAO,mBAAmB,CAAC;QAEpD,4DAA4D;QAC5D,OAAO;YACH,IAAI,EAAE,YAAY,CAAC,OAAO;YAC1B,UAAU,EAAE,EAAE;YACd,GAAG,EAAE,OAAO;SACf,CAAC;IACN,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,aAAa,CAAC,QAA0B;QAC1C,IAAI,CAAC;YACD,QAAQ,QAAQ,CAAC,IAAI,EAAE,CAAC;gBACpB,KAAK,YAAY,CAAC,IAAI;oBAClB,OAAO,MAAM,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;gBACzD,KAAK,YAAY,CAAC,MAAM;oBACpB,OAAO,MAAM,IAAI,CAAC,kBAAkB,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;gBACvD,KAAK,YAAY,CAAC,YAAY;oBAC1B,OAAO,MAAM,IAAI,CAAC,sBAAsB,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;gBAC3D;oBACI,OAAO,KAAK,CAAC;YACrB,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACL,OAAO,KAAK,CAAC;QACjB,CAAC;IACL,CAAC;IAEO,KAAK,CAAC,YAAY,CAAC,GAAW;QAClC,IAAI,CAAC;YACD,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CAAC,MAAM,EAAE;gBAC3C,YAAY,EAAE,IAAI,EAAE,IAAI,EAAE,2DAA2D;aACxF,CAAC,CAAC;YAEH,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YACxC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACvB,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;oBAAE,SAAS;gBAC3B,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;gBAC9C,IAAI,OAAO,KAAK,GAAG,IAAI,UAAU,EAAE,CAAC;oBAChC,OAAO;wBACH,IAAI,EAAE,YAAY,CAAC,IAAI;wBACvB,UAAU;wBACV,GAAG;qBACN,CAAC;gBACN,CAAC;YACL,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACL,yCAAyC;QAC7C,CAAC;QACD,OAAO,IAAI,CAAC;IAChB,CAAC;IAEO,KAAK,CAAC,iBAAiB,CAAC,GAAW;QACvC,IAAI,CAAC;YACD,yDAAyD;YACzD,MAAM,aAAa,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;QACnD,CAAC;QAAC,MAAM,CAAC;YACL,OAAO,IAAI,CAAC;QAChB,CAAC;QAED,IAAI,CAAC;YACD,MAAM,UAAU,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC;YAC1C,MAAM,MAAM,GAAG;;;;;kCAKO,UAAU;;;;;;;OAOrC,CAAC;YAEI,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;YACpE,IAAI,MAAM,CAAC,IAAI,EAAE,KAAK,OAAO,EAAE,CAAC;gBAC5B,OAAO;oBACH,IAAI,EAAE,YAAY,CAAC,MAAM;oBACzB,UAAU,EAAE,GAAG;oBACf,GAAG;iBACN,CAAC;YACN,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACL,uBAAuB;QAC3B,CAAC;QACD,OAAO,IAAI,CAAC;IAChB,CAAC;IAEO,KAAK,CAAC,qBAAqB,CAAC,GAAW;QAC3C,IAAI,CAAC;YACD,mCAAmC;YACnC,MAAM,aAAa,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC;QACrD,CAAC;QAAC,MAAM,CAAC;YACL,OAAO,IAAI,CAAC;QAChB,CAAC;QAED,IAAI,CAAC;YACD,MAAM,UAAU,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC;YAC1C,MAAM,MAAM,GAAG;;;;gCAIK,UAAU;;;;;;OAMnC,CAAC;YAEI,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;YACpE,IAAI,MAAM,CAAC,IAAI,EAAE,KAAK,OAAO,EAAE,CAAC;gBAC5B,OAAO;oBACH,IAAI,EAAE,YAAY,CAAC,YAAY;oBAC/B,UAAU,EAAE,GAAG;oBACf,GAAG;iBACN,CAAC;YACN,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACL,6BAA6B;QACjC,CAAC;QACD,OAAO,IAAI,CAAC;IAChB,CAAC;IAEO,KAAK,CAAC,aAAa,CAAC,UAAkB;QAC1C,IAAI,CAAC;YACD,MAAM,aAAa,CAAC,MAAM,EAAE,CAAC,eAAe,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC;YACjE,OAAO,IAAI,CAAC;QAChB,CAAC;QAAC,MAAM,CAAC;YACL,OAAO,KAAK,CAAC;QACjB,CAAC;IACL,CAAC;IAEO,KAAK,CAAC,kBAAkB,CAAC,GAAW;QACxC,MAAM,UAAU,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC;QAC1C,MAAM,MAAM,GAAG;;;;;;iCAMU,UAAU;;;;;;;;MAQrC,CAAC;QACC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;QACpE,OAAO,MAAM,CAAC,IAAI,EAAE,KAAK,MAAM,CAAC;IACpC,CAAC;IAEO,KAAK,CAAC,sBAAsB,CAAC,GAAW;QAC5C,MAAM,UAAU,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC;QAC1C,MAAM,MAAM,GAAG;;;;;+BAKQ,UAAU;;;;;;;;KAQpC,CAAC;QACE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;QACpE,OAAO,MAAM,CAAC,IAAI,EAAE,KAAK,MAAM,CAAC;IACpC,CAAC;CACJ"}
1
+ {"version":3,"sources":["../../src/terminal/TerminalFocusManager.ts"],"sourcesContent":["import { execFile } from 'child_process';\nimport { promisify } from 'util';\nimport { getProcessTty } from '../utils/process.js';\nimport { escapeAppleScript } from '../utils/applescript.js';\n\nconst execFileAsync = promisify(execFile);\n\nexport enum TerminalType {\n TMUX = 'tmux',\n ITERM2 = 'iterm2',\n TERMINAL_APP = 'terminal-app',\n UNKNOWN = 'unknown',\n}\n\nexport interface TerminalLocation {\n type: TerminalType;\n identifier: string; // e.g., \"session:window.pane\" for tmux, or TTY for others\n tty: string; // e.g., \"/dev/ttys030\"\n}\n\nexport class TerminalFocusManager {\n /**\n * Find the terminal location (emulator info) for a given process ID\n */\n async findTerminal(pid: number): Promise<TerminalLocation | null> {\n const ttyShort = getProcessTty(pid);\n\n // If no TTY or invalid, we can't find the terminal\n if (!ttyShort || ttyShort === '?') {\n return null;\n }\n\n const fullTty = `/dev/${ttyShort}`;\n\n // 1. Check tmux (most specific if running inside it)\n const tmuxLocation = await this.findTmuxPane(fullTty);\n if (tmuxLocation) return tmuxLocation;\n\n // 2. Check iTerm2\n const itermLocation = await this.findITerm2Session(fullTty);\n if (itermLocation) return itermLocation;\n\n // 3. Check Terminal.app\n const terminalAppLocation = await this.findTerminalAppWindow(fullTty);\n if (terminalAppLocation) return terminalAppLocation;\n\n // 4. Fallback: we know the TTY but not the emulator wrapper\n return {\n type: TerminalType.UNKNOWN,\n identifier: '',\n tty: fullTty\n };\n }\n\n /**\n * Focus the terminal identified by the location\n */\n async focusTerminal(location: TerminalLocation): Promise<boolean> {\n try {\n switch (location.type) {\n case TerminalType.TMUX:\n return await this.focusTmuxPane(location.identifier);\n case TerminalType.ITERM2:\n return await this.focusITerm2Session(location.tty);\n case TerminalType.TERMINAL_APP:\n return await this.focusTerminalAppWindow(location.tty);\n default:\n return false;\n }\n } catch {\n return false;\n }\n }\n\n private async findTmuxPane(tty: string): Promise<TerminalLocation | null> {\n try {\n const { stdout } = await execFileAsync('tmux', [\n 'list-panes', '-a', '-F', '#{pane_tty}|#{session_name}:#{window_index}.#{pane_index}'\n ]);\n\n const lines = stdout.trim().split('\\n');\n for (const line of lines) {\n if (!line.trim()) continue;\n const [paneTty, identifier] = line.split('|');\n if (paneTty === tty && identifier) {\n return {\n type: TerminalType.TMUX,\n identifier,\n tty\n };\n }\n }\n } catch {\n // tmux might not be installed or running\n }\n return null;\n }\n\n private async findITerm2Session(tty: string): Promise<TerminalLocation | null> {\n try {\n // Check if iTerm2 is running first to avoid launching it\n await execFileAsync('pgrep', ['-x', 'iTerm2']);\n } catch {\n return null;\n }\n\n try {\n const escapedTty = escapeAppleScript(tty);\n const script = `\n tell application \"iTerm\"\n repeat with w in windows\n repeat with t in tabs of w\n repeat with s in sessions of t\n if tty of s is \"${escapedTty}\" then\n return \"found\"\n end if\n end repeat\n end repeat\n end repeat\n end tell\n `;\n\n const { stdout } = await execFileAsync('osascript', ['-e', script]);\n if (stdout.trim() === \"found\") {\n return {\n type: TerminalType.ITERM2,\n identifier: tty,\n tty\n };\n }\n } catch {\n // iTerm2 script failed\n }\n return null;\n }\n\n private async findTerminalAppWindow(tty: string): Promise<TerminalLocation | null> {\n try {\n // Check if Terminal.app is running\n await execFileAsync('pgrep', ['-x', 'Terminal']);\n } catch {\n return null;\n }\n\n try {\n const escapedTty = escapeAppleScript(tty);\n const script = `\n tell application \"Terminal\"\n repeat with w in windows\n repeat with t in tabs of w\n if tty of t is \"${escapedTty}\" then\n return \"found\"\n end if\n end repeat\n end repeat\n end tell\n `;\n\n const { stdout } = await execFileAsync('osascript', ['-e', script]);\n if (stdout.trim() === \"found\") {\n return {\n type: TerminalType.TERMINAL_APP,\n identifier: tty,\n tty\n };\n }\n } catch {\n // Terminal.app script failed\n }\n return null;\n }\n\n private async focusTmuxPane(identifier: string): Promise<boolean> {\n try {\n await execFileAsync('tmux', ['switch-client', '-t', identifier]);\n return true;\n } catch {\n return false;\n }\n }\n\n private async focusITerm2Session(tty: string): Promise<boolean> {\n const escapedTty = escapeAppleScript(tty);\n const script = `\n tell application \"iTerm\"\n activate\n repeat with w in windows\n repeat with t in tabs of w\n repeat with s in sessions of t\n if tty of s is \"${escapedTty}\" then\n select s\n return \"true\"\n end if\n end repeat\n end repeat\n end repeat\n end tell\n `;\n const { stdout } = await execFileAsync('osascript', ['-e', script]);\n return stdout.trim() === \"true\";\n }\n\n private async focusTerminalAppWindow(tty: string): Promise<boolean> {\n const escapedTty = escapeAppleScript(tty);\n const script = `\n tell application \"Terminal\"\n activate\n repeat with w in windows\n repeat with t in tabs of w\n if tty of t is \"${escapedTty}\" then\n set index of w to 1\n set selected tab of w to t\n return \"true\"\n end if\n end repeat\n end repeat\n end tell\n `;\n const { stdout } = await execFileAsync('osascript', ['-e', script]);\n return stdout.trim() === \"true\";\n }\n}\n"],"names":["execFile","promisify","getProcessTty","escapeAppleScript","execFileAsync","TerminalType","TerminalFocusManager","findTerminal","pid","ttyShort","fullTty","tmuxLocation","findTmuxPane","itermLocation","findITerm2Session","terminalAppLocation","findTerminalAppWindow","type","identifier","tty","focusTerminal","location","focusTmuxPane","focusITerm2Session","focusTerminalAppWindow","stdout","lines","trim","split","line","paneTty","escapedTty","script"],"mappings":"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"}
@@ -4,34 +4,54 @@ const execFileAsync = promisify(execFile);
4
4
  export class TmuxManager {
5
5
  async isAvailable() {
6
6
  try {
7
- await execFileAsync('tmux', ['-V']);
7
+ await execFileAsync('tmux', [
8
+ '-V'
9
+ ]);
8
10
  return true;
9
- }
10
- catch {
11
+ } catch {
11
12
  return false;
12
13
  }
13
14
  }
14
15
  async sessionExists(name) {
15
16
  try {
16
- await execFileAsync('tmux', ['has-session', '-t', name]);
17
+ await execFileAsync('tmux', [
18
+ 'has-session',
19
+ '-t',
20
+ name
21
+ ]);
17
22
  return true;
18
- }
19
- catch {
23
+ } catch {
20
24
  return false;
21
25
  }
22
26
  }
23
27
  async createSession(name, cwd) {
24
- await execFileAsync('tmux', ['new-session', '-d', '-s', name, '-c', cwd]);
28
+ await execFileAsync('tmux', [
29
+ 'new-session',
30
+ '-d',
31
+ '-s',
32
+ name,
33
+ '-c',
34
+ cwd
35
+ ]);
25
36
  }
26
37
  async sendKeys(session, keys) {
27
- await execFileAsync('tmux', ['send-keys', '-t', session, keys, 'Enter']);
38
+ await execFileAsync('tmux', [
39
+ 'send-keys',
40
+ '-t',
41
+ session,
42
+ keys,
43
+ 'Enter'
44
+ ]);
28
45
  }
29
46
  async killSession(name) {
30
47
  try {
31
- await execFileAsync('tmux', ['kill-session', '-t', name]);
32
- }
33
- catch {
34
- // Already gone — ignore
48
+ await execFileAsync('tmux', [
49
+ 'kill-session',
50
+ '-t',
51
+ name
52
+ ]);
53
+ } catch {
54
+ // Already gone — ignore
35
55
  }
36
56
  }
37
57
  /**
@@ -49,18 +69,17 @@ export class TmuxManager {
49
69
  *
50
70
  * Returns null when no descendant matches yet (agent still starting); the
51
71
  * caller's poll loop retries.
52
- */
53
- async findAgentPid(session, matches) {
72
+ */ async findAgentPid(session, matches) {
54
73
  const panePid = await this.getPanePid(session);
55
- if (panePid === null)
56
- return null;
74
+ if (panePid === null) return null;
57
75
  const visited = new Set();
58
- const queue = [panePid];
76
+ const queue = [
77
+ panePid
78
+ ];
59
79
  let deepestMatch = null;
60
- while (queue.length > 0) {
80
+ while(queue.length > 0){
61
81
  const pid = queue.shift();
62
- if (visited.has(pid))
63
- continue;
82
+ if (visited.has(pid)) continue;
64
83
  visited.add(pid);
65
84
  if (pid !== panePid) {
66
85
  const command = await this.getProcessCommand(pid);
@@ -76,37 +95,43 @@ export class TmuxManager {
76
95
  async getPanePid(session) {
77
96
  try {
78
97
  const { stdout } = await execFileAsync('tmux', [
79
- 'list-panes', '-t', session, '-F', '#{pane_pid}',
98
+ 'list-panes',
99
+ '-t',
100
+ session,
101
+ '-F',
102
+ '#{pane_pid}'
80
103
  ]);
81
104
  const panePid = parseInt(stdout.trim().split('\n')[0], 10);
82
105
  return isNaN(panePid) ? null : panePid;
83
- }
84
- catch {
106
+ } catch {
85
107
  return null;
86
108
  }
87
109
  }
88
110
  async pgrepChildren(pid) {
89
111
  try {
90
- const { stdout } = await execFileAsync('pgrep', ['-P', String(pid)]);
91
- return stdout
92
- .trim()
93
- .split('\n')
94
- .map((s) => parseInt(s, 10))
95
- .filter((n) => !isNaN(n));
96
- }
97
- catch {
112
+ const { stdout } = await execFileAsync('pgrep', [
113
+ '-P',
114
+ String(pid)
115
+ ]);
116
+ return stdout.trim().split('\n').map((s)=>parseInt(s, 10)).filter((n)=>!isNaN(n));
117
+ } catch {
98
118
  return [];
99
119
  }
100
120
  }
101
121
  async getProcessCommand(pid) {
102
122
  try {
103
- const { stdout } = await execFileAsync('ps', ['-p', String(pid), '-o', 'command=']);
123
+ const { stdout } = await execFileAsync('ps', [
124
+ '-p',
125
+ String(pid),
126
+ '-o',
127
+ 'command='
128
+ ]);
104
129
  const trimmed = stdout.trim();
105
130
  return trimmed || null;
106
- }
107
- catch {
131
+ } catch {
108
132
  return null;
109
133
  }
110
134
  }
111
135
  }
136
+
112
137
  //# sourceMappingURL=TmuxManager.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"TmuxManager.js","sourceRoot":"","sources":["../../src/terminal/TmuxManager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/terminal/TmuxManager.ts"],"sourcesContent":["import { execFile } from 'child_process';\nimport { promisify } from 'util';\n\nconst execFileAsync = promisify(execFile);\n\nexport class TmuxManager {\n async isAvailable(): Promise<boolean> {\n try {\n await execFileAsync('tmux', ['-V']);\n return true;\n } catch {\n return false;\n }\n }\n\n async sessionExists(name: string): Promise<boolean> {\n try {\n await execFileAsync('tmux', ['has-session', '-t', name]);\n return true;\n } catch {\n return false;\n }\n }\n\n async createSession(name: string, cwd: string): Promise<void> {\n await execFileAsync('tmux', ['new-session', '-d', '-s', name, '-c', cwd]);\n }\n\n async sendKeys(session: string, keys: string): Promise<void> {\n await execFileAsync('tmux', ['send-keys', '-t', session, keys, 'Enter']);\n }\n\n async killSession(name: string): Promise<void> {\n try {\n await execFileAsync('tmux', ['kill-session', '-t', name]);\n } catch {\n // Already gone — ignore\n }\n }\n\n /**\n * Find the actual agent process PID inside a tmux pane.\n *\n * Strategy: BFS the process tree, return the deepest descendant whose\n * `ps` command line is accepted by `matches`. The caller supplies the\n * matcher so this method has no agent-type knowledge.\n *\n * This handles two real-world process shapes:\n * - Wrapper case: shell → claude-wrapper (matches) → claude (matches, deeper)\n * → returns the deeper one\n * - Subprocess case: shell → claude (matches) → MCP server child (doesn't match)\n * → returns claude, not the subprocess\n *\n * Returns null when no descendant matches yet (agent still starting); the\n * caller's poll loop retries.\n */\n async findAgentPid(session: string, matches: (psCommand: string) => boolean): Promise<number | null> {\n const panePid = await this.getPanePid(session);\n if (panePid === null) return null;\n\n const visited = new Set<number>();\n const queue: number[] = [panePid];\n let deepestMatch: number | null = null;\n\n while (queue.length > 0) {\n const pid = queue.shift()!;\n if (visited.has(pid)) continue;\n visited.add(pid);\n\n if (pid !== panePid) {\n const command = await this.getProcessCommand(pid);\n if (command && matches(command)) {\n deepestMatch = pid;\n }\n }\n\n const children = await this.pgrepChildren(pid);\n queue.push(...children);\n }\n\n return deepestMatch;\n }\n\n private async getPanePid(session: string): Promise<number | null> {\n try {\n const { stdout } = await execFileAsync('tmux', [\n 'list-panes', '-t', session, '-F', '#{pane_pid}',\n ]);\n const panePid = parseInt(stdout.trim().split('\\n')[0], 10);\n return isNaN(panePid) ? null : panePid;\n } catch {\n return null;\n }\n }\n\n private async pgrepChildren(pid: number): Promise<number[]> {\n try {\n const { stdout } = await execFileAsync('pgrep', ['-P', String(pid)]);\n return stdout\n .trim()\n .split('\\n')\n .map((s) => parseInt(s, 10))\n .filter((n) => !isNaN(n));\n } catch {\n return [];\n }\n }\n\n private async getProcessCommand(pid: number): Promise<string | null> {\n try {\n const { stdout } = await execFileAsync('ps', ['-p', String(pid), '-o', 'command=']);\n const trimmed = stdout.trim();\n return trimmed || null;\n } catch {\n return null;\n }\n }\n}\n"],"names":["execFile","promisify","execFileAsync","TmuxManager","isAvailable","sessionExists","name","createSession","cwd","sendKeys","session","keys","killSession","findAgentPid","matches","panePid","getPanePid","visited","Set","queue","deepestMatch","length","pid","shift","has","add","command","getProcessCommand","children","pgrepChildren","push","stdout","parseInt","trim","split","isNaN","String","map","s","filter","n","trimmed"],"mappings":"AAAA,SAASA,QAAQ,QAAQ,gBAAgB;AACzC,SAASC,SAAS,QAAQ,OAAO;AAEjC,MAAMC,gBAAgBD,UAAUD;AAEhC,OAAO,MAAMG;IACT,MAAMC,cAAgC;QAClC,IAAI;YACA,MAAMF,cAAc,QAAQ;gBAAC;aAAK;YAClC,OAAO;QACX,EAAE,OAAM;YACJ,OAAO;QACX;IACJ;IAEA,MAAMG,cAAcC,IAAY,EAAoB;QAChD,IAAI;YACA,MAAMJ,cAAc,QAAQ;gBAAC;gBAAe;gBAAMI;aAAK;YACvD,OAAO;QACX,EAAE,OAAM;YACJ,OAAO;QACX;IACJ;IAEA,MAAMC,cAAcD,IAAY,EAAEE,GAAW,EAAiB;QAC1D,MAAMN,cAAc,QAAQ;YAAC;YAAe;YAAM;YAAMI;YAAM;YAAME;SAAI;IAC5E;IAEA,MAAMC,SAASC,OAAe,EAAEC,IAAY,EAAiB;QACzD,MAAMT,cAAc,QAAQ;YAAC;YAAa;YAAMQ;YAASC;YAAM;SAAQ;IAC3E;IAEA,MAAMC,YAAYN,IAAY,EAAiB;QAC3C,IAAI;YACA,MAAMJ,cAAc,QAAQ;gBAAC;gBAAgB;gBAAMI;aAAK;QAC5D,EAAE,OAAM;QACJ,wBAAwB;QAC5B;IACJ;IAEA;;;;;;;;;;;;;;;KAeC,GACD,MAAMO,aAAaH,OAAe,EAAEI,OAAuC,EAA0B;QACjG,MAAMC,UAAU,MAAM,IAAI,CAACC,UAAU,CAACN;QACtC,IAAIK,YAAY,MAAM,OAAO;QAE7B,MAAME,UAAU,IAAIC;QACpB,MAAMC,QAAkB;YAACJ;SAAQ;QACjC,IAAIK,eAA8B;QAElC,MAAOD,MAAME,MAAM,GAAG,EAAG;YACrB,MAAMC,MAAMH,MAAMI,KAAK;YACvB,IAAIN,QAAQO,GAAG,CAACF,MAAM;YACtBL,QAAQQ,GAAG,CAACH;YAEZ,IAAIA,QAAQP,SAAS;gBACjB,MAAMW,UAAU,MAAM,IAAI,CAACC,iBAAiB,CAACL;gBAC7C,IAAII,WAAWZ,QAAQY,UAAU;oBAC7BN,eAAeE;gBACnB;YACJ;YAEA,MAAMM,WAAW,MAAM,IAAI,CAACC,aAAa,CAACP;YAC1CH,MAAMW,IAAI,IAAIF;QAClB;QAEA,OAAOR;IACX;IAEA,MAAcJ,WAAWN,OAAe,EAA0B;QAC9D,IAAI;YACA,MAAM,EAAEqB,MAAM,EAAE,GAAG,MAAM7B,cAAc,QAAQ;gBAC3C;gBAAc;gBAAMQ;gBAAS;gBAAM;aACtC;YACD,MAAMK,UAAUiB,SAASD,OAAOE,IAAI,GAAGC,KAAK,CAAC,KAAK,CAAC,EAAE,EAAE;YACvD,OAAOC,MAAMpB,WAAW,OAAOA;QACnC,EAAE,OAAM;YACJ,OAAO;QACX;IACJ;IAEA,MAAcc,cAAcP,GAAW,EAAqB;QACxD,IAAI;YACA,MAAM,EAAES,MAAM,EAAE,GAAG,MAAM7B,cAAc,SAAS;gBAAC;gBAAMkC,OAAOd;aAAK;YACnE,OAAOS,OACFE,IAAI,GACJC,KAAK,CAAC,MACNG,GAAG,CAAC,CAACC,IAAMN,SAASM,GAAG,KACvBC,MAAM,CAAC,CAACC,IAAM,CAACL,MAAMK;QAC9B,EAAE,OAAM;YACJ,OAAO,EAAE;QACb;IACJ;IAEA,MAAcb,kBAAkBL,GAAW,EAA0B;QACjE,IAAI;YACA,MAAM,EAAES,MAAM,EAAE,GAAG,MAAM7B,cAAc,MAAM;gBAAC;gBAAMkC,OAAOd;gBAAM;gBAAM;aAAW;YAClF,MAAMmB,UAAUV,OAAOE,IAAI;YAC3B,OAAOQ,WAAW;QACtB,EAAE,OAAM;YACJ,OAAO;QACX;IACJ;AACJ"}
@@ -18,9 +18,8 @@ export class TtyWriter {
18
18
  * @param location Terminal location from TerminalFocusManager.findTerminal()
19
19
  * @param message Text to send
20
20
  * @throws Error if terminal type is unsupported or send fails
21
- */
22
- static async send(location, message) {
23
- switch (location.type) {
21
+ */ static async send(location, message) {
22
+ switch(location.type){
24
23
  case TerminalType.TMUX:
25
24
  return TtyWriter.sendViaTmux(location.identifier, message);
26
25
  case TerminalType.ITERM2:
@@ -28,8 +27,7 @@ export class TtyWriter {
28
27
  case TerminalType.TERMINAL_APP:
29
28
  return TtyWriter.sendViaTerminalApp(location.tty, message);
30
29
  default:
31
- throw new Error(`Cannot send input: unsupported terminal type "${location.type}". ` +
32
- 'Supported: tmux, iTerm2, Terminal.app.');
30
+ throw new Error(`Cannot send input: unsupported terminal type "${location.type}". ` + 'Supported: tmux, iTerm2, Terminal.app.');
33
31
  }
34
32
  }
35
33
  static async sendViaTmux(identifier, message) {
@@ -38,16 +36,26 @@ export class TtyWriter {
38
36
  // Claude Code) has bracketed paste enabled, tmux wraps the send-keys
39
37
  // payload in paste brackets — if Enter is included, it gets swallowed
40
38
  // as part of the paste instead of acting as a submit action.
41
- await execFileAsync('tmux', ['send-keys', '-t', identifier, '-l', message]);
42
- await new Promise((resolve) => setTimeout(resolve, 150));
43
- await execFileAsync('tmux', ['send-keys', '-t', identifier, 'Enter']);
39
+ await execFileAsync('tmux', [
40
+ 'send-keys',
41
+ '-t',
42
+ identifier,
43
+ '-l',
44
+ message
45
+ ]);
46
+ await new Promise((resolve)=>setTimeout(resolve, 150));
47
+ await execFileAsync('tmux', [
48
+ 'send-keys',
49
+ '-t',
50
+ identifier,
51
+ 'Enter'
52
+ ]);
44
53
  }
45
54
  /**
46
55
  * Build an AppleScript that finds an iTerm2 session by TTY and runs a
47
56
  * command against it. The `sessionCommand` is inserted inside a
48
57
  * `tell targetSession` block.
49
- */
50
- static iterm2SessionScript(tty, sessionCommand) {
58
+ */ static iterm2SessionScript(tty, sessionCommand) {
51
59
  return `
52
60
  tell application "iTerm"
53
61
  set targetSession to missing value
@@ -75,14 +83,20 @@ return "ok"`;
75
83
  // iTerm2 appends the newline after the paste-end marker (\e[201~), so
76
84
  // the inner TUI (Claude Code, Codex) sees it as a real submit action.
77
85
  const textScript = TtyWriter.iterm2SessionScript(tty, `write text "${escaped}" newline no`);
78
- const { stdout: textResult } = await execFileAsync('osascript', ['-e', textScript]);
86
+ const { stdout: textResult } = await execFileAsync('osascript', [
87
+ '-e',
88
+ textScript
89
+ ]);
79
90
  if (textResult.trim() !== 'ok') {
80
91
  throw new Error(`iTerm2 session not found for TTY ${tty}`);
81
92
  }
82
93
  // Wait for the paste to complete before sending Enter separately
83
- await new Promise((resolve) => setTimeout(resolve, 150));
94
+ await new Promise((resolve)=>setTimeout(resolve, 150));
84
95
  const enterScript = TtyWriter.iterm2SessionScript(tty, 'write text "" newline yes');
85
- const { stdout: enterResult } = await execFileAsync('osascript', ['-e', enterScript]);
96
+ const { stdout: enterResult } = await execFileAsync('osascript', [
97
+ '-e',
98
+ enterScript
99
+ ]);
86
100
  if (enterResult.trim() !== 'ok') {
87
101
  throw new Error(`iTerm2 session disappeared before Enter could be sent for TTY ${tty}`);
88
102
  }
@@ -113,12 +127,15 @@ tell application "Terminal"
113
127
  do script "${escaped}" in targetTab
114
128
  end tell
115
129
  return "ok"`;
116
- const { stdout: textResult } = await execFileAsync('osascript', ['-e', textScript]);
130
+ const { stdout: textResult } = await execFileAsync('osascript', [
131
+ '-e',
132
+ textScript
133
+ ]);
117
134
  if (textResult.trim() !== 'ok') {
118
135
  throw new Error(`Terminal.app tab not found for TTY ${tty}`);
119
136
  }
120
137
  // Wait for the text to be delivered before sending Enter
121
- await new Promise((resolve) => setTimeout(resolve, 150));
138
+ await new Promise((resolve)=>setTimeout(resolve, 150));
122
139
  const enterScript = `
123
140
  tell application "Terminal"
124
141
  set targetTab to missing value
@@ -136,10 +153,14 @@ tell application "Terminal"
136
153
  do script "" in targetTab
137
154
  end tell
138
155
  return "ok"`;
139
- const { stdout: enterResult } = await execFileAsync('osascript', ['-e', enterScript]);
156
+ const { stdout: enterResult } = await execFileAsync('osascript', [
157
+ '-e',
158
+ enterScript
159
+ ]);
140
160
  if (enterResult.trim() !== 'ok') {
141
161
  throw new Error(`Terminal.app tab disappeared before Enter could be sent for TTY ${tty}`);
142
162
  }
143
163
  }
144
164
  }
165
+
145
166
  //# sourceMappingURL=TtyWriter.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"TtyWriter.js","sourceRoot":"","sources":["../../src/terminal/TtyWriter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/terminal/TtyWriter.ts"],"sourcesContent":["import { execFile } from 'child_process';\nimport { promisify } from 'util';\nimport type { TerminalLocation } from './TerminalFocusManager.js';\nimport { TerminalType } from './TerminalFocusManager.js';\nimport { escapeAppleScript } from '../utils/applescript.js';\n\nconst execFileAsync = promisify(execFile);\n\nexport class TtyWriter {\n /**\n * Send a message as keyboard input to a terminal session.\n *\n * Dispatches to the correct mechanism based on terminal type:\n * - tmux: `tmux send-keys`\n * - iTerm2: Two separate AppleScript `write text` calls (text then newline)\n * - Terminal.app: Two separate AppleScript `do script` calls (text then newline)\n *\n * All AppleScript is executed via `execFile('osascript', ['-e', script])`\n * to avoid shell interpolation and command injection.\n *\n * @param location Terminal location from TerminalFocusManager.findTerminal()\n * @param message Text to send\n * @throws Error if terminal type is unsupported or send fails\n */\n static async send(location: TerminalLocation, message: string): Promise<void> {\n switch (location.type) {\n case TerminalType.TMUX:\n return TtyWriter.sendViaTmux(location.identifier, message);\n case TerminalType.ITERM2:\n return TtyWriter.sendViaITerm2(location.tty, message);\n case TerminalType.TERMINAL_APP:\n return TtyWriter.sendViaTerminalApp(location.tty, message);\n default:\n throw new Error(\n `Cannot send input: unsupported terminal type \"${location.type}\". ` +\n 'Supported: tmux, iTerm2, Terminal.app.'\n );\n }\n }\n\n private static async sendViaTmux(identifier: string, message: string): Promise<void> {\n // Send text and Enter as two separate calls so that Enter arrives\n // outside of bracketed paste mode. When the inner application (e.g.\n // Claude Code) has bracketed paste enabled, tmux wraps the send-keys\n // payload in paste brackets — if Enter is included, it gets swallowed\n // as part of the paste instead of acting as a submit action.\n await execFileAsync('tmux', ['send-keys', '-t', identifier, '-l', message]);\n await new Promise((resolve) => setTimeout(resolve, 150));\n await execFileAsync('tmux', ['send-keys', '-t', identifier, 'Enter']);\n }\n\n /**\n * Build an AppleScript that finds an iTerm2 session by TTY and runs a\n * command against it. The `sessionCommand` is inserted inside a\n * `tell targetSession` block.\n */\n private static iterm2SessionScript(tty: string, sessionCommand: string): string {\n return `\ntell application \"iTerm\"\n set targetSession to missing value\n repeat with w in windows\n repeat with t in tabs of w\n repeat with s in sessions of t\n if tty of s is \"${tty}\" then\n set targetSession to s\n exit repeat\n end if\n end repeat\n if targetSession is not missing value then exit repeat\n end repeat\n if targetSession is not missing value then exit repeat\n end repeat\n if targetSession is missing value then return \"not_found\"\n tell targetSession to ${sessionCommand}\nend tell\nreturn \"ok\"`;\n }\n\n private static async sendViaITerm2(tty: string, message: string): Promise<void> {\n const escaped = escapeAppleScript(message);\n // Send text and Enter as two separate write text calls so the newline\n // is delivered outside the bracketed paste sequence of the message body.\n // iTerm2 appends the newline after the paste-end marker (\\e[201~), so\n // the inner TUI (Claude Code, Codex) sees it as a real submit action.\n const textScript = TtyWriter.iterm2SessionScript(tty, `write text \"${escaped}\" newline no`);\n\n const { stdout: textResult } = await execFileAsync('osascript', ['-e', textScript]);\n if (textResult.trim() !== 'ok') {\n throw new Error(`iTerm2 session not found for TTY ${tty}`);\n }\n\n // Wait for the paste to complete before sending Enter separately\n await new Promise((resolve) => setTimeout(resolve, 150));\n\n const enterScript = TtyWriter.iterm2SessionScript(tty, 'write text \"\" newline yes');\n const { stdout: enterResult } = await execFileAsync('osascript', ['-e', enterScript]);\n if (enterResult.trim() !== 'ok') {\n throw new Error(`iTerm2 session disappeared before Enter could be sent for TTY ${tty}`);\n }\n }\n\n private static async sendViaTerminalApp(tty: string, message: string): Promise<void> {\n const escaped = escapeAppleScript(message);\n // Use Terminal.app's `do script` to send text to the correct tab by TTY.\n // We avoid System Events `keystroke` + `key code 36` because it requires\n // accessibility permissions and unreliably delivers the Return key.\n //\n // `do script` with `in` targets a specific tab without opening a new one.\n // We send text and Enter as two separate calls so the newline arrives\n // outside of bracketed paste mode — same pattern as iTerm2 and tmux.\n const textScript = `\ntell application \"Terminal\"\n set targetTab to missing value\n repeat with w in windows\n repeat with i from 1 to count of tabs of w\n set t to tab i of w\n if tty of t is \"${tty}\" then\n set targetTab to t\n exit repeat\n end if\n end repeat\n if targetTab is not missing value then exit repeat\n end repeat\n if targetTab is missing value then return \"not_found\"\n do script \"${escaped}\" in targetTab\nend tell\nreturn \"ok\"`;\n\n const { stdout: textResult } = await execFileAsync('osascript', ['-e', textScript]);\n if (textResult.trim() !== 'ok') {\n throw new Error(`Terminal.app tab not found for TTY ${tty}`);\n }\n\n // Wait for the text to be delivered before sending Enter\n await new Promise((resolve) => setTimeout(resolve, 150));\n\n const enterScript = `\ntell application \"Terminal\"\n set targetTab to missing value\n repeat with w in windows\n repeat with i from 1 to count of tabs of w\n set t to tab i of w\n if tty of t is \"${tty}\" then\n set targetTab to t\n exit repeat\n end if\n end repeat\n if targetTab is not missing value then exit repeat\n end repeat\n if targetTab is missing value then return \"not_found\"\n do script \"\" in targetTab\nend tell\nreturn \"ok\"`;\n\n const { stdout: enterResult } = await execFileAsync('osascript', ['-e', enterScript]);\n if (enterResult.trim() !== 'ok') {\n throw new Error(`Terminal.app tab disappeared before Enter could be sent for TTY ${tty}`);\n }\n }\n}\n"],"names":["execFile","promisify","TerminalType","escapeAppleScript","execFileAsync","TtyWriter","send","location","message","type","TMUX","sendViaTmux","identifier","ITERM2","sendViaITerm2","tty","TERMINAL_APP","sendViaTerminalApp","Error","Promise","resolve","setTimeout","iterm2SessionScript","sessionCommand","escaped","textScript","stdout","textResult","trim","enterScript","enterResult"],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  export { TerminalFocusManager } from './TerminalFocusManager.js';
2
2
  export { TerminalType } from './TerminalFocusManager.js';
3
3
  export { TtyWriter } from './TtyWriter.js';
4
+
4
5
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/terminal/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAEzD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"sources":["../../src/terminal/index.ts"],"sourcesContent":["export { TerminalFocusManager } from './TerminalFocusManager.js';\nexport { TerminalType } from './TerminalFocusManager.js';\nexport type { TerminalLocation } from './TerminalFocusManager.js';\nexport { TtyWriter } from './TtyWriter.js';\n"],"names":["TerminalFocusManager","TerminalType","TtyWriter"],"mappings":"AAAA,SAASA,oBAAoB,QAAQ,4BAA4B;AACjE,SAASC,YAAY,QAAQ,4BAA4B;AAEzD,SAASC,SAAS,QAAQ,iBAAiB"}