@creativeintelligence/abbie 0.1.5 → 0.1.7

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 (142) hide show
  1. package/bin/dev.js +1 -49
  2. package/bin/run.js +42 -49
  3. package/dist/cli/commands/login.js +26 -0
  4. package/dist/cli/commands/project/add.d.ts +0 -1
  5. package/dist/cli/commands/project/add.js +16 -52
  6. package/dist/cli/commands/project/list.js +13 -93
  7. package/dist/cli/commands/project/remove.d.ts +0 -2
  8. package/dist/cli/commands/project/remove.js +11 -28
  9. package/dist/cli/commands/session/list.js +3 -12
  10. package/dist/cli/commands/session/mark-done.js +1 -7
  11. package/dist/cli/commands/session/start.d.ts +0 -1
  12. package/dist/cli/commands/session/start.js +5 -7
  13. package/dist/lib/active-sessions.d.ts +0 -12
  14. package/dist/lib/active-sessions.js +6 -175
  15. package/dist/lib/project-path.d.ts +6 -0
  16. package/dist/lib/project-path.js +21 -0
  17. package/dist/lib.d.ts +1 -2
  18. package/dist/lib.js +2 -4
  19. package/oclif.manifest.json +2569 -6368
  20. package/package.json +1 -1
  21. package/dist/cli/commands/backlog/add.d.ts +0 -22
  22. package/dist/cli/commands/backlog/add.js +0 -65
  23. package/dist/cli/commands/backlog/claim.d.ts +0 -19
  24. package/dist/cli/commands/backlog/claim.js +0 -45
  25. package/dist/cli/commands/backlog/complete.d.ts +0 -18
  26. package/dist/cli/commands/backlog/complete.js +0 -42
  27. package/dist/cli/commands/backlog/list.d.ts +0 -20
  28. package/dist/cli/commands/backlog/list.js +0 -91
  29. package/dist/cli/commands/backlog/pick.d.ts +0 -18
  30. package/dist/cli/commands/backlog/pick.js +0 -42
  31. package/dist/cli/commands/backlog/sync.d.ts +0 -24
  32. package/dist/cli/commands/backlog/sync.js +0 -109
  33. package/dist/cli/commands/daemon.d.ts +0 -56
  34. package/dist/cli/commands/daemon.js +0 -1465
  35. package/dist/cli/commands/docs/lint.d.ts +0 -18
  36. package/dist/cli/commands/docs/lint.js +0 -82
  37. package/dist/cli/commands/docs/sync.d.ts +0 -19
  38. package/dist/cli/commands/docs/sync.js +0 -76
  39. package/dist/cli/commands/gc.d.ts +0 -29
  40. package/dist/cli/commands/gc.js +0 -211
  41. package/dist/cli/commands/index.d.ts +0 -36
  42. package/dist/cli/commands/index.js +0 -228
  43. package/dist/cli/commands/panes/broker.d.ts +0 -17
  44. package/dist/cli/commands/panes/broker.js +0 -57
  45. package/dist/cli/commands/panes/pipe-sink.d.ts +0 -17
  46. package/dist/cli/commands/panes/pipe-sink.js +0 -90
  47. package/dist/cli/commands/panes/snapshot.d.ts +0 -20
  48. package/dist/cli/commands/panes/snapshot.js +0 -125
  49. package/dist/cli/commands/preview/init.d.ts +0 -25
  50. package/dist/cli/commands/preview/init.js +0 -159
  51. package/dist/cli/commands/preview/sync.d.ts +0 -23
  52. package/dist/cli/commands/preview/sync.js +0 -144
  53. package/dist/cli/commands/preview/watch.d.ts +0 -24
  54. package/dist/cli/commands/preview/watch.js +0 -153
  55. package/dist/cli/commands/resource/acquire.d.ts +0 -21
  56. package/dist/cli/commands/resource/acquire.js +0 -90
  57. package/dist/cli/commands/resource/list.d.ts +0 -15
  58. package/dist/cli/commands/resource/list.js +0 -61
  59. package/dist/cli/commands/resource/release.d.ts +0 -18
  60. package/dist/cli/commands/resource/release.js +0 -50
  61. package/dist/cli/commands/resource/wait.d.ts +0 -21
  62. package/dist/cli/commands/resource/wait.js +0 -73
  63. package/dist/cli/commands/session/view.d.ts +0 -24
  64. package/dist/cli/commands/session/view.js +0 -145
  65. package/dist/cli/commands/start.d.ts +0 -37
  66. package/dist/cli/commands/start.js +0 -234
  67. package/dist/cli/commands/triage/claim.d.ts +0 -23
  68. package/dist/cli/commands/triage/claim.js +0 -186
  69. package/dist/cli/commands/triage/list.d.ts +0 -22
  70. package/dist/cli/commands/triage/list.js +0 -112
  71. package/dist/cli/commands/triage/next.d.ts +0 -18
  72. package/dist/cli/commands/triage/next.js +0 -63
  73. package/dist/cli/commands/triage/pull.d.ts +0 -19
  74. package/dist/cli/commands/triage/pull.js +0 -82
  75. package/dist/cli/commands/triage/stats.d.ts +0 -16
  76. package/dist/cli/commands/triage/stats.js +0 -69
  77. package/dist/cli/commands/tunnel/list.d.ts +0 -16
  78. package/dist/cli/commands/tunnel/list.js +0 -98
  79. package/dist/cli/commands/tunnel/start.d.ts +0 -24
  80. package/dist/cli/commands/tunnel/start.js +0 -107
  81. package/dist/cli/commands/tunnel/stop.d.ts +0 -20
  82. package/dist/cli/commands/tunnel/stop.js +0 -90
  83. package/dist/cli/commands/tunnel/url.d.ts +0 -21
  84. package/dist/cli/commands/tunnel/url.js +0 -70
  85. package/dist/cli/commands/windows/context.d.ts +0 -18
  86. package/dist/cli/commands/windows/context.js +0 -326
  87. package/dist/cli/commands/windows/focus.d.ts +0 -17
  88. package/dist/cli/commands/windows/focus.js +0 -103
  89. package/dist/cli/commands/windows/list.d.ts +0 -21
  90. package/dist/cli/commands/windows/list.js +0 -172
  91. package/dist/cli/commands/windows/map.d.ts +0 -17
  92. package/dist/cli/commands/windows/map.js +0 -168
  93. package/dist/cli/commands/windows/read.d.ts +0 -21
  94. package/dist/cli/commands/windows/read.js +0 -241
  95. package/dist/cli/commands/windows/search.d.ts +0 -24
  96. package/dist/cli/commands/windows/search.js +0 -171
  97. package/dist/cli/commands/windows/show.d.ts +0 -19
  98. package/dist/cli/commands/windows/show.js +0 -165
  99. package/dist/cli/commands/windows/watch.d.ts +0 -19
  100. package/dist/cli/commands/windows/watch.js +0 -241
  101. package/dist/lib/managed-session.d.ts +0 -27
  102. package/dist/lib/managed-session.js +0 -105
  103. package/dist/lib/panes/broker.d.ts +0 -130
  104. package/dist/lib/panes/broker.js +0 -97
  105. package/dist/lib/panes/index.d.ts +0 -2
  106. package/dist/lib/panes/index.js +0 -1
  107. package/dist/lib/panes/server.d.ts +0 -17
  108. package/dist/lib/panes/server.js +0 -308
  109. package/dist/lib/preview/manager.d.ts +0 -77
  110. package/dist/lib/preview/manager.js +0 -369
  111. package/dist/lib/preview/schema.d.ts +0 -2
  112. package/dist/lib/preview/schema.js +0 -32
  113. package/dist/lib/preview/sprite.d.ts +0 -85
  114. package/dist/lib/preview/sprite.js +0 -321
  115. package/dist/lib/preview/watcher.d.ts +0 -63
  116. package/dist/lib/preview/watcher.js +0 -185
  117. package/dist/lib/project-identity.d.ts +0 -16
  118. package/dist/lib/project-identity.js +0 -75
  119. package/dist/lib/tmux/bridge.d.ts +0 -133
  120. package/dist/lib/tmux/bridge.js +0 -315
  121. package/dist/lib/tmux/context.d.ts +0 -82
  122. package/dist/lib/tmux/context.js +0 -239
  123. package/dist/lib/tmux/index.d.ts +0 -8
  124. package/dist/lib/tmux/index.js +0 -11
  125. package/dist/lib/tmux/map.d.ts +0 -57
  126. package/dist/lib/tmux/map.js +0 -198
  127. package/dist/lib/tmux/panes.d.ts +0 -27
  128. package/dist/lib/tmux/panes.js +0 -151
  129. package/dist/lib/tmux/redaction.d.ts +0 -57
  130. package/dist/lib/tmux/redaction.js +0 -152
  131. package/dist/lib/web/analytics.d.ts +0 -63
  132. package/dist/lib/web/analytics.js +0 -168
  133. package/dist/lib/web/server.d.ts +0 -26
  134. package/dist/lib/web/server.js +0 -697
  135. package/dist/lib/web/tmux-bridge.d.ts +0 -7
  136. package/dist/lib/web/tmux-bridge.js +0 -7
  137. package/dist/lib/windows/index.d.ts +0 -3
  138. package/dist/lib/windows/index.js +0 -2
  139. package/dist/lib/windows/inventory.d.ts +0 -21
  140. package/dist/lib/windows/inventory.js +0 -263
  141. package/dist/lib/windows/types.d.ts +0 -46
  142. package/dist/lib/windows/types.js +0 -1
