@ai-devkit/agent-manager 0.22.1 → 0.25.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 (59) hide show
  1. package/README.md +3 -0
  2. package/dist/__tests__/AgentManager.test.js +9 -1
  3. package/dist/__tests__/AgentManager.test.js.map +1 -1
  4. package/dist/__tests__/adapters/GrokCliAdapter.test.js +403 -0
  5. package/dist/__tests__/adapters/GrokCliAdapter.test.js.map +1 -0
  6. package/dist/__tests__/terminal/TerminalFocusManager.test.js +180 -0
  7. package/dist/__tests__/terminal/TerminalFocusManager.test.js.map +1 -1
  8. package/dist/__tests__/terminal/TtyWriter.test.js +206 -0
  9. package/dist/__tests__/terminal/TtyWriter.test.js.map +1 -1
  10. package/dist/__tests__/utils/agent-requests.test.js +90 -0
  11. package/dist/__tests__/utils/agent-requests.test.js.map +1 -0
  12. package/dist/__tests__/utils/agents.test.js +6 -0
  13. package/dist/__tests__/utils/agents.test.js.map +1 -1
  14. package/dist/adapters/AgentAdapter.d.ts +1 -1
  15. package/dist/adapters/AgentAdapter.d.ts.map +1 -1
  16. package/dist/adapters/AgentAdapter.js.map +1 -1
  17. package/dist/adapters/GrokCliAdapter.d.ts +79 -0
  18. package/dist/adapters/GrokCliAdapter.d.ts.map +1 -0
  19. package/dist/adapters/GrokCliAdapter.js +306 -0
  20. package/dist/adapters/GrokCliAdapter.js.map +1 -0
  21. package/dist/adapters/index.d.ts +1 -0
  22. package/dist/adapters/index.d.ts.map +1 -1
  23. package/dist/adapters/index.js +1 -0
  24. package/dist/adapters/index.js.map +1 -1
  25. package/dist/index.d.ts +3 -0
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +2 -0
  28. package/dist/index.js.map +1 -1
  29. package/dist/terminal/TerminalFocusManager.d.ts +11 -0
  30. package/dist/terminal/TerminalFocusManager.d.ts.map +1 -1
  31. package/dist/terminal/TerminalFocusManager.js +84 -10
  32. package/dist/terminal/TerminalFocusManager.js.map +1 -1
  33. package/dist/terminal/TtyWriter.d.ts +19 -0
  34. package/dist/terminal/TtyWriter.d.ts.map +1 -1
  35. package/dist/terminal/TtyWriter.js +167 -1
  36. package/dist/terminal/TtyWriter.js.map +1 -1
  37. package/dist/utils/agent-requests.d.ts +10 -0
  38. package/dist/utils/agent-requests.d.ts.map +1 -0
  39. package/dist/utils/agent-requests.js +22 -0
  40. package/dist/utils/agent-requests.js.map +1 -0
  41. package/dist/utils/agents.d.ts +1 -1
  42. package/dist/utils/agents.d.ts.map +1 -1
  43. package/dist/utils/agents.js +4 -0
  44. package/dist/utils/agents.js.map +1 -1
  45. package/package.json +2 -2
  46. package/src/__tests__/AgentManager.test.ts +7 -1
  47. package/src/__tests__/adapters/GrokCliAdapter.test.ts +307 -0
  48. package/src/__tests__/terminal/TerminalFocusManager.test.ts +187 -0
  49. package/src/__tests__/terminal/TtyWriter.test.ts +234 -0
  50. package/src/__tests__/utils/agent-requests.test.ts +74 -0
  51. package/src/__tests__/utils/agents.test.ts +7 -0
  52. package/src/adapters/AgentAdapter.ts +1 -1
  53. package/src/adapters/GrokCliAdapter.ts +394 -0
  54. package/src/adapters/index.ts +1 -0
  55. package/src/index.ts +4 -0
  56. package/src/terminal/TerminalFocusManager.ts +103 -11
  57. package/src/terminal/TtyWriter.ts +161 -1
  58. package/src/utils/agent-requests.ts +28 -0
  59. package/src/utils/agents.ts +2 -1
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC"}
@@ -2,6 +2,7 @@ export { ClaudeCodeAdapter } from './ClaudeCodeAdapter.js';
2
2
  export { CodexAdapter } from './CodexAdapter.js';
3
3
  export { CopilotAdapter } from './CopilotAdapter.js';
4
4
  export { GeminiCliAdapter } from './GeminiCliAdapter.js';
5
+ export { GrokCliAdapter } from './GrokCliAdapter.js';
5
6
  export { OpenCodeAdapter } from './OpenCodeAdapter.js';
6
7
  export { PiAdapter } from './PiAdapter.js';
7
8
  export { AgentStatus } from './AgentAdapter.js';
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/adapters/index.ts"],"sourcesContent":["export { ClaudeCodeAdapter } from './ClaudeCodeAdapter.js';\nexport { CodexAdapter } from './CodexAdapter.js';\nexport { CopilotAdapter } from './CopilotAdapter.js';\nexport { GeminiCliAdapter } from './GeminiCliAdapter.js';\nexport { OpenCodeAdapter } from './OpenCodeAdapter.js';\nexport { PiAdapter } from './PiAdapter.js';\nexport { AgentStatus } from './AgentAdapter.js';\nexport type { AgentAdapter, AgentType, AgentInfo, ProcessInfo } from './AgentAdapter.js';\n"],"names":["ClaudeCodeAdapter","CodexAdapter","CopilotAdapter","GeminiCliAdapter","OpenCodeAdapter","PiAdapter","AgentStatus"],"mappings":"AAAA,SAASA,iBAAiB,QAAQ,yBAAyB;AAC3D,SAASC,YAAY,QAAQ,oBAAoB;AACjD,SAASC,cAAc,QAAQ,sBAAsB;AACrD,SAASC,gBAAgB,QAAQ,wBAAwB;AACzD,SAASC,eAAe,QAAQ,uBAAuB;AACvD,SAASC,SAAS,QAAQ,iBAAiB;AAC3C,SAASC,WAAW,QAAQ,oBAAoB"}
1
+ {"version":3,"sources":["../../src/adapters/index.ts"],"sourcesContent":["export { ClaudeCodeAdapter } from './ClaudeCodeAdapter.js';\nexport { CodexAdapter } from './CodexAdapter.js';\nexport { CopilotAdapter } from './CopilotAdapter.js';\nexport { GeminiCliAdapter } from './GeminiCliAdapter.js';\nexport { GrokCliAdapter } from './GrokCliAdapter.js';\nexport { OpenCodeAdapter } from './OpenCodeAdapter.js';\nexport { PiAdapter } from './PiAdapter.js';\nexport { AgentStatus } from './AgentAdapter.js';\nexport type { AgentAdapter, AgentType, AgentInfo, ProcessInfo } from './AgentAdapter.js';\n"],"names":["ClaudeCodeAdapter","CodexAdapter","CopilotAdapter","GeminiCliAdapter","GrokCliAdapter","OpenCodeAdapter","PiAdapter","AgentStatus"],"mappings":"AAAA,SAASA,iBAAiB,QAAQ,yBAAyB;AAC3D,SAASC,YAAY,QAAQ,oBAAoB;AACjD,SAASC,cAAc,QAAQ,sBAAsB;AACrD,SAASC,gBAAgB,QAAQ,wBAAwB;AACzD,SAASC,cAAc,QAAQ,sBAAsB;AACrD,SAASC,eAAe,QAAQ,uBAAuB;AACvD,SAASC,SAAS,QAAQ,iBAAiB;AAC3C,SAASC,WAAW,QAAQ,oBAAoB"}
package/dist/index.d.ts CHANGED
@@ -3,6 +3,7 @@ export { ClaudeCodeAdapter } from './adapters/ClaudeCodeAdapter.js';
3
3
  export { CodexAdapter } from './adapters/CodexAdapter.js';
4
4
  export { CopilotAdapter } from './adapters/CopilotAdapter.js';
5
5
  export { GeminiCliAdapter } from './adapters/GeminiCliAdapter.js';
