agentlytics 0.1.15 → 0.1.17
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/README.md +2 -0
- package/editors/antigravity.js +507 -0
- package/editors/claude.js +100 -1
- package/editors/codex.js +61 -0
- package/editors/copilot.js +68 -1
- package/editors/cursor.js +73 -1
- package/editors/index.js +22 -2
- package/editors/opencode.js +168 -18
- package/editors/vscode.js +70 -1
- package/editors/windsurf.js +154 -59
- package/package.json +1 -1
- package/server.js +10 -0
- package/ui/src/App.jsx +4 -1
- package/ui/src/lib/api.js +5 -0
- package/ui/src/pages/Subscriptions.jsx +413 -0
package/editors/copilot.js
CHANGED
|
@@ -171,6 +171,73 @@ function safeParse(str) {
|
|
|
171
171
|
try { return JSON.parse(str); } catch { return {}; }
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
+
// ============================================================
|
|
175
|
+
// Usage / quota data from GitHub Copilot internal API
|
|
176
|
+
// ============================================================
|
|
177
|
+
|
|
178
|
+
function getCopilotToken() {
|
|
179
|
+
// GitHub Copilot stores its OAuth token in ~/.config/github-copilot/apps.json
|
|
180
|
+
const appsPath = path.join(os.homedir(), '.config', 'github-copilot', 'apps.json');
|
|
181
|
+
try {
|
|
182
|
+
if (!fs.existsSync(appsPath)) return null;
|
|
183
|
+
const data = JSON.parse(fs.readFileSync(appsPath, 'utf-8'));
|
|
184
|
+
for (const entry of Object.values(data)) {
|
|
185
|
+
if (entry.oauth_token) return { token: entry.oauth_token, user: entry.user || null };
|
|
186
|
+
}
|
|
187
|
+
} catch {}
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function fetchCopilotStatus(token) {
|
|
192
|
+
return new Promise((resolve) => {
|
|
193
|
+
const https = require('https');
|
|
194
|
+
const req = https.get('https://api.github.com/copilot_internal/v2/token', {
|
|
195
|
+
headers: {
|
|
196
|
+
'Authorization': `token ${token}`,
|
|
197
|
+
'Accept': 'application/json',
|
|
198
|
+
'User-Agent': 'agentlytics/1.0',
|
|
199
|
+
},
|
|
200
|
+
timeout: 10000,
|
|
201
|
+
}, (res) => {
|
|
202
|
+
let data = '';
|
|
203
|
+
res.on('data', (chunk) => { data += chunk; });
|
|
204
|
+
res.on('end', () => {
|
|
205
|
+
try { resolve(JSON.parse(data)); } catch { resolve(null); }
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
req.on('error', () => resolve(null));
|
|
209
|
+
req.on('timeout', () => { req.destroy(); resolve(null); });
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async function getUsage() {
|
|
214
|
+
const creds = getCopilotToken();
|
|
215
|
+
if (!creds) return null;
|
|
216
|
+
|
|
217
|
+
const status = await fetchCopilotStatus(creds.token);
|
|
218
|
+
if (!status || status.message) return null;
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
source: 'copilot-cli',
|
|
222
|
+
plan: {
|
|
223
|
+
name: status.sku || null,
|
|
224
|
+
individual: status.individual || false,
|
|
225
|
+
},
|
|
226
|
+
features: {
|
|
227
|
+
chat: status.chat_enabled || false,
|
|
228
|
+
codeReview: status.code_review_enabled || false,
|
|
229
|
+
agentMode: status.agent_mode_auto_approval || false,
|
|
230
|
+
},
|
|
231
|
+
limits: {
|
|
232
|
+
quotas: status.limited_user_quotas || null,
|
|
233
|
+
resetDate: status.limited_user_reset_date || null,
|
|
234
|
+
},
|
|
235
|
+
user: {
|
|
236
|
+
login: creds.user || null,
|
|
237
|
+
},
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
174
241
|
const labels = { 'copilot-cli': 'Copilot CLI' };
|
|
175
242
|
|
|
176
|
-
module.exports = { name, labels, getChats, getMessages };
|
|
243
|
+
module.exports = { name, labels, getChats, getMessages, getUsage };
|
package/editors/cursor.js
CHANGED
|
@@ -341,6 +341,78 @@ function getMessages(chat) {
|
|
|
341
341
|
return msgs;
|
|
342
342
|
}
|
|
343
343
|
|
|
344
|
+
// ============================================================
|
|
345
|
+
// Usage / quota data from Cursor REST API
|
|
346
|
+
// ============================================================
|
|
347
|
+
|
|
348
|
+
function getCursorAccessToken() {
|
|
349
|
+
try {
|
|
350
|
+
const db = new Database(GLOBAL_STORAGE_DB, { readonly: true });
|
|
351
|
+
const row = db.prepare("SELECT value FROM ItemTable WHERE key = 'cursorAuth/accessToken'").get();
|
|
352
|
+
db.close();
|
|
353
|
+
return row ? row.value : null;
|
|
354
|
+
} catch { return null; }
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function cursorApiFetch(endpoint, token) {
|
|
358
|
+
return new Promise((resolve) => {
|
|
359
|
+
const https = require('https');
|
|
360
|
+
const url = `https://api2.cursor.sh/auth/${endpoint}`;
|
|
361
|
+
const req = https.get(url, {
|
|
362
|
+
headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
|
|
363
|
+
timeout: 10000,
|
|
364
|
+
}, (res) => {
|
|
365
|
+
let data = '';
|
|
366
|
+
res.on('data', (chunk) => { data += chunk; });
|
|
367
|
+
res.on('end', () => {
|
|
368
|
+
try { resolve(JSON.parse(data)); } catch { resolve(null); }
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
req.on('error', () => resolve(null));
|
|
372
|
+
req.on('timeout', () => { req.destroy(); resolve(null); });
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
async function getUsage() {
|
|
377
|
+
const token = getCursorAccessToken();
|
|
378
|
+
if (!token) return null;
|
|
379
|
+
|
|
380
|
+
const [profile, usage] = await Promise.all([
|
|
381
|
+
cursorApiFetch('full_stripe_profile', token),
|
|
382
|
+
cursorApiFetch('usage', token),
|
|
383
|
+
]);
|
|
384
|
+
|
|
385
|
+
if (!profile && !usage) return null;
|
|
386
|
+
|
|
387
|
+
const result = {
|
|
388
|
+
source: 'cursor',
|
|
389
|
+
plan: {
|
|
390
|
+
name: profile?.individualMembershipType || profile?.membershipType || null,
|
|
391
|
+
status: profile?.subscriptionStatus || null,
|
|
392
|
+
isTeamMember: profile?.isTeamMember || false,
|
|
393
|
+
isYearlyPlan: profile?.isYearlyPlan || false,
|
|
394
|
+
},
|
|
395
|
+
usage: {},
|
|
396
|
+
startOfMonth: usage?.startOfMonth || null,
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
// Parse per-model usage from the usage endpoint
|
|
400
|
+
if (usage) {
|
|
401
|
+
for (const [model, data] of Object.entries(usage)) {
|
|
402
|
+
if (model === 'startOfMonth') continue;
|
|
403
|
+
result.usage[model] = {
|
|
404
|
+
numRequests: data.numRequests || 0,
|
|
405
|
+
numRequestsTotal: data.numRequestsTotal || 0,
|
|
406
|
+
numTokens: data.numTokens || 0,
|
|
407
|
+
maxRequestUsage: data.maxRequestUsage || null,
|
|
408
|
+
maxTokenUsage: data.maxTokenUsage || null,
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return result;
|
|
414
|
+
}
|
|
415
|
+
|
|
344
416
|
const labels = { 'cursor': 'Cursor' };
|
|
345
417
|
|
|
346
|
-
module.exports = { name, labels, getChats, getMessages };
|
|
418
|
+
module.exports = { name, labels, getChats, getMessages, getUsage };
|
package/editors/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const cursor = require('./cursor');
|
|
2
2
|
const windsurf = require('./windsurf');
|
|
3
|
+
const antigravity = require('./antigravity');
|
|
3
4
|
const claude = require('./claude');
|
|
4
5
|
const vscode = require('./vscode');
|
|
5
6
|
const zed = require('./zed');
|
|
@@ -12,7 +13,7 @@ const commandcode = require('./commandcode');
|
|
|
12
13
|
const goose = require('./goose');
|
|
13
14
|
const kiro = require('./kiro');
|
|
14
15
|
|
|
15
|
-
const editors = [cursor, windsurf, claude, vscode, zed, opencode, codex, gemini, copilot, cursorAgent, commandcode, goose, kiro];
|
|
16
|
+
const editors = [cursor, windsurf, antigravity, claude, vscode, zed, opencode, codex, gemini, copilot, cursorAgent, commandcode, goose, kiro];
|
|
16
17
|
|
|
17
18
|
// Build a unified source → display-label map from all editor modules
|
|
18
19
|
const editorLabels = {};
|
|
@@ -60,4 +61,23 @@ function resetCaches() {
|
|
|
60
61
|
}
|
|
61
62
|
}
|
|
62
63
|
|
|
63
|
-
|
|
64
|
+
/**
|
|
65
|
+
* Get usage / quota data from all editors that support it.
|
|
66
|
+
* Returns an array of usage objects, one per editor/variant.
|
|
67
|
+
*/
|
|
68
|
+
async function getAllUsage() {
|
|
69
|
+
const results = [];
|
|
70
|
+
for (const editor of editors) {
|
|
71
|
+
if (typeof editor.getUsage !== 'function') continue;
|
|
72
|
+
try {
|
|
73
|
+
const usage = await editor.getUsage();
|
|
74
|
+
if (!usage) continue;
|
|
75
|
+
// Windsurf returns an array (one per variant), Cursor returns a single object
|
|
76
|
+
if (Array.isArray(usage)) results.push(...usage);
|
|
77
|
+
else results.push(usage);
|
|
78
|
+
} catch { /* skip broken adapters */ }
|
|
79
|
+
}
|
|
80
|
+
return results;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
module.exports = { getAllChats, getMessages, editors, editorLabels, resetCaches, getAllUsage };
|
package/editors/opencode.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const os = require('os');
|
|
4
|
+
const Database = require('better-sqlite3');
|
|
4
5
|
|
|
5
6
|
// OpenCode stores data in different locations depending on the platform
|
|
6
7
|
// - Windows: %LOCALAPPDATA%\opencode\storage (not Roaming)
|
|
@@ -22,6 +23,118 @@ const SESSION_DIR = path.join(STORAGE_DIR, 'session');
|
|
|
22
23
|
const MESSAGE_DIR = path.join(STORAGE_DIR, 'message');
|
|
23
24
|
const PART_DIR = path.join(STORAGE_DIR, 'part');
|
|
24
25
|
|
|
26
|
+
// OpenCode also stores data in a SQLite database (older/primary store)
|
|
27
|
+
function getOpenCodeDbPath() {
|
|
28
|
+
const home = os.homedir();
|
|
29
|
+
switch (process.platform) {
|
|
30
|
+
case 'win32':
|
|
31
|
+
return path.join(home, 'AppData', 'Local', 'opencode', 'opencode.db');
|
|
32
|
+
case 'darwin':
|
|
33
|
+
case 'linux':
|
|
34
|
+
default:
|
|
35
|
+
return path.join(home, '.local', 'share', 'opencode', 'opencode.db');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const DB_PATH = getOpenCodeDbPath();
|
|
40
|
+
|
|
41
|
+
// ============================================================
|
|
42
|
+
// Query SQLite using better-sqlite3
|
|
43
|
+
// ============================================================
|
|
44
|
+
|
|
45
|
+
function queryDb(sql) {
|
|
46
|
+
if (!fs.existsSync(DB_PATH)) return [];
|
|
47
|
+
try {
|
|
48
|
+
const db = new Database(DB_PATH, { readonly: true });
|
|
49
|
+
const rows = db.prepare(sql).all();
|
|
50
|
+
db.close();
|
|
51
|
+
return rows;
|
|
52
|
+
} catch {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function getSqliteSessions() {
|
|
58
|
+
return queryDb(
|
|
59
|
+
`SELECT s.id, s.title, s.directory, s.time_created, s.time_updated,
|
|
60
|
+
p.worktree, p.name as project_name,
|
|
61
|
+
(SELECT count(*) FROM message m WHERE m.session_id = s.id) as msg_count
|
|
62
|
+
FROM session s LEFT JOIN project p ON s.project_id = p.id
|
|
63
|
+
ORDER BY s.time_updated DESC`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function getSqliteMessages(sessionId) {
|
|
68
|
+
if (!fs.existsSync(DB_PATH)) return [];
|
|
69
|
+
try {
|
|
70
|
+
const db = new Database(DB_PATH, { readonly: true });
|
|
71
|
+
const messages = db.prepare(
|
|
72
|
+
`SELECT m.id as msg_id, m.data as msg_data, m.time_created
|
|
73
|
+
FROM message m WHERE m.session_id = ? ORDER BY m.time_created ASC`
|
|
74
|
+
).all(sessionId);
|
|
75
|
+
|
|
76
|
+
const result = [];
|
|
77
|
+
for (const msg of messages) {
|
|
78
|
+
let msgData;
|
|
79
|
+
try { msgData = JSON.parse(msg.msg_data); } catch { continue; }
|
|
80
|
+
|
|
81
|
+
const role = msgData.role;
|
|
82
|
+
if (!role) continue;
|
|
83
|
+
|
|
84
|
+
const parts = db.prepare(
|
|
85
|
+
`SELECT data FROM part WHERE message_id = ? ORDER BY time_created ASC`
|
|
86
|
+
).all(msg.msg_id);
|
|
87
|
+
|
|
88
|
+
const contentParts = [];
|
|
89
|
+
for (const part of parts) {
|
|
90
|
+
let partData;
|
|
91
|
+
try { partData = JSON.parse(part.data); } catch { continue; }
|
|
92
|
+
const type = partData.type;
|
|
93
|
+
|
|
94
|
+
if (type === 'text' && partData.text) {
|
|
95
|
+
contentParts.push(partData.text);
|
|
96
|
+
} else if (type === 'thinking' || type === 'reasoning') {
|
|
97
|
+
if (partData.text) contentParts.push(`[thinking] ${partData.text}`);
|
|
98
|
+
} else if (type === 'tool-call' || type === 'tool_use' || type === 'tool-use' || type === 'tool') {
|
|
99
|
+
const toolName = partData.name || partData.toolName || partData.tool || 'tool';
|
|
100
|
+
let argKeys = '';
|
|
101
|
+
try {
|
|
102
|
+
const input = typeof partData.input === 'string' ? JSON.parse(partData.input) : (partData.input || partData.args || partData.arguments || partData.state?.input || {});
|
|
103
|
+
argKeys = typeof input === 'object' ? Object.keys(input).join(', ') : '';
|
|
104
|
+
} catch {}
|
|
105
|
+
contentParts.push(`[tool-call: ${toolName}(${argKeys})]`);
|
|
106
|
+
} else if (type === 'tool-result' || type === 'tool_result') {
|
|
107
|
+
const preview = (partData.text || partData.output || partData.state?.output || '').substring(0, 500);
|
|
108
|
+
contentParts.push(`[tool-result] ${preview}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const content = contentParts.join('\n');
|
|
113
|
+
if (!content) continue;
|
|
114
|
+
|
|
115
|
+
let modelValue = null;
|
|
116
|
+
if (typeof msgData.modelID === 'string') {
|
|
117
|
+
modelValue = msgData.modelID;
|
|
118
|
+
} else if (msgData.model && typeof msgData.model === 'object' && msgData.model.modelID) {
|
|
119
|
+
modelValue = msgData.model.modelID;
|
|
120
|
+
} else if (typeof msgData.model === 'string') {
|
|
121
|
+
modelValue = msgData.model;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
result.push({
|
|
125
|
+
role: role === 'user' ? 'user' : role === 'assistant' ? 'assistant' : role,
|
|
126
|
+
content,
|
|
127
|
+
_model: modelValue,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
db.close();
|
|
132
|
+
return result;
|
|
133
|
+
} catch {
|
|
134
|
+
return [];
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
25
138
|
// ============================================================
|
|
26
139
|
// Scan JSON files from OpenCode storage
|
|
27
140
|
// ============================================================
|
|
@@ -156,27 +269,64 @@ function getMessagesForSession(sessionId) {
|
|
|
156
269
|
const name = 'opencode';
|
|
157
270
|
|
|
158
271
|
function getChats() {
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
272
|
+
const seen = new Set();
|
|
273
|
+
const chats = [];
|
|
274
|
+
|
|
275
|
+
// 1. JSON file-based sessions (newer storage format)
|
|
276
|
+
const fileSessions = getAllSessions();
|
|
277
|
+
for (const s of fileSessions) {
|
|
278
|
+
seen.add(s.id);
|
|
279
|
+
chats.push({
|
|
280
|
+
source: 'opencode',
|
|
281
|
+
composerId: s.id,
|
|
282
|
+
name: s.title || null,
|
|
283
|
+
createdAt: s.time?.created || null,
|
|
284
|
+
lastUpdatedAt: s.time?.updated || null,
|
|
285
|
+
mode: s.mode || 'opencode',
|
|
286
|
+
folder: s.directory || null,
|
|
287
|
+
encrypted: false,
|
|
288
|
+
bubbleCount: getMessageCount(s.id),
|
|
289
|
+
_agent: s.agent,
|
|
290
|
+
_model: s.modelID,
|
|
291
|
+
_provider: s.providerID,
|
|
292
|
+
_sessionData: s,
|
|
293
|
+
_storageType: 'file',
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// 2. SQLite sessions (older/primary store) — add any not already found in files
|
|
298
|
+
const dbSessions = getSqliteSessions();
|
|
299
|
+
for (const row of dbSessions) {
|
|
300
|
+
if (seen.has(row.id)) continue;
|
|
301
|
+
seen.add(row.id);
|
|
302
|
+
chats.push({
|
|
303
|
+
source: 'opencode',
|
|
304
|
+
composerId: row.id,
|
|
305
|
+
name: cleanTitle(row.title),
|
|
306
|
+
createdAt: row.time_created || null,
|
|
307
|
+
lastUpdatedAt: row.time_updated || null,
|
|
308
|
+
mode: 'opencode',
|
|
309
|
+
folder: row.worktree || row.directory || null,
|
|
310
|
+
encrypted: false,
|
|
311
|
+
bubbleCount: row.msg_count || 0,
|
|
312
|
+
_storageType: 'sqlite',
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return chats.sort((a, b) => (b.lastUpdatedAt || 0) - (a.lastUpdatedAt || 0));
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function cleanTitle(title) {
|
|
320
|
+
if (!title) return null;
|
|
321
|
+
if (title.startsWith('New session - ')) return null;
|
|
322
|
+
return title.substring(0, 120) || null;
|
|
176
323
|
}
|
|
177
324
|
|
|
178
325
|
function getMessages(chat) {
|
|
179
|
-
|
|
326
|
+
// Prefer file-based messages; fall back to SQLite
|
|
327
|
+
const fileMessages = getMessagesForSession(chat.composerId);
|
|
328
|
+
if (fileMessages.length > 0) return fileMessages;
|
|
329
|
+
return getSqliteMessages(chat.composerId);
|
|
180
330
|
}
|
|
181
331
|
|
|
182
332
|
const labels = { 'opencode': 'OpenCode' };
|
package/editors/vscode.js
CHANGED
|
@@ -315,6 +315,75 @@ function getMessages(chat) {
|
|
|
315
315
|
return messages;
|
|
316
316
|
}
|
|
317
317
|
|
|
318
|
+
// ============================================================
|
|
319
|
+
// Usage / quota data from GitHub Copilot internal API
|
|
320
|
+
// ============================================================
|
|
321
|
+
|
|
322
|
+
function getCopilotToken() {
|
|
323
|
+
const appsPath = path.join(os.homedir(), '.config', 'github-copilot', 'apps.json');
|
|
324
|
+
try {
|
|
325
|
+
if (!fs.existsSync(appsPath)) return null;
|
|
326
|
+
const data = JSON.parse(fs.readFileSync(appsPath, 'utf-8'));
|
|
327
|
+
// Pick the first available oauth_token
|
|
328
|
+
for (const entry of Object.values(data)) {
|
|
329
|
+
if (entry.oauth_token) return { token: entry.oauth_token, user: entry.user || null };
|
|
330
|
+
}
|
|
331
|
+
} catch {}
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function fetchCopilotStatus(token) {
|
|
336
|
+
return new Promise((resolve) => {
|
|
337
|
+
const https = require('https');
|
|
338
|
+
const req = https.get('https://api.github.com/copilot_internal/v2/token', {
|
|
339
|
+
headers: {
|
|
340
|
+
'Authorization': `token ${token}`,
|
|
341
|
+
'Accept': 'application/json',
|
|
342
|
+
'User-Agent': 'agentlytics/1.0',
|
|
343
|
+
},
|
|
344
|
+
timeout: 10000,
|
|
345
|
+
}, (res) => {
|
|
346
|
+
let data = '';
|
|
347
|
+
res.on('data', (chunk) => { data += chunk; });
|
|
348
|
+
res.on('end', () => {
|
|
349
|
+
try { resolve(JSON.parse(data)); } catch { resolve(null); }
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
req.on('error', () => resolve(null));
|
|
353
|
+
req.on('timeout', () => { req.destroy(); resolve(null); });
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
async function getUsage() {
|
|
358
|
+
const creds = getCopilotToken();
|
|
359
|
+
if (!creds) return null;
|
|
360
|
+
|
|
361
|
+
const status = await fetchCopilotStatus(creds.token);
|
|
362
|
+
if (!status || status.message) return null;
|
|
363
|
+
|
|
364
|
+
return {
|
|
365
|
+
source: 'vscode',
|
|
366
|
+
plan: {
|
|
367
|
+
name: status.sku || null,
|
|
368
|
+
individual: status.individual || false,
|
|
369
|
+
},
|
|
370
|
+
features: {
|
|
371
|
+
chat: status.chat_enabled || false,
|
|
372
|
+
codeReview: status.code_review_enabled || false,
|
|
373
|
+
agentMode: status.agent_mode_auto_approval || false,
|
|
374
|
+
xcode: status.xcode || false,
|
|
375
|
+
mcp: status.mcp || false,
|
|
376
|
+
},
|
|
377
|
+
limits: {
|
|
378
|
+
quotas: status.limited_user_quotas || null,
|
|
379
|
+
resetDate: status.limited_user_reset_date || null,
|
|
380
|
+
},
|
|
381
|
+
user: {
|
|
382
|
+
login: creds.user || null,
|
|
383
|
+
},
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
318
387
|
const labels = { 'vscode': 'VS Code', 'vscode-insiders': 'VS Code Insiders' };
|
|
319
388
|
|
|
320
|
-
module.exports = { name, labels, getChats, getMessages };
|
|
389
|
+
module.exports = { name, labels, getChats, getMessages, getUsage };
|