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/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
- * Deeply discovers and tails session logs across all projects.
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
- ...this.logs[existingIndex],
172
- ...payload,
173
- sessionId: sessionId || this.logs[existingIndex].sessionId,
174
- response: payload.response
175
- ? { ...this.logs[existingIndex].response, ...payload.response }
176
- : this.logs[existingIndex].response,
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gemini-cli-devtools",
3
- "version": "0.1.2",
3
+ "version": "0.1.5",
4
4
  "type": "module",
5
5
  "main": "dist/src/index.js",
6
6
  "types": "dist/src/index.d.ts",