sessioncast-cli 2.0.9 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/api-client.d.ts +7 -0
- package/dist/agent/api-client.js +56 -1
- package/dist/agent/debug.d.ts +3 -0
- package/dist/agent/debug.js +17 -0
- package/dist/agent/session-handler.d.ts +5 -0
- package/dist/agent/session-handler.js +44 -0
- package/dist/agent/tmux.d.ts +13 -0
- package/dist/agent/tmux.js +30 -0
- package/dist/agent/types.d.ts +21 -0
- package/dist/agent/websocket.d.ts +1 -0
- package/dist/agent/websocket.js +13 -0
- package/dist/commands/agent.d.ts +1 -0
- package/dist/commands/agent.js +5 -0
- package/dist/index.js +1 -0
- package/package.json +1 -1
|
@@ -20,6 +20,13 @@ export declare class ApiWebSocketClient {
|
|
|
20
20
|
private handleLlmChat;
|
|
21
21
|
private handleSendKeys;
|
|
22
22
|
private handleListSessions;
|
|
23
|
+
/**
|
|
24
|
+
* Handle capability_request from relay — evaluate each requested capability
|
|
25
|
+
* against the agent's config and respond with capability_grant.
|
|
26
|
+
*
|
|
27
|
+
* Config values: true = auto-grant, false = auto-deny, 'ask' = deny + log notice
|
|
28
|
+
*/
|
|
29
|
+
private handleCapabilityRequest;
|
|
23
30
|
private sendApiResponse;
|
|
24
31
|
private send;
|
|
25
32
|
private scheduleReconnect;
|
package/dist/agent/api-client.js
CHANGED
|
@@ -138,6 +138,9 @@ class ApiWebSocketClient {
|
|
|
138
138
|
case 'list_sessions':
|
|
139
139
|
await this.handleListSessions(message);
|
|
140
140
|
break;
|
|
141
|
+
case 'capability_request':
|
|
142
|
+
this.handleCapabilityRequest(message);
|
|
143
|
+
break;
|
|
141
144
|
}
|
|
142
145
|
}
|
|
143
146
|
async handleExec(message) {
|
|
@@ -212,7 +215,18 @@ class ApiWebSocketClient {
|
|
|
212
215
|
try {
|
|
213
216
|
console.log('[API] list_sessions');
|
|
214
217
|
const sessions = tmux.listSessions();
|
|
215
|
-
|
|
218
|
+
const enriched = sessions.map(s => {
|
|
219
|
+
const cwd = tmux.getPaneCwd(s.name);
|
|
220
|
+
const git = cwd ? tmux.getGitInfo(cwd) : null;
|
|
221
|
+
return {
|
|
222
|
+
...s,
|
|
223
|
+
cwd: cwd || undefined,
|
|
224
|
+
gitBranch: git?.branch || undefined,
|
|
225
|
+
gitRemote: git?.remote || undefined,
|
|
226
|
+
gitRepo: git?.repo || undefined
|
|
227
|
+
};
|
|
228
|
+
});
|
|
229
|
+
this.sendApiResponse(meta.requestId, { sessions: enriched });
|
|
216
230
|
}
|
|
217
231
|
catch (error) {
|
|
218
232
|
this.sendApiResponse(meta.requestId, {
|
|
@@ -221,6 +235,47 @@ class ApiWebSocketClient {
|
|
|
221
235
|
});
|
|
222
236
|
}
|
|
223
237
|
}
|
|
238
|
+
/**
|
|
239
|
+
* Handle capability_request from relay — evaluate each requested capability
|
|
240
|
+
* against the agent's config and respond with capability_grant.
|
|
241
|
+
*
|
|
242
|
+
* Config values: true = auto-grant, false = auto-deny, 'ask' = deny + log notice
|
|
243
|
+
*/
|
|
244
|
+
handleCapabilityRequest(message) {
|
|
245
|
+
const meta = message.meta;
|
|
246
|
+
if (!meta)
|
|
247
|
+
return;
|
|
248
|
+
const from = meta.from;
|
|
249
|
+
const requestedStr = meta.capabilities || '';
|
|
250
|
+
const requested = requestedStr.split(',').map(s => s.trim()).filter(Boolean);
|
|
251
|
+
const capConfig = this.apiConfig.capabilities || {};
|
|
252
|
+
const granted = [];
|
|
253
|
+
const denied = [];
|
|
254
|
+
for (const cap of requested) {
|
|
255
|
+
const setting = capConfig[cap];
|
|
256
|
+
if (setting === true) {
|
|
257
|
+
granted.push(cap);
|
|
258
|
+
}
|
|
259
|
+
else if (setting === 'ask') {
|
|
260
|
+
// Future: interactive prompt. For now, deny and log notice.
|
|
261
|
+
console.log(`[API] Capability '${cap}' requires user consent (configured as 'ask'). Denying by default.`);
|
|
262
|
+
denied.push(cap);
|
|
263
|
+
}
|
|
264
|
+
else if (setting === false || setting === undefined) {
|
|
265
|
+
// Explicitly denied or not configured → deny
|
|
266
|
+
denied.push(cap);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
console.log(`[API] Capability request from SDK: requested=[${requested}], granted=[${granted}], denied=[${denied}]`);
|
|
270
|
+
this.send({
|
|
271
|
+
type: 'capability_grant',
|
|
272
|
+
meta: {
|
|
273
|
+
from: from || '',
|
|
274
|
+
granted: granted.join(','),
|
|
275
|
+
denied: denied.join(',')
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
}
|
|
224
279
|
sendApiResponse(requestId, response) {
|
|
225
280
|
this.send({
|
|
226
281
|
type: 'api_response',
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setDebug = setDebug;
|
|
4
|
+
exports.isDebug = isDebug;
|
|
5
|
+
exports.debugLog = debugLog;
|
|
6
|
+
let debugEnabled = false;
|
|
7
|
+
function setDebug(enabled) {
|
|
8
|
+
debugEnabled = enabled;
|
|
9
|
+
}
|
|
10
|
+
function isDebug() {
|
|
11
|
+
return debugEnabled;
|
|
12
|
+
}
|
|
13
|
+
function debugLog(tag, ...args) {
|
|
14
|
+
if (debugEnabled) {
|
|
15
|
+
console.log(`[DEBUG][${tag}]`, ...args);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -17,6 +17,8 @@ export declare class TmuxSessionHandler {
|
|
|
17
17
|
private captureTimer;
|
|
18
18
|
private lastPaneIds;
|
|
19
19
|
private lastPaneScreens;
|
|
20
|
+
private currentMetaJson;
|
|
21
|
+
private metaCheckTimer;
|
|
20
22
|
private pendingUploads;
|
|
21
23
|
private uploadDir;
|
|
22
24
|
constructor(options: SessionHandlerOptions);
|
|
@@ -50,6 +52,9 @@ export declare class TmuxSessionHandler {
|
|
|
50
52
|
* Get content type from file extension
|
|
51
53
|
*/
|
|
52
54
|
private getContentType;
|
|
55
|
+
private startMetaTracking;
|
|
56
|
+
private stopMetaTracking;
|
|
57
|
+
private checkAndSendMeta;
|
|
53
58
|
private handleKeys;
|
|
54
59
|
private startScreenCapture;
|
|
55
60
|
private stopScreenCapture;
|
|
@@ -39,6 +39,7 @@ const tmux = __importStar(require("./tmux"));
|
|
|
39
39
|
const fs = __importStar(require("fs"));
|
|
40
40
|
const path = __importStar(require("path"));
|
|
41
41
|
const sentry_1 = require("../sentry");
|
|
42
|
+
const debug_1 = require("./debug");
|
|
42
43
|
// Capture intervals
|
|
43
44
|
const CAPTURE_INTERVAL_ACTIVE_MS = 50;
|
|
44
45
|
const CAPTURE_INTERVAL_IDLE_MS = 200;
|
|
@@ -46,6 +47,7 @@ const ACTIVE_THRESHOLD_MS = 2000;
|
|
|
46
47
|
const FORCE_SEND_INTERVAL_MS = 10000;
|
|
47
48
|
const USE_COMPRESSION = true;
|
|
48
49
|
const MIN_COMPRESS_SIZE = 512;
|
|
50
|
+
const META_CHECK_INTERVAL_MS = 15000;
|
|
49
51
|
class TmuxSessionHandler {
|
|
50
52
|
constructor(options) {
|
|
51
53
|
this.wsClient = null;
|
|
@@ -57,6 +59,9 @@ class TmuxSessionHandler {
|
|
|
57
59
|
// Multi-pane tracking
|
|
58
60
|
this.lastPaneIds = '';
|
|
59
61
|
this.lastPaneScreens = new Map();
|
|
62
|
+
// Metadata tracking (cwd + git info)
|
|
63
|
+
this.currentMetaJson = '';
|
|
64
|
+
this.metaCheckTimer = null;
|
|
60
65
|
// File upload handling
|
|
61
66
|
this.pendingUploads = new Map();
|
|
62
67
|
this.uploadDir = process.cwd(); // Default to current working directory
|
|
@@ -88,6 +93,7 @@ class TmuxSessionHandler {
|
|
|
88
93
|
this.wsClient.on('connected', () => {
|
|
89
94
|
console.log(`[${this.tmuxSession}] Connected to relay`);
|
|
90
95
|
this.startScreenCapture();
|
|
96
|
+
this.startMetaTracking();
|
|
91
97
|
});
|
|
92
98
|
this.wsClient.on('disconnected', ({ code, reason }) => {
|
|
93
99
|
console.log(`[${this.tmuxSession}] Disconnected: code=${code}, reason=${reason}`);
|
|
@@ -294,6 +300,43 @@ class TmuxSessionHandler {
|
|
|
294
300
|
};
|
|
295
301
|
return types[ext] || 'application/octet-stream';
|
|
296
302
|
}
|
|
303
|
+
startMetaTracking() {
|
|
304
|
+
this.checkAndSendMeta();
|
|
305
|
+
this.metaCheckTimer = setInterval(() => this.checkAndSendMeta(), META_CHECK_INTERVAL_MS);
|
|
306
|
+
}
|
|
307
|
+
stopMetaTracking() {
|
|
308
|
+
if (this.metaCheckTimer) {
|
|
309
|
+
clearInterval(this.metaCheckTimer);
|
|
310
|
+
this.metaCheckTimer = null;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
checkAndSendMeta() {
|
|
314
|
+
try {
|
|
315
|
+
const cwd = tmux.getPaneCwd(this.tmuxSession);
|
|
316
|
+
if (!cwd)
|
|
317
|
+
return;
|
|
318
|
+
const gitInfo = tmux.getGitInfo(cwd);
|
|
319
|
+
const meta = { cwd };
|
|
320
|
+
if (gitInfo?.branch)
|
|
321
|
+
meta.gitBranch = gitInfo.branch;
|
|
322
|
+
if (gitInfo?.remote)
|
|
323
|
+
meta.gitRemote = gitInfo.remote;
|
|
324
|
+
if (gitInfo?.repo)
|
|
325
|
+
meta.gitRepo = gitInfo.repo;
|
|
326
|
+
const json = JSON.stringify(meta);
|
|
327
|
+
(0, debug_1.debugLog)(this.tmuxSession, 'Meta check:', { cwd, gitInfo });
|
|
328
|
+
const changed = json !== this.currentMetaJson;
|
|
329
|
+
(0, debug_1.debugLog)(this.tmuxSession, changed ? 'Meta CHANGED, sending' : 'Meta unchanged, skip');
|
|
330
|
+
if (changed) {
|
|
331
|
+
this.currentMetaJson = json;
|
|
332
|
+
this.wsClient?.sendSessionMeta(meta);
|
|
333
|
+
console.log(`[${this.tmuxSession}] Session meta updated: ${json}`);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
catch (error) {
|
|
337
|
+
// Silently ignore meta check errors
|
|
338
|
+
}
|
|
339
|
+
}
|
|
297
340
|
handleKeys(keys, paneId) {
|
|
298
341
|
const target = paneId || this.tmuxSession;
|
|
299
342
|
tmux.sendKeys(target, keys, false);
|
|
@@ -405,6 +448,7 @@ class TmuxSessionHandler {
|
|
|
405
448
|
console.log(`[${this.tmuxSession}] Stopping`);
|
|
406
449
|
this.running = false;
|
|
407
450
|
this.stopScreenCapture();
|
|
451
|
+
this.stopMetaTracking();
|
|
408
452
|
if (this.wsClient) {
|
|
409
453
|
this.wsClient.destroy();
|
|
410
454
|
this.wsClient = null;
|
package/dist/agent/tmux.d.ts
CHANGED
|
@@ -56,4 +56,17 @@ export declare function listPanes(sessionName: string): PaneData[] | null;
|
|
|
56
56
|
* Capture a specific pane by its pane ID (e.g., %0, %1)
|
|
57
57
|
*/
|
|
58
58
|
export declare function capturePaneById(sessionName: string, paneId: string): string | null;
|
|
59
|
+
/**
|
|
60
|
+
* Git info for a directory
|
|
61
|
+
*/
|
|
62
|
+
export interface GitInfo {
|
|
63
|
+
branch: string | null;
|
|
64
|
+
remote: string | null;
|
|
65
|
+
repo: string | null;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Detect git info (branch, remote, repo) for a given directory.
|
|
69
|
+
* Returns null if the directory is not inside a git work tree.
|
|
70
|
+
*/
|
|
71
|
+
export declare function getGitInfo(cwd: string): GitInfo | null;
|
|
59
72
|
export type { PaneData } from './tmux-executor';
|
package/dist/agent/tmux.js
CHANGED
|
@@ -14,7 +14,10 @@ exports.getActivePane = getActivePane;
|
|
|
14
14
|
exports.getPaneCwd = getPaneCwd;
|
|
15
15
|
exports.listPanes = listPanes;
|
|
16
16
|
exports.capturePaneById = capturePaneById;
|
|
17
|
+
exports.getGitInfo = getGitInfo;
|
|
17
18
|
const tmux_executor_1 = require("./tmux-executor");
|
|
19
|
+
const child_process_1 = require("child_process");
|
|
20
|
+
const debug_1 = require("./debug");
|
|
18
21
|
// Lazy-initialized executor (created on first use)
|
|
19
22
|
let executor = null;
|
|
20
23
|
/**
|
|
@@ -171,3 +174,30 @@ function listPanes(sessionName) {
|
|
|
171
174
|
function capturePaneById(sessionName, paneId) {
|
|
172
175
|
return getExecutor().capturePaneById(sessionName, paneId);
|
|
173
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* Detect git info (branch, remote, repo) for a given directory.
|
|
179
|
+
* Returns null if the directory is not inside a git work tree.
|
|
180
|
+
*/
|
|
181
|
+
function getGitInfo(cwd) {
|
|
182
|
+
try {
|
|
183
|
+
(0, child_process_1.execSync)('git rev-parse --is-inside-work-tree', { cwd, stdio: 'pipe', timeout: 3000 });
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
const run = (cmd) => {
|
|
189
|
+
try {
|
|
190
|
+
return (0, child_process_1.execSync)(cmd, { cwd, encoding: 'utf-8', stdio: 'pipe', timeout: 3000 }).trim();
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
const branch = run('git rev-parse --abbrev-ref HEAD');
|
|
197
|
+
const remote = run('git config --get remote.origin.url');
|
|
198
|
+
const repo = remote
|
|
199
|
+
? remote.replace(/.*github\.com[:/]/, '').replace(/\.git$/, '')
|
|
200
|
+
: null;
|
|
201
|
+
(0, debug_1.debugLog)('git', `cwd=${cwd}, branch=${branch}, remote=${remote}, repo=${repo}`);
|
|
202
|
+
return { branch, remote, repo };
|
|
203
|
+
}
|
package/dist/agent/types.d.ts
CHANGED
|
@@ -9,6 +9,21 @@ export interface ApiConfig {
|
|
|
9
9
|
agentId?: string;
|
|
10
10
|
exec?: ExecConfig;
|
|
11
11
|
llm?: LlmConfig;
|
|
12
|
+
capabilities?: CapabilitiesConfig;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Per-capability consent config for the CLI agent.
|
|
16
|
+
* Each capability can be:
|
|
17
|
+
* true — auto-grant when SDK requests it
|
|
18
|
+
* false — auto-deny
|
|
19
|
+
* 'ask' — deny with a log notice (future: interactive prompt)
|
|
20
|
+
*/
|
|
21
|
+
export interface CapabilitiesConfig {
|
|
22
|
+
exec?: boolean | 'ask';
|
|
23
|
+
exec_cwd?: boolean | 'ask';
|
|
24
|
+
llm_chat?: boolean | 'ask';
|
|
25
|
+
send_keys?: boolean | 'ask';
|
|
26
|
+
list_sessions?: boolean | 'ask';
|
|
12
27
|
}
|
|
13
28
|
export interface ExecConfig {
|
|
14
29
|
enabled: boolean;
|
|
@@ -37,6 +52,12 @@ export interface TmuxSession {
|
|
|
37
52
|
created?: string;
|
|
38
53
|
attached: boolean;
|
|
39
54
|
}
|
|
55
|
+
export interface SessionMetadata {
|
|
56
|
+
cwd: string | null;
|
|
57
|
+
gitBranch: string | null;
|
|
58
|
+
gitRemote: string | null;
|
|
59
|
+
gitRepo: string | null;
|
|
60
|
+
}
|
|
40
61
|
export interface ExecResult {
|
|
41
62
|
exitCode: number;
|
|
42
63
|
stdout: string;
|
|
@@ -33,6 +33,7 @@ export declare class RelayWebSocketClient extends EventEmitter {
|
|
|
33
33
|
sendScreenWithMeta(data: Buffer, meta: Record<string, string>): boolean;
|
|
34
34
|
sendScreenCompressed(data: Buffer): boolean;
|
|
35
35
|
sendScreenCompressedWithMeta(data: Buffer, meta: Record<string, string>): boolean;
|
|
36
|
+
sendSessionMeta(meta: Record<string, string>): boolean;
|
|
36
37
|
sendPaneLayout(panes: Array<{
|
|
37
38
|
id: string;
|
|
38
39
|
index: number;
|
package/dist/agent/websocket.js
CHANGED
|
@@ -40,6 +40,7 @@ exports.RelayWebSocketClient = void 0;
|
|
|
40
40
|
const ws_1 = __importDefault(require("ws"));
|
|
41
41
|
const events_1 = require("events");
|
|
42
42
|
const zlib = __importStar(require("zlib"));
|
|
43
|
+
const debug_1 = require("./debug");
|
|
43
44
|
const MAX_RECONNECT_ATTEMPTS = 5;
|
|
44
45
|
const BASE_RECONNECT_DELAY_MS = 2000;
|
|
45
46
|
const MAX_RECONNECT_DELAY_MS = 60000;
|
|
@@ -116,6 +117,7 @@ class RelayWebSocketClient extends events_1.EventEmitter {
|
|
|
116
117
|
});
|
|
117
118
|
}
|
|
118
119
|
handleMessage(message) {
|
|
120
|
+
(0, debug_1.debugLog)('WS:IN', message.type, message.session);
|
|
119
121
|
switch (message.type) {
|
|
120
122
|
case 'keys':
|
|
121
123
|
if (message.session === this.sessionId && message.payload) {
|
|
@@ -232,6 +234,8 @@ class RelayWebSocketClient extends events_1.EventEmitter {
|
|
|
232
234
|
return false;
|
|
233
235
|
}
|
|
234
236
|
try {
|
|
237
|
+
(0, debug_1.debugLog)('WS:OUT', message.type, message.session, message.type === 'sessionMeta' ? JSON.stringify(message.meta) :
|
|
238
|
+
message.payload ? `payload=${message.payload.length}bytes` : '');
|
|
235
239
|
this.ws.send(JSON.stringify(message));
|
|
236
240
|
return true;
|
|
237
241
|
}
|
|
@@ -291,6 +295,15 @@ class RelayWebSocketClient extends events_1.EventEmitter {
|
|
|
291
295
|
return this.sendScreenWithMeta(data, meta);
|
|
292
296
|
}
|
|
293
297
|
}
|
|
298
|
+
sendSessionMeta(meta) {
|
|
299
|
+
if (!this.isConnected)
|
|
300
|
+
return false;
|
|
301
|
+
return this.send({
|
|
302
|
+
type: 'sessionMeta',
|
|
303
|
+
session: this.sessionId,
|
|
304
|
+
meta
|
|
305
|
+
});
|
|
306
|
+
}
|
|
294
307
|
sendPaneLayout(panes) {
|
|
295
308
|
if (!this.isConnected)
|
|
296
309
|
return false;
|
package/dist/commands/agent.d.ts
CHANGED
package/dist/commands/agent.js
CHANGED
|
@@ -7,8 +7,13 @@ exports.startAgent = startAgent;
|
|
|
7
7
|
const chalk_1 = __importDefault(require("chalk"));
|
|
8
8
|
const runner_1 = require("../agent/runner");
|
|
9
9
|
const sentry_1 = require("../sentry");
|
|
10
|
+
const debug_1 = require("../agent/debug");
|
|
10
11
|
async function startAgent(options) {
|
|
11
12
|
try {
|
|
13
|
+
if (options.debug) {
|
|
14
|
+
(0, debug_1.setDebug)(true);
|
|
15
|
+
console.log(chalk_1.default.yellow('[DEBUG] Debug mode enabled'));
|
|
16
|
+
}
|
|
12
17
|
const config = runner_1.AgentRunner.loadConfig(options.config);
|
|
13
18
|
const runner = new runner_1.AgentRunner(config);
|
|
14
19
|
await runner.start();
|
package/dist/index.js
CHANGED
|
@@ -209,6 +209,7 @@ program
|
|
|
209
209
|
.command('agent')
|
|
210
210
|
.description('Start the SessionCast agent')
|
|
211
211
|
.option('-c, --config <path>', 'Path to config file')
|
|
212
|
+
.option('-d, --debug', 'Enable debug logging')
|
|
212
213
|
.action(agent_1.startAgent);
|
|
213
214
|
// Help examples
|
|
214
215
|
program.on('--help', () => {
|