6
+ export { GrokCliAdapter } from './adapters/GrokCliAdapter.js';
6
7
  export { OpenCodeAdapter } from './adapters/OpenCodeAdapter.js';
7
8
  export { PiAdapter } from './adapters/PiAdapter.js';
8
9
  export { AgentStatus } from './adapters/AgentAdapter.js';
@@ -18,4 +19,6 @@ export type { RegistryEntry } from './utils/AgentRegistry.js';
18
19
  export { TmuxManager } from './terminal/TmuxManager.js';
19
20
  export { AGENTS } from './utils/agents.js';
20
21
  export type { AgentConfig, StartableAgentType } from './utils/agents.js';
22
+ export type { AgentRequest } from './utils/agent-requests.js';
23
+ export { getAgentRequestPath, readLatestAgentRequest, writeAgentRequest } from './utils/agent-requests.js';
21
24
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,YAAY,EACR,YAAY,EACZ,SAAS,EACT,SAAS,EACT,WAAW,EACX,mBAAmB,EACnB,cAAc,EACd,mBAAmB,GACtB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AACxF,YAAY,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AAC3E,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAEpD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,YAAY,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC1D,YAAY,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE3D,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AACnG,YAAY,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxD,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,YAAY,EACR,YAAY,EACZ,SAAS,EACT,SAAS,EACT,WAAW,EACX,mBAAmB,EACnB,cAAc,EACd,mBAAmB,GACtB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AACxF,YAAY,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AAC3E,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAEpD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,YAAY,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC1D,YAAY,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE3D,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AACnG,YAAY,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxD,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAEzE,YAAY,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC"}
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@ export { ClaudeCodeAdapter } from './adapters/ClaudeCodeAdapter.js';
3
3
  export { CodexAdapter } from './adapters/CodexAdapter.js';
4
4
  export { CopilotAdapter } from './adapters/CopilotAdapter.js';
5
5
  export { GeminiCliAdapter } from './adapters/GeminiCliAdapter.js';
6
+ export { GrokCliAdapter } from './adapters/GrokCliAdapter.js';
6
7
  export { OpenCodeAdapter } from './adapters/OpenCodeAdapter.js';
7
8
  export { PiAdapter } from './adapters/PiAdapter.js';
8
9
  export { AgentStatus } from './adapters/AgentAdapter.js';
@@ -12,5 +13,6 @@ export { getProcessTty } from './utils/process.js';
12
13
  export { AgentRegistry, RenameNotFoundError, RenameConflictError } from './utils/AgentRegistry.js';
13
14
  export { TmuxManager } from './terminal/TmuxManager.js';
14
15
  export { AGENTS } from './utils/agents.js';
16
+ export { getAgentRequestPath, readLatestAgentRequest, writeAgentRequest } from './utils/agent-requests.js';
15
17
 
