agent-tank 0.9.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/LICENSE +21 -0
- package/README.md +675 -0
- package/bin/agent-tank.js +301 -0
- package/media/www-preview.png +0 -0
- package/package.json +65 -0
- package/src/activity-monitor.js +312 -0
- package/src/agents/api-response-parser.js +255 -0
- package/src/agents/base.js +396 -0
- package/src/agents/claude.js +388 -0
- package/src/agents/codex-pty-helpers.js +226 -0
- package/src/agents/codex-rpc-helpers.js +167 -0
- package/src/agents/codex.js +383 -0
- package/src/agents/gemini.js +302 -0
- package/src/agents/keepalive-helper.js +115 -0
- package/src/agents/oauth-helper.js +210 -0
- package/src/agents/pty-output-parser.js +301 -0
- package/src/auto-refresh-manager.js +246 -0
- package/src/client-auto-refresh-helpers.js +143 -0
- package/src/client-auto-refresh.js +321 -0
- package/src/client-favicon.js +198 -0
- package/src/client-monitor.js +242 -0
- package/src/client-script.js +147 -0
- package/src/discovery.js +128 -0
- package/src/history-store.js +273 -0
- package/src/http-handler.js +182 -0
- package/src/icons.js +67 -0
- package/src/index.js +376 -0
- package/src/json-rpc-client.js +285 -0
- package/src/keepalive-manager.js +163 -0
- package/src/logger.js +213 -0
- package/src/pace-attachment.js +104 -0
- package/src/pace-evaluator.js +269 -0
- package/src/public-status.js +276 -0
- package/src/server.js +144 -0
- package/src/snapshot-metrics.js +79 -0
- package/src/status-page.css +1149 -0
- package/src/status-page.js +128 -0
- package/src/usage-formatters.js +259 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* History Store Module
|
|
3
|
+
*
|
|
4
|
+
* Stores historical usage snapshots to a local JSON file for trend analysis
|
|
5
|
+
* and pace evaluation. Automatically prunes records older than the configured
|
|
6
|
+
* retention period.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('node:fs');
|
|
10
|
+
const path = require('node:path');
|
|
11
|
+
const os = require('node:os');
|
|
12
|
+
|
|
13
|
+
// Default configuration
|
|
14
|
+
const DEFAULT_RETENTION_DAYS = 14;
|
|
15
|
+
const DEFAULT_HISTORY_DIR = path.join(os.homedir(), '.agent-tank');
|
|
16
|
+
const DEFAULT_HISTORY_FILE = 'history.json';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* HistoryStore manages persistent storage of usage snapshots.
|
|
20
|
+
*
|
|
21
|
+
* Usage snapshots are stored with timestamps and automatically pruned
|
|
22
|
+
* based on the configured retention period.
|
|
23
|
+
*/
|
|
24
|
+
class HistoryStore {
|
|
25
|
+
/**
|
|
26
|
+
* Create a HistoryStore instance.
|
|
27
|
+
*
|
|
28
|
+
* @param {Object} options - Configuration options
|
|
29
|
+
* @param {number} [options.retentionDays=14] - Number of days to retain history
|
|
30
|
+
* @param {string} [options.historyDir] - Directory for history file (default: ~/.agent-tank)
|
|
31
|
+
* @param {string} [options.historyFile] - History filename (default: history.json)
|
|
32
|
+
*/
|
|
33
|
+
constructor(options = {}) {
|
|
34
|
+
this.retentionDays = options.retentionDays ?? DEFAULT_RETENTION_DAYS;
|
|
35
|
+
this.historyDir = options.historyDir ?? DEFAULT_HISTORY_DIR;
|
|
36
|
+
this.historyFile = options.historyFile ?? DEFAULT_HISTORY_FILE;
|
|
37
|
+
this.historyPath = path.join(this.historyDir, this.historyFile);
|
|
38
|
+
|
|
39
|
+
// In-memory cache of history data
|
|
40
|
+
this._history = null;
|
|
41
|
+
this._initialized = false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Ensure the history directory exists.
|
|
46
|
+
* @private
|
|
47
|
+
*/
|
|
48
|
+
_ensureDirectory() {
|
|
49
|
+
if (!fs.existsSync(this.historyDir)) {
|
|
50
|
+
fs.mkdirSync(this.historyDir, { recursive: true });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Load history from disk into memory.
|
|
56
|
+
* @private
|
|
57
|
+
*/
|
|
58
|
+
_loadHistory() {
|
|
59
|
+
if (this._initialized) {
|
|
60
|
+
return this._history;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
this._ensureDirectory();
|
|
65
|
+
|
|
66
|
+
if (fs.existsSync(this.historyPath)) {
|
|
67
|
+
const data = fs.readFileSync(this.historyPath, 'utf8');
|
|
68
|
+
this._history = JSON.parse(data);
|
|
69
|
+
|
|
70
|
+
// Validate structure
|
|
71
|
+
if (!Array.isArray(this._history)) {
|
|
72
|
+
console.warn('[HistoryStore] Invalid history format, resetting to empty array');
|
|
73
|
+
this._history = [];
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
this._history = [];
|
|
77
|
+
}
|
|
78
|
+
} catch (err) {
|
|
79
|
+
console.error('[HistoryStore] Error loading history:', err.message);
|
|
80
|
+
this._history = [];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
this._initialized = true;
|
|
84
|
+
return this._history;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Save history from memory to disk.
|
|
89
|
+
* @private
|
|
90
|
+
*/
|
|
91
|
+
_saveHistory() {
|
|
92
|
+
try {
|
|
93
|
+
this._ensureDirectory();
|
|
94
|
+
fs.writeFileSync(this.historyPath, JSON.stringify(this._history, null, 2));
|
|
95
|
+
} catch (err) {
|
|
96
|
+
console.error('[HistoryStore] Error saving history:', err.message);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Calculate the cutoff timestamp for pruning.
|
|
102
|
+
* @private
|
|
103
|
+
* @returns {number} Unix timestamp in milliseconds
|
|
104
|
+
*/
|
|
105
|
+
_getCutoffTimestamp() {
|
|
106
|
+
const now = Date.now();
|
|
107
|
+
const retentionMs = this.retentionDays * 24 * 60 * 60 * 1000;
|
|
108
|
+
return now - retentionMs;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Prune records older than the retention period.
|
|
113
|
+
* @private
|
|
114
|
+
*/
|
|
115
|
+
_pruneOldRecords() {
|
|
116
|
+
const cutoff = this._getCutoffTimestamp();
|
|
117
|
+
const originalLength = this._history.length;
|
|
118
|
+
|
|
119
|
+
this._history = this._history.filter(record => {
|
|
120
|
+
const recordTime = new Date(record.timestamp).getTime();
|
|
121
|
+
return recordTime >= cutoff;
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const prunedCount = originalLength - this._history.length;
|
|
125
|
+
if (prunedCount > 0) {
|
|
126
|
+
console.log(`[HistoryStore] Pruned ${prunedCount} old records (>${this.retentionDays} days)`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Add a usage snapshot to history.
|
|
132
|
+
*
|
|
133
|
+
* @param {string} agentName - Name of the agent (e.g., 'claude', 'gemini', 'codex')
|
|
134
|
+
* @param {Object} usageData - The usage data to store
|
|
135
|
+
* @param {string} [timestamp] - ISO timestamp (defaults to current time)
|
|
136
|
+
* @returns {Object} The stored record
|
|
137
|
+
*/
|
|
138
|
+
addSnapshot(agentName, usageData, timestamp = null) {
|
|
139
|
+
if (!agentName || typeof agentName !== 'string') {
|
|
140
|
+
throw new Error('agentName is required and must be a string');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!usageData || typeof usageData !== 'object') {
|
|
144
|
+
throw new Error('usageData is required and must be an object');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
this._loadHistory();
|
|
148
|
+
|
|
149
|
+
const record = {
|
|
150
|
+
timestamp: timestamp || new Date().toISOString(),
|
|
151
|
+
agent: agentName,
|
|
152
|
+
usage: usageData
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
this._history.push(record);
|
|
156
|
+
this._pruneOldRecords();
|
|
157
|
+
this._saveHistory();
|
|
158
|
+
|
|
159
|
+
return record;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Get all history records, optionally filtered by agent.
|
|
164
|
+
*
|
|
165
|
+
* @param {string} [agentName] - Filter by agent name (optional)
|
|
166
|
+
* @returns {Array} Array of history records
|
|
167
|
+
*/
|
|
168
|
+
getHistory(agentName = null) {
|
|
169
|
+
this._loadHistory();
|
|
170
|
+
this._pruneOldRecords();
|
|
171
|
+
|
|
172
|
+
if (agentName) {
|
|
173
|
+
return this._history.filter(record => record.agent === agentName);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return [...this._history];
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Get the most recent snapshot for an agent.
|
|
181
|
+
*
|
|
182
|
+
* @param {string} agentName - Name of the agent
|
|
183
|
+
* @returns {Object|null} The most recent record or null
|
|
184
|
+
*/
|
|
185
|
+
getLatestSnapshot(agentName) {
|
|
186
|
+
const agentHistory = this.getHistory(agentName);
|
|
187
|
+
|
|
188
|
+
if (agentHistory.length === 0) {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Sort by timestamp descending and return the first
|
|
193
|
+
return agentHistory.sort((a, b) =>
|
|
194
|
+
new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
|
|
195
|
+
)[0];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Get snapshots within a specific time window.
|
|
200
|
+
*
|
|
201
|
+
* @param {string} agentName - Name of the agent
|
|
202
|
+
* @param {number} windowSeconds - Time window in seconds from now
|
|
203
|
+
* @returns {Array} Array of records within the window
|
|
204
|
+
*/
|
|
205
|
+
getSnapshotsInWindow(agentName, windowSeconds) {
|
|
206
|
+
const agentHistory = this.getHistory(agentName);
|
|
207
|
+
const cutoff = Date.now() - (windowSeconds * 1000);
|
|
208
|
+
|
|
209
|
+
return agentHistory.filter(record =>
|
|
210
|
+
new Date(record.timestamp).getTime() >= cutoff
|
|
211
|
+
).sort((a, b) =>
|
|
212
|
+
new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Clear all history records.
|
|
218
|
+
*
|
|
219
|
+
* @param {string} [agentName] - Clear only records for this agent (optional)
|
|
220
|
+
*/
|
|
221
|
+
clearHistory(agentName = null) {
|
|
222
|
+
this._loadHistory();
|
|
223
|
+
|
|
224
|
+
if (agentName) {
|
|
225
|
+
this._history = this._history.filter(record => record.agent !== agentName);
|
|
226
|
+
} else {
|
|
227
|
+
this._history = [];
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
this._saveHistory();
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Get statistics about stored history.
|
|
235
|
+
*
|
|
236
|
+
* @returns {Object} Statistics object
|
|
237
|
+
*/
|
|
238
|
+
getStats() {
|
|
239
|
+
this._loadHistory();
|
|
240
|
+
|
|
241
|
+
const stats = {
|
|
242
|
+
totalRecords: this._history.length,
|
|
243
|
+
retentionDays: this.retentionDays,
|
|
244
|
+
historyPath: this.historyPath,
|
|
245
|
+
agents: {}
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
for (const record of this._history) {
|
|
249
|
+
if (!stats.agents[record.agent]) {
|
|
250
|
+
stats.agents[record.agent] = {
|
|
251
|
+
count: 0,
|
|
252
|
+
oldest: null,
|
|
253
|
+
newest: null
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const agentStats = stats.agents[record.agent];
|
|
258
|
+
agentStats.count++;
|
|
259
|
+
|
|
260
|
+
const recordTime = new Date(record.timestamp).getTime();
|
|
261
|
+
if (!agentStats.oldest || recordTime < new Date(agentStats.oldest).getTime()) {
|
|
262
|
+
agentStats.oldest = record.timestamp;
|
|
263
|
+
}
|
|
264
|
+
if (!agentStats.newest || recordTime > new Date(agentStats.newest).getTime()) {
|
|
265
|
+
agentStats.newest = record.timestamp;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return stats;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
module.exports = { HistoryStore, DEFAULT_RETENTION_DAYS };
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP request handler utilities for AgentTank server.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const { statusPage } = require('./status-page.js');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Handle GET / - Status HTML page
|
|
9
|
+
*/
|
|
10
|
+
function handleStatusPage(res, tank) {
|
|
11
|
+
res.setHeader('Content-Type', 'text/html');
|
|
12
|
+
res.writeHead(200);
|
|
13
|
+
res.end(statusPage(tank.getStatus()));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Handle GET /status - JSON status for all agents
|
|
18
|
+
*/
|
|
19
|
+
function handleStatusJson(res, tank) {
|
|
20
|
+
const status = tank.getStatus();
|
|
21
|
+
console.log(`[HTTP] GET /status - returning status for ${Object.keys(status).length} agents`);
|
|
22
|
+
res.setHeader('Content-Type', 'application/json');
|
|
23
|
+
res.writeHead(200);
|
|
24
|
+
res.end(JSON.stringify(status, null, 2));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Handle GET /config - Server configuration
|
|
29
|
+
*/
|
|
30
|
+
function handleConfig(res, tank) {
|
|
31
|
+
const config = {
|
|
32
|
+
autoRefresh: {
|
|
33
|
+
enabled: tank.autoRefresh.enabled && tank.autoRefresh.interval > 0,
|
|
34
|
+
interval: tank.autoRefresh.interval,
|
|
35
|
+
},
|
|
36
|
+
history: {
|
|
37
|
+
retentionDays: tank.historyRetentionDays,
|
|
38
|
+
},
|
|
39
|
+
lastRefreshedAt: tank.lastRefreshedAt,
|
|
40
|
+
};
|
|
41
|
+
res.setHeader('Content-Type', 'application/json');
|
|
42
|
+
res.writeHead(200);
|
|
43
|
+
res.end(JSON.stringify(config, null, 2));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Handle GET /history - History statistics
|
|
48
|
+
*/
|
|
49
|
+
function handleHistoryStats(res, tank) {
|
|
50
|
+
const stats = tank.getHistoryStats();
|
|
51
|
+
res.setHeader('Content-Type', 'application/json');
|
|
52
|
+
res.writeHead(200);
|
|
53
|
+
res.end(JSON.stringify(stats, null, 2));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Handle GET /history/:agent - Agent-specific history
|
|
58
|
+
*/
|
|
59
|
+
function handleAgentHistory(res, tank, agentName) {
|
|
60
|
+
const history = tank.getHistory(agentName);
|
|
61
|
+
res.setHeader('Content-Type', 'application/json');
|
|
62
|
+
res.writeHead(200);
|
|
63
|
+
res.end(JSON.stringify(history, null, 2));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Handle GET /status/:agent - Agent-specific status
|
|
68
|
+
*/
|
|
69
|
+
function handleAgentStatus(res, tank, agentName) {
|
|
70
|
+
const status = tank.getAgentStatus(agentName);
|
|
71
|
+
if (!status) {
|
|
72
|
+
res.writeHead(404);
|
|
73
|
+
res.end(JSON.stringify({ error: 'Agent not found' }));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
res.setHeader('Content-Type', 'application/json');
|
|
77
|
+
res.writeHead(200);
|
|
78
|
+
res.end(JSON.stringify(status, null, 2));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Handle POST /refresh - Refresh all agents
|
|
83
|
+
*/
|
|
84
|
+
async function handleRefreshAll(res, tank) {
|
|
85
|
+
await tank.refreshAll();
|
|
86
|
+
res.setHeader('Content-Type', 'application/json');
|
|
87
|
+
res.writeHead(200);
|
|
88
|
+
res.end(JSON.stringify({ success: true, status: tank.getStatus() }));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Handle POST /refresh/:agent - Refresh specific agent
|
|
93
|
+
*/
|
|
94
|
+
async function handleRefreshAgent(res, tank, agentName) {
|
|
95
|
+
try {
|
|
96
|
+
await tank.refreshAgent(agentName);
|
|
97
|
+
res.setHeader('Content-Type', 'application/json');
|
|
98
|
+
res.writeHead(200);
|
|
99
|
+
res.end(JSON.stringify({ success: true, status: tank.getAgentStatus(agentName) }));
|
|
100
|
+
} catch (err) {
|
|
101
|
+
res.writeHead(404);
|
|
102
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Handle 404 - Not Found
|
|
108
|
+
*/
|
|
109
|
+
function handleNotFound(res) {
|
|
110
|
+
res.writeHead(404);
|
|
111
|
+
res.end(JSON.stringify({ error: 'Not found' }));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Route and handle HTTP requests.
|
|
116
|
+
* @param {Object} req - HTTP request
|
|
117
|
+
* @param {Object} res - HTTP response
|
|
118
|
+
* @param {Object} tank - AgentTank instance
|
|
119
|
+
*/
|
|
120
|
+
async function handleRequest(req, res, tank) {
|
|
121
|
+
const url = new URL(req.url, `http://localhost:${tank.port}`);
|
|
122
|
+
const path = url.pathname;
|
|
123
|
+
|
|
124
|
+
// CORS headers
|
|
125
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
126
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
127
|
+
|
|
128
|
+
if (req.method === 'OPTIONS') {
|
|
129
|
+
res.writeHead(204);
|
|
130
|
+
res.end();
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (!tank.authenticate(req, url)) {
|
|
135
|
+
res.setHeader('WWW-Authenticate', 'Basic realm="LLM Limit Watcher"');
|
|
136
|
+
res.writeHead(401);
|
|
137
|
+
res.end(JSON.stringify({ error: 'Unauthorized' }));
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Route requests
|
|
142
|
+
if (req.method === 'GET') {
|
|
143
|
+
if (path === '/') {
|
|
144
|
+
handleStatusPage(res, tank);
|
|
145
|
+
} else if (path === '/status') {
|
|
146
|
+
handleStatusJson(res, tank);
|
|
147
|
+
} else if (path === '/config') {
|
|
148
|
+
handleConfig(res, tank);
|
|
149
|
+
} else if (path === '/history') {
|
|
150
|
+
handleHistoryStats(res, tank);
|
|
151
|
+
} else if (path.startsWith('/history/')) {
|
|
152
|
+
handleAgentHistory(res, tank, path.slice(9));
|
|
153
|
+
} else if (path.startsWith('/status/')) {
|
|
154
|
+
handleAgentStatus(res, tank, path.slice(8));
|
|
155
|
+
} else {
|
|
156
|
+
handleNotFound(res);
|
|
157
|
+
}
|
|
158
|
+
} else if (req.method === 'POST') {
|
|
159
|
+
if (path === '/refresh') {
|
|
160
|
+
await handleRefreshAll(res, tank);
|
|
161
|
+
} else if (path.startsWith('/refresh/')) {
|
|
162
|
+
await handleRefreshAgent(res, tank, path.slice(9));
|
|
163
|
+
} else {
|
|
164
|
+
handleNotFound(res);
|
|
165
|
+
}
|
|
166
|
+
} else {
|
|
167
|
+
handleNotFound(res);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
module.exports = {
|
|
172
|
+
handleRequest,
|
|
173
|
+
handleStatusPage,
|
|
174
|
+
handleStatusJson,
|
|
175
|
+
handleConfig,
|
|
176
|
+
handleHistoryStats,
|
|
177
|
+
handleAgentHistory,
|
|
178
|
+
handleAgentStatus,
|
|
179
|
+
handleRefreshAll,
|
|
180
|
+
handleRefreshAgent,
|
|
181
|
+
handleNotFound
|
|
182
|
+
};
|
package/src/icons.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// Brand icons for each agent - inline SVG for performance and styling flexibility
|
|
2
|
+
|
|
3
|
+
// Tank logo icon for Agent Tank branding - minimalist U-shaped vertical tank
|
|
4
|
+
const tankIcon = `<svg class="tank-logo" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg">
|
|
5
|
+
<path d="M6 4v12a4 4 0 0 0 4 4h4a4 4 0 0 0 4-4V4"/>
|
|
6
|
+
<line x1="6" y1="4" x2="18" y2="4"/>
|
|
7
|
+
<line x1="8" y1="8" x2="16" y2="8" opacity="0.3"/>
|
|
8
|
+
<line x1="8" y1="12" x2="16" y2="12" opacity="0.3"/>
|
|
9
|
+
</svg>`;
|
|
10
|
+
|
|
11
|
+
const agentIcons = {
|
|
12
|
+
claude: `<svg class="agent-icon" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
|
13
|
+
<path d="M4.709 15.955l4.72-2.647.08-.23-.08-.128H9.2l-.79-.048-2.698-.073-2.339-.097-2.266-.122-.571-.121L0 11.784l.055-.352.48-.321.686.06 1.52.103 2.278.158 1.652.097 2.449.255h.389l.055-.157-.134-.098-.103-.097-2.358-1.596-2.552-1.688-1.336-.972-.724-.491-.364-.462-.158-1.008.656-.722.881.06.225.061.893.686 1.908 1.476 2.491 1.833.365.304.145-.103.019-.073-.164-.274-1.355-2.446-1.446-2.49-.644-1.032-.17-.619a2.97 2.97 0 01-.104-.729L6.283.134 6.696 0l.996.134.42.364.62 1.414 1.002 2.229 1.555 3.03.456.898.243.832.091.255h.158V9.01l.128-1.706.237-2.095.23-2.695.08-.76.376-.91.747-.492.584.28.48.685-.067.444-.286 1.851-.559 2.903-.364 1.942h.212l.243-.242.985-1.306 1.652-2.064.73-.82.85-.904.547-.431h1.033l.76 1.129-.34 1.166-1.064 1.347-.881 1.142-1.264 1.7-.79 1.36.073.11.188-.02 2.856-.606 1.543-.28 1.841-.315.833.388.091.395-.328.807-1.969.486-2.309.462-3.439.813-.042.03.049.061 1.549.146.662.036h1.622l3.02.225.79.522.474.638-.079.485-1.215.62-1.64-.389-3.829-.91-1.312-.329h-.182v.11l1.093 1.068 2.006 1.81 2.509 2.33.127.578-.322.455-.34-.049-2.205-1.657-.851-.747-1.926-1.62h-.128v.17l.444.649 2.345 3.521.122 1.08-.17.353-.608.213-.668-.122-1.374-1.925-1.415-2.167-1.143-1.943-.14.08-.674 7.254-.316.37-.729.28-.607-.461-.322-.747.322-1.476.389-1.924.315-1.53.286-1.9.17-.632-.012-.042-.14.018-1.434 1.967-2.18 2.945-1.726 1.845-.414.164-.717-.37.067-.662.401-.589 2.388-3.036 1.44-1.882.93-1.086-.006-.158h-.055L4.132 18.56l-1.13.146-.487-.456.061-.746.231-.243 1.908-1.312-.006.006z" />
|
|
14
|
+
</svg>`,
|
|
15
|
+
gemini: `<svg class="agent-icon" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
|
16
|
+
<path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" />
|
|
17
|
+
</svg>`,
|
|
18
|
+
codex: `<svg class="agent-icon" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
|
19
|
+
<path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z" />
|
|
20
|
+
</svg>`
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// Inline SVG refresh icon for card headers
|
|
24
|
+
const refreshIcon = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
25
|
+
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8"/>
|
|
26
|
+
<path d="M21 3v5h-5"/>
|
|
27
|
+
</svg>`;
|
|
28
|
+
|
|
29
|
+
// Crosshair/target icon for tracking metrics
|
|
30
|
+
const trackIcon = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
31
|
+
<circle cx="12" cy="12" r="10"/>
|
|
32
|
+
<circle cx="12" cy="12" r="6"/>
|
|
33
|
+
<circle cx="12" cy="12" r="2"/>
|
|
34
|
+
<line x1="12" y1="2" x2="12" y2="6"/>
|
|
35
|
+
<line x1="12" y1="18" x2="12" y2="22"/>
|
|
36
|
+
<line x1="2" y1="12" x2="6" y2="12"/>
|
|
37
|
+
<line x1="18" y1="12" x2="22" y2="12"/>
|
|
38
|
+
</svg>`;
|
|
39
|
+
|
|
40
|
+
// Monitor icon for top nav toggle
|
|
41
|
+
const monitorIcon = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
42
|
+
<rect x="2" y="3" width="20" height="14" rx="2" ry="2"/>
|
|
43
|
+
<line x1="8" y1="21" x2="16" y2="21"/>
|
|
44
|
+
<line x1="12" y1="17" x2="12" y2="21"/>
|
|
45
|
+
</svg>`;
|
|
46
|
+
|
|
47
|
+
// Sync icon (circular arrows) for Refresh All button
|
|
48
|
+
const syncIcon = `<svg class="sync-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
49
|
+
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8"/>
|
|
50
|
+
<path d="M21 3v5h-5"/>
|
|
51
|
+
</svg>`;
|
|
52
|
+
|
|
53
|
+
// Copy icon for copying API endpoint
|
|
54
|
+
const copyIcon = `<svg class="copy-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
55
|
+
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>
|
|
56
|
+
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>
|
|
57
|
+
</svg>`;
|
|
58
|
+
|
|
59
|
+
module.exports = {
|
|
60
|
+
agentIcons,
|
|
61
|
+
refreshIcon,
|
|
62
|
+
trackIcon,
|
|
63
|
+
monitorIcon,
|
|
64
|
+
tankIcon,
|
|
65
|
+
syncIcon,
|
|
66
|
+
copyIcon
|
|
67
|
+
};
|