@creativeintelligence/abbie 0.1.6 → 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 (141) hide show
  1. package/bin/dev.js +1 -49
  2. package/bin/run.js +42 -49
  3. package/dist/cli/commands/project/add.d.ts +0 -1
  4. package/dist/cli/commands/project/add.js +16 -52
  5. package/dist/cli/commands/project/list.js +13 -93
  6. package/dist/cli/commands/project/remove.d.ts +0 -2
  7. package/dist/cli/commands/project/remove.js +11 -28
  8. package/dist/cli/commands/session/list.js +3 -12
  9. package/dist/cli/commands/session/mark-done.js +1 -7
  10. package/dist/cli/commands/session/start.d.ts +0 -1
  11. package/dist/cli/commands/session/start.js +5 -7
  12. package/dist/lib/active-sessions.d.ts +0 -12
  13. package/dist/lib/active-sessions.js +6 -175
  14. package/dist/lib/project-path.d.ts +6 -0
  15. package/dist/lib/project-path.js +21 -0
  16. package/dist/lib.d.ts +1 -2
  17. package/dist/lib.js +2 -4
  18. package/oclif.manifest.json +2569 -6368
  19. package/package.json +1 -1
  20. package/dist/cli/commands/backlog/add.d.ts +0 -22
  21. package/dist/cli/commands/backlog/add.js +0 -65
  22. package/dist/cli/commands/backlog/claim.d.ts +0 -19
  23. package/dist/cli/commands/backlog/claim.js +0 -45
  24. package/dist/cli/commands/backlog/complete.d.ts +0 -18
  25. package/dist/cli/commands/backlog/complete.js +0 -42
  26. package/dist/cli/commands/backlog/list.d.ts +0 -20
  27. package/dist/cli/commands/backlog/list.js +0 -91
  28. package/dist/cli/commands/backlog/pick.d.ts +0 -18
  29. package/dist/cli/commands/backlog/pick.js +0 -42
  30. package/dist/cli/commands/backlog/sync.d.ts +0 -24
  31. package/dist/cli/commands/backlog/sync.js +0 -109
  32. package/dist/cli/commands/daemon.d.ts +0 -56
  33. package/dist/cli/commands/daemon.js +0 -1465
  34. package/dist/cli/commands/docs/lint.d.ts +0 -18
  35. package/dist/cli/commands/docs/lint.js +0 -82
  36. package/dist/cli/commands/docs/sync.d.ts +0 -19
  37. package/dist/cli/commands/docs/sync.js +0 -76
  38. package/dist/cli/commands/gc.d.ts +0 -29
  39. package/dist/cli/commands/gc.js +0 -211
  40. package/dist/cli/commands/index.d.ts +0 -36
  41. package/dist/cli/commands/index.js +0 -228
  42. package/dist/cli/commands/panes/broker.d.ts +0 -17
  43. package/dist/cli/commands/panes/broker.js +0 -57
  44. package/dist/cli/commands/panes/pipe-sink.d.ts +0 -17
  45. package/dist/cli/commands/panes/pipe-sink.js +0 -90
  46. package/dist/cli/commands/panes/snapshot.d.ts +0 -20
  47. package/dist/cli/commands/panes/snapshot.js +0 -125
  48. package/dist/cli/commands/preview/init.d.ts +0 -25
  49. package/dist/cli/commands/preview/init.js +0 -159
  50. package/dist/cli/commands/preview/sync.d.ts +0 -23
  51. package/dist/cli/commands/preview/sync.js +0 -144
  52. package/dist/cli/commands/preview/watch.d.ts +0 -24
  53. package/dist/cli/commands/preview/watch.js +0 -153
  54. package/dist/cli/commands/resource/acquire.d.ts +0 -21
  55. package/dist/cli/commands/resource/acquire.js +0 -90
  56. package/dist/cli/commands/resource/list.d.ts +0 -15
  57. package/dist/cli/commands/resource/list.js +0 -61
  58. package/dist/cli/commands/resource/release.d.ts +0 -18
  59. package/dist/cli/commands/resource/release.js +0 -50
  60. package/dist/cli/commands/resource/wait.d.ts +0 -21
  61. package/dist/cli/commands/resource/wait.js +0 -73
  62. package/dist/cli/commands/session/view.d.ts +0 -24
  63. package/dist/cli/commands/session/view.js +0 -145
  64. package/dist/cli/commands/start.d.ts +0 -37
  65. package/dist/cli/commands/start.js +0 -234
  66. package/dist/cli/commands/triage/claim.d.ts +0 -23
  67. package/dist/cli/commands/triage/claim.js +0 -186
  68. package/dist/cli/commands/triage/list.d.ts +0 -22
  69. package/dist/cli/commands/triage/list.js +0 -112
  70. package/dist/cli/commands/triage/next.d.ts +0 -18
  71. package/dist/cli/commands/triage/next.js +0 -63
  72. package/dist/cli/commands/triage/pull.d.ts +0 -19
  73. package/dist/cli/commands/triage/pull.js +0 -82
  74. package/dist/cli/commands/triage/stats.d.ts +0 -16
  75. package/dist/cli/commands/triage/stats.js +0 -69
  76. package/dist/cli/commands/tunnel/list.d.ts +0 -16
  77. package/dist/cli/commands/tunnel/list.js +0 -98
  78. package/dist/cli/commands/tunnel/start.d.ts +0 -24
  79. package/dist/cli/commands/tunnel/start.js +0 -107
  80. package/dist/cli/commands/tunnel/stop.d.ts +0 -20
  81. package/dist/cli/commands/tunnel/stop.js +0 -90
  82. package/dist/cli/commands/tunnel/url.d.ts +0 -21
  83. package/dist/cli/commands/tunnel/url.js +0 -70
  84. package/dist/cli/commands/windows/context.d.ts +0 -18
  85. package/dist/cli/commands/windows/context.js +0 -326
  86. package/dist/cli/commands/windows/focus.d.ts +0 -17
  87. package/dist/cli/commands/windows/focus.js +0 -103
  88. package/dist/cli/commands/windows/list.d.ts +0 -21
  89. package/dist/cli/commands/windows/list.js +0 -172
  90. package/dist/cli/commands/windows/map.d.ts +0 -17
  91. package/dist/cli/commands/windows/map.js +0 -168
  92. package/dist/cli/commands/windows/read.d.ts +0 -21
  93. package/dist/cli/commands/windows/read.js +0 -241
  94. package/dist/cli/commands/windows/search.d.ts +0 -24
  95. package/dist/cli/commands/windows/search.js +0 -171
  96. package/dist/cli/commands/windows/show.d.ts +0 -19
  97. package/dist/cli/commands/windows/show.js +0 -165
  98. package/dist/cli/commands/windows/watch.d.ts +0 -19
  99. package/dist/cli/commands/windows/watch.js +0 -241
  100. package/dist/lib/managed-session.d.ts +0 -27
  101. package/dist/lib/managed-session.js +0 -105
  102. package/dist/lib/panes/broker.d.ts +0 -130
  103. package/dist/lib/panes/broker.js +0 -97
  104. package/dist/lib/panes/index.d.ts +0 -2
  105. package/dist/lib/panes/index.js +0 -1
  106. package/dist/lib/panes/server.d.ts +0 -17
  107. package/dist/lib/panes/server.js +0 -308
  108. package/dist/lib/preview/manager.d.ts +0 -77
  109. package/dist/lib/preview/manager.js +0 -369
  110. package/dist/lib/preview/schema.d.ts +0 -2
  111. package/dist/lib/preview/schema.js +0 -32
  112. package/dist/lib/preview/sprite.d.ts +0 -85
  113. package/dist/lib/preview/sprite.js +0 -321
  114. package/dist/lib/preview/watcher.d.ts +0 -63
  115. package/dist/lib/preview/watcher.js +0 -185
  116. package/dist/lib/project-identity.d.ts +0 -16
  117. package/dist/lib/project-identity.js +0 -75
  118. package/dist/lib/tmux/bridge.d.ts +0 -133
  119. package/dist/lib/tmux/bridge.js +0 -315
  120. package/dist/lib/tmux/context.d.ts +0 -82
  121. package/dist/lib/tmux/context.js +0 -239
  122. package/dist/lib/tmux/index.d.ts +0 -8
  123. package/dist/lib/tmux/index.js +0 -11
  124. package/dist/lib/tmux/map.d.ts +0 -57
  125. package/dist/lib/tmux/map.js +0 -198
  126. package/dist/lib/tmux/panes.d.ts +0 -27
  127. package/dist/lib/tmux/panes.js +0 -151
  128. package/dist/lib/tmux/redaction.d.ts +0 -57
  129. package/dist/lib/tmux/redaction.js +0 -152
  130. package/dist/lib/web/analytics.d.ts +0 -63
  131. package/dist/lib/web/analytics.js +0 -168
  132. package/dist/lib/web/server.d.ts +0 -26
  133. package/dist/lib/web/server.js +0 -697
  134. package/dist/lib/web/tmux-bridge.d.ts +0 -7
  135. package/dist/lib/web/tmux-bridge.js +0 -7
  136. package/dist/lib/windows/index.d.ts +0 -3
  137. package/dist/lib/windows/index.js +0 -2
  138. package/dist/lib/windows/inventory.d.ts +0 -21
  139. package/dist/lib/windows/inventory.js +0 -263
  140. package/dist/lib/windows/types.d.ts +0 -46
  141. 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
- }