@@ -1,239 +0,0 @@
1
- /**
2
- * tmux context builder.
3
- * Builds a structured snapshot of the tmux workspace for agent injection.
4
- */
5
- import * as bridge from "./bridge.js";
6
- import { redactContent, shouldFilterCommand, shouldFilterPath, shouldFilterSession, shouldFilterWindow, } from "./redaction.js";
7
- /**
8
- * Allowed commands for pane capture.
9
- * Only capture panes running these commands.
10
- */
11
- const ALLOWED_COMMANDS = new Set(["nvim", "vim", "vi", "zsh", "bash", "fish", "sh"]);
12
- /**
13
- * Build a structured tmux context.
14
- */
15
- export async function buildContext(options = {}) {
16
- const { maxBytes = 200000, maxTotalLines = 4000, maxSessions, maxWindowsPerSession, maxPanesPerWindow, redact = true, redactMode = "balanced", filter = {}, includeAllCommands = false, } = options;
17
- // Check if tmux is running
18
- const tmuxRunning = await bridge.isRunning();
19
- if (!tmuxRunning) {
20
- return {
21
- generatedAt: new Date().toISOString(),
22
- tmuxRunning: false,
23
- truncated: false,
24
- sessions: [],
25
- };
26
- }
27
- // Get all sessions
28
- let sessions = await bridge.listSessions();
29
- // Apply session filters
30
- sessions = sessions.filter((s) => !shouldFilterSession(s.name, filter));
31
- // Apply maxSessions limit
32
- if (maxSessions !== undefined && sessions.length > maxSessions) {
33
- sessions = sessions.slice(0, maxSessions);
34
- }
35
- const result = {
36
- generatedAt: new Date().toISOString(),
37
- tmuxRunning: true,
38
- truncated: false,
39
- sessions: [],
40
- };
41
- let totalBytes = 0;
42
- let totalLines = 0;
43
- let truncated = false;
44
- let truncationReason;
45
- for (const session of sessions) {
46
- // Check byte limit
47
- if (totalBytes >= maxBytes) {
48
- truncated = true;
49
- truncationReason = "max-bytes";
50
- break;
51
- }
52
- // Check line limit
53
- if (totalLines >= maxTotalLines) {
54
- truncated = true;
55
- truncationReason = "max-lines";
56
- break;
57
- }
58
- let windows = await bridge.listWindows(session.name);
59
- // Apply window filters
60
- windows = windows.filter((w) => !shouldFilterWindow(w.name, filter));
61
- // Apply maxWindowsPerSession limit
62
- if (maxWindowsPerSession !== undefined && windows.length > maxWindowsPerSession) {
63
- windows = windows.slice(0, maxWindowsPerSession);
64
- }
65
- const sessionContext = {
66
- name: session.name,
67
- attached: session.attached,
68
- windows: [],
69
- };
70
- for (const window of windows) {
71
- // Check limits again
72
- if (totalBytes >= maxBytes || totalLines >= maxTotalLines) {
73
- truncated = true;
74
- truncationReason = totalBytes >= maxBytes ? "max-bytes" : "max-lines";
75
- break;
76
- }
77
- let panes = await bridge.listPanes(window.index, session.name);
78
- // Filter by allowed commands unless includeAllCommands is true
79
- if (!includeAllCommands) {
80
- panes = panes.filter((p) => ALLOWED_COMMANDS.has(p.currentCommand));
81
- }
82
- // Apply path and command filters
83
- panes = panes.filter((p) => {
84
- if (shouldFilterPath(p.currentPath, filter))
85
- return false;
86
- if (shouldFilterCommand(p.currentCommand, filter))
87
- return false;
88
- return true;
89
- });
90
- // Apply maxPanesPerWindow limit
91
- if (maxPanesPerWindow !== undefined && panes.length > maxPanesPerWindow) {
92
- panes = panes.slice(0, maxPanesPerWindow);
93
- }
94
- // Skip windows with no eligible panes
95
- if (panes.length === 0) {
96
- continue;
97
- }
98
- const windowContext = {
99
- id: window.id,
100
- index: window.index,
101
- name: window.name,
102
- active: window.active,
103
- paneCount: window.panes,
104
- panes: [],
105
- };
106
- for (const pane of panes) {
107
- // Check limits
108
- if (totalBytes >= maxBytes || totalLines >= maxTotalLines) {
109
- truncated = true;
110
- truncationReason = totalBytes >= maxBytes ? "max-bytes" : "max-lines";
111
- break;
112
- }
113
- // Capture the visible screen (based on pane height)
114
- const capturedContent = await bridge.capturePane(window.index, pane.index, {
115
- session: session.name,
116
- lines: pane.height,
117
- });
118
- let screenContent = capturedContent;
119
- let wasRedacted = false;
120
- // Apply redaction
121
- if (redact) {
122
- const redactionResult = redactContent(capturedContent, redactMode);
123
- screenContent = redactionResult.content;
124
- wasRedacted = redactionResult.wasRedacted;
125
- }
126
- // Calculate bytes and lines
127
- const contentBytes = Buffer.byteLength(screenContent, "utf-8");
128
- const contentLines = screenContent.split("\n").length;
129
- // Check if adding this would exceed limits
130
- if (totalBytes + contentBytes > maxBytes) {
131
- // Truncate content to fit
132
- const remainingBytes = maxBytes - totalBytes;
133
- screenContent = truncateToBytes(screenContent, remainingBytes);
134
- truncated = true;
135
- truncationReason = "max-bytes";
136
- }
137
- if (totalLines + contentLines > maxTotalLines) {
138
- // Truncate lines to fit
139
- const remainingLines = maxTotalLines - totalLines;
140
- const lines = screenContent.split("\n");
141
- screenContent = lines.slice(0, remainingLines).join("\n");
142
- truncated = true;
143
- truncationReason = "max-lines";
144
- }
145
- totalBytes += Buffer.byteLength(screenContent, "utf-8");
146
- totalLines += screenContent.split("\n").length;
147
- const paneContext = {
148
- index: pane.index,
149
- active: pane.active,
150
- cwd: pane.currentPath,
151
- cmd: pane.currentCommand,
152
- size: `${pane.width}x${pane.height}`,
153
- screen: screenContent,
154
- redacted: wasRedacted,
155
- };
156
- windowContext.panes.push(paneContext);
157
- }
158
- if (windowContext.panes.length > 0) {
159
- sessionContext.windows.push(windowContext);
160
- }
161
- }
162
- if (sessionContext.windows.length > 0) {
163
- result.sessions.push(sessionContext);
164
- }
165
- }
166
- result.truncated = truncated;
167
- if (truncationReason) {
168
- result.truncationReason = truncationReason;
169
- }
170
- return result;
171
- }
172
- /**
173
- * Truncate a string to a maximum number of bytes.
174
- */
175
- function truncateToBytes(str, maxBytes) {
176
- const buf = Buffer.from(str, "utf-8");
177
- if (buf.length <= maxBytes) {
178
- return str;
179
- }
180
- // Binary search for the right length
181
- let low = 0;
182
- let high = str.length;
183
- while (low < high) {
184
- const mid = Math.ceil((low + high) / 2);
185
- const slice = str.slice(0, mid);
186
- if (Buffer.byteLength(slice, "utf-8") <= maxBytes) {
187
- low = mid;
188
- }
189
- else {
190
- high = mid - 1;
191
- }
192
- }
193
- return str.slice(0, low);
194
- }
195
- /**
196
- * Escape XML special characters.
197
- */
198
- function escapeXml(str) {
199
- return str
200
- .replace(/&/g, "&amp;")
201
- .replace(/</g, "&lt;")
202
- .replace(/>/g, "&gt;")
203
- .replace(/"/g, "&quot;")
204
- .replace(/'/g, "&apos;");
205
- }
206
- /**
207
- * Serialize context to XML.
208
- */
209
- export function toXml(context) {
210
- const lines = [];
211
- lines.push(`<tmux_context generated_at="${escapeXml(context.generatedAt)}" tmux_running="${context.tmuxRunning}" truncated="${context.truncated}"${context.truncationReason ? ` truncation_reason="${escapeXml(context.truncationReason)}"` : ""}>`);
212
- for (const session of context.sessions) {
213
- lines.push(` <session name="${escapeXml(session.name)}" attached="${session.attached}">`);
214
- for (const window of session.windows) {
215
- lines.push(` <window id="${escapeXml(window.id)}" index="${window.index}" name="${escapeXml(window.name)}" active="${window.active}" panes="${window.paneCount}">`);
216
- for (const pane of window.panes) {
217
- lines.push(` <pane index="${pane.index}" active="${pane.active}" cwd="${escapeXml(pane.cwd)}" cmd="${escapeXml(pane.cmd)}" size="${pane.size}">`);
218
- lines.push(` <screen redacted="${pane.redacted}">`);
219
- // Indent screen content and escape
220
- const screenLines = pane.screen.split("\n");
221
- for (const screenLine of screenLines) {
222
- lines.push(` ${escapeXml(screenLine)}`);
223
- }
224
- lines.push(" </screen>");
225
- lines.push(" </pane>");
226
- }
227
- lines.push(" </window>");
228
- }
229
- lines.push(" </session>");
230
- }
231
- lines.push("</tmux_context>");
232
- return lines.join("\n");
233
- }
234
- /**
235
- * Serialize context to JSON.
236
- */
237
- export function toJson(context) {
238
- return JSON.stringify(context, null, 2);
239
- }
@@ -1,8 +0,0 @@
1
- /**
2
- * tmux module - bridge and context building for agent prompts.
3
- */
4
- export * from "./bridge.js";
5
- export { buildContext, type ContextOptions, type PaneContext, type SessionContext, type TmuxContext, toJson, toXml, type WindowContext, } from "./context.js";
6
- export { type AgentPaneMapping, correlateAgentsToPanes, detectAgentType, extractProjectFromPath, getAllPanes, getFocusedPane, isAgentProcess, type MapResult, type PaneLocation, pathsMatch, type UnmappedPane, } from "./map.js";
7
- export { type FilterConfig, isSensitiveCommand, type RedactMode, redactContent, redactLine, redactTokens, shouldFilterCommand, shouldFilterPath, shouldFilterSession, shouldFilterWindow, shouldRedactLine, } from "./redaction.js";
8
- //# sourceMappingURL=index.d.ts.map
@@ -1,11 +0,0 @@
1
- /**
2
- * tmux module - bridge and context building for agent prompts.
3
- */
4
- // Core bridge primitives
5
- export * from "./bridge.js";
6
- // Context building
7
- export { buildContext, toJson, toXml, } from "./context.js";
8
- // Agent ↔ pane mapping
9
- export { correlateAgentsToPanes, detectAgentType, extractProjectFromPath, getAllPanes, getFocusedPane, isAgentProcess, pathsMatch, } from "./map.js";
10
- // Redaction utilities
11
- export { isSensitiveCommand, redactContent, redactLine, redactTokens, shouldFilterCommand, shouldFilterPath, shouldFilterSession, shouldFilterWindow, shouldRedactLine, } from "./redaction.js";
@@ -1,57 +0,0 @@
1
- /**
2
- * Agent ↔ tmux pane correlation.
3
- * Maps active agent sessions to their tmux panes.
4
- */
5
- import { type ActiveSession } from "../active-sessions.js";
6
- export interface PaneLocation {
7
- sessionName: string;
8
- windowId: string;
9
- windowIndex: number;
10
- windowName: string;
11
- paneIndex: number;
12
- target: string;
13
- cmd: string;
14
- cwd: string;
15
- }
16
- export interface AgentPaneMapping {
17
- session: ActiveSession;
18
- pane: PaneLocation | null;
19
- matchType: "process" | "cwd" | "none";
20
- }
21
- export interface UnmappedPane extends PaneLocation {
22
- active: boolean;
23
- }
24
- export interface MapResult {
25
- mappings: AgentPaneMapping[];
26
- unmappedPanes: UnmappedPane[];
27
- }
28
- /**
29
- * Get all panes across all tmux sessions.
30
- */
31
- export declare function getAllPanes(): Promise<PaneLocation[]>;
32
- /**
33
- * Check if a pane is running an agent CLI.
34
- */
35
- export declare function isAgentProcess(cmd: string): boolean;
36
- /**
37
- * Detect which agent type a process is.
38
- */
39
- export declare function detectAgentType(cmd: string): string | null;
40
- /**
41
- * Check if two paths match (handles trailing slashes, ~).
42
- */
43
- export declare function pathsMatch(path1: string, path2: string): boolean;
44
- /**
45
- * Correlate active agent sessions with tmux panes.
46
- */
47
- export declare function correlateAgentsToPanes(): Promise<MapResult>;
48
- /**
49
- * Get the currently focused tmux pane.
50
- */
51
- export declare function getFocusedPane(): Promise<PaneLocation | null>;
52
- /**
53
- * Extract project name from a path.
54
- * Matches ~/Developer/apps/{project} or ~/Developer/utils/{project} patterns.
55
- */
56
- export declare function extractProjectFromPath(path: string): string | null;
57
- //# sourceMappingURL=map.d.ts.map
@@ -1,198 +0,0 @@
1
- /**
2
- * Agent ↔ tmux pane correlation.
3
- * Maps active agent sessions to their tmux panes.
4
- */
5
- import { getActiveSessionManager } from "../active-sessions.js";
6
- import * as bridge from "./bridge.js";
7
- /** Agent CLI process names to detect */
8
- const AGENT_PROCESSES = new Set([
9
- "claude",
10
- "codex",
11
- "copilot",
12
- "gemini",
13
- "pi",
14
- "opencode",
15
- "droid",
16
- ]);
17
- /**
18
- * Get all panes across all tmux sessions.
19
- */
20
- export async function getAllPanes() {
21
- const panes = [];
22
- const sessions = await bridge.listSessions();
23
- for (const session of sessions) {
24
- const windows = await bridge.listWindows(session.name);
25
- for (const window of windows) {
26
- const windowPanes = await bridge.listPanes(window.index, session.name);
27
- for (const pane of windowPanes) {
28
- panes.push({
29
- sessionName: session.name,
30
- windowId: window.id,
31
- windowIndex: window.index,
32
- windowName: window.name,
33
- paneIndex: pane.index,
34
- target: `${session.name}:${window.id}.${pane.index}`,
35
- cmd: pane.currentCommand,
36
- cwd: pane.currentPath,
37
- });
38
- }
39
- }
40
- }
41
- return panes;
42
- }
43
- /**
44
- * Check if a pane is running an agent CLI.
45
- */
46
- export function isAgentProcess(cmd) {
47
- const lowerCmd = cmd.toLowerCase();
48
- return AGENT_PROCESSES.has(lowerCmd);
49
- }
50
- /**
51
- * Detect which agent type a process is.
52
- */
53
- export function detectAgentType(cmd) {
54
- const lowerCmd = cmd.toLowerCase();
55
- if (AGENT_PROCESSES.has(lowerCmd)) {
56
- return lowerCmd;
57
- }
58
- return null;
59
- }
60
- /**
61
- * Normalize a path for comparison.
62
- */
63
- function normalizePath(path) {
64
- // Remove trailing slashes, resolve ~ to home
65
- let normalized = path.replace(/\/+$/, "");
66
- if (normalized.startsWith("~")) {
67
- const home = process.env.HOME || "/Users/luke";
68
- normalized = normalized.replace(/^~/, home);
69
- }
70
- return normalized;
71
- }
72
- /**
73
- * Check if two paths match (handles trailing slashes, ~).
74
- */
75
- export function pathsMatch(path1, path2) {
76
- return normalizePath(path1) === normalizePath(path2);
77
- }
78
- /**
79
- * Correlate active agent sessions with tmux panes.
80
- */
81
- export async function correlateAgentsToPanes() {
82
- const manager = getActiveSessionManager();
83
- const activeSessions = await manager.listAsync({ status: "running" });
84
- const allPanes = await getAllPanes();
85
- const mappings = [];
86
- const mappedPaneTargets = new Set();
87
- for (const session of activeSessions) {
88
- let matchedPane = null;
89
- let matchType = "none";
90
- // Strategy 1: Find pane running the agent CLI with matching cwd
91
- for (const pane of allPanes) {
92
- if (isAgentProcess(pane.cmd) && session.cwd && pathsMatch(pane.cwd, session.cwd)) {
93
- matchedPane = pane;
94
- matchType = "process";
95
- break;
96
- }
97
- }
98
- // Strategy 2: If no process match, try cwd match only
99
- if (!matchedPane && session.cwd) {
100
- for (const pane of allPanes) {
101
- if (pathsMatch(pane.cwd, session.cwd)) {
102
- matchedPane = pane;
103
- matchType = "cwd";
104
- break;
105
- }
106
- }
107
- }
108
- mappings.push({
109
- session,
110
- pane: matchedPane,
111
- matchType,
112
- });
113
- if (matchedPane) {
114
- mappedPaneTargets.add(matchedPane.target);
115
- }
116
- }
117
- // Find unmapped panes (excluding agent processes that weren't matched)
118
- const unmappedPanes = [];
119
- for (const pane of allPanes) {
120
- if (!mappedPaneTargets.has(pane.target)) {
121
- // Get active status
122
- const sessions = await bridge.listSessions();
123
- const tmuxSession = sessions.find((s) => s.name === pane.sessionName);
124
- let isActive = false;
125
- if (tmuxSession) {
126
- const windows = await bridge.listWindows(pane.sessionName);
127
- const window = windows.find((w) => w.id === pane.windowId);
128
- if (window) {
129
- const panes = await bridge.listPanes(window.index, pane.sessionName);
130
- const p = panes.find((pp) => pp.index === pane.paneIndex);
131
- isActive = p?.active ?? false;
132
- }
133
- }
134
- unmappedPanes.push({
135
- ...pane,
136
- active: isActive,
137
- });
138
- }
139
- }
140
- return { mappings, unmappedPanes };
141
- }
142
- /**
143
- * Get the currently focused tmux pane.
144
- */
145
- export async function getFocusedPane() {
146
- const running = await bridge.isRunning();
147
- if (!running)
148
- return null;
149
- // Get all sessions and find the attached one
150
- const sessions = await bridge.listSessions();
151
- const attachedSession = sessions.find((s) => s.attached);
152
- if (!attachedSession)
153
- return null;
154
- // Get windows and find the active one
155
- const windows = await bridge.listWindows(attachedSession.name);
156
- const activeWindow = windows.find((w) => w.active);
157
- if (!activeWindow)
158
- return null;
159
- // Get panes and find the active one
160
- const panes = await bridge.listPanes(activeWindow.index, attachedSession.name);
161
- const activePane = panes.find((p) => p.active);
162
- if (!activePane)
163
- return null;
164
- return {
165
- sessionName: attachedSession.name,
166
- windowId: activeWindow.id,
167
- windowIndex: activeWindow.index,
168
- windowName: activeWindow.name,
169
- paneIndex: activePane.index,
170
- target: `${attachedSession.name}:${activeWindow.id}.${activePane.index}`,
171
- cmd: activePane.currentCommand,
172
- cwd: activePane.currentPath,
173
- };
174
- }
175
- /**
176
- * Extract project name from a path.
177
- * Matches ~/Developer/apps/{project} or ~/Developer/utils/{project} patterns.
178
- */
179
- export function extractProjectFromPath(path) {
180
- const normalized = normalizePath(path);
181
- const home = process.env.HOME || "/Users/luke";
182
- // Pattern: ~/Developer/apps/{project}/...
183
- const appsMatch = normalized.match(new RegExp(`^${home}/Developer/apps/([^/]+)`));
184
- if (appsMatch) {
185
- return appsMatch[1];
186
- }
187
- // Pattern: ~/Developer/utils/{project}/...
188
- const utilsMatch = normalized.match(new RegExp(`^${home}/Developer/utils/([^/]+)`));
189
- if (utilsMatch) {
190
- return utilsMatch[1];
191
- }
192
- // Pattern: ~/Developer/{project}/...
193
- const devMatch = normalized.match(new RegExp(`^${home}/Developer/([^/]+)`));
194
- if (devMatch) {
195
- return devMatch[1];
196
- }
197
- return null;
198
- }
@@ -1,27 +0,0 @@
1
- export type PaneMetrics = {
2
- paneId: string;
3
- cols: number;
4
- rows: number;
5
- cursorX: number;
6
- cursorY: number;
7
- cursorFlag: number;
8
- title: string;
9
- };
10
- export declare function getPaneMetrics(paneId: string): Promise<PaneMetrics | null>;
11
- export declare function capturePaneScreen(paneId: string, opts: {
12
- ansi: boolean;
13
- rows: number;
14
- cursor?: {
15
- x: number;
16
- y: number;
17
- visible: boolean;
18
- };
19
- }): Promise<string>;
20
- export declare function ensurePipePane(paneId: string, shellCommand: string, opts: {
21
- onlyIfUnset: boolean;
22
- bidirectional: boolean;
23
- }): Promise<{
24
- attached: boolean;
25
- error?: string;
26
- }>;
27
- //# sourceMappingURL=panes.d.ts.map
@@ -1,151 +0,0 @@
1
- import { spawn } from "node:child_process";
2
- function isCsiFinalByte(code) {
3
- return code >= 0x40 && code <= 0x7e;
4
- }
5
- function scanAnsiEscape(input, start) {
6
- if (input[start] !== "\u001b")
7
- return start;
8
- const next = input[start + 1];
9
- // CSI: ESC [ ... final
10
- if (next === "[") {
11
- let i = start + 2;
12
- for (; i < input.length; i++) {
13
- const code = input.charCodeAt(i);
14
- if (isCsiFinalByte(code))
15
- return i + 1;
16
- }
17
- return input.length;
18
- }
19
- // OSC: ESC ] ... BEL or ESC \
20
- if (next === "]") {
21
- let i = start + 2;
22
- for (; i < input.length; i++) {
23
- const code = input.charCodeAt(i);
24
- if (code === 0x07 /* BEL */)
25
- return i + 1;
26
- if (input[i] === "\u001b" && input[i + 1] === "\\")
27
- return i + 2;
28
- }
29
- return input.length;
30
- }
31
- return Math.min(input.length, start + 2);
32
- }
33
- function nextCodePoint(input, index) {
34
- const cp = input.codePointAt(index) ?? 0;
35
- const size = cp > 0xffff ? 2 : 1;
36
- return { cp, size };
37
- }
38
- function markCursorAnsi(line, cursorX) {
39
- // Best-effort: wrap the cursor cell with reverse-video toggles (7/27).
40
- const x = Math.max(0, cursorX);
41
- let out = "";
42
- let visible = 0;
43
- let i = 0;
44
- while (i < line.length) {
45
- if (line[i] === "\u001b") {
46
- const end = scanAnsiEscape(line, i);
47
- out += line.slice(i, end);
48
- i = end;
49
- continue;
50
- }
51
- const { cp, size } = nextCodePoint(line, i);
52
- const ch = String.fromCodePoint(cp);
53
- if (visible === x)
54
- out += "\u001b[7m";
55
- out += ch;
56
- if (visible === x)
57
- out += "\u001b[27m";
58
- visible += 1;
59
- i += size;
60
- }
61
- // If cursor is past EOL, render a reversed space.
62
- if (x >= visible) {
63
- out += "\u001b[7m \u001b[27m";
64
- }
65
- return out;
66
- }
67
- function runTmuxRaw(args) {
68
- return new Promise((resolve, reject) => {
69
- const proc = spawn("tmux", args, { stdio: ["ignore", "pipe", "pipe"] });
70
- let stdout = "";
71
- let stderr = "";
72
- proc.stdout.on("data", (d) => {
73
- stdout += d.toString("utf8");
74
- });
75
- proc.stderr.on("data", (d) => {
76
- stderr += d.toString("utf8");
77
- });
78
- proc.on("close", (code) => {
79
- if (code === 0)
80
- resolve(stdout);
81
- else
82
- reject(new Error(stderr.trim() || `tmux ${args.join(" ")} exited ${code}`));
83
- });
84
- proc.on("error", reject);
85
- });
86
- }
87
- export async function getPaneMetrics(paneId) {
88
- try {
89
- const out = await runTmuxRaw([
90
- "display-message",
91
- "-p",
92
- "-t",
93
- paneId,
94
- "#{pane_width}\t#{pane_height}\t#{cursor_x}\t#{cursor_y}\t#{cursor_flag}\t#{pane_title}",
95
- ]);
96
- const [colsRaw, rowsRaw, cursorXRaw, cursorYRaw, cursorFlagRaw, titleRaw] = out
97
- .trimEnd()
98
- .split("\t");
99
- const cols = Number.parseInt(colsRaw ?? "", 10);
100
- const rows = Number.parseInt(rowsRaw ?? "", 10);
101
- const cursorX = Number.parseInt(cursorXRaw ?? "", 10);
102
- const cursorY = Number.parseInt(cursorYRaw ?? "", 10);
103
- const cursorFlag = Number.parseInt(cursorFlagRaw ?? "", 10);
104
- const title = titleRaw ?? "";
105
- if (!Number.isFinite(cols) || !Number.isFinite(rows))
106
- return null;
107
- return { paneId, cols, rows, cursorX, cursorY, cursorFlag, title };
108
- }
109
- catch {
110
- return null;
111
- }
112
- }
113
- export async function capturePaneScreen(paneId, opts) {
114
- const rows = Math.max(1, opts.rows);
115
- const args = ["capture-pane", "-t", paneId, "-p"];
116
- if (opts.ansi)
117
- args.push("-e");
118
- args.push("-S", `-${rows}`);
119
- try {
120
- // Preserve all whitespace; do not trim (important for full-screen apps).
121
- const raw = await runTmuxRaw(args);
122
- const cursor = opts.cursor;
123
- if (!cursor || !cursor.visible)
124
- return raw;
125
- const lines = raw.replace(/\n$/, "").split("\n");
126
- if (cursor.y < 0 || cursor.y >= lines.length)
127
- return raw;
128
- const target = lines[cursor.y] ?? "";
129
- lines[cursor.y] = opts.ansi ? markCursorAnsi(target, cursor.x) : target;
130
- return `${lines.join("\n")}\n`;
131
- }
132
- catch {
133
- return "";
134
- }
135
- }
136
- export async function ensurePipePane(paneId, shellCommand, opts) {
137
- const args = ["pipe-pane", "-t", paneId];
138
- if (opts.onlyIfUnset)
139
- args.push("-o");
140
- if (opts.bidirectional)
141
- args.push("-I", "-O");
142
- // If neither -I nor -O are specified, tmux defaults to -O (stdin connected).
143
- args.push(shellCommand);
144
- try {
145
- await runTmuxRaw(args);
146
- return { attached: true };
147
- }
148
- catch (err) {
149
- return { attached: false, error: err instanceof Error ? err.message : String(err) };
150
- }
151
- }