agentgui 1.0.759 → 1.0.761
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/lib/routes-tools.js +185 -0
- package/lib/routes-util.js +106 -0
- package/package.json +1 -1
- package/server.js +8 -361
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import * as toolInstallMachine from './tool-install-machine.js';
|
|
2
|
+
|
|
3
|
+
export function register(deps) {
|
|
4
|
+
const { sendJSON, parseBody, queries, broadcastSync, logError, toolManager } = deps;
|
|
5
|
+
|
|
6
|
+
function mapTool(t) {
|
|
7
|
+
return {
|
|
8
|
+
id: t.id, name: t.name, pkg: t.pkg,
|
|
9
|
+
category: t.category || 'plugin',
|
|
10
|
+
installed: t.installed || false,
|
|
11
|
+
status: t.installed ? (t.isUpToDate ? 'installed' : 'needs_update') : 'not_installed',
|
|
12
|
+
isUpToDate: t.isUpToDate || false,
|
|
13
|
+
upgradeNeeded: t.upgradeNeeded || false,
|
|
14
|
+
hasUpdate: (t.upgradeNeeded && t.installed) || false,
|
|
15
|
+
installedVersion: t.installedVersion || null,
|
|
16
|
+
publishedVersion: t.publishedVersion || null,
|
|
17
|
+
machineState: toolInstallMachine.getState(t.id),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const routes = {};
|
|
22
|
+
|
|
23
|
+
routes['GET /api/tools'] = async (req, res) => {
|
|
24
|
+
try {
|
|
25
|
+
const tools = await Promise.race([
|
|
26
|
+
toolManager.getAllToolsAsync(true),
|
|
27
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 5000))
|
|
28
|
+
]);
|
|
29
|
+
sendJSON(req, res, 200, { tools: tools.map(mapTool) });
|
|
30
|
+
} catch (err) {
|
|
31
|
+
sendJSON(req, res, 200, { tools: toolManager.getAllToolsSync().map(mapTool) });
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
routes['POST /api/tools/update'] = async (req, res) => {
|
|
36
|
+
const allToolIds = ['cli-claude', 'cli-opencode', 'cli-gemini', 'cli-kilo', 'cli-codex', 'gm-cc', 'gm-oc', 'gm-gc', 'gm-kilo'];
|
|
37
|
+
sendJSON(req, res, 200, { updating: true, toolCount: allToolIds.length });
|
|
38
|
+
broadcastSync({ type: 'tools_update_started', tools: allToolIds });
|
|
39
|
+
setImmediate(async () => {
|
|
40
|
+
const results = {};
|
|
41
|
+
for (const toolId of allToolIds) {
|
|
42
|
+
try {
|
|
43
|
+
const result = await toolManager.update(toolId, (msg) => broadcastSync({ type: 'tool_update_progress', toolId, data: msg }));
|
|
44
|
+
results[toolId] = result;
|
|
45
|
+
if (result.success) {
|
|
46
|
+
const version = result.version || null;
|
|
47
|
+
queries.updateToolStatus(toolId, { status: 'installed', version, installed_at: Date.now() });
|
|
48
|
+
queries.addToolInstallHistory(toolId, 'update', 'success', null);
|
|
49
|
+
const freshStatus = await toolManager.checkToolStatusAsync(toolId);
|
|
50
|
+
broadcastSync({ type: 'tool_update_complete', toolId, data: { ...result, ...freshStatus } });
|
|
51
|
+
} else {
|
|
52
|
+
queries.updateToolStatus(toolId, { status: 'failed', error_message: result.error });
|
|
53
|
+
queries.addToolInstallHistory(toolId, 'update', 'failed', result.error);
|
|
54
|
+
broadcastSync({ type: 'tool_update_failed', toolId, data: result });
|
|
55
|
+
}
|
|
56
|
+
} catch (err) {
|
|
57
|
+
queries.updateToolStatus(toolId, { status: 'failed', error_message: err.message });
|
|
58
|
+
queries.addToolInstallHistory(toolId, 'update', 'failed', err.message);
|
|
59
|
+
broadcastSync({ type: 'tool_update_failed', toolId, data: { success: false, error: err.message } });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
broadcastSync({ type: 'tools_update_complete', data: results });
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
routes['POST /api/tools/refresh-all'] = async (req, res) => {
|
|
67
|
+
sendJSON(req, res, 200, { refreshing: true, toolCount: 4 });
|
|
68
|
+
broadcastSync({ type: 'tools_refresh_started' });
|
|
69
|
+
setImmediate(async () => {
|
|
70
|
+
const tools = toolManager.getAllTools();
|
|
71
|
+
for (const tool of tools) {
|
|
72
|
+
queries.updateToolStatus(tool.id, { status: tool.installed ? 'installed' : 'not_installed', version: tool.installedVersion, last_check_at: Date.now() });
|
|
73
|
+
if (tool.installed) {
|
|
74
|
+
const status = await toolManager.checkToolStatusAsync(tool.id);
|
|
75
|
+
if (status?.upgradeNeeded) queries.updateToolStatus(tool.id, { update_available: 1, latest_version: status.publishedVersion });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
broadcastSync({ type: 'tools_refresh_complete', data: tools });
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
routes['_match'] = (method, pathOnly) => {
|
|
83
|
+
const key = `${method} ${pathOnly}`;
|
|
84
|
+
if (routes[key]) return routes[key];
|
|
85
|
+
let m;
|
|
86
|
+
if ((m = pathOnly.match(/^\/api\/tools\/([^/]+)\/status$/))) return (req, res) => handleToolStatus(req, res, m[1]);
|
|
87
|
+
if (method === 'POST' && (m = pathOnly.match(/^\/api\/tools\/([^/]+)\/install$/))) return (req, res) => handleToolInstall(req, res, m[1]);
|
|
88
|
+
if (method === 'POST' && (m = pathOnly.match(/^\/api\/tools\/([^/]+)\/update$/))) return (req, res) => handleToolUpdate(req, res, m[1]);
|
|
89
|
+
if (method === 'GET' && (m = pathOnly.match(/^\/api\/tools\/([^/]+)\/history$/))) return (req, res) => handleToolHistory(req, res, m[1]);
|
|
90
|
+
return null;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
async function handleToolStatus(req, res, toolId) {
|
|
94
|
+
const dbStatus = queries.getToolStatus(toolId);
|
|
95
|
+
const tmStatus = toolManager.checkToolStatus(toolId);
|
|
96
|
+
if (!tmStatus && !dbStatus) { sendJSON(req, res, 404, { error: 'Tool not found' }); return; }
|
|
97
|
+
const status = {
|
|
98
|
+
toolId, installed: tmStatus?.installed || (dbStatus?.status === 'installed'),
|
|
99
|
+
isUpToDate: tmStatus?.isUpToDate || false, upgradeNeeded: tmStatus?.upgradeNeeded || false,
|
|
100
|
+
status: dbStatus?.status || (tmStatus?.installed ? 'installed' : 'not_installed'),
|
|
101
|
+
installedVersion: dbStatus?.version || tmStatus?.installedVersion || null,
|
|
102
|
+
timestamp: Date.now(), error_message: dbStatus?.error_message || null
|
|
103
|
+
};
|
|
104
|
+
if (status.installed) { const updates = await toolManager.checkForUpdates(toolId); status.hasUpdate = updates.needsUpdate || false; }
|
|
105
|
+
sendJSON(req, res, 200, status);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function handleToolInstall(req, res, toolId) {
|
|
109
|
+
const tool = toolManager.getToolConfig(toolId);
|
|
110
|
+
if (!tool) { sendJSON(req, res, 404, { error: 'Tool not found' }); return; }
|
|
111
|
+
const existing = queries.getToolStatus(toolId);
|
|
112
|
+
if (!existing) queries.insertToolInstallation(toolId, { status: 'not_installed' });
|
|
113
|
+
queries.updateToolStatus(toolId, { status: 'installing' });
|
|
114
|
+
sendJSON(req, res, 200, { success: true, installing: true, estimatedTime: 60000 });
|
|
115
|
+
let done = false;
|
|
116
|
+
const timeout = setTimeout(() => {
|
|
117
|
+
if (!done) { done = true; queries.updateToolStatus(toolId, { status: 'failed', error_message: 'Install timeout after 6 minutes' }); broadcastSync({ type: 'tool_install_failed', toolId, data: { success: false, error: 'Install timeout after 6 minutes' } }); queries.addToolInstallHistory(toolId, 'install', 'failed', 'Install timeout after 6 minutes'); }
|
|
118
|
+
}, 360000);
|
|
119
|
+
toolManager.install(toolId, (msg) => broadcastSync({ type: 'tool_install_progress', toolId, data: msg })).then(async (result) => {
|
|
120
|
+
clearTimeout(timeout); if (done) return; done = true;
|
|
121
|
+
if (result.success) {
|
|
122
|
+
const version = result.version || null;
|
|
123
|
+
queries.updateToolStatus(toolId, { status: 'installed', version, installed_at: Date.now() });
|
|
124
|
+
const freshStatus = await toolManager.checkToolStatusAsync(toolId);
|
|
125
|
+
broadcastSync({ type: 'tool_install_complete', toolId, data: { success: true, version, ...freshStatus } });
|
|
126
|
+
queries.addToolInstallHistory(toolId, 'install', 'success', null);
|
|
127
|
+
} else {
|
|
128
|
+
queries.updateToolStatus(toolId, { status: 'failed', error_message: result.error });
|
|
129
|
+
broadcastSync({ type: 'tool_install_failed', toolId, data: result });
|
|
130
|
+
queries.addToolInstallHistory(toolId, 'install', 'failed', result.error);
|
|
131
|
+
}
|
|
132
|
+
}).catch((err) => {
|
|
133
|
+
clearTimeout(timeout); if (done) return; done = true;
|
|
134
|
+
const error = err?.message || 'Unknown error';
|
|
135
|
+
logError('toolInstall', err, { toolId });
|
|
136
|
+
queries.updateToolStatus(toolId, { status: 'failed', error_message: error });
|
|
137
|
+
broadcastSync({ type: 'tool_install_failed', toolId, data: { success: false, error } });
|
|
138
|
+
queries.addToolInstallHistory(toolId, 'install', 'failed', error);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function handleToolUpdate(req, res, toolId) {
|
|
143
|
+
const body = await parseBody(req);
|
|
144
|
+
const tool = toolManager.getToolConfig(toolId);
|
|
145
|
+
if (!tool) { sendJSON(req, res, 404, { error: 'Tool not found' }); return; }
|
|
146
|
+
const current = await toolManager.checkToolStatusAsync(toolId);
|
|
147
|
+
if (!current || !current.installed) { sendJSON(req, res, 400, { error: 'Tool not installed' }); return; }
|
|
148
|
+
queries.updateToolStatus(toolId, { status: 'updating' });
|
|
149
|
+
sendJSON(req, res, 200, { success: true, updating: true });
|
|
150
|
+
let done = false;
|
|
151
|
+
const timeout = setTimeout(() => {
|
|
152
|
+
if (!done) { done = true; queries.updateToolStatus(toolId, { status: 'failed', error_message: 'Update timeout after 6 minutes' }); broadcastSync({ type: 'tool_update_failed', toolId, data: { success: false, error: 'Update timeout after 6 minutes' } }); queries.addToolInstallHistory(toolId, 'update', 'failed', 'Update timeout after 6 minutes'); }
|
|
153
|
+
}, 360000);
|
|
154
|
+
toolManager.update(toolId, (msg) => broadcastSync({ type: 'tool_update_progress', toolId, data: msg })).then(async (result) => {
|
|
155
|
+
clearTimeout(timeout); if (done) return; done = true;
|
|
156
|
+
if (result.success) {
|
|
157
|
+
const version = result.version || null;
|
|
158
|
+
queries.updateToolStatus(toolId, { status: 'installed', version, installed_at: Date.now() });
|
|
159
|
+
const freshStatus = await toolManager.checkToolStatusAsync(toolId);
|
|
160
|
+
broadcastSync({ type: 'tool_update_complete', toolId, data: { success: true, version, ...freshStatus } });
|
|
161
|
+
queries.addToolInstallHistory(toolId, 'update', 'success', null);
|
|
162
|
+
} else {
|
|
163
|
+
queries.updateToolStatus(toolId, { status: 'failed', error_message: result.error });
|
|
164
|
+
broadcastSync({ type: 'tool_update_failed', toolId, data: result });
|
|
165
|
+
queries.addToolInstallHistory(toolId, 'update', 'failed', result.error);
|
|
166
|
+
}
|
|
167
|
+
}).catch((err) => {
|
|
168
|
+
clearTimeout(timeout); if (done) return; done = true;
|
|
169
|
+
const error = err?.message || 'Unknown error';
|
|
170
|
+
logError('toolUpdate', err, { toolId });
|
|
171
|
+
queries.updateToolStatus(toolId, { status: 'failed', error_message: error });
|
|
172
|
+
broadcastSync({ type: 'tool_update_failed', toolId, data: { success: false, error } });
|
|
173
|
+
queries.addToolInstallHistory(toolId, 'update', 'failed', error);
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async function handleToolHistory(req, res, toolId) {
|
|
178
|
+
const url = new URL(req.url, 'http://localhost');
|
|
179
|
+
const limit = Math.min(parseInt(url.searchParams.get('limit')) || 20, 100);
|
|
180
|
+
const offset = parseInt(url.searchParams.get('offset')) || 0;
|
|
181
|
+
sendJSON(req, res, 200, { history: queries.getToolInstallHistory(toolId, limit, offset) });
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return routes;
|
|
185
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import { execSync } from 'child_process';
|
|
5
|
+
|
|
6
|
+
export function register(deps) {
|
|
7
|
+
const { sendJSON, parseBody, queries, STARTUP_CWD, PKG_VERSION } = deps;
|
|
8
|
+
const routes = {};
|
|
9
|
+
|
|
10
|
+
routes['GET /api/import/claude-code'] = async (req, res) => {
|
|
11
|
+
const result = queries.importClaudeCodeConversations();
|
|
12
|
+
sendJSON(req, res, 200, { imported: result });
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
routes['GET /api/discover/claude-code'] = async (req, res) => {
|
|
16
|
+
const discovered = queries.discoverClaudeCodeConversations();
|
|
17
|
+
sendJSON(req, res, 200, { discovered });
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
routes['GET /api/home'] = async (req, res) => {
|
|
21
|
+
sendJSON(req, res, 200, { home: os.homedir(), cwd: STARTUP_CWD });
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
routes['GET /api/version'] = async (req, res) => {
|
|
25
|
+
sendJSON(req, res, 200, { version: PKG_VERSION });
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
routes['POST /api/clone'] = async (req, res) => {
|
|
29
|
+
const body = await parseBody(req);
|
|
30
|
+
const repo = (body.repo || '').trim();
|
|
31
|
+
if (!repo || !/^[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+$/.test(repo)) {
|
|
32
|
+
sendJSON(req, res, 400, { error: 'Invalid repo format. Use org/repo or user/repo' });
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const cloneDir = STARTUP_CWD || os.homedir();
|
|
36
|
+
const repoName = repo.split('/')[1];
|
|
37
|
+
const targetPath = path.join(cloneDir, repoName);
|
|
38
|
+
if (fs.existsSync(targetPath)) {
|
|
39
|
+
sendJSON(req, res, 409, { error: `Directory already exists: ${repoName}`, path: targetPath });
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const isWindows = os.platform() === 'win32';
|
|
44
|
+
execSync('git clone https://github.com/' + repo + '.git', {
|
|
45
|
+
cwd: cloneDir, encoding: 'utf-8', timeout: 120000,
|
|
46
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
47
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
|
|
48
|
+
shell: isWindows
|
|
49
|
+
});
|
|
50
|
+
sendJSON(req, res, 200, { ok: true, repo, path: targetPath, name: repoName });
|
|
51
|
+
} catch (err) {
|
|
52
|
+
sendJSON(req, res, 500, { error: (err.stderr || err.message || 'Clone failed').trim() });
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
routes['POST /api/folders'] = async (req, res) => {
|
|
57
|
+
const body = await parseBody(req);
|
|
58
|
+
const folderPath = body.path || STARTUP_CWD;
|
|
59
|
+
try {
|
|
60
|
+
const expandedPath = folderPath.startsWith('~') ? folderPath.replace('~', os.homedir()) : folderPath;
|
|
61
|
+
const entries = fs.readdirSync(expandedPath, { withFileTypes: true });
|
|
62
|
+
const folders = entries
|
|
63
|
+
.filter(e => e.isDirectory() && !e.name.startsWith('.'))
|
|
64
|
+
.map(e => ({ name: e.name }))
|
|
65
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
66
|
+
sendJSON(req, res, 200, { folders });
|
|
67
|
+
} catch (err) {
|
|
68
|
+
sendJSON(req, res, 400, { error: err.message });
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
routes['GET /api/git/check-remote-ownership'] = async (req, res) => {
|
|
73
|
+
try {
|
|
74
|
+
const isWindows = os.platform() === 'win32';
|
|
75
|
+
const result = execSync('git remote get-url origin' + (isWindows ? '' : ' 2>/dev/null'), { encoding: 'utf-8', cwd: STARTUP_CWD, shell: isWindows });
|
|
76
|
+
const remoteUrl = result.trim();
|
|
77
|
+
const statusResult = execSync('git status --porcelain' + (isWindows ? '' : ' 2>/dev/null'), { encoding: 'utf-8', cwd: STARTUP_CWD, shell: isWindows });
|
|
78
|
+
const hasChanges = statusResult.trim().length > 0;
|
|
79
|
+
const unpushedResult = execSync('git rev-list --count --not --remotes' + (isWindows ? '' : ' 2>/dev/null'), { encoding: 'utf-8', cwd: STARTUP_CWD, shell: isWindows });
|
|
80
|
+
const hasUnpushed = parseInt(unpushedResult.trim() || '0', 10) > 0;
|
|
81
|
+
const ownsRemote = !remoteUrl.includes('github.com/') || remoteUrl.includes(process.env.GITHUB_USER || '');
|
|
82
|
+
sendJSON(req, res, 200, { ownsRemote, hasChanges, hasUnpushed, remoteUrl });
|
|
83
|
+
} catch {
|
|
84
|
+
sendJSON(req, res, 200, { ownsRemote: false, hasChanges: false, hasUnpushed: false, remoteUrl: '' });
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
routes['POST /api/git/push'] = async (req, res) => {
|
|
89
|
+
try {
|
|
90
|
+
const isWindows = os.platform() === 'win32';
|
|
91
|
+
const gitCommand = isWindows
|
|
92
|
+
? 'git add -A & git commit -m "Auto-commit" & git push'
|
|
93
|
+
: 'git add -A && git commit -m "Auto-commit" && git push';
|
|
94
|
+
execSync(gitCommand, { encoding: 'utf-8', cwd: STARTUP_CWD, shell: isWindows });
|
|
95
|
+
sendJSON(req, res, 200, { success: true });
|
|
96
|
+
} catch (err) {
|
|
97
|
+
sendJSON(req, res, 500, { error: err.message });
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
routes['_match'] = (method, pathOnly) => {
|
|
102
|
+
return routes[`${method} ${pathOnly}`] || null;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
return routes;
|
|
106
|
+
}
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -21,6 +21,8 @@ import { getGeminiOAuthCreds, startGeminiOAuth, exchangeGeminiOAuthCode, handleG
|
|
|
21
21
|
import { initSpeechManager, getSpeech, ensurePocketTtsSetup, voiceCacheManager, modelDownloadState, broadcastModelProgress, ensureModelsDownloaded, eagerTTS } from './lib/speech-manager.js';
|
|
22
22
|
import { register as registerSpeechRoutes } from './lib/routes-speech.js';
|
|
23
23
|
import { register as registerOAuthRoutes } from './lib/routes-oauth.js';
|
|
24
|
+
import { register as registerUtilRoutes } from './lib/routes-util.js';
|
|
25
|
+
import { register as registerToolRoutes } from './lib/routes-tools.js';
|
|
24
26
|
import { startCodexOAuth, exchangeCodexOAuthCode, handleCodexOAuthCallback, getCodexOAuthStatus, getCodexOAuthState, CODEX_HOME, CODEX_AUTH_FILE } from './lib/oauth-codex.js';
|
|
25
27
|
import { WSOptimizer } from './lib/ws-optimizer.js';
|
|
26
28
|
import { WsRouter } from './lib/ws-protocol.js';
|
|
@@ -1277,264 +1279,8 @@ const server = http.createServer(async (req, res) => {
|
|
|
1277
1279
|
return;
|
|
1278
1280
|
}
|
|
1279
1281
|
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
try {
|
|
1283
|
-
// Return immediately with cached data (non-blocking) - skip network version checks
|
|
1284
|
-
const tools = await Promise.race([
|
|
1285
|
-
toolManager.getAllToolsAsync(true), // skipPublishedVersion=true for fast response
|
|
1286
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 5000))
|
|
1287
|
-
]);
|
|
1288
|
-
const result = tools.map((t) => ({
|
|
1289
|
-
id: t.id,
|
|
1290
|
-
name: t.name,
|
|
1291
|
-
pkg: t.pkg,
|
|
1292
|
-
category: t.category || 'plugin',
|
|
1293
|
-
installed: t.installed,
|
|
1294
|
-
status: t.installed ? (t.isUpToDate ? 'installed' : 'needs_update') : 'not_installed',
|
|
1295
|
-
isUpToDate: t.isUpToDate,
|
|
1296
|
-
upgradeNeeded: t.upgradeNeeded,
|
|
1297
|
-
hasUpdate: t.upgradeNeeded && t.installed,
|
|
1298
|
-
installedVersion: t.installedVersion,
|
|
1299
|
-
publishedVersion: t.publishedVersion,
|
|
1300
|
-
machineState: toolInstallMachine.getState(t.id),
|
|
1301
|
-
}));
|
|
1302
|
-
sendJSON(req, res, 200, { tools: result });
|
|
1303
|
-
} catch (err) {
|
|
1304
|
-
console.log('[TOOLS-API] Error getting tools, returning cached status:', err.message);
|
|
1305
|
-
const tools = toolManager.getAllToolsSync().map((t) => ({
|
|
1306
|
-
id: t.id,
|
|
1307
|
-
name: t.name,
|
|
1308
|
-
pkg: t.pkg,
|
|
1309
|
-
category: t.category || 'plugin',
|
|
1310
|
-
installed: t.installed || false,
|
|
1311
|
-
status: (t.installed) ? (t.isUpToDate ? 'installed' : 'needs_update') : 'not_installed',
|
|
1312
|
-
isUpToDate: t.isUpToDate || false,
|
|
1313
|
-
upgradeNeeded: t.upgradeNeeded || false,
|
|
1314
|
-
hasUpdate: (t.upgradeNeeded && t.installed) || false,
|
|
1315
|
-
installedVersion: t.installedVersion || null,
|
|
1316
|
-
publishedVersion: t.publishedVersion || null,
|
|
1317
|
-
machineState: toolInstallMachine.getState(t.id),
|
|
1318
|
-
}));
|
|
1319
|
-
sendJSON(req, res, 200, { tools });
|
|
1320
|
-
}
|
|
1321
|
-
return;
|
|
1322
|
-
}
|
|
1323
|
-
|
|
1324
|
-
if (pathOnly.match(/^\/api\/tools\/([^/]+)\/status$/)) {
|
|
1325
|
-
const toolId = pathOnly.match(/^\/api\/tools\/([^/]+)\/status$/)[1];
|
|
1326
|
-
const dbStatus = queries.getToolStatus(toolId);
|
|
1327
|
-
const tmStatus = toolManager.checkToolStatus(toolId);
|
|
1328
|
-
if (!tmStatus && !dbStatus) {
|
|
1329
|
-
sendJSON(req, res, 404, { error: 'Tool not found' });
|
|
1330
|
-
return;
|
|
1331
|
-
}
|
|
1332
|
-
|
|
1333
|
-
// Merge database status with tool manager status
|
|
1334
|
-
const status = {
|
|
1335
|
-
toolId,
|
|
1336
|
-
installed: tmStatus?.installed || (dbStatus?.status === 'installed'),
|
|
1337
|
-
isUpToDate: tmStatus?.isUpToDate || false,
|
|
1338
|
-
upgradeNeeded: tmStatus?.upgradeNeeded || false,
|
|
1339
|
-
status: dbStatus?.status || (tmStatus?.installed ? 'installed' : 'not_installed'),
|
|
1340
|
-
installedVersion: dbStatus?.version || tmStatus?.installedVersion || null,
|
|
1341
|
-
timestamp: Date.now(),
|
|
1342
|
-
error_message: dbStatus?.error_message || null
|
|
1343
|
-
};
|
|
1344
|
-
|
|
1345
|
-
if (status.installed) {
|
|
1346
|
-
const updates = await toolManager.checkForUpdates(toolId);
|
|
1347
|
-
status.hasUpdate = updates.needsUpdate || false;
|
|
1348
|
-
}
|
|
1349
|
-
sendJSON(req, res, 200, status);
|
|
1350
|
-
return;
|
|
1351
|
-
}
|
|
1352
|
-
|
|
1353
|
-
if (pathOnly.match(/^\/api\/tools\/([^/]+)\/install$/) && req.method === 'POST') {
|
|
1354
|
-
const toolId = pathOnly.match(/^\/api\/tools\/([^/]+)\/install$/)[1];
|
|
1355
|
-
const tool = toolManager.getToolConfig(toolId);
|
|
1356
|
-
if (!tool) {
|
|
1357
|
-
sendJSON(req, res, 404, { error: 'Tool not found' });
|
|
1358
|
-
return;
|
|
1359
|
-
}
|
|
1360
|
-
const existing = queries.getToolStatus(toolId);
|
|
1361
|
-
if (!existing) {
|
|
1362
|
-
queries.insertToolInstallation(toolId, { status: 'not_installed' });
|
|
1363
|
-
}
|
|
1364
|
-
queries.updateToolStatus(toolId, { status: 'installing' });
|
|
1365
|
-
sendJSON(req, res, 200, { success: true, installing: true, estimatedTime: 60000 });
|
|
1366
|
-
|
|
1367
|
-
let installCompleted = false;
|
|
1368
|
-
const installTimeout = setTimeout(() => {
|
|
1369
|
-
if (!installCompleted) {
|
|
1370
|
-
installCompleted = true;
|
|
1371
|
-
queries.updateToolStatus(toolId, { status: 'failed', error_message: 'Install timeout after 6 minutes' });
|
|
1372
|
-
broadcastSync({ type: 'tool_install_failed', toolId, data: { success: false, error: 'Install timeout after 6 minutes' } });
|
|
1373
|
-
queries.addToolInstallHistory(toolId, 'install', 'failed', 'Install timeout after 6 minutes');
|
|
1374
|
-
}
|
|
1375
|
-
}, 360000);
|
|
1376
|
-
|
|
1377
|
-
toolManager.install(toolId, (msg) => {
|
|
1378
|
-
broadcastSync({ type: 'tool_install_progress', toolId, data: msg });
|
|
1379
|
-
}).then(async (result) => {
|
|
1380
|
-
clearTimeout(installTimeout);
|
|
1381
|
-
if (installCompleted) return;
|
|
1382
|
-
installCompleted = true;
|
|
1383
|
-
if (result.success) {
|
|
1384
|
-
const version = result.version || null;
|
|
1385
|
-
console.log(`[TOOLS-API] Install succeeded for ${toolId}, version: ${version}`);
|
|
1386
|
-
queries.updateToolStatus(toolId, { status: 'installed', version, installed_at: Date.now() });
|
|
1387
|
-
const freshStatus = await toolManager.checkToolStatusAsync(toolId);
|
|
1388
|
-
console.log(`[TOOLS-API] Fresh status after install for ${toolId}:`, JSON.stringify(freshStatus));
|
|
1389
|
-
broadcastSync({ type: 'tool_install_complete', toolId, data: { success: true, version, ...freshStatus } });
|
|
1390
|
-
queries.addToolInstallHistory(toolId, 'install', 'success', null);
|
|
1391
|
-
} else {
|
|
1392
|
-
console.error(`[TOOLS-API] Install failed for ${toolId}:`, result.error);
|
|
1393
|
-
queries.updateToolStatus(toolId, { status: 'failed', error_message: result.error });
|
|
1394
|
-
broadcastSync({ type: 'tool_install_failed', toolId, data: result });
|
|
1395
|
-
queries.addToolInstallHistory(toolId, 'install', 'failed', result.error);
|
|
1396
|
-
}
|
|
1397
|
-
}).catch((err) => {
|
|
1398
|
-
clearTimeout(installTimeout);
|
|
1399
|
-
if (installCompleted) return;
|
|
1400
|
-
installCompleted = true;
|
|
1401
|
-
const error = err?.message || 'Unknown error';
|
|
1402
|
-
console.error(`[TOOLS-API] Install error for ${toolId}:`, error);
|
|
1403
|
-
logError('toolInstall', err, { toolId });
|
|
1404
|
-
queries.updateToolStatus(toolId, { status: 'failed', error_message: error });
|
|
1405
|
-
broadcastSync({ type: 'tool_install_failed', toolId, data: { success: false, error } });
|
|
1406
|
-
queries.addToolInstallHistory(toolId, 'install', 'failed', error);
|
|
1407
|
-
});
|
|
1408
|
-
return;
|
|
1409
|
-
}
|
|
1410
|
-
|
|
1411
|
-
if (pathOnly.match(/^\/api\/tools\/([^/]+)\/update$/) && req.method === 'POST') {
|
|
1412
|
-
const toolId = pathOnly.match(/^\/api\/tools\/([^/]+)\/update$/)[1];
|
|
1413
|
-
const body = await parseBody(req);
|
|
1414
|
-
const tool = toolManager.getToolConfig(toolId);
|
|
1415
|
-
if (!tool) {
|
|
1416
|
-
sendJSON(req, res, 404, { error: 'Tool not found' });
|
|
1417
|
-
return;
|
|
1418
|
-
}
|
|
1419
|
-
const current = await toolManager.checkToolStatusAsync(toolId);
|
|
1420
|
-
if (!current || !current.installed) {
|
|
1421
|
-
sendJSON(req, res, 400, { error: 'Tool not installed' });
|
|
1422
|
-
return;
|
|
1423
|
-
}
|
|
1424
|
-
queries.updateToolStatus(toolId, { status: 'updating' });
|
|
1425
|
-
sendJSON(req, res, 200, { success: true, updating: true });
|
|
1426
|
-
|
|
1427
|
-
let updateCompleted = false;
|
|
1428
|
-
const updateTimeout = setTimeout(() => {
|
|
1429
|
-
if (!updateCompleted) {
|
|
1430
|
-
updateCompleted = true;
|
|
1431
|
-
queries.updateToolStatus(toolId, { status: 'failed', error_message: 'Update timeout after 6 minutes' });
|
|
1432
|
-
broadcastSync({ type: 'tool_update_failed', toolId, data: { success: false, error: 'Update timeout after 6 minutes' } });
|
|
1433
|
-
queries.addToolInstallHistory(toolId, 'update', 'failed', 'Update timeout after 6 minutes');
|
|
1434
|
-
}
|
|
1435
|
-
}, 360000);
|
|
1436
|
-
|
|
1437
|
-
toolManager.update(toolId, (msg) => {
|
|
1438
|
-
broadcastSync({ type: 'tool_update_progress', toolId, data: msg });
|
|
1439
|
-
}).then(async (result) => {
|
|
1440
|
-
clearTimeout(updateTimeout);
|
|
1441
|
-
if (updateCompleted) return;
|
|
1442
|
-
updateCompleted = true;
|
|
1443
|
-
if (result.success) {
|
|
1444
|
-
const version = result.version || null;
|
|
1445
|
-
console.log(`[TOOLS-API] Update succeeded for ${toolId}, version: ${version}`);
|
|
1446
|
-
queries.updateToolStatus(toolId, { status: 'installed', version, installed_at: Date.now() });
|
|
1447
|
-
const freshStatus = await toolManager.checkToolStatusAsync(toolId);
|
|
1448
|
-
console.log(`[TOOLS-API] Fresh status after update for ${toolId}:`, JSON.stringify(freshStatus));
|
|
1449
|
-
broadcastSync({ type: 'tool_update_complete', toolId, data: { success: true, version, ...freshStatus } });
|
|
1450
|
-
queries.addToolInstallHistory(toolId, 'update', 'success', null);
|
|
1451
|
-
} else {
|
|
1452
|
-
console.error(`[TOOLS-API] Update failed for ${toolId}:`, result.error);
|
|
1453
|
-
queries.updateToolStatus(toolId, { status: 'failed', error_message: result.error });
|
|
1454
|
-
broadcastSync({ type: 'tool_update_failed', toolId, data: result });
|
|
1455
|
-
queries.addToolInstallHistory(toolId, 'update', 'failed', result.error);
|
|
1456
|
-
}
|
|
1457
|
-
}).catch((err) => {
|
|
1458
|
-
clearTimeout(updateTimeout);
|
|
1459
|
-
if (updateCompleted) return;
|
|
1460
|
-
updateCompleted = true;
|
|
1461
|
-
const error = err?.message || 'Unknown error';
|
|
1462
|
-
console.error(`[TOOLS-API] Update error for ${toolId}:`, error);
|
|
1463
|
-
logError('toolUpdate', err, { toolId });
|
|
1464
|
-
queries.updateToolStatus(toolId, { status: 'failed', error_message: error });
|
|
1465
|
-
broadcastSync({ type: 'tool_update_failed', toolId, data: { success: false, error } });
|
|
1466
|
-
queries.addToolInstallHistory(toolId, 'update', 'failed', error);
|
|
1467
|
-
});
|
|
1468
|
-
return;
|
|
1469
|
-
}
|
|
1470
|
-
|
|
1471
|
-
if (pathOnly.match(/^\/api\/tools\/([^/]+)\/history$/) && req.method === 'GET') {
|
|
1472
|
-
const toolId = pathOnly.match(/^\/api\/tools\/([^/]+)\/history$/)[1];
|
|
1473
|
-
const url = new URL(req.url, 'http://localhost');
|
|
1474
|
-
const limit = Math.min(parseInt(url.searchParams.get('limit')) || 20, 100);
|
|
1475
|
-
const offset = parseInt(url.searchParams.get('offset')) || 0;
|
|
1476
|
-
const history = queries.getToolInstallHistory(toolId, limit, offset);
|
|
1477
|
-
sendJSON(req, res, 200, { history });
|
|
1478
|
-
return;
|
|
1479
|
-
}
|
|
1480
|
-
|
|
1481
|
-
if (pathOnly === '/api/tools/update' && req.method === 'POST') {
|
|
1482
|
-
const allToolIds = ['cli-claude', 'cli-opencode', 'cli-gemini', 'cli-kilo', 'cli-codex', 'gm-cc', 'gm-oc', 'gm-gc', 'gm-kilo'];
|
|
1483
|
-
sendJSON(req, res, 200, { updating: true, toolCount: allToolIds.length });
|
|
1484
|
-
broadcastSync({ type: 'tools_update_started', tools: allToolIds });
|
|
1485
|
-
setImmediate(async () => {
|
|
1486
|
-
const toolIds = allToolIds;
|
|
1487
|
-
const results = {};
|
|
1488
|
-
for (const toolId of toolIds) {
|
|
1489
|
-
try {
|
|
1490
|
-
const result = await toolManager.update(toolId, (msg) => {
|
|
1491
|
-
broadcastSync({ type: 'tool_update_progress', toolId, data: msg });
|
|
1492
|
-
});
|
|
1493
|
-
results[toolId] = result;
|
|
1494
|
-
if (result.success) {
|
|
1495
|
-
const version = result.version || null;
|
|
1496
|
-
queries.updateToolStatus(toolId, { status: 'installed', version, installed_at: Date.now() });
|
|
1497
|
-
queries.addToolInstallHistory(toolId, 'update', 'success', null);
|
|
1498
|
-
const freshStatus = await toolManager.checkToolStatusAsync(toolId);
|
|
1499
|
-
broadcastSync({ type: 'tool_update_complete', toolId, data: { ...result, ...freshStatus } });
|
|
1500
|
-
} else {
|
|
1501
|
-
queries.updateToolStatus(toolId, { status: 'failed', error_message: result.error });
|
|
1502
|
-
queries.addToolInstallHistory(toolId, 'update', 'failed', result.error);
|
|
1503
|
-
broadcastSync({ type: 'tool_update_failed', toolId, data: result });
|
|
1504
|
-
}
|
|
1505
|
-
} catch (err) {
|
|
1506
|
-
queries.updateToolStatus(toolId, { status: 'failed', error_message: err.message });
|
|
1507
|
-
queries.addToolInstallHistory(toolId, 'update', 'failed', err.message);
|
|
1508
|
-
broadcastSync({ type: 'tool_update_failed', toolId, data: { success: false, error: err.message } });
|
|
1509
|
-
}
|
|
1510
|
-
}
|
|
1511
|
-
broadcastSync({ type: 'tools_update_complete', data: results });
|
|
1512
|
-
});
|
|
1513
|
-
return;
|
|
1514
|
-
}
|
|
1515
|
-
|
|
1516
|
-
if (pathOnly === '/api/tools/refresh-all' && req.method === 'POST') {
|
|
1517
|
-
sendJSON(req, res, 200, { refreshing: true, toolCount: 4 });
|
|
1518
|
-
broadcastSync({ type: 'tools_refresh_started' });
|
|
1519
|
-
setImmediate(async () => {
|
|
1520
|
-
const tools = toolManager.getAllTools();
|
|
1521
|
-
for (const tool of tools) {
|
|
1522
|
-
queries.updateToolStatus(tool.id, {
|
|
1523
|
-
status: tool.installed ? 'installed' : 'not_installed',
|
|
1524
|
-
version: tool.installedVersion,
|
|
1525
|
-
last_check_at: Date.now()
|
|
1526
|
-
});
|
|
1527
|
-
if (tool.installed) {
|
|
1528
|
-
const status = await toolManager.checkToolStatusAsync(tool.id);
|
|
1529
|
-
if (status && status.upgradeNeeded) {
|
|
1530
|
-
queries.updateToolStatus(tool.id, { update_available: 1, latest_version: status.publishedVersion });
|
|
1531
|
-
}
|
|
1532
|
-
}
|
|
1533
|
-
}
|
|
1534
|
-
broadcastSync({ type: 'tools_refresh_complete', data: tools });
|
|
1535
|
-
});
|
|
1536
|
-
return;
|
|
1537
|
-
}
|
|
1282
|
+
const toolHandler = _toolRoutes._match(req.method, pathOnly);
|
|
1283
|
+
if (toolHandler) { await toolHandler(req, res); return; }
|
|
1538
1284
|
|
|
1539
1285
|
if (pathOnly === '/api/ws-stats' && req.method === 'GET') {
|
|
1540
1286
|
const stats = wsOptimizer.getStats();
|
|
@@ -2175,113 +1921,12 @@ const server = http.createServer(async (req, res) => {
|
|
|
2175
1921
|
return;
|
|
2176
1922
|
}
|
|
2177
1923
|
|
|
2178
|
-
if (pathOnly === '/api/import/claude-code' && req.method === 'GET') {
|
|
2179
|
-
const result = queries.importClaudeCodeConversations();
|
|
2180
|
-
sendJSON(req, res, 200, { imported: result });
|
|
2181
|
-
return;
|
|
2182
|
-
}
|
|
2183
|
-
|
|
2184
|
-
if (pathOnly === '/api/discover/claude-code' && req.method === 'GET') {
|
|
2185
|
-
const discovered = queries.discoverClaudeCodeConversations();
|
|
2186
|
-
sendJSON(req, res, 200, { discovered });
|
|
2187
|
-
return;
|
|
2188
|
-
}
|
|
2189
|
-
|
|
2190
|
-
if (pathOnly === '/api/home' && req.method === 'GET') {
|
|
2191
|
-
sendJSON(req, res, 200, { home: os.homedir(), cwd: STARTUP_CWD });
|
|
2192
|
-
return;
|
|
2193
|
-
}
|
|
2194
|
-
|
|
2195
|
-
if (pathOnly === '/api/version' && req.method === 'GET') {
|
|
2196
|
-
sendJSON(req, res, 200, { version: PKG_VERSION });
|
|
2197
|
-
return;
|
|
2198
|
-
}
|
|
2199
|
-
|
|
2200
1924
|
const speechHandler = _speechRoutes._match(req.method, pathOnly);
|
|
2201
1925
|
if (speechHandler) { await speechHandler(req, res, pathOnly); return; }
|
|
2202
1926
|
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
const repo = (body.repo || '').trim();
|
|
2206
|
-
if (!repo || !/^[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+$/.test(repo)) {
|
|
2207
|
-
sendJSON(req, res, 400, { error: 'Invalid repo format. Use org/repo or user/repo' });
|
|
2208
|
-
return;
|
|
2209
|
-
}
|
|
2210
|
-
const cloneDir = STARTUP_CWD || os.homedir();
|
|
2211
|
-
const repoName = repo.split('/')[1];
|
|
2212
|
-
const targetPath = path.join(cloneDir, repoName);
|
|
2213
|
-
if (fs.existsSync(targetPath)) {
|
|
2214
|
-
sendJSON(req, res, 409, { error: `Directory already exists: ${repoName}`, path: targetPath });
|
|
2215
|
-
return;
|
|
2216
|
-
}
|
|
2217
|
-
try {
|
|
2218
|
-
const isWindows = os.platform() === 'win32';
|
|
2219
|
-
execSync('git clone https://github.com/' + repo + '.git', {
|
|
2220
|
-
cwd: cloneDir,
|
|
2221
|
-
encoding: 'utf-8',
|
|
2222
|
-
timeout: 120000,
|
|
2223
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
2224
|
-
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
|
|
2225
|
-
shell: isWindows
|
|
2226
|
-
});
|
|
2227
|
-
sendJSON(req, res, 200, { ok: true, repo, path: targetPath, name: repoName });
|
|
2228
|
-
} catch (err) {
|
|
2229
|
-
const stderr = err.stderr || err.message || 'Clone failed';
|
|
2230
|
-
sendJSON(req, res, 500, { error: stderr.trim() });
|
|
2231
|
-
}
|
|
2232
|
-
return;
|
|
2233
|
-
}
|
|
2234
|
-
|
|
2235
|
-
if (pathOnly === '/api/folders' && req.method === 'POST') {
|
|
2236
|
-
const body = await parseBody(req);
|
|
2237
|
-
const folderPath = body.path || STARTUP_CWD;
|
|
2238
|
-
try {
|
|
2239
|
-
const expandedPath = folderPath.startsWith('~') ?
|
|
2240
|
-
folderPath.replace('~', os.homedir()) : folderPath;
|
|
2241
|
-
const entries = fs.readdirSync(expandedPath, { withFileTypes: true });
|
|
2242
|
-
const folders = entries
|
|
2243
|
-
.filter(e => e.isDirectory() && !e.name.startsWith('.'))
|
|
2244
|
-
.map(e => ({ name: e.name }))
|
|
2245
|
-
.sort((a, b) => a.name.localeCompare(b.name));
|
|
2246
|
-
sendJSON(req, res, 200, { folders });
|
|
2247
|
-
} catch (err) {
|
|
2248
|
-
sendJSON(req, res, 400, { error: err.message });
|
|
2249
|
-
}
|
|
2250
|
-
return;
|
|
2251
|
-
}
|
|
1927
|
+
const utilHandler = _utilRoutes._match(req.method, pathOnly);
|
|
1928
|
+
if (utilHandler) { await utilHandler(req, res); return; }
|
|
2252
1929
|
|
|
2253
|
-
if (pathOnly === '/api/git/check-remote-ownership' && req.method === 'GET') {
|
|
2254
|
-
try {
|
|
2255
|
-
const isWindows = os.platform() === 'win32';
|
|
2256
|
-
const result = execSync('git remote get-url origin' + (isWindows ? '' : ' 2>/dev/null'), { encoding: 'utf-8', cwd: STARTUP_CWD, shell: isWindows });
|
|
2257
|
-
const remoteUrl = result.trim();
|
|
2258
|
-
const statusResult = execSync('git status --porcelain' + (isWindows ? '' : ' 2>/dev/null'), { encoding: 'utf-8', cwd: STARTUP_CWD, shell: isWindows });
|
|
2259
|
-
const hasChanges = statusResult.trim().length > 0;
|
|
2260
|
-
const unpushedResult = execSync('git rev-list --count --not --remotes' + (isWindows ? '' : ' 2>/dev/null'), { encoding: 'utf-8', cwd: STARTUP_CWD, shell: isWindows });
|
|
2261
|
-
const hasUnpushed = parseInt(unpushedResult.trim() || '0', 10) > 0;
|
|
2262
|
-
const ownsRemote = !remoteUrl.includes('github.com/') || remoteUrl.includes(process.env.GITHUB_USER || '');
|
|
2263
|
-
sendJSON(req, res, 200, { ownsRemote, hasChanges, hasUnpushed, remoteUrl });
|
|
2264
|
-
} catch {
|
|
2265
|
-
sendJSON(req, res, 200, { ownsRemote: false, hasChanges: false, hasUnpushed: false, remoteUrl: '' });
|
|
2266
|
-
}
|
|
2267
|
-
return;
|
|
2268
|
-
}
|
|
2269
|
-
|
|
2270
|
-
if (pathOnly === '/api/git/push' && req.method === 'POST') {
|
|
2271
|
-
try {
|
|
2272
|
-
const isWindows = os.platform() === 'win32';
|
|
2273
|
-
const gitCommand = isWindows
|
|
2274
|
-
? 'git add -A & git commit -m "Auto-commit" & git push'
|
|
2275
|
-
: 'git add -A && git commit -m "Auto-commit" && git push';
|
|
2276
|
-
execSync(gitCommand, { encoding: 'utf-8', cwd: STARTUP_CWD, shell: isWindows });
|
|
2277
|
-
sendJSON(req, res, 200, { success: true });
|
|
2278
|
-
} catch (err) {
|
|
2279
|
-
sendJSON(req, res, 500, { error: err.message });
|
|
2280
|
-
}
|
|
2281
|
-
return;
|
|
2282
|
-
}
|
|
2283
|
-
|
|
2284
|
-
// ============================================================
|
|
2285
1930
|
// THREAD API ENDPOINTS (ACP v0.2.3)
|
|
2286
1931
|
// ============================================================
|
|
2287
1932
|
|
|
@@ -3425,6 +3070,8 @@ const wsRouter = new WsRouter();
|
|
|
3425
3070
|
initSpeechManager({ broadcastSync, syncClients, queries });
|
|
3426
3071
|
const _speechRoutes = registerSpeechRoutes({ sendJSON, parseBody, broadcastSync, debugLog });
|
|
3427
3072
|
const _oauthRoutes = registerOAuthRoutes({ sendJSON, parseBody, PORT, BASE_URL, rootDir });
|
|
3073
|
+
const _utilRoutes = registerUtilRoutes({ sendJSON, parseBody, queries, STARTUP_CWD, PKG_VERSION });
|
|
3074
|
+
const _toolRoutes = registerToolRoutes({ sendJSON, parseBody, queries, broadcastSync, logError, toolManager });
|
|
3428
3075
|
|
|
3429
3076
|
registerConvHandlers(wsRouter, {
|
|
3430
3077
|
queries, activeExecutions, rateLimitState,
|