genbox 1.0.209 → 1.0.211
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/commands/provider-command.js +34 -10
- package/dist/commands/session/index.js +8 -0
- package/dist/commands/session/logs.js +387 -0
- package/dist/commands/session/send.js +408 -0
- package/dist/commands/session/show.js +339 -0
- package/dist/commands/session/watch.js +462 -0
- package/dist/utils/ssh-proxy.js +85 -0
- package/package.json +1 -1
|
@@ -444,7 +444,7 @@ async function startLocalMultipass(genbox) {
|
|
|
444
444
|
* Create and attach to a session on a genbox
|
|
445
445
|
*/
|
|
446
446
|
async function startSessionOnGenbox(provider, genbox, options = {}) {
|
|
447
|
-
const { initialPrompt } = options;
|
|
447
|
+
const { initialPrompt, background = false } = options;
|
|
448
448
|
let targetGenbox = genbox;
|
|
449
449
|
let sshTarget;
|
|
450
450
|
let sshPortArgs = [];
|
|
@@ -592,13 +592,32 @@ exec ${cliCommand}
|
|
|
592
592
|
// Wait for session to start
|
|
593
593
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
594
594
|
console.log(chalk_1.default.green(`Session created: ${sessionName}`));
|
|
595
|
-
console.log(chalk_1.default.dim(`Image auto-upload: ${chalk_1.default.green('enabled')} - Paste local image paths, they'll be uploaded automatically`));
|
|
596
|
-
console.log(chalk_1.default.dim('Tip: Detach with Ctrl+\\\n'));
|
|
597
|
-
// Attach to session using SSH proxy with image path interception
|
|
598
595
|
// Extract IP address from sshTarget (format: dev@IP or dev@localhost)
|
|
599
596
|
const attachIpAddress = sshTarget.split('@')[1];
|
|
600
|
-
// Get port from sshPortArgs if it's a Docker container
|
|
601
597
|
const sshPort = sshPortArgs.length >= 2 ? parseInt(sshPortArgs[1], 10) : undefined;
|
|
598
|
+
// Background mode: inject prompt and return immediately
|
|
599
|
+
if (background) {
|
|
600
|
+
if (initialPrompt) {
|
|
601
|
+
console.log(chalk_1.default.dim('Injecting prompt in background...'));
|
|
602
|
+
// Spawn background process to attach, inject prompt, then detach
|
|
603
|
+
const { injectPromptInBackground } = await Promise.resolve().then(() => __importStar(require('../utils/ssh-proxy')));
|
|
604
|
+
injectPromptInBackground({
|
|
605
|
+
ipAddress: attachIpAddress === 'localhost' ? '127.0.0.1' : attachIpAddress,
|
|
606
|
+
keyPath,
|
|
607
|
+
genboxName: targetGenbox.name,
|
|
608
|
+
socketPath,
|
|
609
|
+
user: 'dev',
|
|
610
|
+
port: sshPort,
|
|
611
|
+
prompt: initialPrompt,
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
console.log(chalk_1.default.dim(`\nSession running in background.`));
|
|
615
|
+
console.log(chalk_1.default.dim(`Attach with: ${chalk_1.default.cyan(`gb ${provider} attach ${sessionName}`)}`));
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
// Interactive mode: attach to session
|
|
619
|
+
console.log(chalk_1.default.dim(`Image auto-upload: ${chalk_1.default.green('enabled')} - Paste local image paths, they'll be uploaded automatically`));
|
|
620
|
+
console.log(chalk_1.default.dim('Tip: Detach with Ctrl+\\\n'));
|
|
602
621
|
const proc = (0, ssh_proxy_1.attachWithProxy)({
|
|
603
622
|
ipAddress: attachIpAddress === 'localhost' ? '127.0.0.1' : attachIpAddress,
|
|
604
623
|
keyPath,
|
|
@@ -611,7 +630,7 @@ exec ${cliCommand}
|
|
|
611
630
|
});
|
|
612
631
|
await new Promise(resolve => proc.on('close', () => resolve()));
|
|
613
632
|
console.log(chalk_1.default.dim('\nDetached from session.'));
|
|
614
|
-
console.log(chalk_1.default.dim(`Reattach with: ${chalk_1.default.cyan(`gb ${provider}
|
|
633
|
+
console.log(chalk_1.default.dim(`Reattach with: ${chalk_1.default.cyan(`gb ${provider} attach ${sessionName}`)}`));
|
|
615
634
|
}
|
|
616
635
|
/**
|
|
617
636
|
* Attach to an existing session
|
|
@@ -944,7 +963,7 @@ async function runInteractiveFlow(provider) {
|
|
|
944
963
|
* 5. Starts session with the composed prompt
|
|
945
964
|
*/
|
|
946
965
|
async function runPromptModeFlow(provider, options = {}) {
|
|
947
|
-
const { useEditor = false } = options;
|
|
966
|
+
const { useEditor = false, background = false } = options;
|
|
948
967
|
console.log(chalk_1.default.bold(`\n${provider.charAt(0).toUpperCase() + provider.slice(1)} - Interactive Prompt Mode`));
|
|
949
968
|
if (useEditor) {
|
|
950
969
|
console.log(chalk_1.default.dim('Compose your prompt in $EDITOR with optional images\n'));
|
|
@@ -1053,7 +1072,7 @@ async function runPromptModeFlow(provider, options = {}) {
|
|
|
1053
1072
|
finalPrompt = text;
|
|
1054
1073
|
}
|
|
1055
1074
|
// Start session with prompt
|
|
1056
|
-
await startSessionOnGenbox(provider, genbox, { initialPrompt: finalPrompt });
|
|
1075
|
+
await startSessionOnGenbox(provider, genbox, { initialPrompt: finalPrompt, background });
|
|
1057
1076
|
break;
|
|
1058
1077
|
}
|
|
1059
1078
|
case 'create-genbox': {
|
|
@@ -1085,7 +1104,7 @@ async function runPromptModeFlow(provider, options = {}) {
|
|
|
1085
1104
|
else {
|
|
1086
1105
|
finalPrompt = text;
|
|
1087
1106
|
}
|
|
1088
|
-
await startSessionOnGenbox(provider, genbox, { initialPrompt: finalPrompt });
|
|
1107
|
+
await startSessionOnGenbox(provider, genbox, { initialPrompt: finalPrompt, background });
|
|
1089
1108
|
break;
|
|
1090
1109
|
}
|
|
1091
1110
|
case 'direct': {
|
|
@@ -1410,11 +1429,13 @@ function createProviderCommand(provider) {
|
|
|
1410
1429
|
.option('-y, --yes', 'Skip confirmations')
|
|
1411
1430
|
.option('-p, --prompt-mode', 'Interactive prompt mode: compose prompt with optional images')
|
|
1412
1431
|
.option('-e, --editor', 'Use $EDITOR for prompt input (with -p)')
|
|
1432
|
+
.option('-b, --background', 'Run session in background (with -p), attach later')
|
|
1413
1433
|
.addHelpText('after', `
|
|
1414
1434
|
Examples:
|
|
1415
1435
|
gb ${provider} Interactive mode
|
|
1416
1436
|
gb ${provider} -p Prompt mode: inline multiline input
|
|
1417
1437
|
gb ${provider} -p -e Prompt mode: open $EDITOR for input
|
|
1438
|
+
gb ${provider} -p -b Prompt mode: run in background
|
|
1418
1439
|
gb ${provider} list List all ${provider} sessions
|
|
1419
1440
|
gb ${provider} attach [session] Attach to a session
|
|
1420
1441
|
gb ${provider} stop [session] Stop a session
|
|
@@ -1427,7 +1448,10 @@ Examples:
|
|
|
1427
1448
|
try {
|
|
1428
1449
|
// -p / --prompt-mode: Interactive prompt with optional images
|
|
1429
1450
|
if (options.promptMode) {
|
|
1430
|
-
await runPromptModeFlow(provider, {
|
|
1451
|
+
await runPromptModeFlow(provider, {
|
|
1452
|
+
useEditor: options.editor,
|
|
1453
|
+
background: options.background,
|
|
1454
|
+
});
|
|
1431
1455
|
return;
|
|
1432
1456
|
}
|
|
1433
1457
|
// --on: Use specific genbox
|
|
@@ -65,6 +65,10 @@ const stop_1 = require("./stop");
|
|
|
65
65
|
const kill_1 = require("./kill");
|
|
66
66
|
const migrate_1 = require("./migrate");
|
|
67
67
|
const attach_1 = require("./attach");
|
|
68
|
+
const watch_1 = require("./watch");
|
|
69
|
+
const logs_1 = require("./logs");
|
|
70
|
+
const show_1 = require("./show");
|
|
71
|
+
const send_1 = require("./send");
|
|
68
72
|
const child_process_1 = require("child_process");
|
|
69
73
|
const os = __importStar(require("os"));
|
|
70
74
|
const path = __importStar(require("path"));
|
|
@@ -1768,6 +1772,10 @@ exports.sessionCommand = new commander_1.Command('session')
|
|
|
1768
1772
|
.addCommand(stop_1.sessionStopCommand)
|
|
1769
1773
|
.addCommand(kill_1.sessionKillCommand)
|
|
1770
1774
|
.addCommand(migrate_1.sessionMigrateCommand)
|
|
1775
|
+
.addCommand(watch_1.sessionWatchCommand)
|
|
1776
|
+
.addCommand(logs_1.sessionLogsCommand)
|
|
1777
|
+
.addCommand(show_1.sessionShowCommand)
|
|
1778
|
+
.addCommand(send_1.sessionSendCommand)
|
|
1771
1779
|
.action(async (sessionArg, providerArgs, options) => {
|
|
1772
1780
|
try {
|
|
1773
1781
|
// Clean up stale sockets on startup (silently)
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Session Logs Command
|
|
4
|
+
*
|
|
5
|
+
* Stream session transcript and activity logs.
|
|
6
|
+
* Shows real-time updates of what the AI is doing.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* gb session logs <session> # Show session transcript
|
|
10
|
+
* gb session logs <session> -f # Follow mode (like tail -f)
|
|
11
|
+
* gb session logs <session> --json # JSON output
|
|
12
|
+
*/
|
|
13
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
16
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
17
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
18
|
+
}
|
|
19
|
+
Object.defineProperty(o, k2, desc);
|
|
20
|
+
}) : (function(o, m, k, k2) {
|
|
21
|
+
if (k2 === undefined) k2 = k;
|
|
22
|
+
o[k2] = m[k];
|
|
23
|
+
}));
|
|
24
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
25
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
26
|
+
}) : function(o, v) {
|
|
27
|
+
o["default"] = v;
|
|
28
|
+
});
|
|
29
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
30
|
+
var ownKeys = function(o) {
|
|
31
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
32
|
+
var ar = [];
|
|
33
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
34
|
+
return ar;
|
|
35
|
+
};
|
|
36
|
+
return ownKeys(o);
|
|
37
|
+
};
|
|
38
|
+
return function (mod) {
|
|
39
|
+
if (mod && mod.__esModule) return mod;
|
|
40
|
+
var result = {};
|
|
41
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
42
|
+
__setModuleDefault(result, mod);
|
|
43
|
+
return result;
|
|
44
|
+
};
|
|
45
|
+
})();
|
|
46
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
47
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
48
|
+
};
|
|
49
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
50
|
+
exports.sessionLogsCommand = void 0;
|
|
51
|
+
const commander_1 = require("commander");
|
|
52
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
53
|
+
const path = __importStar(require("path"));
|
|
54
|
+
const os = __importStar(require("os"));
|
|
55
|
+
const fs = __importStar(require("fs"));
|
|
56
|
+
const unified_session_1 = require("../../lib/unified-session");
|
|
57
|
+
const api_1 = require("../../api");
|
|
58
|
+
/**
|
|
59
|
+
* Get SSH key path
|
|
60
|
+
*/
|
|
61
|
+
function getPrivateSshKey() {
|
|
62
|
+
const home = os.homedir();
|
|
63
|
+
const potentialKeys = [
|
|
64
|
+
path.join(home, '.ssh', 'id_ed25519'),
|
|
65
|
+
path.join(home, '.ssh', 'id_rsa'),
|
|
66
|
+
];
|
|
67
|
+
for (const keyPath of potentialKeys) {
|
|
68
|
+
if (fs.existsSync(keyPath)) {
|
|
69
|
+
return keyPath;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Format timestamp
|
|
76
|
+
*/
|
|
77
|
+
function formatTime(date) {
|
|
78
|
+
const d = typeof date === 'string' ? new Date(date) : date;
|
|
79
|
+
return d.toLocaleTimeString('en-US', { hour12: false });
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Find session by name or ID
|
|
83
|
+
*/
|
|
84
|
+
async function findSession(nameOrId) {
|
|
85
|
+
const result = await (0, unified_session_1.listAllSessions)({ includeEnded: true });
|
|
86
|
+
// Check local sessions
|
|
87
|
+
for (const session of result.sessions) {
|
|
88
|
+
if (session.name === nameOrId ||
|
|
89
|
+
session.id === nameOrId ||
|
|
90
|
+
session.id.startsWith(nameOrId) ||
|
|
91
|
+
session.name.includes(nameOrId)) {
|
|
92
|
+
return { session };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// Check remote sessions
|
|
96
|
+
for (const remote of result.remoteSessions) {
|
|
97
|
+
if (remote.name === nameOrId ||
|
|
98
|
+
remote.name.includes(nameOrId)) {
|
|
99
|
+
return { remoteSession: remote };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Fetch messages from API
|
|
106
|
+
*/
|
|
107
|
+
async function fetchMessages(sessionId, limit = 50) {
|
|
108
|
+
try {
|
|
109
|
+
const response = await (0, api_1.fetchApi)(`/sessions/v2/${sessionId}/messages?limit=${limit}`);
|
|
110
|
+
return response.messages || [];
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return [];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Fetch events from API
|
|
118
|
+
*/
|
|
119
|
+
async function fetchEvents(sessionId, limit = 100) {
|
|
120
|
+
try {
|
|
121
|
+
const response = await (0, api_1.fetchApi)(`/sessions/v2/${sessionId}/events?limit=${limit}`);
|
|
122
|
+
return response.events || [];
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Get role icon
|
|
130
|
+
*/
|
|
131
|
+
function getRoleIcon(role) {
|
|
132
|
+
switch (role) {
|
|
133
|
+
case 'user':
|
|
134
|
+
return '💬';
|
|
135
|
+
case 'assistant':
|
|
136
|
+
return '🤖';
|
|
137
|
+
default:
|
|
138
|
+
return '📝';
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Get event icon
|
|
143
|
+
*/
|
|
144
|
+
function getEventIcon(event) {
|
|
145
|
+
switch (event) {
|
|
146
|
+
case 'session_start':
|
|
147
|
+
return '🚀';
|
|
148
|
+
case 'session_end':
|
|
149
|
+
return '🏁';
|
|
150
|
+
case 'prompt_submitted':
|
|
151
|
+
return '💬';
|
|
152
|
+
case 'response_started':
|
|
153
|
+
return '🤔';
|
|
154
|
+
case 'response_completed':
|
|
155
|
+
return '✅';
|
|
156
|
+
case 'tool_started':
|
|
157
|
+
return '⚙';
|
|
158
|
+
case 'tool_completed':
|
|
159
|
+
return '✓';
|
|
160
|
+
case 'tool_error':
|
|
161
|
+
return '❌';
|
|
162
|
+
case 'state_change':
|
|
163
|
+
return '🔄';
|
|
164
|
+
case 'error':
|
|
165
|
+
return '⚠';
|
|
166
|
+
default:
|
|
167
|
+
return '•';
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Format a message for display
|
|
172
|
+
*/
|
|
173
|
+
function formatMessage(msg) {
|
|
174
|
+
const icon = getRoleIcon(msg.role);
|
|
175
|
+
const time = chalk_1.default.dim(`[${formatTime(msg.createdAt)}]`);
|
|
176
|
+
const roleColor = msg.role === 'user' ? chalk_1.default.cyan : chalk_1.default.green;
|
|
177
|
+
const roleLabel = roleColor(msg.role === 'user' ? 'User' : 'Assistant');
|
|
178
|
+
let output = `${time} ${icon} ${roleLabel}:`;
|
|
179
|
+
if (msg.textPreview) {
|
|
180
|
+
// Wrap text for readability
|
|
181
|
+
const preview = msg.textPreview.substring(0, 200);
|
|
182
|
+
output += ` ${preview}${msg.textPreview.length > 200 ? '...' : ''}`;
|
|
183
|
+
}
|
|
184
|
+
if (msg.toolsUsed && msg.toolsUsed.length > 0) {
|
|
185
|
+
output += chalk_1.default.dim(` [Tools: ${msg.toolsUsed.join(', ')}]`);
|
|
186
|
+
}
|
|
187
|
+
return output;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Format an event for display
|
|
191
|
+
*/
|
|
192
|
+
function formatEvent(evt) {
|
|
193
|
+
const icon = getEventIcon(evt.event);
|
|
194
|
+
const time = chalk_1.default.dim(`[${formatTime(evt.createdAt)}]`);
|
|
195
|
+
const eventLabel = evt.event.replace(/_/g, ' ');
|
|
196
|
+
let output = `${time} ${icon} ${eventLabel}`;
|
|
197
|
+
if (evt.data) {
|
|
198
|
+
if (evt.data.toolName) {
|
|
199
|
+
output += chalk_1.default.cyan(` ${evt.data.toolName}`);
|
|
200
|
+
}
|
|
201
|
+
if (evt.data.filePath) {
|
|
202
|
+
output += chalk_1.default.dim(` ${evt.data.filePath}`);
|
|
203
|
+
}
|
|
204
|
+
if (evt.data.currentState) {
|
|
205
|
+
output += chalk_1.default.yellow(` → ${evt.data.currentState}`);
|
|
206
|
+
}
|
|
207
|
+
if (evt.data.error) {
|
|
208
|
+
output += chalk_1.default.red(` ${evt.data.error}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return output;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Format for JSON output
|
|
215
|
+
*/
|
|
216
|
+
function formatJson(data) {
|
|
217
|
+
console.log(JSON.stringify(data, null, 2));
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Display transcript from API
|
|
221
|
+
*/
|
|
222
|
+
async function displayApiTranscript(session, options) {
|
|
223
|
+
const sessionId = session.apiSessionIdV2;
|
|
224
|
+
if (!sessionId) {
|
|
225
|
+
console.log(chalk_1.default.yellow('\nSession is not synced to API. No transcript available.'));
|
|
226
|
+
console.log(chalk_1.default.dim('Enable sync with: gb session start --sync'));
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
const limit = parseInt(options.tail || '50');
|
|
230
|
+
if (options.json) {
|
|
231
|
+
const messages = await fetchMessages(sessionId, limit);
|
|
232
|
+
const events = options.events ? await fetchEvents(sessionId, limit) : undefined;
|
|
233
|
+
formatJson({ messages, events });
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (options.events) {
|
|
237
|
+
// Show events
|
|
238
|
+
console.log(chalk_1.default.bold(`\nSession Events: ${session.name}`));
|
|
239
|
+
console.log(chalk_1.default.dim('─'.repeat(70)));
|
|
240
|
+
const events = await fetchEvents(sessionId, limit);
|
|
241
|
+
if (events.length === 0) {
|
|
242
|
+
console.log(chalk_1.default.dim('\nNo events recorded.\n'));
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
for (const evt of events.reverse()) {
|
|
246
|
+
console.log(formatEvent(evt));
|
|
247
|
+
}
|
|
248
|
+
console.log('');
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
// Show messages
|
|
252
|
+
console.log(chalk_1.default.bold(`\nSession Transcript: ${session.name}`));
|
|
253
|
+
console.log(chalk_1.default.dim('─'.repeat(70)));
|
|
254
|
+
const messages = await fetchMessages(sessionId, limit);
|
|
255
|
+
if (messages.length === 0) {
|
|
256
|
+
console.log(chalk_1.default.dim('\nNo messages recorded.\n'));
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
for (const msg of messages.reverse()) {
|
|
260
|
+
console.log(formatMessage(msg));
|
|
261
|
+
}
|
|
262
|
+
console.log('');
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Follow mode - continuously poll for updates
|
|
266
|
+
*/
|
|
267
|
+
async function followMode(session, options) {
|
|
268
|
+
const sessionId = session.apiSessionIdV2;
|
|
269
|
+
if (!sessionId) {
|
|
270
|
+
console.log(chalk_1.default.yellow('\nSession is not synced to API. Follow mode unavailable.'));
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
console.log(chalk_1.default.bold(`\nFollowing: ${session.name}`));
|
|
274
|
+
console.log(chalk_1.default.dim('─'.repeat(70)));
|
|
275
|
+
console.log(chalk_1.default.dim('Press Ctrl+C to stop\n'));
|
|
276
|
+
let lastMessageId = null;
|
|
277
|
+
let lastEventTime = null;
|
|
278
|
+
while (true) {
|
|
279
|
+
try {
|
|
280
|
+
if (options.events || options.tools) {
|
|
281
|
+
// Poll events
|
|
282
|
+
const events = await fetchEvents(sessionId, 20);
|
|
283
|
+
for (const evt of events.reverse()) {
|
|
284
|
+
if (lastEventTime && new Date(evt.createdAt) <= new Date(lastEventTime)) {
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
// Filter for tools only if --tools specified
|
|
288
|
+
if (options.tools &&
|
|
289
|
+
!evt.event.includes('tool') &&
|
|
290
|
+
evt.event !== 'state_change') {
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
console.log(formatEvent(evt));
|
|
294
|
+
lastEventTime = evt.createdAt;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
// Poll messages
|
|
299
|
+
const messages = await fetchMessages(sessionId, 10);
|
|
300
|
+
for (const msg of messages.reverse()) {
|
|
301
|
+
if (lastMessageId === msg.messageId)
|
|
302
|
+
continue;
|
|
303
|
+
if (lastMessageId) {
|
|
304
|
+
console.log(formatMessage(msg));
|
|
305
|
+
}
|
|
306
|
+
lastMessageId = msg.messageId;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
311
|
+
// Silently continue on errors
|
|
312
|
+
}
|
|
313
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Display local session info when no API sync
|
|
318
|
+
*/
|
|
319
|
+
function displayLocalSessionInfo(session) {
|
|
320
|
+
console.log(chalk_1.default.bold(`\nSession: ${session.name}`));
|
|
321
|
+
console.log(chalk_1.default.dim('─'.repeat(50)));
|
|
322
|
+
console.log(` Type: ${session.type}`);
|
|
323
|
+
console.log(` Provider: ${session.provider}`);
|
|
324
|
+
console.log(` Status: ${session.status}`);
|
|
325
|
+
console.log(` Created: ${new Date(session.createdAt).toLocaleString()}`);
|
|
326
|
+
if (session.infrastructure?.dtachSocketPath) {
|
|
327
|
+
console.log(` Socket: ${session.infrastructure.dtachSocketPath}`);
|
|
328
|
+
}
|
|
329
|
+
console.log(chalk_1.default.dim('\nThis session is not synced to API. Transcript not available.'));
|
|
330
|
+
console.log(chalk_1.default.dim('To enable recording, start sessions with --sync flag.'));
|
|
331
|
+
console.log('');
|
|
332
|
+
console.log(chalk_1.default.bold('To attach to this session:'));
|
|
333
|
+
console.log(chalk_1.default.cyan(` gb ${session.provider} attach ${session.name}\n`));
|
|
334
|
+
}
|
|
335
|
+
exports.sessionLogsCommand = new commander_1.Command('logs')
|
|
336
|
+
.description('View session transcript and activity logs')
|
|
337
|
+
.argument('<session>', 'Session name or ID')
|
|
338
|
+
.option('-f, --follow', 'Follow mode - continuously show new activity')
|
|
339
|
+
.option('--json', 'Output as JSON')
|
|
340
|
+
.option('-n, --tail <lines>', 'Number of messages to show (default: 50)')
|
|
341
|
+
.option('--events', 'Show events instead of messages')
|
|
342
|
+
.option('--tools', 'Show only tool executions (with -f)')
|
|
343
|
+
.addHelpText('after', `
|
|
344
|
+
Examples:
|
|
345
|
+
gb session logs claude-swift-fox # View transcript
|
|
346
|
+
gb session logs claude-swift-fox -f # Follow mode
|
|
347
|
+
gb session logs abc123 --events # View events
|
|
348
|
+
gb session logs abc123 --json # JSON output
|
|
349
|
+
`)
|
|
350
|
+
.action(async (sessionArg, options) => {
|
|
351
|
+
try {
|
|
352
|
+
const found = await findSession(sessionArg);
|
|
353
|
+
if (!found) {
|
|
354
|
+
console.log(chalk_1.default.red(`\nSession not found: ${sessionArg}`));
|
|
355
|
+
console.log(chalk_1.default.dim('\nRun `gb session list` to see available sessions.\n'));
|
|
356
|
+
process.exit(1);
|
|
357
|
+
}
|
|
358
|
+
if (found.remoteSession) {
|
|
359
|
+
console.log(chalk_1.default.yellow(`\nRemote session: ${found.remoteSession.name}`));
|
|
360
|
+
console.log(chalk_1.default.dim(`Running on: ${found.remoteSession.genboxName}`));
|
|
361
|
+
console.log(chalk_1.default.dim('\nRemote sessions must be attached to view logs.'));
|
|
362
|
+
console.log(chalk_1.default.cyan(`\n gb ${found.remoteSession.name.split('-')[0]} attach ${found.remoteSession.name}\n`));
|
|
363
|
+
process.exit(0);
|
|
364
|
+
}
|
|
365
|
+
const session = found.session;
|
|
366
|
+
// Check if session has API sync
|
|
367
|
+
if (!session.syncEnabled || !session.apiSessionIdV2) {
|
|
368
|
+
displayLocalSessionInfo(session);
|
|
369
|
+
process.exit(0);
|
|
370
|
+
}
|
|
371
|
+
if (options.follow) {
|
|
372
|
+
await followMode(session, options);
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
await displayApiTranscript(session, options);
|
|
376
|
+
}
|
|
377
|
+
process.exit(0);
|
|
378
|
+
}
|
|
379
|
+
catch (error) {
|
|
380
|
+
if (error instanceof api_1.AuthenticationError) {
|
|
381
|
+
console.error(chalk_1.default.red('\nNot logged in. Run `gb login` first.\n'));
|
|
382
|
+
process.exit(1);
|
|
383
|
+
}
|
|
384
|
+
console.error(chalk_1.default.red(`Error: ${error.message}`));
|
|
385
|
+
process.exit(1);
|
|
386
|
+
}
|
|
387
|
+
});
|