agentgui 1.0.758 → 1.0.760
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/claude-runner.js +1266 -189
- package/lib/routes-oauth.js +105 -0
- package/lib/routes-util.js +106 -0
- package/package.json +1 -1
- package/server.js +8 -227
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { startGeminiOAuth, exchangeGeminiOAuthCode, getGeminiOAuthState } from './oauth-gemini.js';
|
|
2
|
+
import { startCodexOAuth, exchangeCodexOAuthCode, getCodexOAuthState } from './oauth-codex.js';
|
|
3
|
+
|
|
4
|
+
export function register(deps) {
|
|
5
|
+
const { sendJSON, parseBody, PORT, BASE_URL, rootDir } = deps;
|
|
6
|
+
const routes = {};
|
|
7
|
+
|
|
8
|
+
routes['POST /api/gemini-oauth/start'] = async (req, res) => {
|
|
9
|
+
try {
|
|
10
|
+
const result = await startGeminiOAuth(req, { PORT, BASE_URL, rootDir });
|
|
11
|
+
sendJSON(req, res, 200, { authUrl: result.authUrl, mode: result.mode });
|
|
12
|
+
} catch (e) {
|
|
13
|
+
console.error('[gemini-oauth] /api/gemini-oauth/start failed:', e);
|
|
14
|
+
sendJSON(req, res, 500, { error: e.message });
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
routes['GET /api/gemini-oauth/status'] = async (req, res) => {
|
|
19
|
+
sendJSON(req, res, 200, getGeminiOAuthState());
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
routes['POST /api/gemini-oauth/relay'] = async (req, res) => {
|
|
23
|
+
try {
|
|
24
|
+
const body = await parseBody(req);
|
|
25
|
+
const { code, state: stateParam } = body;
|
|
26
|
+
if (!code || !stateParam) { sendJSON(req, res, 400, { error: 'Missing code or state' }); return; }
|
|
27
|
+
const email = await exchangeGeminiOAuthCode(code, stateParam);
|
|
28
|
+
sendJSON(req, res, 200, { success: true, email });
|
|
29
|
+
} catch (e) {
|
|
30
|
+
sendJSON(req, res, 400, { error: e.message });
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
routes['POST /api/gemini-oauth/complete'] = async (req, res) => {
|
|
35
|
+
try {
|
|
36
|
+
const body = await parseBody(req);
|
|
37
|
+
const pastedUrl = (body.url || '').trim();
|
|
38
|
+
if (!pastedUrl) { sendJSON(req, res, 400, { error: 'No URL provided' }); return; }
|
|
39
|
+
let parsed;
|
|
40
|
+
try { parsed = new URL(pastedUrl); } catch (_) {
|
|
41
|
+
sendJSON(req, res, 400, { error: 'Invalid URL. Paste the full URL from the browser address bar.' }); return;
|
|
42
|
+
}
|
|
43
|
+
const error = parsed.searchParams.get('error');
|
|
44
|
+
if (error) { sendJSON(req, res, 200, { error: parsed.searchParams.get('error_description') || error }); return; }
|
|
45
|
+
const code = parsed.searchParams.get('code');
|
|
46
|
+
const state = parsed.searchParams.get('state');
|
|
47
|
+
const email = await exchangeGeminiOAuthCode(code, state);
|
|
48
|
+
sendJSON(req, res, 200, { success: true, email });
|
|
49
|
+
} catch (e) {
|
|
50
|
+
sendJSON(req, res, 400, { error: e.message });
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
routes['POST /api/codex-oauth/start'] = async (req, res) => {
|
|
55
|
+
try {
|
|
56
|
+
const result = await startCodexOAuth(req, { PORT, BASE_URL });
|
|
57
|
+
sendJSON(req, res, 200, { authUrl: result.authUrl, mode: result.mode });
|
|
58
|
+
} catch (e) {
|
|
59
|
+
console.error('[codex-oauth] /api/codex-oauth/start failed:', e);
|
|
60
|
+
sendJSON(req, res, 500, { error: e.message });
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
routes['GET /api/codex-oauth/status'] = async (req, res) => {
|
|
65
|
+
sendJSON(req, res, 200, getCodexOAuthState());
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
routes['POST /api/codex-oauth/relay'] = async (req, res) => {
|
|
69
|
+
try {
|
|
70
|
+
const body = await parseBody(req);
|
|
71
|
+
const { code, state: stateParam } = body;
|
|
72
|
+
if (!code || !stateParam) { sendJSON(req, res, 400, { error: 'Missing code or state' }); return; }
|
|
73
|
+
const email = await exchangeCodexOAuthCode(code, stateParam);
|
|
74
|
+
sendJSON(req, res, 200, { success: true, email });
|
|
75
|
+
} catch (e) {
|
|
76
|
+
sendJSON(req, res, 400, { error: e.message });
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
routes['POST /api/codex-oauth/complete'] = async (req, res) => {
|
|
81
|
+
try {
|
|
82
|
+
const body = await parseBody(req);
|
|
83
|
+
const pastedUrl = (body.url || '').trim();
|
|
84
|
+
if (!pastedUrl) { sendJSON(req, res, 400, { error: 'No URL provided' }); return; }
|
|
85
|
+
let parsed;
|
|
86
|
+
try { parsed = new URL(pastedUrl); } catch (_) {
|
|
87
|
+
sendJSON(req, res, 400, { error: 'Invalid URL. Paste the full URL from the browser address bar.' }); return;
|
|
88
|
+
}
|
|
89
|
+
const error = parsed.searchParams.get('error');
|
|
90
|
+
if (error) { sendJSON(req, res, 200, { error: parsed.searchParams.get('error_description') || error }); return; }
|
|
91
|
+
const code = parsed.searchParams.get('code');
|
|
92
|
+
const state = parsed.searchParams.get('state');
|
|
93
|
+
const email = await exchangeCodexOAuthCode(code, state);
|
|
94
|
+
sendJSON(req, res, 200, { success: true, email });
|
|
95
|
+
} catch (e) {
|
|
96
|
+
sendJSON(req, res, 400, { error: e.message });
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
routes['_match'] = (method, pathOnly) => {
|
|
101
|
+
return routes[`${method} ${pathOnly}`] || null;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
return routes;
|
|
105
|
+
}
|
|
@@ -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
|
@@ -20,6 +20,8 @@ import { findCommand, queryACPServerAgents, discoverAgents, discoverExternalACPS
|
|
|
20
20
|
import { getGeminiOAuthCreds, startGeminiOAuth, exchangeGeminiOAuthCode, handleGeminiOAuthCallback, getGeminiOAuthStatus, getGeminiOAuthState } from './lib/oauth-gemini.js';
|
|
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
|
+
import { register as registerOAuthRoutes } from './lib/routes-oauth.js';
|
|
24
|
+
import { register as registerUtilRoutes } from './lib/routes-util.js';
|
|
23
25
|
import { startCodexOAuth, exchangeCodexOAuthCode, handleCodexOAuthCallback, getCodexOAuthStatus, getCodexOAuthState, CODEX_HOME, CODEX_AUTH_FILE } from './lib/oauth-codex.js';
|
|
24
26
|
import { WSOptimizer } from './lib/ws-optimizer.js';
|
|
25
27
|
import { WsRouter } from './lib/ws-protocol.js';
|
|
@@ -1999,130 +2001,8 @@ const server = http.createServer(async (req, res) => {
|
|
|
1999
2001
|
return;
|
|
2000
2002
|
}
|
|
2001
2003
|
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
const result = await startGeminiOAuth(req, { PORT, BASE_URL, rootDir });
|
|
2005
|
-
sendJSON(req, res, 200, { authUrl: result.authUrl, mode: result.mode });
|
|
2006
|
-
} catch (e) {
|
|
2007
|
-
console.error('[gemini-oauth] /api/gemini-oauth/start failed:', e);
|
|
2008
|
-
sendJSON(req, res, 500, { error: e.message });
|
|
2009
|
-
}
|
|
2010
|
-
return;
|
|
2011
|
-
}
|
|
2012
|
-
|
|
2013
|
-
if (pathOnly === '/api/gemini-oauth/status' && req.method === 'GET') {
|
|
2014
|
-
sendJSON(req, res, 200, getGeminiOAuthState());
|
|
2015
|
-
return;
|
|
2016
|
-
}
|
|
2017
|
-
|
|
2018
|
-
if (pathOnly === '/api/gemini-oauth/relay' && req.method === 'POST') {
|
|
2019
|
-
try {
|
|
2020
|
-
const body = await parseBody(req);
|
|
2021
|
-
const { code, state: stateParam } = body;
|
|
2022
|
-
if (!code || !stateParam) {
|
|
2023
|
-
sendJSON(req, res, 400, { error: 'Missing code or state' });
|
|
2024
|
-
return;
|
|
2025
|
-
}
|
|
2026
|
-
const email = await exchangeGeminiOAuthCode(code, stateParam);
|
|
2027
|
-
sendJSON(req, res, 200, { success: true, email });
|
|
2028
|
-
} catch (e) {
|
|
2029
|
-
sendJSON(req, res, 400, { error: e.message });
|
|
2030
|
-
}
|
|
2031
|
-
return;
|
|
2032
|
-
}
|
|
2033
|
-
|
|
2034
|
-
if (pathOnly === '/api/gemini-oauth/complete' && req.method === 'POST') {
|
|
2035
|
-
try {
|
|
2036
|
-
const body = await parseBody(req);
|
|
2037
|
-
const pastedUrl = (body.url || '').trim();
|
|
2038
|
-
if (!pastedUrl) {
|
|
2039
|
-
sendJSON(req, res, 400, { error: 'No URL provided' });
|
|
2040
|
-
return;
|
|
2041
|
-
}
|
|
2042
|
-
|
|
2043
|
-
let parsed;
|
|
2044
|
-
try { parsed = new URL(pastedUrl); } catch (_) {
|
|
2045
|
-
sendJSON(req, res, 400, { error: 'Invalid URL. Paste the full URL from the browser address bar.' });
|
|
2046
|
-
return;
|
|
2047
|
-
}
|
|
2048
|
-
|
|
2049
|
-
const error = parsed.searchParams.get('error');
|
|
2050
|
-
if (error) {
|
|
2051
|
-
const desc = parsed.searchParams.get('error_description') || error;
|
|
2052
|
-
sendJSON(req, res, 200, { error: desc });
|
|
2053
|
-
return;
|
|
2054
|
-
}
|
|
2055
|
-
|
|
2056
|
-
const code = parsed.searchParams.get('code');
|
|
2057
|
-
const state = parsed.searchParams.get('state');
|
|
2058
|
-
const email = await exchangeGeminiOAuthCode(code, state);
|
|
2059
|
-
sendJSON(req, res, 200, { success: true, email });
|
|
2060
|
-
} catch (e) {
|
|
2061
|
-
sendJSON(req, res, 400, { error: e.message });
|
|
2062
|
-
}
|
|
2063
|
-
return;
|
|
2064
|
-
}
|
|
2065
|
-
|
|
2066
|
-
if (pathOnly === '/api/codex-oauth/start' && req.method === 'POST') {
|
|
2067
|
-
try {
|
|
2068
|
-
const result = await startCodexOAuth(req, { PORT, BASE_URL });
|
|
2069
|
-
sendJSON(req, res, 200, { authUrl: result.authUrl, mode: result.mode });
|
|
2070
|
-
} catch (e) {
|
|
2071
|
-
console.error('[codex-oauth] /api/codex-oauth/start failed:', e);
|
|
2072
|
-
sendJSON(req, res, 500, { error: e.message });
|
|
2073
|
-
}
|
|
2074
|
-
return;
|
|
2075
|
-
}
|
|
2076
|
-
|
|
2077
|
-
if (pathOnly === '/api/codex-oauth/status' && req.method === 'GET') {
|
|
2078
|
-
sendJSON(req, res, 200, getCodexOAuthState());
|
|
2079
|
-
return;
|
|
2080
|
-
}
|
|
2081
|
-
|
|
2082
|
-
if (pathOnly === '/api/codex-oauth/relay' && req.method === 'POST') {
|
|
2083
|
-
try {
|
|
2084
|
-
const body = await parseBody(req);
|
|
2085
|
-
const { code, state: stateParam } = body;
|
|
2086
|
-
if (!code || !stateParam) {
|
|
2087
|
-
sendJSON(req, res, 400, { error: 'Missing code or state' });
|
|
2088
|
-
return;
|
|
2089
|
-
}
|
|
2090
|
-
const email = await exchangeCodexOAuthCode(code, stateParam);
|
|
2091
|
-
sendJSON(req, res, 200, { success: true, email });
|
|
2092
|
-
} catch (e) {
|
|
2093
|
-
sendJSON(req, res, 400, { error: e.message });
|
|
2094
|
-
}
|
|
2095
|
-
return;
|
|
2096
|
-
}
|
|
2097
|
-
|
|
2098
|
-
if (pathOnly === '/api/codex-oauth/complete' && req.method === 'POST') {
|
|
2099
|
-
try {
|
|
2100
|
-
const body = await parseBody(req);
|
|
2101
|
-
const pastedUrl = (body.url || '').trim();
|
|
2102
|
-
if (!pastedUrl) {
|
|
2103
|
-
sendJSON(req, res, 400, { error: 'No URL provided' });
|
|
2104
|
-
return;
|
|
2105
|
-
}
|
|
2106
|
-
let parsed;
|
|
2107
|
-
try { parsed = new URL(pastedUrl); } catch (_) {
|
|
2108
|
-
sendJSON(req, res, 400, { error: 'Invalid URL. Paste the full URL from the browser address bar.' });
|
|
2109
|
-
return;
|
|
2110
|
-
}
|
|
2111
|
-
const error = parsed.searchParams.get('error');
|
|
2112
|
-
if (error) {
|
|
2113
|
-
const desc = parsed.searchParams.get('error_description') || error;
|
|
2114
|
-
sendJSON(req, res, 200, { error: desc });
|
|
2115
|
-
return;
|
|
2116
|
-
}
|
|
2117
|
-
const code = parsed.searchParams.get('code');
|
|
2118
|
-
const state = parsed.searchParams.get('state');
|
|
2119
|
-
const email = await exchangeCodexOAuthCode(code, state);
|
|
2120
|
-
sendJSON(req, res, 200, { success: true, email });
|
|
2121
|
-
} catch (e) {
|
|
2122
|
-
sendJSON(req, res, 400, { error: e.message });
|
|
2123
|
-
}
|
|
2124
|
-
return;
|
|
2125
|
-
}
|
|
2004
|
+
const oauthHandler = _oauthRoutes._match(req.method, pathOnly);
|
|
2005
|
+
if (oauthHandler) { await oauthHandler(req, res); return; }
|
|
2126
2006
|
|
|
2127
2007
|
const agentAuthMatch = pathOnly.match(/^\/api\/agents\/([^/]+)\/auth$/);
|
|
2128
2008
|
if (agentAuthMatch && req.method === 'POST') {
|
|
@@ -2296,113 +2176,12 @@ const server = http.createServer(async (req, res) => {
|
|
|
2296
2176
|
return;
|
|
2297
2177
|
}
|
|
2298
2178
|
|
|
2299
|
-
if (pathOnly === '/api/import/claude-code' && req.method === 'GET') {
|
|
2300
|
-
const result = queries.importClaudeCodeConversations();
|
|
2301
|
-
sendJSON(req, res, 200, { imported: result });
|
|
2302
|
-
return;
|
|
2303
|
-
}
|
|
2304
|
-
|
|
2305
|
-
if (pathOnly === '/api/discover/claude-code' && req.method === 'GET') {
|
|
2306
|
-
const discovered = queries.discoverClaudeCodeConversations();
|
|
2307
|
-
sendJSON(req, res, 200, { discovered });
|
|
2308
|
-
return;
|
|
2309
|
-
}
|
|
2310
|
-
|
|
2311
|
-
if (pathOnly === '/api/home' && req.method === 'GET') {
|
|
2312
|
-
sendJSON(req, res, 200, { home: os.homedir(), cwd: STARTUP_CWD });
|
|
2313
|
-
return;
|
|
2314
|
-
}
|
|
2315
|
-
|
|
2316
|
-
if (pathOnly === '/api/version' && req.method === 'GET') {
|
|
2317
|
-
sendJSON(req, res, 200, { version: PKG_VERSION });
|
|
2318
|
-
return;
|
|
2319
|
-
}
|
|
2320
|
-
|
|
2321
2179
|
const speechHandler = _speechRoutes._match(req.method, pathOnly);
|
|
2322
2180
|
if (speechHandler) { await speechHandler(req, res, pathOnly); return; }
|
|
2323
2181
|
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
const repo = (body.repo || '').trim();
|
|
2327
|
-
if (!repo || !/^[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+$/.test(repo)) {
|
|
2328
|
-
sendJSON(req, res, 400, { error: 'Invalid repo format. Use org/repo or user/repo' });
|
|
2329
|
-
return;
|
|
2330
|
-
}
|
|
2331
|
-
const cloneDir = STARTUP_CWD || os.homedir();
|
|
2332
|
-
const repoName = repo.split('/')[1];
|
|
2333
|
-
const targetPath = path.join(cloneDir, repoName);
|
|
2334
|
-
if (fs.existsSync(targetPath)) {
|
|
2335
|
-
sendJSON(req, res, 409, { error: `Directory already exists: ${repoName}`, path: targetPath });
|
|
2336
|
-
return;
|
|
2337
|
-
}
|
|
2338
|
-
try {
|
|
2339
|
-
const isWindows = os.platform() === 'win32';
|
|
2340
|
-
execSync('git clone https://github.com/' + repo + '.git', {
|
|
2341
|
-
cwd: cloneDir,
|
|
2342
|
-
encoding: 'utf-8',
|
|
2343
|
-
timeout: 120000,
|
|
2344
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
2345
|
-
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
|
|
2346
|
-
shell: isWindows
|
|
2347
|
-
});
|
|
2348
|
-
sendJSON(req, res, 200, { ok: true, repo, path: targetPath, name: repoName });
|
|
2349
|
-
} catch (err) {
|
|
2350
|
-
const stderr = err.stderr || err.message || 'Clone failed';
|
|
2351
|
-
sendJSON(req, res, 500, { error: stderr.trim() });
|
|
2352
|
-
}
|
|
2353
|
-
return;
|
|
2354
|
-
}
|
|
2355
|
-
|
|
2356
|
-
if (pathOnly === '/api/folders' && req.method === 'POST') {
|
|
2357
|
-
const body = await parseBody(req);
|
|
2358
|
-
const folderPath = body.path || STARTUP_CWD;
|
|
2359
|
-
try {
|
|
2360
|
-
const expandedPath = folderPath.startsWith('~') ?
|
|
2361
|
-
folderPath.replace('~', os.homedir()) : folderPath;
|
|
2362
|
-
const entries = fs.readdirSync(expandedPath, { withFileTypes: true });
|
|
2363
|
-
const folders = entries
|
|
2364
|
-
.filter(e => e.isDirectory() && !e.name.startsWith('.'))
|
|
2365
|
-
.map(e => ({ name: e.name }))
|
|
2366
|
-
.sort((a, b) => a.name.localeCompare(b.name));
|
|
2367
|
-
sendJSON(req, res, 200, { folders });
|
|
2368
|
-
} catch (err) {
|
|
2369
|
-
sendJSON(req, res, 400, { error: err.message });
|
|
2370
|
-
}
|
|
2371
|
-
return;
|
|
2372
|
-
}
|
|
2182
|
+
const utilHandler = _utilRoutes._match(req.method, pathOnly);
|
|
2183
|
+
if (utilHandler) { await utilHandler(req, res); return; }
|
|
2373
2184
|
|
|
2374
|
-
if (pathOnly === '/api/git/check-remote-ownership' && req.method === 'GET') {
|
|
2375
|
-
try {
|
|
2376
|
-
const isWindows = os.platform() === 'win32';
|
|
2377
|
-
const result = execSync('git remote get-url origin' + (isWindows ? '' : ' 2>/dev/null'), { encoding: 'utf-8', cwd: STARTUP_CWD, shell: isWindows });
|
|
2378
|
-
const remoteUrl = result.trim();
|
|
2379
|
-
const statusResult = execSync('git status --porcelain' + (isWindows ? '' : ' 2>/dev/null'), { encoding: 'utf-8', cwd: STARTUP_CWD, shell: isWindows });
|
|
2380
|
-
const hasChanges = statusResult.trim().length > 0;
|
|
2381
|
-
const unpushedResult = execSync('git rev-list --count --not --remotes' + (isWindows ? '' : ' 2>/dev/null'), { encoding: 'utf-8', cwd: STARTUP_CWD, shell: isWindows });
|
|
2382
|
-
const hasUnpushed = parseInt(unpushedResult.trim() || '0', 10) > 0;
|
|
2383
|
-
const ownsRemote = !remoteUrl.includes('github.com/') || remoteUrl.includes(process.env.GITHUB_USER || '');
|
|
2384
|
-
sendJSON(req, res, 200, { ownsRemote, hasChanges, hasUnpushed, remoteUrl });
|
|
2385
|
-
} catch {
|
|
2386
|
-
sendJSON(req, res, 200, { ownsRemote: false, hasChanges: false, hasUnpushed: false, remoteUrl: '' });
|
|
2387
|
-
}
|
|
2388
|
-
return;
|
|
2389
|
-
}
|
|
2390
|
-
|
|
2391
|
-
if (pathOnly === '/api/git/push' && req.method === 'POST') {
|
|
2392
|
-
try {
|
|
2393
|
-
const isWindows = os.platform() === 'win32';
|
|
2394
|
-
const gitCommand = isWindows
|
|
2395
|
-
? 'git add -A & git commit -m "Auto-commit" & git push'
|
|
2396
|
-
: 'git add -A && git commit -m "Auto-commit" && git push';
|
|
2397
|
-
execSync(gitCommand, { encoding: 'utf-8', cwd: STARTUP_CWD, shell: isWindows });
|
|
2398
|
-
sendJSON(req, res, 200, { success: true });
|
|
2399
|
-
} catch (err) {
|
|
2400
|
-
sendJSON(req, res, 500, { error: err.message });
|
|
2401
|
-
}
|
|
2402
|
-
return;
|
|
2403
|
-
}
|
|
2404
|
-
|
|
2405
|
-
// ============================================================
|
|
2406
2185
|
// THREAD API ENDPOINTS (ACP v0.2.3)
|
|
2407
2186
|
// ============================================================
|
|
2408
2187
|
|
|
@@ -3545,6 +3324,8 @@ const wsRouter = new WsRouter();
|
|
|
3545
3324
|
|
|
3546
3325
|
initSpeechManager({ broadcastSync, syncClients, queries });
|
|
3547
3326
|
const _speechRoutes = registerSpeechRoutes({ sendJSON, parseBody, broadcastSync, debugLog });
|
|
3327
|
+
const _oauthRoutes = registerOAuthRoutes({ sendJSON, parseBody, PORT, BASE_URL, rootDir });
|
|
3328
|
+
const _utilRoutes = registerUtilRoutes({ sendJSON, parseBody, queries, STARTUP_CWD, PKG_VERSION });
|
|
3548
3329
|
|
|
3549
3330
|
registerConvHandlers(wsRouter, {
|
|
3550
3331
|
queries, activeExecutions, rateLimitState,
|