gemini-cli-devtools 0.1.2 → 0.1.5
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/client/assets/index-CAfB090D.js +55 -0
- package/dist/client/index.html +1 -1
- package/dist/src/bin.js +0 -21
- package/dist/src/index.js +57 -114
- package/package.json +1 -1
- package/dist/client/assets/index-BCw3ir29.js +0 -55
package/dist/src/index.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import http from 'node:http';
|
|
7
7
|
import { EventEmitter } from 'node:events';
|
|
8
|
+
import { execFile } from 'node:child_process';
|
|
8
9
|
import fs from 'node:fs';
|
|
9
10
|
import path from 'node:path';
|
|
10
11
|
import os from 'node:os';
|
|
@@ -21,7 +22,7 @@ const isDev = (() => {
|
|
|
21
22
|
/**
|
|
22
23
|
* DevTools Viewer
|
|
23
24
|
*
|
|
24
|
-
*
|
|
25
|
+
* Receives logs via WebSocket from CLI sessions.
|
|
25
26
|
*/
|
|
26
27
|
export class DevTools extends EventEmitter {
|
|
27
28
|
static instance;
|
|
@@ -31,7 +32,6 @@ export class DevTools extends EventEmitter {
|
|
|
31
32
|
wss = null;
|
|
32
33
|
sessions = new Map();
|
|
33
34
|
port = 25417;
|
|
34
|
-
watchedFiles = new Map(); // filePath -> lastSize
|
|
35
35
|
constructor() {
|
|
36
36
|
super();
|
|
37
37
|
}
|
|
@@ -47,110 +47,6 @@ export class DevTools extends EventEmitter {
|
|
|
47
47
|
getConsoleLogs() {
|
|
48
48
|
return this.consoleLogs;
|
|
49
49
|
}
|
|
50
|
-
/**
|
|
51
|
-
* Main entry for log discovery.
|
|
52
|
-
* It scans both user home and system tmp for .gemini/tmp folders.
|
|
53
|
-
*/
|
|
54
|
-
setLogFile() {
|
|
55
|
-
const potentialRoots = [
|
|
56
|
-
path.join(os.homedir(), '.gemini', 'tmp'),
|
|
57
|
-
path.join(os.tmpdir(), '.gemini', 'tmp'),
|
|
58
|
-
];
|
|
59
|
-
for (const baseDir of potentialRoots) {
|
|
60
|
-
if (fs.existsSync(baseDir)) {
|
|
61
|
-
console.log(`🔍 Scanning for logs in: ${baseDir}`);
|
|
62
|
-
this.deepDiscover(baseDir);
|
|
63
|
-
this.watchRoot(baseDir);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
watchRoot(root) {
|
|
68
|
-
try {
|
|
69
|
-
fs.watch(root, { recursive: true }, (_event, filename) => {
|
|
70
|
-
if (filename &&
|
|
71
|
-
filename.includes('session-') &&
|
|
72
|
-
filename.endsWith('.jsonl')) {
|
|
73
|
-
this.deepDiscover(root);
|
|
74
|
-
}
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
catch {
|
|
78
|
-
setInterval(() => this.deepDiscover(root), 2000);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
deepDiscover(dir) {
|
|
82
|
-
try {
|
|
83
|
-
const items = fs.readdirSync(dir);
|
|
84
|
-
for (const item of items) {
|
|
85
|
-
const fullPath = path.join(dir, item);
|
|
86
|
-
let stats;
|
|
87
|
-
try {
|
|
88
|
-
stats = fs.statSync(fullPath);
|
|
89
|
-
}
|
|
90
|
-
catch {
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
if (stats.isDirectory()) {
|
|
94
|
-
if (item === 'logs' || item.length > 20) {
|
|
95
|
-
this.deepDiscover(fullPath);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
else if (item.startsWith('session-') && item.endsWith('.jsonl')) {
|
|
99
|
-
if (!this.watchedFiles.has(fullPath)) {
|
|
100
|
-
this.watchedFiles.set(fullPath, 0);
|
|
101
|
-
this.readNewLogs(fullPath, 0);
|
|
102
|
-
}
|
|
103
|
-
else if (stats.size > this.watchedFiles.get(fullPath)) {
|
|
104
|
-
this.readNewLogs(fullPath, this.watchedFiles.get(fullPath));
|
|
105
|
-
this.watchedFiles.set(fullPath, stats.size);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
catch {
|
|
111
|
-
/* ignore */
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
readNewLogs(filePath, startByte) {
|
|
115
|
-
try {
|
|
116
|
-
const filename = path.basename(filePath);
|
|
117
|
-
const sessionMatch = filename.match(/session-(.*)\.jsonl/);
|
|
118
|
-
const fallbackSessionId = sessionMatch ? sessionMatch[1] : undefined;
|
|
119
|
-
const stream = fs.createReadStream(filePath, { start: startByte });
|
|
120
|
-
let buffer = '';
|
|
121
|
-
stream.on('data', (chunk) => {
|
|
122
|
-
buffer += chunk.toString();
|
|
123
|
-
const lines = buffer.split('\n');
|
|
124
|
-
buffer = lines.pop() || '';
|
|
125
|
-
for (const line of lines) {
|
|
126
|
-
if (!line.trim())
|
|
127
|
-
continue;
|
|
128
|
-
try {
|
|
129
|
-
const entry = JSON.parse(line);
|
|
130
|
-
const sid = entry.sessionId || fallbackSessionId;
|
|
131
|
-
if (entry.type === 'console') {
|
|
132
|
-
this.addInternalConsoleLog(entry.payload, sid, entry.timestamp);
|
|
133
|
-
}
|
|
134
|
-
else if (entry.type === 'network') {
|
|
135
|
-
this.addInternalNetworkLog(entry.payload, sid, entry.timestamp);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
catch {
|
|
139
|
-
/* ignore */
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
try {
|
|
143
|
-
this.watchedFiles.set(filePath, fs.statSync(filePath).size);
|
|
144
|
-
}
|
|
145
|
-
catch {
|
|
146
|
-
/* ignore */
|
|
147
|
-
}
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
catch {
|
|
151
|
-
/* ignore */
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
50
|
addInternalConsoleLog(payload, sessionId, timestamp) {
|
|
155
51
|
this.consoleLogs.push({
|
|
156
52
|
...payload,
|
|
@@ -167,20 +63,36 @@ export class DevTools extends EventEmitter {
|
|
|
167
63
|
return;
|
|
168
64
|
const existingIndex = this.logs.findIndex((l) => l.id === payload.id);
|
|
169
65
|
if (existingIndex > -1) {
|
|
170
|
-
this.logs[existingIndex]
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
66
|
+
const existing = this.logs[existingIndex];
|
|
67
|
+
// Handle chunk accumulation
|
|
68
|
+
if (payload.chunk) {
|
|
69
|
+
const chunks = existing.chunks || [];
|
|
70
|
+
chunks.push(payload.chunk);
|
|
71
|
+
console.log(`[DevTools] Received chunk ${payload.chunk.index} for request ${payload.id}, total chunks: ${chunks.length}`);
|
|
72
|
+
this.logs[existingIndex] = {
|
|
73
|
+
...existing,
|
|
74
|
+
chunks,
|
|
75
|
+
sessionId: sessionId || existing.sessionId,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
this.logs[existingIndex] = {
|
|
80
|
+
...existing,
|
|
81
|
+
...payload,
|
|
82
|
+
sessionId: sessionId || existing.sessionId,
|
|
83
|
+
chunks: existing.chunks, // Preserve chunks
|
|
84
|
+
response: payload.response
|
|
85
|
+
? { ...existing.response, ...payload.response }
|
|
86
|
+
: existing.response,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
178
89
|
}
|
|
179
90
|
else if (payload.url) {
|
|
180
91
|
this.logs.push({
|
|
181
92
|
...payload,
|
|
182
93
|
sessionId,
|
|
183
94
|
timestamp: timestamp || Date.now(),
|
|
95
|
+
chunks: payload.chunk ? [payload.chunk] : undefined,
|
|
184
96
|
});
|
|
185
97
|
if (this.logs.length > 2000)
|
|
186
98
|
this.logs.shift();
|
|
@@ -262,6 +174,37 @@ export class DevTools extends EventEmitter {
|
|
|
262
174
|
}
|
|
263
175
|
});
|
|
264
176
|
}
|
|
177
|
+
else if (req.method === 'POST' && req.url === '/api/to-textproto') {
|
|
178
|
+
let body = '';
|
|
179
|
+
req.on('data', (chunk) => (body += chunk));
|
|
180
|
+
req.on('end', () => {
|
|
181
|
+
const tmpIn = path.join(os.tmpdir(), `req-${Date.now()}.json`);
|
|
182
|
+
fs.writeFileSync(tmpIn, body);
|
|
183
|
+
const script = `
|
|
184
|
+
import sys
|
|
185
|
+
sys.path.insert(0, '/tmp/proto_gen')
|
|
186
|
+
from google.cloud.aiplatform.v1 import prediction_service_pb2
|
|
187
|
+
from google.protobuf.json_format import Parse
|
|
188
|
+
from google.protobuf import text_format
|
|
189
|
+
import json
|
|
190
|
+
|
|
191
|
+
with open(sys.argv[1]) as f:
|
|
192
|
+
msg = Parse(f.read(), prediction_service_pb2.GenerateContentRequest(), ignore_unknown_fields=True)
|
|
193
|
+
print(text_format.MessageToString(msg, as_utf8=True), end='')
|
|
194
|
+
`;
|
|
195
|
+
execFile('/tmp/proto_venv/bin/python3', ['-c', script, tmpIn], { maxBuffer: 10 * 1024 * 1024 }, (err, stdout, stderr) => {
|
|
196
|
+
fs.unlinkSync(tmpIn);
|
|
197
|
+
if (err) {
|
|
198
|
+
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
199
|
+
res.end(stderr || err.message);
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
203
|
+
res.end(stdout);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
}
|
|
265
208
|
else if (isDev) {
|
|
266
209
|
// Dev mode: proxy to Vite
|
|
267
210
|
this.proxyToVite(req, res);
|