16
18
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export { AgentManager } from './AgentManager.js';\n\nexport { ClaudeCodeAdapter } from './adapters/ClaudeCodeAdapter.js';\nexport { CodexAdapter } from './adapters/CodexAdapter.js';\nexport { CopilotAdapter } from './adapters/CopilotAdapter.js';\nexport { GeminiCliAdapter } from './adapters/GeminiCliAdapter.js';\nexport { OpenCodeAdapter } from './adapters/OpenCodeAdapter.js';\nexport { PiAdapter } from './adapters/PiAdapter.js';\nexport { AgentStatus } from './adapters/AgentAdapter.js';\nexport type {\n AgentAdapter,\n AgentType,\n AgentInfo,\n ProcessInfo,\n ConversationMessage,\n SessionSummary,\n ListSessionsOptions,\n} from './adapters/AgentAdapter.js';\n\nexport { TerminalFocusManager, TerminalType } from './terminal/TerminalFocusManager.js';\nexport type { TerminalLocation } from './terminal/TerminalFocusManager.js';\nexport { TtyWriter } from './terminal/TtyWriter.js';\n\nexport { getProcessTty } from './utils/process.js';\nexport type { AgentSortKey } from './utils/sortAgents.js';\nexport type { ListAgentsOptions } from './AgentManager.js';\n\nexport { AgentRegistry, RenameNotFoundError, RenameConflictError } from './utils/AgentRegistry.js';\nexport type { RegistryEntry } from './utils/AgentRegistry.js';\nexport { TmuxManager } from './terminal/TmuxManager.js';\nexport { AGENTS } from './utils/agents.js';\nexport type { AgentConfig, StartableAgentType } from './utils/agents.js';\n"],"names":["AgentManager","ClaudeCodeAdapter","CodexAdapter","CopilotAdapter","GeminiCliAdapter","OpenCodeAdapter","PiAdapter","AgentStatus","TerminalFocusManager","TerminalType","TtyWriter","getProcessTty","AgentRegistry","RenameNotFoundError","RenameConflictError","TmuxManager","AGENTS"],"mappings":"AAAA,SAASA,YAAY,QAAQ,oBAAoB;AAEjD,SAASC,iBAAiB,QAAQ,kCAAkC;AACpE,SAASC,YAAY,QAAQ,6BAA6B;AAC1D,SAASC,cAAc,QAAQ,+BAA+B;AAC9D,SAASC,gBAAgB,QAAQ,iCAAiC;AAClE,SAASC,eAAe,QAAQ,gCAAgC;AAChE,SAASC,SAAS,QAAQ,0BAA0B;AACpD,SAASC,WAAW,QAAQ,6BAA6B;AAWzD,SAASC,oBAAoB,EAAEC,YAAY,QAAQ,qCAAqC;AAExF,SAASC,SAAS,QAAQ,0BAA0B;AAEpD,SAASC,aAAa,QAAQ,qBAAqB;AAInD,SAASC,aAAa,EAAEC,mBAAmB,EAAEC,mBAAmB,QAAQ,2BAA2B;AAEnG,SAASC,WAAW,QAAQ,4BAA4B;AACxD,SAASC,MAAM,QAAQ,oBAAoB"}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export { AgentManager } from './AgentManager.js';\n\nexport { ClaudeCodeAdapter } from './adapters/ClaudeCodeAdapter.js';\nexport { CodexAdapter } from './adapters/CodexAdapter.js';\nexport { CopilotAdapter } from './adapters/CopilotAdapter.js';\nexport { GeminiCliAdapter } from './adapters/GeminiCliAdapter.js';\nexport { GrokCliAdapter } from './adapters/GrokCliAdapter.js';\nexport { OpenCodeAdapter } from './adapters/OpenCodeAdapter.js';\nexport { PiAdapter } from './adapters/PiAdapter.js';\nexport { AgentStatus } from './adapters/AgentAdapter.js';\nexport type {\n AgentAdapter,\n AgentType,\n AgentInfo,\n ProcessInfo,\n ConversationMessage,\n SessionSummary,\n ListSessionsOptions,\n} from './adapters/AgentAdapter.js';\n\nexport { TerminalFocusManager, TerminalType } from './terminal/TerminalFocusManager.js';\nexport type { TerminalLocation } from './terminal/TerminalFocusManager.js';\nexport { TtyWriter } from './terminal/TtyWriter.js';\n\nexport { getProcessTty } from './utils/process.js';\nexport type { AgentSortKey } from './utils/sortAgents.js';\nexport type { ListAgentsOptions } from './AgentManager.js';\n\nexport { AgentRegistry, RenameNotFoundError, RenameConflictError } from './utils/AgentRegistry.js';\nexport type { RegistryEntry } from './utils/AgentRegistry.js';\nexport { TmuxManager } from './terminal/TmuxManager.js';\nexport { AGENTS } from './utils/agents.js';\nexport type { AgentConfig, StartableAgentType } from './utils/agents.js';\n\nexport type { AgentRequest } from './utils/agent-requests.js';\nexport { getAgentRequestPath, readLatestAgentRequest, writeAgentRequest } from './utils/agent-requests.js';\n"],"names":["AgentManager","ClaudeCodeAdapter","CodexAdapter","CopilotAdapter","GeminiCliAdapter","GrokCliAdapter","OpenCodeAdapter","PiAdapter","AgentStatus","TerminalFocusManager","TerminalType","TtyWriter","getProcessTty","AgentRegistry","RenameNotFoundError","RenameConflictError","TmuxManager","AGENTS","getAgentRequestPath","readLatestAgentRequest","writeAgentRequest"],"mappings":"AAAA,SAASA,YAAY,QAAQ,oBAAoB;AAEjD,SAASC,iBAAiB,QAAQ,kCAAkC;AACpE,SAASC,YAAY,QAAQ,6BAA6B;AAC1D,SAASC,cAAc,QAAQ,+BAA+B;AAC9D,SAASC,gBAAgB,QAAQ,iCAAiC;AAClE,SAASC,cAAc,QAAQ,+BAA+B;AAC9D,SAASC,eAAe,QAAQ,gCAAgC;AAChE,SAASC,SAAS,QAAQ,0BAA0B;AACpD,SAASC,WAAW,QAAQ,6BAA6B;AAWzD,SAASC,oBAAoB,EAAEC,YAAY,QAAQ,qCAAqC;AAExF,SAASC,SAAS,QAAQ,0BAA0B;AAEpD,SAASC,aAAa,QAAQ,qBAAqB;AAInD,SAASC,aAAa,EAAEC,mBAAmB,EAAEC,mBAAmB,QAAQ,2BAA2B;AAEnG,SAASC,WAAW,QAAQ,4BAA4B;AACxD,SAASC,MAAM,QAAQ,oBAAoB;AAI3C,SAASC,mBAAmB,EAAEC,sBAAsB,EAAEC,iBAAiB,QAAQ,4BAA4B"}
@@ -1,5 +1,6 @@
1
1
  export declare enum TerminalType {
2
2
  TMUX = "tmux",
3
+ WEZTERM = "wezterm",
3
4
  ITERM2 = "iterm2",
4
5
  TERMINAL_APP = "terminal-app",
5
6
  UNKNOWN = "unknown"
@@ -9,7 +10,15 @@ export interface TerminalLocation {
9
10
  identifier: string;
10
11
  tty: string;
11
12
  }
13
+ /**
14
+ * Optional trace sink. When provided to {@link TerminalFocusManager}, each
15
+ * discovery/focus step reports a human-readable line so callers (e.g. the
16
+ * `agent open --debug` command) can inspect the matching/focus decision path.
17
+ */
18
+ export type TerminalDebugLogger = (message: string) => void;
12
19
  export declare class TerminalFocusManager {
20
+ private readonly debug?;
21
+ constructor(debug?: TerminalDebugLogger | undefined);
13
22
  /**
14
23
  * Find the terminal location (emulator info) for a given process ID
15
24
  */
@@ -18,6 +27,8 @@ export declare class TerminalFocusManager {
18
27
  * Focus the terminal identified by the location
19
28
  */
20
29
  focusTerminal(location: TerminalLocation): Promise<boolean>;
30
+ private findWeztermPane;
31
+ private focusWeztermPane;
21
32
  private findTmuxPane;
22
33
  private findITerm2Session;
23
34
  private findTerminalAppWindow;
@@ -1 +1 @@
1
- {"version":3,"file":"TerminalFocusManager.d.ts","sourceRoot":"","sources":["../../src/terminal/TerminalFocusManager.ts"],"names":[],"mappings":"AAOA,oBAAY,YAAY;IACpB,IAAI,SAAS;IACb,MAAM,WAAW;IACjB,YAAY,iBAAiB;IAC7B,OAAO,YAAY;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC7B,IAAI,EAAE,YAAY,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;CACf;AAED,qBAAa,oBAAoB;IAC7B;;OAEG;IACG,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;IA8BjE;;OAEG;IACG,aAAa,CAAC,QAAQ,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC;YAiBnD,YAAY;YAwBZ,iBAAiB;YAsCjB,qBAAqB;YAoCrB,gBAAgB;YAQhB,aAAa;YASb,kBAAkB;YAqBlB,sBAAsB;CAmBvC"}
1
+ {"version":3,"file":"TerminalFocusManager.d.ts","sourceRoot":"","sources":["../../src/terminal/TerminalFocusManager.ts"],"names":[],"mappings":"AAOA,oBAAY,YAAY;IACpB,IAAI,SAAS;IACb,OAAO,YAAY;IACnB,MAAM,WAAW;IACjB,YAAY,iBAAiB;IAC7B,OAAO,YAAY;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC7B,IAAI,EAAE,YAAY,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;CACf;AAaD;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;AAE5D,qBAAa,oBAAoB;IACjB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;gBAAN,KAAK,CAAC,EAAE,mBAAmB,YAAA;IAExD;;OAEG;IACG,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;IAqDjE;;OAEG;IACG,aAAa,CAAC,QAAQ,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC;YA2BnD,eAAe;YA6Bf,gBAAgB;YAShB,YAAY;YAwBZ,iBAAiB;YAsCjB,qBAAqB;YAoCrB,gBAAgB;YAQhB,aAAa;YASb,kBAAkB;YAqBlB,sBAAsB;CAmBvC"}
@@ -5,31 +5,58 @@ import { escapeAppleScript } from '../utils/applescript.js';
5
5
  const execFileAsync = promisify(execFile);
6
6
  export var TerminalType = /*#__PURE__*/ function(TerminalType) {
7
7
  TerminalType["TMUX"] = "tmux";
8
+ TerminalType["WEZTERM"] = "wezterm";
8
9
  TerminalType["ITERM2"] = "iterm2";
9
10
  TerminalType["TERMINAL_APP"] = "terminal-app";
10
11
  TerminalType["UNKNOWN"] = "unknown";
11
12
  return TerminalType;
12
13
  }({});
13
14
  export class TerminalFocusManager {
15
+ debug;
16
+ constructor(debug){
17
+ this.debug = debug;
18
+ }
14
19
  /**
15
20
  * Find the terminal location (emulator info) for a given process ID
16
21
  */ async findTerminal(pid) {
17
22
  const ttyShort = getProcessTty(pid);
18
23
  // If no TTY or invalid, we can't find the terminal
19
24
  if (!ttyShort || ttyShort === '?') {
25
+ this.debug?.(`findTerminal(pid=${pid}): no usable TTY, cannot resolve terminal`);
20
26
  return null;
21
27
  }
22
28
  const fullTty = `/dev/${ttyShort}`;
29
+ this.debug?.(`findTerminal(pid=${pid}): resolving terminal for ${fullTty}`);
23
30
  // 1. Check tmux (most specific if running inside it)
24
31
  const tmuxLocation = await this.findTmuxPane(fullTty);
25
- if (tmuxLocation) return tmuxLocation;
26
- // 2. Check iTerm2
32
+ if (tmuxLocation) {
33
+ this.debug?.(`findTerminal: matched tmux (identifier=${tmuxLocation.identifier})`);
34
+ return tmuxLocation;
35
+ }
36
+ this.debug?.('findTerminal: tmux no match');
37
+ // 2. Check WezTerm (cross-platform, via its CLI — no AppleScript)
38
+ const weztermLocation = await this.findWeztermPane(fullTty);
39
+ if (weztermLocation) {
40
+ this.debug?.(`findTerminal: matched wezterm (pane_id=${weztermLocation.identifier})`);
41
+ return weztermLocation;
42
+ }
43
+ this.debug?.('findTerminal: wezterm no match');
44
+ // 3. Check iTerm2
27
45
  const itermLocation = await this.findITerm2Session(fullTty);
28
- if (itermLocation) return itermLocation;
29
- // 3. Check Terminal.app
46
+ if (itermLocation) {
47
+ this.debug?.(`findTerminal: matched iTerm2 (tty=${itermLocation.tty})`);
48
+ return itermLocation;
49
+ }
50
+ this.debug?.('findTerminal: iTerm2 no match');
51
+ // 4. Check Terminal.app
30
52
  const terminalAppLocation = await this.findTerminalAppWindow(fullTty);
31
- if (terminalAppLocation) return terminalAppLocation;
32
- // 4. Fallback: we know the TTY but not the emulator wrapper
53
+ if (terminalAppLocation) {
54
+ this.debug?.(`findTerminal: matched Terminal.app (tty=${terminalAppLocation.tty})`);
55
+ return terminalAppLocation;
56
+ }
57
+ this.debug?.('findTerminal: Terminal.app no match');
58
+ // 5. Fallback: we know the TTY but not the emulator wrapper
59
+ this.debug?.('findTerminal: no emulator matched; returning UNKNOWN');
33
60
  return {
34
61
  type: "unknown",
35
62
  identifier: '',
@@ -39,17 +66,64 @@ export class TerminalFocusManager {
39
66
  /**
40
67
  * Focus the terminal identified by the location
41
68
  */ async focusTerminal(location) {
69
+ this.debug?.(`focusTerminal: focusing ${location.type} (identifier=${location.identifier}, tty=${location.tty})`);
70
+ let success = false;
42
71
  try {
43
72
  switch(location.type){
44
73
  case "tmux":
45
- return await this.focusTmuxPane(location.identifier);
74
+ success = await this.focusTmuxPane(location.identifier);
75
+ break;
76
+ case "wezterm":
77
+ success = await this.focusWeztermPane(location.identifier);
78
+ break;
46
79
  case "iterm2":
47
- return await this.focusITerm2Session(location.tty);
80
+ success = await this.focusITerm2Session(location.tty);
81
+ break;
48
82
  case "terminal-app":
49
- return await this.focusTerminalAppWindow(location.tty);
83
+ success = await this.focusTerminalAppWindow(location.tty);
84
+ break;
50
85
  default:
51
- return false;
86
+ success = false;
87
+ }
88
+ } catch {
89
+ success = false;
90
+ }
91
+ this.debug?.(`focusTerminal: ${success ? 'succeeded' : 'failed'} for ${location.type}`);
92
+ return success;
93
+ }
94
+ async findWeztermPane(tty) {
95
+ try {
96
+ const { stdout } = await execFileAsync('wezterm', [
97
+ 'cli',
98
+ 'list',
99
+ '--format',
100
+ 'json'
101
+ ]);
102
+ const panes = JSON.parse(stdout);
103
+ if (!Array.isArray(panes)) return null;
104
+ for (const pane of panes){
105
+ if (pane && typeof pane.tty_name === 'string' && pane.tty_name === tty && pane.pane_id != null) {
106
+ return {
107
+ type: "wezterm",
108
+ identifier: String(pane.pane_id),
109
+ tty
110
+ };
111
+ }
52
112
  }
113
+ } catch {
114
+ // wezterm not installed, not running, or returned invalid JSON
115
+ }
116
+ return null;
117
+ }
118
+ async focusWeztermPane(paneId) {
119
+ try {
120
+ await execFileAsync('wezterm', [
121
+ 'cli',
122
+ 'activate-pane',
123
+ '--pane-id',
124
+ paneId
125
+ ]);
126
+ return true;
53
127
  } catch {
54
128
  return false;
55
129
  }
@@ -1 +1 @@
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 if (!await this.isProcessRunning('iTerm2')) return null;\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 if (!await this.isProcessRunning('Terminal')) return null;\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 isProcessRunning(name: string): Promise<boolean> {\n const { stdout } = await execFileAsync('ps', ['-Axo', 'comm']);\n return stdout\n .split('\\n')\n .map((line) => line.trim())\n .some((command) => command === name || command.endsWith(`/${name}`));\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","isProcessRunning","escapedTty","script","name","map","some","command","endsWith"],"mappings":"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"}
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 WEZTERM = 'wezterm',\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, WezTerm pane id, or TTY for others\n tty: string; // e.g., \"/dev/ttys030\"\n}\n\n/**\n * Subset of a `wezterm cli list --format json` entry. Only `pane_id` and\n * `tty_name` are read; extra fields are ignored so schema additions across\n * WezTerm versions don't break parsing. (The TTY is exposed as `tty_name` in\n * the JSON, not `tty`.)\n */\ninterface WeztermPaneEntry {\n pane_id?: number;\n tty_name?: string | null;\n}\n\n/**\n * Optional trace sink. When provided to {@link TerminalFocusManager}, each\n * discovery/focus step reports a human-readable line so callers (e.g. the\n * `agent open --debug` command) can inspect the matching/focus decision path.\n */\nexport type TerminalDebugLogger = (message: string) => void;\n\nexport class TerminalFocusManager {\n constructor(private readonly debug?: TerminalDebugLogger) {}\n\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 this.debug?.(`findTerminal(pid=${pid}): no usable TTY, cannot resolve terminal`);\n return null;\n }\n\n const fullTty = `/dev/${ttyShort}`;\n this.debug?.(`findTerminal(pid=${pid}): resolving terminal for ${fullTty}`);\n\n // 1. Check tmux (most specific if running inside it)\n const tmuxLocation = await this.findTmuxPane(fullTty);\n if (tmuxLocation) {\n this.debug?.(`findTerminal: matched tmux (identifier=${tmuxLocation.identifier})`);\n return tmuxLocation;\n }\n this.debug?.('findTerminal: tmux no match');\n\n // 2. Check WezTerm (cross-platform, via its CLI — no AppleScript)\n const weztermLocation = await this.findWeztermPane(fullTty);\n if (weztermLocation) {\n this.debug?.(`findTerminal: matched wezterm (pane_id=${weztermLocation.identifier})`);\n return weztermLocation;\n }\n this.debug?.('findTerminal: wezterm no match');\n\n // 3. Check iTerm2\n const itermLocation = await this.findITerm2Session(fullTty);\n if (itermLocation) {\n this.debug?.(`findTerminal: matched iTerm2 (tty=${itermLocation.tty})`);\n return itermLocation;\n }\n this.debug?.('findTerminal: iTerm2 no match');\n\n // 4. Check Terminal.app\n const terminalAppLocation = await this.findTerminalAppWindow(fullTty);\n if (terminalAppLocation) {\n this.debug?.(`findTerminal: matched Terminal.app (tty=${terminalAppLocation.tty})`);\n return terminalAppLocation;\n }\n this.debug?.('findTerminal: Terminal.app no match');\n\n // 5. Fallback: we know the TTY but not the emulator wrapper\n this.debug?.('findTerminal: no emulator matched; returning UNKNOWN');\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 this.debug?.(`focusTerminal: focusing ${location.type} (identifier=${location.identifier}, tty=${location.tty})`);\n let success = false;\n try {\n switch (location.type) {\n case TerminalType.TMUX:\n success = await this.focusTmuxPane(location.identifier);\n break;\n case TerminalType.WEZTERM:\n success = await this.focusWeztermPane(location.identifier);\n break;\n case TerminalType.ITERM2:\n success = await this.focusITerm2Session(location.tty);\n break;\n case TerminalType.TERMINAL_APP:\n success = await this.focusTerminalAppWindow(location.tty);\n break;\n default:\n success = false;\n }\n } catch {\n success = false;\n }\n this.debug?.(`focusTerminal: ${success ? 'succeeded' : 'failed'} for ${location.type}`);\n return success;\n }\n\n private async findWeztermPane(tty: string): Promise<TerminalLocation | null> {\n try {\n const { stdout } = await execFileAsync('wezterm', [\n 'cli', 'list', '--format', 'json',\n ]);\n\n const panes = JSON.parse(stdout) as WeztermPaneEntry[];\n if (!Array.isArray(panes)) return null;\n\n for (const pane of panes) {\n if (\n pane &&\n typeof pane.tty_name === 'string' &&\n pane.tty_name === tty &&\n pane.pane_id != null\n ) {\n return {\n type: TerminalType.WEZTERM,\n identifier: String(pane.pane_id),\n tty,\n };\n }\n }\n } catch {\n // wezterm not installed, not running, or returned invalid JSON\n }\n return null;\n }\n\n private async focusWeztermPane(paneId: string): Promise<boolean> {\n try {\n await execFileAsync('wezterm', ['cli', 'activate-pane', '--pane-id', paneId]);\n return true;\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 if (!await this.isProcessRunning('iTerm2')) return null;\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 if (!await this.isProcessRunning('Terminal')) return null;\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 isProcessRunning(name: string): Promise<boolean> {\n const { stdout } = await execFileAsync('ps', ['-Axo', 'comm']);\n return stdout\n .split('\\n')\n .map((line) => line.trim())\n .some((command) => command === name || command.endsWith(`/${name}`));\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","debug","findTerminal","pid","ttyShort","fullTty","tmuxLocation","findTmuxPane","identifier","weztermLocation","findWeztermPane","itermLocation","findITerm2Session","tty","terminalAppLocation","findTerminalAppWindow","type","focusTerminal","location","success","focusTmuxPane","focusWeztermPane","focusITerm2Session","focusTerminalAppWindow","stdout","panes","JSON","parse","Array","isArray","pane","tty_name","pane_id","String","paneId","lines","trim","split","line","paneTty","isProcessRunning","escapedTty","script","name","map","some","command","endsWith"],"mappings":"AAAA,SAASA,QAAQ,QAAQ,gBAAgB;AACzC,SAASC,SAAS,QAAQ,OAAO;AACjC,SAASC,aAAa,QAAQ,sBAAsB;AACpD,SAASC,iBAAiB,QAAQ,0BAA0B;AAE5D,MAAMC,gBAAgBH,UAAUD;AAEhC,OAAO,IAAA,AAAKK,sCAAAA;;;;;;WAAAA;MAMX;AA0BD,OAAO,MAAMC;;IACT,YAAY,AAAiBC,KAA2B,CAAE;aAA7BA,QAAAA;IAA8B;IAE3D;;KAEC,GACD,MAAMC,aAAaC,GAAW,EAAoC;QAC9D,MAAMC,WAAWR,cAAcO;QAE/B,mDAAmD;QACnD,IAAI,CAACC,YAAYA,aAAa,KAAK;YAC/B,IAAI,CAACH,KAAK,GAAG,CAAC,iBAAiB,EAAEE,IAAI,yCAAyC,CAAC;YAC/E,OAAO;QACX;QAEA,MAAME,UAAU,CAAC,KAAK,EAAED,UAAU;QAClC,IAAI,CAACH,KAAK,GAAG,CAAC,iBAAiB,EAAEE,IAAI,0BAA0B,EAAEE,SAAS;QAE1E,qDAAqD;QACrD,MAAMC,eAAe,MAAM,IAAI,CAACC,YAAY,CAACF;QAC7C,IAAIC,cAAc;YACd,IAAI,CAACL,KAAK,GAAG,CAAC,uCAAuC,EAAEK,aAAaE,UAAU,CAAC,CAAC,CAAC;YACjF,OAAOF;QACX;QACA,IAAI,CAACL,KAAK,GAAG;QAEb,kEAAkE;QAClE,MAAMQ,kBAAkB,MAAM,IAAI,CAACC,eAAe,CAACL;QACnD,IAAII,iBAAiB;YACjB,IAAI,CAACR,KAAK,GAAG,CAAC,uCAAuC,EAAEQ,gBAAgBD,UAAU,CAAC,CAAC,CAAC;YACpF,OAAOC;QACX;QACA,IAAI,CAACR,KAAK,GAAG;QAEb,kBAAkB;QAClB,MAAMU,gBAAgB,MAAM,IAAI,CAACC,iBAAiB,CAACP;QACnD,IAAIM,eAAe;YACf,IAAI,CAACV,KAAK,GAAG,CAAC,kCAAkC,EAAEU,cAAcE,GAAG,CAAC,CAAC,CAAC;YACtE,OAAOF;QACX;QACA,IAAI,CAACV,KAAK,GAAG;QAEb,wBAAwB;QACxB,MAAMa,sBAAsB,MAAM,IAAI,CAACC,qBAAqB,CAACV;QAC7D,IAAIS,qBAAqB;YACrB,IAAI,CAACb,KAAK,GAAG,CAAC,wCAAwC,EAAEa,oBAAoBD,GAAG,CAAC,CAAC,CAAC;YAClF,OAAOC;QACX;QACA,IAAI,CAACb,KAAK,GAAG;QAEb,4DAA4D;QAC5D,IAAI,CAACA,KAAK,GAAG;QACb,OAAO;YACHe,IAAI;YACJR,YAAY;YACZK,KAAKR;QACT;IACJ;IAEA;;KAEC,GACD,MAAMY,cAAcC,QAA0B,EAAoB;QAC9D,IAAI,CAACjB,KAAK,GAAG,CAAC,wBAAwB,EAAEiB,SAASF,IAAI,CAAC,aAAa,EAAEE,SAASV,UAAU,CAAC,MAAM,EAAEU,SAASL,GAAG,CAAC,CAAC,CAAC;QAChH,IAAIM,UAAU;QACd,IAAI;YACA,OAAQD,SAASF,IAAI;gBACjB;oBACIG,UAAU,MAAM,IAAI,CAACC,aAAa,CAACF,SAASV,UAAU;oBACtD;gBACJ;oBACIW,UAAU,MAAM,IAAI,CAACE,gBAAgB,CAACH,SAASV,UAAU;oBACzD;gBACJ;oBACIW,UAAU,MAAM,IAAI,CAACG,kBAAkB,CAACJ,SAASL,GAAG;oBACpD;gBACJ;oBACIM,UAAU,MAAM,IAAI,CAACI,sBAAsB,CAACL,SAASL,GAAG;oBACxD;gBACJ;oBACIM,UAAU;YAClB;QACJ,EAAE,OAAM;YACJA,UAAU;QACd;QACA,IAAI,CAAClB,KAAK,GAAG,CAAC,eAAe,EAAEkB,UAAU,cAAc,SAAS,KAAK,EAAED,SAASF,IAAI,EAAE;QACtF,OAAOG;IACX;IAEA,MAAcT,gBAAgBG,GAAW,EAAoC;QACzE,IAAI;YACA,MAAM,EAAEW,MAAM,EAAE,GAAG,MAAM1B,cAAc,WAAW;gBAC9C;gBAAO;gBAAQ;gBAAY;aAC9B;YAED,MAAM2B,QAAQC,KAAKC,KAAK,CAACH;YACzB,IAAI,CAACI,MAAMC,OAAO,CAACJ,QAAQ,OAAO;YAElC,KAAK,MAAMK,QAAQL,MAAO;gBACtB,IACIK,QACA,OAAOA,KAAKC,QAAQ,KAAK,YACzBD,KAAKC,QAAQ,KAAKlB,OAClBiB,KAAKE,OAAO,IAAI,MAClB;oBACE,OAAO;wBACHhB,IAAI;wBACJR,YAAYyB,OAAOH,KAAKE,OAAO;wBAC/BnB;oBACJ;gBACJ;YACJ;QACJ,EAAE,OAAM;QACJ,+DAA+D;QACnE;QACA,OAAO;IACX;IAEA,MAAcQ,iBAAiBa,MAAc,EAAoB;QAC7D,IAAI;YACA,MAAMpC,cAAc,WAAW;gBAAC;gBAAO;gBAAiB;gBAAaoC;aAAO;YAC5E,OAAO;QACX,EAAE,OAAM;YACJ,OAAO;QACX;IACJ;IAEA,MAAc3B,aAAaM,GAAW,EAAoC;QACtE,IAAI;YACA,MAAM,EAAEW,MAAM,EAAE,GAAG,MAAM1B,cAAc,QAAQ;gBAC3C;gBAAc;gBAAM;gBAAM;aAC7B;YAED,MAAMqC,QAAQX,OAAOY,IAAI,GAAGC,KAAK,CAAC;YAClC,KAAK,MAAMC,QAAQH,MAAO;gBACtB,IAAI,CAACG,KAAKF,IAAI,IAAI;gBAClB,MAAM,CAACG,SAAS/B,WAAW,GAAG8B,KAAKD,KAAK,CAAC;gBACzC,IAAIE,YAAY1B,OAAOL,YAAY;oBAC/B,OAAO;wBACHQ,IAAI;wBACJR;wBACAK;oBACJ;gBACJ;YACJ;QACJ,EAAE,OAAM;QACJ,yCAAyC;QAC7C;QACA,OAAO;IACX;IAEA,MAAcD,kBAAkBC,GAAW,EAAoC;QAC3E,IAAI;YACA,0DAA0D;YAC1D,IAAI,CAAC,MAAM,IAAI,CAAC2B,gBAAgB,CAAC,WAAW,OAAO;QACvD,EAAE,OAAM;YACJ,OAAO;QACX;QAEA,IAAI;YACA,MAAMC,aAAa5C,kBAAkBgB;YACrC,MAAM6B,SAAS,CAAC;;;;;gCAKI,EAAED,WAAW;;;;;;;MAOvC,CAAC;YAEK,MAAM,EAAEjB,MAAM,EAAE,GAAG,MAAM1B,cAAc,aAAa;gBAAC;gBAAM4C;aAAO;YAClE,IAAIlB,OAAOY,IAAI,OAAO,SAAS;gBAC3B,OAAO;oBACHpB,IAAI;oBACJR,YAAYK;oBACZA;gBACJ;YACJ;QACJ,EAAE,OAAM;QACJ,uBAAuB;QAC3B;QACA,OAAO;IACX;IAEA,MAAcE,sBAAsBF,GAAW,EAAoC;QAC/E,IAAI;YACA,mCAAmC;YACnC,IAAI,CAAC,MAAM,IAAI,CAAC2B,gBAAgB,CAAC,aAAa,OAAO;QACzD,EAAE,OAAM;YACJ,OAAO;QACX;QAEA,IAAI;YACA,MAAMC,aAAa5C,kBAAkBgB;YACrC,MAAM6B,SAAS,CAAC;;;;8BAIE,EAAED,WAAW;;;;;;MAMrC,CAAC;YAEK,MAAM,EAAEjB,MAAM,EAAE,GAAG,MAAM1B,cAAc,aAAa;gBAAC;gBAAM4C;aAAO;YAClE,IAAIlB,OAAOY,IAAI,OAAO,SAAS;gBAC3B,OAAO;oBACHpB,IAAI;oBACJR,YAAYK;oBACZA;gBACJ;YACJ;QACJ,EAAE,OAAM;QACJ,6BAA6B;QACjC;QACA,OAAO;IACX;IAEA,MAAc2B,iBAAiBG,IAAY,EAAoB;QAC3D,MAAM,EAAEnB,MAAM,EAAE,GAAG,MAAM1B,cAAc,MAAM;YAAC;YAAQ;SAAO;QAC7D,OAAO0B,OACFa,KAAK,CAAC,MACNO,GAAG,CAAC,CAACN,OAASA,KAAKF,IAAI,IACvBS,IAAI,CAAC,CAACC,UAAYA,YAAYH,QAAQG,QAAQC,QAAQ,CAAC,CAAC,CAAC,EAAEJ,MAAM;IAC1E;IAEA,MAAcvB,cAAcZ,UAAkB,EAAoB;QAC9D,IAAI;YACA,MAAMV,cAAc,QAAQ;gBAAC;gBAAiB;gBAAMU;aAAW;YAC/D,OAAO;QACX,EAAE,OAAM;YACJ,OAAO;QACX;IACJ;IAEA,MAAcc,mBAAmBT,GAAW,EAAoB;QAC5D,MAAM4B,aAAa5C,kBAAkBgB;QACrC,MAAM6B,SAAS,CAAC;;;;;;+BAMO,EAAED,WAAW;;;;;;;;KAQvC,CAAC;QACE,MAAM,EAAEjB,MAAM,EAAE,GAAG,MAAM1B,cAAc,aAAa;YAAC;YAAM4C;SAAO;QAClE,OAAOlB,OAAOY,IAAI,OAAO;IAC7B;IAEA,MAAcb,uBAAuBV,GAAW,EAAoB;QAChE,MAAM4B,aAAa5C,kBAAkBgB;QACrC,MAAM6B,SAAS,CAAC;;;;;6BAKK,EAAED,WAAW;;;;;;;;IAQtC,CAAC;QACG,MAAM,EAAEjB,MAAM,EAAE,GAAG,MAAM1B,cAAc,aAAa;YAAC;YAAM4C;SAAO;QAClE,OAAOlB,OAAOY,IAAI,OAAO;IAC7B;AACJ"}
@@ -16,6 +16,25 @@ export declare class TtyWriter {
16
16
  * @throws Error if terminal type is unsupported or send fails
17
17
  */
18
18
  static send(location: TerminalLocation, message: string): Promise<void>;
19
+ /**
20
+ * Send a single raw key (e.g. "1", "Enter", "Up") to the terminal as a
21
+ * keystroke — bypassing bracketed paste and without auto-appending Enter.
22
+ *
23
+ * Use this when the target TUI distinguishes between typed text and raw
24
+ * keypresses (e.g. an `AskUserQuestion` picker that selects on digit-key
25
+ * press, not on a pasted digit followed by Enter).
26
+ *
27
+ * - tmux: `tmux send-keys -t <id> <key>` — direct keystroke, no paste buffer.
28
+ * - WezTerm: `wezterm cli send-text --pane-id <id> --no-paste <key>`.
29
+ * - iTerm2 / Terminal.app: AppleScript via System Events. Requires
30
+ * Accessibility permissions.
31
+ */
32
+ static sendKey(location: TerminalLocation, key: string): Promise<void>;
33
+ private static sendKeyViaTmux;
34
+ private static sendKeyViaWezterm;
35
+ private static sendKeyViaITerm2;
36
+ private static sendKeyViaTerminalApp;
37
+ private static sendViaWezterm;
19
38
  private static sendViaTmux;
20
39
  private static execFileWithInput;
21
40
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"TtyWriter.d.ts","sourceRoot":"","sources":["../../src/terminal/TtyWriter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAMlE,qBAAa,SAAS;IAClB;;;;;;;;;;;;;;OAcG;WACU,IAAI,CAAC,QAAQ,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;mBAgBxD,WAAW;mBAWX,iBAAiB;IAiBtC;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,mBAAmB;mBAsBb,aAAa;mBAuBb,kBAAkB;CA0D1C"}
1
+ {"version":3,"file":"TtyWriter.d.ts","sourceRoot":"","sources":["../../src/terminal/TtyWriter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAmBlE,qBAAa,SAAS;IAClB;;;;;;;;;;;;;;OAcG;WACU,IAAI,CAAC,QAAQ,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkB7E;;;;;;;;;;;;OAYG;WACU,OAAO,CAAC,QAAQ,EAAE,gBAAgB,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;mBAkBvD,cAAc;mBAOd,iBAAiB;mBASjB,gBAAgB;mBAkChB,qBAAqB;mBA+BrB,cAAc;mBAuBd,WAAW;mBAWX,iBAAiB;IAiBtC;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,mBAAmB;mBAsBb,aAAa;mBAuBb,kBAAkB;CA0D1C"}
@@ -3,6 +3,15 @@ import { promisify } from 'util';
3
3
  import { TerminalType } from './TerminalFocusManager.js';
4
4
  import { escapeAppleScript } from '../utils/applescript.js';
5
5
  const execFileAsync = promisify(execFile);
6
+ /**
7
+ * Carriage return byte (0x0d). Sent as a fixed discrete argv element with
8
+ * `--no-paste` to deliver Enter literally (shell equivalent: $'\x0d').
9
+ */ const CARRIAGE_RETURN = '\x0d';
10
+ /**
11
+ * Escape byte (0x1b). Recognized by `sendKey` and translated to the
12
+ * backend-native representation (`Escape` for tmux, `key code 53` for
13
+ * AppleScript, the literal byte for WezTerm).
14
+ */ const ESCAPE_BYTE = '\x1b';
6
15
  export class TtyWriter {
7
16
  /**
8
17
  * Send a message as keyboard input to a terminal session.
@@ -22,14 +31,163 @@ export class TtyWriter {
22
31
  switch(location.type){
23
32
  case TerminalType.TMUX:
24
33
  return TtyWriter.sendViaTmux(location.identifier, message);
34
+ case TerminalType.WEZTERM:
35
+ return TtyWriter.sendViaWezterm(location.identifier, message);
25
36
  case TerminalType.ITERM2:
26
37
  return TtyWriter.sendViaITerm2(location.tty, message);
27
38
  case TerminalType.TERMINAL_APP:
28
39
  return TtyWriter.sendViaTerminalApp(location.tty, message);
29
40
  default:
30
- throw new Error(`Cannot send input: unsupported terminal type "${location.type}". ` + 'Supported: tmux, iTerm2, Terminal.app.');
41
+ throw new Error(`Cannot send input: unsupported terminal type "${location.type}". ` + 'Supported: tmux, WezTerm, iTerm2, Terminal.app.');
31
42
  }
32
43
  }
44
+ /**
45
+ * Send a single raw key (e.g. "1", "Enter", "Up") to the terminal as a
46
+ * keystroke — bypassing bracketed paste and without auto-appending Enter.
47
+ *
48
+ * Use this when the target TUI distinguishes between typed text and raw
49
+ * keypresses (e.g. an `AskUserQuestion` picker that selects on digit-key
50
+ * press, not on a pasted digit followed by Enter).
51
+ *
52
+ * - tmux: `tmux send-keys -t <id> <key>` — direct keystroke, no paste buffer.
53
+ * - WezTerm: `wezterm cli send-text --pane-id <id> --no-paste <key>`.
54
+ * - iTerm2 / Terminal.app: AppleScript via System Events. Requires
55
+ * Accessibility permissions.
56
+ */ static async sendKey(location, key) {
57
+ switch(location.type){
58
+ case TerminalType.TMUX:
59
+ return TtyWriter.sendKeyViaTmux(location.identifier, key);
60
+ case TerminalType.WEZTERM:
61
+ return TtyWriter.sendKeyViaWezterm(location.identifier, key);
62
+ case TerminalType.ITERM2:
63
+ return TtyWriter.sendKeyViaITerm2(location.tty, key);
64
+ case TerminalType.TERMINAL_APP:
65
+ return TtyWriter.sendKeyViaTerminalApp(location.tty, key);
66
+ default:
67
+ throw new Error(`Cannot send key: unsupported terminal type "${location.type}". ` + 'Supported: tmux, WezTerm, iTerm2, Terminal.app.');
68
+ }
69
+ }
70
+ static async sendKeyViaTmux(identifier, key) {
71
+ // tmux send-keys interprets named keys (Enter, Up, Escape, ...) and
72
+ // passes literals through. No bracketed paste, no auto-Enter.
73
+ const arg = key === ESCAPE_BYTE ? 'Escape' : key;
74
+ await execFileAsync('tmux', [
75
+ 'send-keys',
76
+ '-t',
77
+ identifier,
78
+ arg
79
+ ]);
80
+ }
81
+ static async sendKeyViaWezterm(paneId, key) {
82
+ // --no-paste delivers the key bytes literally outside bracketed-paste
83
+ // markers; the TUI sees a raw keystroke. For Esc (`\x1b`), wezterm
84
+ // accepts the byte directly.
85
+ await execFileAsync('wezterm', [
86
+ 'cli',
87
+ 'send-text',
88
+ '--pane-id',
89
+ paneId,
90
+ '--no-paste',
91
+ key
92
+ ]);
93
+ }
94
+ static async sendKeyViaITerm2(tty, key) {
95
+ // Focus the target session, then press the key via System Events so the
96
+ // inner TUI sees a raw keystroke (not a bracketed-paste text run).
97
+ const action = appleScriptKeyAction(key);
98
+ const script = `
99
+ tell application "iTerm"
100
+ set targetSession to missing value
101
+ repeat with w in windows
102
+ repeat with t in tabs of w
103
+ repeat with s in sessions of t
104
+ if tty of s is "${tty}" then
105
+ set targetSession to s
106
+ set frontmost of w to true
107
+ tell t to select
108
+ tell s to select
109
+ exit repeat
110
+ end if
111
+ end repeat
112
+ if targetSession is not missing value then exit repeat
113
+ end repeat
114
+ if targetSession is not missing value then exit repeat
115
+ end repeat
116
+ if targetSession is missing value then return "not_found"
117
+ activate
118
+ end tell
119
+ tell application "System Events" to ${action}
120
+ return "ok"`;
121
+ const { stdout } = await execFileAsync('osascript', [
122
+ '-e',
123
+ script
124
+ ]);
125
+ if (stdout.trim() !== 'ok') {
126
+ throw new Error(`iTerm2 session not found for TTY ${tty}`);
127
+ }
128
+ }
129
+ static async sendKeyViaTerminalApp(tty, key) {
130
+ const action = appleScriptKeyAction(key);
131
+ const script = `
132
+ tell application "Terminal"
133
+ set targetTab to missing value
134
+ set targetWindow to missing value
135
+ repeat with w in windows
136
+ repeat with i from 1 to count of tabs of w
137
+ set t to tab i of w
138
+ if tty of t is "${tty}" then
139
+ set targetTab to t
140
+ set targetWindow to w
141
+ exit repeat
142
+ end if
143
+ end repeat
144
+ if targetTab is not missing value then exit repeat
145
+ end repeat
146
+ if targetTab is missing value then return "not_found"
147
+ set selected of targetTab to true
148
+ set frontmost of targetWindow to true
149
+ activate
150
+ end tell
151
+ tell application "System Events" to ${action}
152
+ return "ok"`;
153
+ const { stdout } = await execFileAsync('osascript', [
154
+ '-e',
155
+ script
156
+ ]);
157
+ if (stdout.trim() !== 'ok') {
158
+ throw new Error(`Terminal.app tab not found for TTY ${tty}`);
159
+ }
160
+ }
161
+ static async sendViaWezterm(paneId, message) {
162
+ // Two explicit CLI calls, mirroring the text-then-Enter convention used
163
+ // by tmux / iTerm2 / Terminal.app so a bracketed-paste-aware TUI still
164
+ // sees Enter as a submit.
165
+ //
166
+ // Step 1 (text): write the message to stdin so prompt contents are not
167
+ // exposed through process arguments. execFile still spawns wezterm
168
+ // directly (no shell), so shell metacharacters remain inert.
169
+ // Step 2 (Enter): pass a fixed carriage return (0x0d) as a discrete
170
+ // argv element (the JS char '\x0d') with --no-paste, so the CR is
171
+ // delivered literally rather than wrapped in paste brackets. The
172
+ // equivalent shell command is:
173
+ // wezterm cli send-text --pane-id <id> --no-paste $'\x0d'
174
+ // (ANSI-C quoting, note the leading $).
175
+ await TtyWriter.execFileWithInput('wezterm', [
176
+ 'cli',
177
+ 'send-text',
178
+ '--pane-id',
179
+ paneId
180
+ ], message);
181
+ await new Promise((resolve)=>setTimeout(resolve, 150));
182
+ await execFileAsync('wezterm', [
183
+ 'cli',
184
+ 'send-text',
185
+ '--pane-id',
186
+ paneId,
187
+ '--no-paste',
188
+ CARRIAGE_RETURN
189
+ ]);
190
+ }
33
191
  static async sendViaTmux(identifier, message) {
34
192
  // Paste the message body using tmux bracketed paste, then send Enter as
35
193
  // a separate key so the inner TUI treats it as submission rather than
@@ -185,5 +343,13 @@ return "ok"`;
185
343
  }
186
344
  }
187
345
  }
346
+ /**
347
+ * AppleScript `keystroke` only delivers typeable characters; non-typeable
348
+ * keys (Esc, arrows, F-keys, …) must be sent via `key code <N>`. Add more
349
+ * mappings here as new special keys are needed.
350
+ */ function appleScriptKeyAction(key) {
351
+ if (key === ESCAPE_BYTE) return 'key code 53';
352
+ return `keystroke "${escapeAppleScript(key)}"`;
353
+ }
188
354
 
189
355
  //# sourceMappingURL=TtyWriter.js.map
@@ -1 +1 @@
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 // Paste the message body using tmux bracketed paste, then send Enter as\n // a separate key so the inner TUI treats it as submission rather than\n // pasted content.\n const bufferName = `ai-devkit-send-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;\n await TtyWriter.execFileWithInput('tmux', ['load-buffer', '-b', bufferName, '-'], message);\n await execFileAsync('tmux', ['paste-buffer', '-t', identifier, '-b', bufferName, '-p', '-d']);\n await new Promise((resolve) => setTimeout(resolve, 150));\n await execFileAsync('tmux', ['send-keys', '-t', identifier, 'Enter']);\n }\n\n private static async execFileWithInput(command: string, args: string[], input: string): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n const child = execFile(command, args, (error) => {\n if (error) {\n reject(error);\n return;\n }\n resolve();\n });\n if (!child.stdin) {\n reject(new Error(`Cannot write stdin to ${command}`));\n return;\n }\n child.stdin.end(input);\n });\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","bufferName","process","pid","Date","now","Math","random","toString","slice","execFileWithInput","Promise","resolve","setTimeout","command","args","input","reject","child","error","stdin","end","iterm2SessionScript","sessionCommand","escaped","textScript","stdout","textResult","trim","enterScript","enterResult"],"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\n/**\n * Carriage return byte (0x0d). Sent as a fixed discrete argv element with\n * `--no-paste` to deliver Enter literally (shell equivalent: $'\\x0d').\n */\nconst CARRIAGE_RETURN = '\\x0d';\n\n/**\n * Escape byte (0x1b). Recognized by `sendKey` and translated to the\n * backend-native representation (`Escape` for tmux, `key code 53` for\n * AppleScript, the literal byte for WezTerm).\n */\nconst ESCAPE_BYTE = '\\x1b';\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.WEZTERM:\n return TtyWriter.sendViaWezterm(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, WezTerm, iTerm2, Terminal.app.'\n );\n }\n }\n\n /**\n * Send a single raw key (e.g. \"1\", \"Enter\", \"Up\") to the terminal as a\n * keystroke — bypassing bracketed paste and without auto-appending Enter.\n *\n * Use this when the target TUI distinguishes between typed text and raw\n * keypresses (e.g. an `AskUserQuestion` picker that selects on digit-key\n * press, not on a pasted digit followed by Enter).\n *\n * - tmux: `tmux send-keys -t <id> <key>` — direct keystroke, no paste buffer.\n * - WezTerm: `wezterm cli send-text --pane-id <id> --no-paste <key>`.\n * - iTerm2 / Terminal.app: AppleScript via System Events. Requires\n * Accessibility permissions.\n */\n static async sendKey(location: TerminalLocation, key: string): Promise<void> {\n switch (location.type) {\n case TerminalType.TMUX:\n return TtyWriter.sendKeyViaTmux(location.identifier, key);\n case TerminalType.WEZTERM:\n return TtyWriter.sendKeyViaWezterm(location.identifier, key);\n case TerminalType.ITERM2:\n return TtyWriter.sendKeyViaITerm2(location.tty, key);\n case TerminalType.TERMINAL_APP:\n return TtyWriter.sendKeyViaTerminalApp(location.tty, key);\n default:\n throw new Error(\n `Cannot send key: unsupported terminal type \"${location.type}\". ` +\n 'Supported: tmux, WezTerm, iTerm2, Terminal.app.'\n );\n }\n }\n\n private static async sendKeyViaTmux(identifier: string, key: string): Promise<void> {\n // tmux send-keys interprets named keys (Enter, Up, Escape, ...) and\n // passes literals through. No bracketed paste, no auto-Enter.\n const arg = key === ESCAPE_BYTE ? 'Escape' : key;\n await execFileAsync('tmux', ['send-keys', '-t', identifier, arg]);\n }\n\n private static async sendKeyViaWezterm(paneId: string, key: string): Promise<void> {\n // --no-paste delivers the key bytes literally outside bracketed-paste\n // markers; the TUI sees a raw keystroke. For Esc (`\\x1b`), wezterm\n // accepts the byte directly.\n await execFileAsync('wezterm', [\n 'cli', 'send-text', '--pane-id', paneId, '--no-paste', key,\n ]);\n }\n\n private static async sendKeyViaITerm2(tty: string, key: string): Promise<void> {\n // Focus the target session, then press the key via System Events so the\n // inner TUI sees a raw keystroke (not a bracketed-paste text run).\n const action = appleScriptKeyAction(key);\n const script = `\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 set frontmost of w to true\n tell t to select\n tell s to select\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 activate\nend tell\ntell application \"System Events\" to ${action}\nreturn \"ok\"`;\n\n const { stdout } = await execFileAsync('osascript', ['-e', script]);\n if (stdout.trim() !== 'ok') {\n throw new Error(`iTerm2 session not found for TTY ${tty}`);\n }\n }\n\n private static async sendKeyViaTerminalApp(tty: string, key: string): Promise<void> {\n const action = appleScriptKeyAction(key);\n const script = `\ntell application \"Terminal\"\n set targetTab to missing value\n set targetWindow 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 set targetWindow to w\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 set selected of targetTab to true\n set frontmost of targetWindow to true\n activate\nend tell\ntell application \"System Events\" to ${action}\nreturn \"ok\"`;\n\n const { stdout } = await execFileAsync('osascript', ['-e', script]);\n if (stdout.trim() !== 'ok') {\n throw new Error(`Terminal.app tab not found for TTY ${tty}`);\n }\n }\n\n private static async sendViaWezterm(paneId: string, message: string): Promise<void> {\n // Two explicit CLI calls, mirroring the text-then-Enter convention used\n // by tmux / iTerm2 / Terminal.app so a bracketed-paste-aware TUI still\n // sees Enter as a submit.\n //\n // Step 1 (text): write the message to stdin so prompt contents are not\n // exposed through process arguments. execFile still spawns wezterm\n // directly (no shell), so shell metacharacters remain inert.\n // Step 2 (Enter): pass a fixed carriage return (0x0d) as a discrete\n // argv element (the JS char '\\x0d') with --no-paste, so the CR is\n // delivered literally rather than wrapped in paste brackets. The\n // equivalent shell command is:\n // wezterm cli send-text --pane-id <id> --no-paste $'\\x0d'\n // (ANSI-C quoting, note the leading $).\n await TtyWriter.execFileWithInput('wezterm', [\n 'cli', 'send-text', '--pane-id', paneId,\n ], message);\n await new Promise((resolve) => setTimeout(resolve, 150));\n await execFileAsync('wezterm', [\n 'cli', 'send-text', '--pane-id', paneId, '--no-paste', CARRIAGE_RETURN,\n ]);\n }\n\n private static async sendViaTmux(identifier: string, message: string): Promise<void> {\n // Paste the message body using tmux bracketed paste, then send Enter as\n // a separate key so the inner TUI treats it as submission rather than\n // pasted content.\n const bufferName = `ai-devkit-send-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;\n await TtyWriter.execFileWithInput('tmux', ['load-buffer', '-b', bufferName, '-'], message);\n await execFileAsync('tmux', ['paste-buffer', '-t', identifier, '-b', bufferName, '-p', '-d']);\n await new Promise((resolve) => setTimeout(resolve, 150));\n await execFileAsync('tmux', ['send-keys', '-t', identifier, 'Enter']);\n }\n\n private static async execFileWithInput(command: string, args: string[], input: string): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n const child = execFile(command, args, (error) => {\n if (error) {\n reject(error);\n return;\n }\n resolve();\n });\n if (!child.stdin) {\n reject(new Error(`Cannot write stdin to ${command}`));\n return;\n }\n child.stdin.end(input);\n });\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\n/**\n * AppleScript `keystroke` only delivers typeable characters; non-typeable\n * keys (Esc, arrows, F-keys, …) must be sent via `key code <N>`. Add more\n * mappings here as new special keys are needed.\n */\nfunction appleScriptKeyAction(key: string): string {\n if (key === ESCAPE_BYTE) return 'key code 53';\n return `keystroke \"${escapeAppleScript(key)}\"`;\n}\n"],"names":["execFile","promisify","TerminalType","escapeAppleScript","execFileAsync","CARRIAGE_RETURN","ESCAPE_BYTE","TtyWriter","send","location","message","type","TMUX","sendViaTmux","identifier","WEZTERM","sendViaWezterm","ITERM2","sendViaITerm2","tty","TERMINAL_APP","sendViaTerminalApp","Error","sendKey","key","sendKeyViaTmux","sendKeyViaWezterm","sendKeyViaITerm2","sendKeyViaTerminalApp","arg","paneId","action","appleScriptKeyAction","script","stdout","trim","execFileWithInput","Promise","resolve","setTimeout","bufferName","process","pid","Date","now","Math","random","toString","slice","command","args","input","reject","child","error","stdin","end","iterm2SessionScript","sessionCommand","escaped","textScript","textResult","enterScript","enterResult"],"mappings":"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"}