@creativeintelligence/abbie 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/dev.js +1 -49
- package/bin/run.js +42 -49
- package/dist/cli/commands/project/add.d.ts +0 -1
- package/dist/cli/commands/project/add.js +16 -52
- package/dist/cli/commands/project/list.js +13 -93
- package/dist/cli/commands/project/remove.d.ts +0 -2
- package/dist/cli/commands/project/remove.js +11 -28
- package/dist/cli/commands/session/list.js +3 -12
- package/dist/cli/commands/session/mark-done.js +1 -7
- package/dist/cli/commands/session/start.d.ts +0 -1
- package/dist/cli/commands/session/start.js +5 -7
- package/dist/lib/active-sessions.d.ts +0 -12
- package/dist/lib/active-sessions.js +6 -175
- package/dist/lib/project-path.d.ts +6 -0
- package/dist/lib/project-path.js +21 -0
- package/dist/lib.d.ts +1 -2
- package/dist/lib.js +2 -4
- package/oclif.manifest.json +2569 -6368
- package/package.json +21 -10
- package/dist/cli/commands/backlog/add.d.ts +0 -22
- package/dist/cli/commands/backlog/add.js +0 -65
- package/dist/cli/commands/backlog/claim.d.ts +0 -19
- package/dist/cli/commands/backlog/claim.js +0 -45
- package/dist/cli/commands/backlog/complete.d.ts +0 -18
- package/dist/cli/commands/backlog/complete.js +0 -42
- package/dist/cli/commands/backlog/list.d.ts +0 -20
- package/dist/cli/commands/backlog/list.js +0 -91
- package/dist/cli/commands/backlog/pick.d.ts +0 -18
- package/dist/cli/commands/backlog/pick.js +0 -42
- package/dist/cli/commands/backlog/sync.d.ts +0 -24
- package/dist/cli/commands/backlog/sync.js +0 -109
- package/dist/cli/commands/daemon.d.ts +0 -56
- package/dist/cli/commands/daemon.js +0 -1465
- package/dist/cli/commands/docs/lint.d.ts +0 -18
- package/dist/cli/commands/docs/lint.js +0 -82
- package/dist/cli/commands/docs/sync.d.ts +0 -19
- package/dist/cli/commands/docs/sync.js +0 -76
- package/dist/cli/commands/gc.d.ts +0 -29
- package/dist/cli/commands/gc.js +0 -211
- package/dist/cli/commands/index.d.ts +0 -36
- package/dist/cli/commands/index.js +0 -228
- package/dist/cli/commands/panes/broker.d.ts +0 -17
- package/dist/cli/commands/panes/broker.js +0 -57
- package/dist/cli/commands/panes/pipe-sink.d.ts +0 -17
- package/dist/cli/commands/panes/pipe-sink.js +0 -90
- package/dist/cli/commands/panes/snapshot.d.ts +0 -20
- package/dist/cli/commands/panes/snapshot.js +0 -125
- package/dist/cli/commands/preview/init.d.ts +0 -25
- package/dist/cli/commands/preview/init.js +0 -159
- package/dist/cli/commands/preview/sync.d.ts +0 -23
- package/dist/cli/commands/preview/sync.js +0 -144
- package/dist/cli/commands/preview/watch.d.ts +0 -24
- package/dist/cli/commands/preview/watch.js +0 -153
- package/dist/cli/commands/resource/acquire.d.ts +0 -21
- package/dist/cli/commands/resource/acquire.js +0 -90
- package/dist/cli/commands/resource/list.d.ts +0 -15
- package/dist/cli/commands/resource/list.js +0 -61
- package/dist/cli/commands/resource/release.d.ts +0 -18
- package/dist/cli/commands/resource/release.js +0 -50
- package/dist/cli/commands/resource/wait.d.ts +0 -21
- package/dist/cli/commands/resource/wait.js +0 -73
- package/dist/cli/commands/session/view.d.ts +0 -24
- package/dist/cli/commands/session/view.js +0 -145
- package/dist/cli/commands/start.d.ts +0 -37
- package/dist/cli/commands/start.js +0 -234
- package/dist/cli/commands/triage/claim.d.ts +0 -23
- package/dist/cli/commands/triage/claim.js +0 -186
- package/dist/cli/commands/triage/list.d.ts +0 -22
- package/dist/cli/commands/triage/list.js +0 -112
- package/dist/cli/commands/triage/next.d.ts +0 -18
- package/dist/cli/commands/triage/next.js +0 -63
- package/dist/cli/commands/triage/pull.d.ts +0 -19
- package/dist/cli/commands/triage/pull.js +0 -82
- package/dist/cli/commands/triage/stats.d.ts +0 -16
- package/dist/cli/commands/triage/stats.js +0 -69
- package/dist/cli/commands/tunnel/list.d.ts +0 -16
- package/dist/cli/commands/tunnel/list.js +0 -98
- package/dist/cli/commands/tunnel/start.d.ts +0 -24
- package/dist/cli/commands/tunnel/start.js +0 -107
- package/dist/cli/commands/tunnel/stop.d.ts +0 -20
- package/dist/cli/commands/tunnel/stop.js +0 -90
- package/dist/cli/commands/tunnel/url.d.ts +0 -21
- package/dist/cli/commands/tunnel/url.js +0 -70
- package/dist/cli/commands/windows/context.d.ts +0 -18
- package/dist/cli/commands/windows/context.js +0 -326
- package/dist/cli/commands/windows/focus.d.ts +0 -17
- package/dist/cli/commands/windows/focus.js +0 -103
- package/dist/cli/commands/windows/list.d.ts +0 -21
- package/dist/cli/commands/windows/list.js +0 -172
- package/dist/cli/commands/windows/map.d.ts +0 -17
- package/dist/cli/commands/windows/map.js +0 -168
- package/dist/cli/commands/windows/read.d.ts +0 -21
- package/dist/cli/commands/windows/read.js +0 -241
- package/dist/cli/commands/windows/search.d.ts +0 -24
- package/dist/cli/commands/windows/search.js +0 -171
- package/dist/cli/commands/windows/show.d.ts +0 -19
- package/dist/cli/commands/windows/show.js +0 -165
- package/dist/cli/commands/windows/watch.d.ts +0 -19
- package/dist/cli/commands/windows/watch.js +0 -241
- package/dist/lib/managed-session.d.ts +0 -27
- package/dist/lib/managed-session.js +0 -105
- package/dist/lib/panes/broker.d.ts +0 -130
- package/dist/lib/panes/broker.js +0 -97
- package/dist/lib/panes/index.d.ts +0 -2
- package/dist/lib/panes/index.js +0 -1
- package/dist/lib/panes/server.d.ts +0 -17
- package/dist/lib/panes/server.js +0 -308
- package/dist/lib/preview/manager.d.ts +0 -77
- package/dist/lib/preview/manager.js +0 -369
- package/dist/lib/preview/schema.d.ts +0 -2
- package/dist/lib/preview/schema.js +0 -32
- package/dist/lib/preview/sprite.d.ts +0 -85
- package/dist/lib/preview/sprite.js +0 -321
- package/dist/lib/preview/watcher.d.ts +0 -63
- package/dist/lib/preview/watcher.js +0 -185
- package/dist/lib/project-identity.d.ts +0 -16
- package/dist/lib/project-identity.js +0 -75
- package/dist/lib/tmux/bridge.d.ts +0 -133
- package/dist/lib/tmux/bridge.js +0 -315
- package/dist/lib/tmux/context.d.ts +0 -82
- package/dist/lib/tmux/context.js +0 -239
- package/dist/lib/tmux/index.d.ts +0 -8
- package/dist/lib/tmux/index.js +0 -11
- package/dist/lib/tmux/map.d.ts +0 -57
- package/dist/lib/tmux/map.js +0 -198
- package/dist/lib/tmux/panes.d.ts +0 -27
- package/dist/lib/tmux/panes.js +0 -151
- package/dist/lib/tmux/redaction.d.ts +0 -57
- package/dist/lib/tmux/redaction.js +0 -152
- package/dist/lib/web/analytics.d.ts +0 -63
- package/dist/lib/web/analytics.js +0 -168
- package/dist/lib/web/server.d.ts +0 -26
- package/dist/lib/web/server.js +0 -697
- package/dist/lib/web/tmux-bridge.d.ts +0 -7
- package/dist/lib/web/tmux-bridge.js +0 -7
- package/dist/lib/windows/index.d.ts +0 -3
- package/dist/lib/windows/index.js +0 -2
- package/dist/lib/windows/inventory.d.ts +0 -21
- package/dist/lib/windows/inventory.js +0 -263
- package/dist/lib/windows/types.d.ts +0 -46
- package/dist/lib/windows/types.js +0 -1
package/dist/lib/tmux/context.js
DELETED
|
@@ -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, "&")
|
|
201
|
-
.replace(/</g, "<")
|
|
202
|
-
.replace(/>/g, ">")
|
|
203
|
-
.replace(/"/g, """)
|
|
204
|
-
.replace(/'/g, "'");
|
|
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
|
-
}
|
package/dist/lib/tmux/index.d.ts
DELETED
|
@@ -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
|
package/dist/lib/tmux/index.js
DELETED
|
@@ -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";
|
package/dist/lib/tmux/map.d.ts
DELETED
|
@@ -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
|
package/dist/lib/tmux/map.js
DELETED
|
@@ -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
|
-
}
|
package/dist/lib/tmux/panes.d.ts
DELETED
|
@@ -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
|
package/dist/lib/tmux/panes.js
DELETED
|
@@ -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
|
-
}
|