@adversity/coding-tool-x 3.1.0 → 3.1.1
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/CHANGELOG.md +15 -18
- package/README.md +8 -8
- package/dist/web/assets/ConfigTemplates-Bidwfdf2.css +1 -0
- package/dist/web/assets/ConfigTemplates-ZrK_s7ma.js +1 -0
- package/dist/web/assets/Home-B8YfhZ3c.js +1 -0
- package/dist/web/assets/Home-Di2qsylF.css +1 -0
- package/dist/web/assets/PluginManager-BD7QUZbU.js +1 -0
- package/dist/web/assets/PluginManager-ROyoZ-6m.css +1 -0
- package/dist/web/assets/ProjectList-C1fQb9OW.css +1 -0
- package/dist/web/assets/ProjectList-DRb1DuHV.js +1 -0
- package/dist/web/assets/SessionList-BGJWyneI.css +1 -0
- package/dist/web/assets/SessionList-lZ0LKzfT.js +1 -0
- package/dist/web/assets/SkillManager-C1xG5B4Q.js +1 -0
- package/dist/web/assets/SkillManager-D7pd-d_P.css +1 -0
- package/dist/web/assets/Terminal-DGNJeVtc.css +1 -0
- package/dist/web/assets/Terminal-DksBo_lM.js +1 -0
- package/dist/web/assets/WorkspaceManager-Burx7XOo.js +1 -0
- package/dist/web/assets/WorkspaceManager-CrwgQgmP.css +1 -0
- package/dist/web/assets/icons-kcfLIMBB.js +1 -0
- package/dist/web/assets/index-Ufv5rCa5.css +1 -0
- package/dist/web/assets/index-lAkrRC3h.js +2 -0
- package/dist/web/assets/markdown-BfC0goYb.css +10 -0
- package/dist/web/assets/markdown-C9MYpaSi.js +1 -0
- package/dist/web/assets/naive-ui-CSrLusZZ.js +1 -0
- package/dist/web/assets/{vendors-D2HHw_aW.js → vendors-CO3Upi1d.js} +2 -2
- package/dist/web/assets/vue-vendor-DqyWIXEb.js +45 -0
- package/dist/web/assets/xterm-6GBZ9nXN.css +32 -0
- package/dist/web/assets/xterm-BJzAjXCH.js +13 -0
- package/dist/web/index.html +8 -6
- package/package.json +4 -2
- package/src/commands/channels.js +48 -1
- package/src/commands/cli-type.js +4 -2
- package/src/commands/daemon.js +81 -12
- package/src/commands/doctor.js +10 -9
- package/src/commands/list.js +1 -1
- package/src/commands/logs.js +6 -4
- package/src/commands/port-config.js +24 -4
- package/src/commands/proxy-control.js +12 -6
- package/src/commands/search.js +1 -1
- package/src/commands/security.js +3 -2
- package/src/commands/stats.js +226 -52
- package/src/commands/switch.js +1 -1
- package/src/commands/toggle-proxy.js +31 -6
- package/src/commands/update.js +97 -0
- package/src/commands/workspace.js +1 -1
- package/src/config/default.js +39 -2
- package/src/config/loader.js +74 -8
- package/src/config/paths.js +105 -33
- package/src/index.js +64 -3
- package/src/plugins/constants.js +3 -2
- package/src/plugins/plugin-api.js +1 -1
- package/src/reset-config.js +4 -2
- package/src/server/api/agents.js +57 -14
- package/src/server/api/channels.js +112 -33
- package/src/server/api/codex-channels.js +111 -18
- package/src/server/api/codex-proxy.js +14 -8
- package/src/server/api/commands.js +71 -18
- package/src/server/api/config-export.js +0 -6
- package/src/server/api/config-registry.js +11 -3
- package/src/server/api/config.js +376 -5
- package/src/server/api/convert.js +133 -0
- package/src/server/api/dashboard.js +22 -6
- package/src/server/api/gemini-channels.js +107 -18
- package/src/server/api/gemini-proxy.js +14 -8
- package/src/server/api/gemini-sessions.js +1 -1
- package/src/server/api/health-check.js +4 -3
- package/src/server/api/mcp.js +3 -3
- package/src/server/api/opencode-channels.js +419 -0
- package/src/server/api/opencode-projects.js +99 -0
- package/src/server/api/opencode-proxy.js +198 -0
- package/src/server/api/opencode-sessions.js +403 -0
- package/src/server/api/opencode-statistics.js +57 -0
- package/src/server/api/plugins.js +66 -19
- package/src/server/api/prompts.js +2 -2
- package/src/server/api/proxy.js +7 -4
- package/src/server/api/sessions.js +3 -0
- package/src/server/api/skills.js +69 -18
- package/src/server/api/workspaces.js +78 -6
- package/src/server/codex-proxy-server.js +30 -18
- package/src/server/dev-server.js +1 -1
- package/src/server/gemini-proxy-server.js +15 -3
- package/src/server/index.js +165 -58
- package/src/server/opencode-proxy-server.js +4375 -0
- package/src/server/proxy-server.js +27 -18
- package/src/server/services/agents-service.js +61 -24
- package/src/server/services/channel-scheduler.js +9 -5
- package/src/server/services/channels.js +64 -37
- package/src/server/services/codex-channels.js +56 -43
- package/src/server/services/codex-settings-manager.js +271 -49
- package/src/server/services/codex-statistics-service.js +2 -2
- package/src/server/services/commands-service.js +84 -25
- package/src/server/services/config-export-service.js +7 -45
- package/src/server/services/config-registry-service.js +63 -17
- package/src/server/services/config-sync-manager.js +160 -7
- package/src/server/services/config-templates-service.js +204 -51
- package/src/server/services/env-checker.js +26 -12
- package/src/server/services/env-manager.js +126 -18
- package/src/server/services/favorites.js +5 -3
- package/src/server/services/gemini-channels.js +33 -44
- package/src/server/services/gemini-statistics-service.js +2 -2
- package/src/server/services/mcp-service.js +350 -9
- package/src/server/services/model-detector.js +707 -221
- package/src/server/services/network-access.js +80 -0
- package/src/server/services/opencode-channels.js +206 -0
- package/src/server/services/opencode-gateway-converter.js +639 -0
- package/src/server/services/opencode-sessions.js +663 -0
- package/src/server/services/opencode-settings-manager.js +342 -0
- package/src/server/services/opencode-statistics-service.js +255 -0
- package/src/server/services/plugins-service.js +479 -22
- package/src/server/services/prompts-service.js +53 -11
- package/src/server/services/proxy-runtime.js +1 -1
- package/src/server/services/repo-scanner-base.js +1 -1
- package/src/server/services/security-config.js +1 -1
- package/src/server/services/session-cache.js +1 -1
- package/src/server/services/skill-service.js +300 -46
- package/src/server/services/speed-test.js +464 -186
- package/src/server/services/statistics-service.js +2 -2
- package/src/server/services/terminal-commands.js +10 -3
- package/src/server/services/terminal-config.js +1 -1
- package/src/server/services/ui-config.js +1 -1
- package/src/server/services/workspace-service.js +57 -100
- package/src/server/websocket-server.js +132 -3
- package/src/ui/menu.js +49 -40
- package/src/utils/port-helper.js +22 -8
- package/src/utils/session.js +5 -4
- package/dist/web/assets/icons-CO_2OFES.js +0 -1
- package/dist/web/assets/index-DI8QOi-E.js +0 -14
- package/dist/web/assets/index-uLHGdeZh.css +0 -41
- package/dist/web/assets/naive-ui-B1re3c-e.js +0 -1
- package/dist/web/assets/vue-vendor-6JaYHOiI.js +0 -44
- package/src/server/api/oauth.js +0 -294
- package/src/server/api/permissions.js +0 -385
- package/src/server/config/oauth-providers.js +0 -68
- package/src/server/services/oauth-callback-server.js +0 -284
- package/src/server/services/oauth-service.js +0 -378
- package/src/server/services/oauth-token-storage.js +0 -135
- package/src/server/services/permission-templates-service.js +0 -308
package/src/server/api/oauth.js
DELETED
|
@@ -1,294 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const express = require('express');
|
|
4
|
-
const router = express.Router();
|
|
5
|
-
const { OAUTH_PROVIDERS, getProviderConfig } = require('../config/oauth-providers');
|
|
6
|
-
const {
|
|
7
|
-
startFlow,
|
|
8
|
-
getPendingFlow,
|
|
9
|
-
completeFlow,
|
|
10
|
-
failFlow,
|
|
11
|
-
cancelFlow,
|
|
12
|
-
exchangeCodeForToken,
|
|
13
|
-
refreshToken
|
|
14
|
-
} = require('../services/oauth-service');
|
|
15
|
-
const {
|
|
16
|
-
startCallbackServer,
|
|
17
|
-
stopCallbackServer,
|
|
18
|
-
isServerRunning
|
|
19
|
-
} = require('../services/oauth-callback-server');
|
|
20
|
-
const {
|
|
21
|
-
getAllTokens,
|
|
22
|
-
getToken,
|
|
23
|
-
saveToken,
|
|
24
|
-
updateToken,
|
|
25
|
-
deleteToken
|
|
26
|
-
} = require('../services/oauth-token-storage');
|
|
27
|
-
|
|
28
|
-
// Track callback servers by state
|
|
29
|
-
const stateToPort = new Map();
|
|
30
|
-
|
|
31
|
-
// GET /api/oauth/providers - List available OAuth providers
|
|
32
|
-
router.get('/providers', (req, res) => {
|
|
33
|
-
try {
|
|
34
|
-
const providers = Object.entries(OAUTH_PROVIDERS).map(([id, config]) => ({
|
|
35
|
-
id,
|
|
36
|
-
name: config.name,
|
|
37
|
-
scopes: config.scopes
|
|
38
|
-
}));
|
|
39
|
-
res.json({ providers });
|
|
40
|
-
} catch (error) {
|
|
41
|
-
console.error('Error fetching OAuth providers:', error);
|
|
42
|
-
res.status(500).json({ error: error.message });
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
// POST /api/oauth/start - Start OAuth flow
|
|
47
|
-
router.post('/start', async (req, res) => {
|
|
48
|
-
try {
|
|
49
|
-
const { provider, channelId, mode = 'browser' } = req.body;
|
|
50
|
-
|
|
51
|
-
if (!provider) {
|
|
52
|
-
return res.status(400).json({ error: 'Missing required field: provider' });
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Validate provider
|
|
56
|
-
if (!OAUTH_PROVIDERS[provider]) {
|
|
57
|
-
return res.status(400).json({ error: `Unknown provider: ${provider}` });
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const providerConfig = getProviderConfig(provider);
|
|
61
|
-
const { state, authUrl } = startFlow(provider, channelId);
|
|
62
|
-
const callbackPort = providerConfig.callbackPort;
|
|
63
|
-
const callbackPath = providerConfig.callbackPath;
|
|
64
|
-
|
|
65
|
-
// Track state -> port mapping
|
|
66
|
-
stateToPort.set(state, callbackPort);
|
|
67
|
-
|
|
68
|
-
// Start callback server
|
|
69
|
-
await startCallbackServer(callbackPort, callbackPath, async (callbackData) => {
|
|
70
|
-
const { code, state: cbState, error, errorDescription } = callbackData;
|
|
71
|
-
|
|
72
|
-
if (error) {
|
|
73
|
-
failFlow(cbState, `${error}: ${errorDescription || 'Unknown error'}`);
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (!code) {
|
|
78
|
-
failFlow(cbState, 'No authorization code received');
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Get pending flow to retrieve code verifier
|
|
83
|
-
const flow = getPendingFlow(cbState);
|
|
84
|
-
if (!flow) {
|
|
85
|
-
console.error('OAuth callback received for unknown state:', cbState);
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
try {
|
|
90
|
-
// Exchange code for token
|
|
91
|
-
const tokenResult = await exchangeCodeForToken(
|
|
92
|
-
flow.provider,
|
|
93
|
-
code,
|
|
94
|
-
flow.codeVerifier
|
|
95
|
-
);
|
|
96
|
-
|
|
97
|
-
// Calculate expiry time
|
|
98
|
-
const now = Date.now();
|
|
99
|
-
const expiresAt = tokenResult.expiresIn
|
|
100
|
-
? now + (tokenResult.expiresIn * 1000)
|
|
101
|
-
: null;
|
|
102
|
-
|
|
103
|
-
// Save token
|
|
104
|
-
const savedToken = saveToken({
|
|
105
|
-
provider: flow.provider,
|
|
106
|
-
channelId: flow.channelId,
|
|
107
|
-
accessToken: tokenResult.accessToken,
|
|
108
|
-
refreshToken: tokenResult.refreshToken,
|
|
109
|
-
idToken: tokenResult.idToken,
|
|
110
|
-
expiresAt,
|
|
111
|
-
scope: tokenResult.scope
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
// Mark flow as completed
|
|
115
|
-
completeFlow(cbState, savedToken.id);
|
|
116
|
-
console.log(`OAuth flow completed for provider: ${flow.provider}, tokenId: ${savedToken.id}`);
|
|
117
|
-
} catch (err) {
|
|
118
|
-
console.error('Error exchanging code for token:', err);
|
|
119
|
-
failFlow(cbState, err.message);
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
// Open browser if mode is 'browser'
|
|
124
|
-
if (mode === 'browser') {
|
|
125
|
-
try {
|
|
126
|
-
const open = require('open');
|
|
127
|
-
await open(authUrl);
|
|
128
|
-
} catch (err) {
|
|
129
|
-
console.error('Failed to open browser:', err);
|
|
130
|
-
// Don't fail the request, user can still open URL manually
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
res.json({
|
|
135
|
-
success: true,
|
|
136
|
-
stateId: state,
|
|
137
|
-
authUrl,
|
|
138
|
-
callbackPort
|
|
139
|
-
});
|
|
140
|
-
} catch (error) {
|
|
141
|
-
console.error('Error starting OAuth flow:', error);
|
|
142
|
-
res.status(500).json({ error: error.message });
|
|
143
|
-
}
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
// GET /api/oauth/status/:stateId - Get OAuth flow status
|
|
147
|
-
router.get('/status/:stateId', (req, res) => {
|
|
148
|
-
try {
|
|
149
|
-
const { stateId } = req.params;
|
|
150
|
-
const flow = getPendingFlow(stateId);
|
|
151
|
-
|
|
152
|
-
if (!flow) {
|
|
153
|
-
return res.status(404).json({
|
|
154
|
-
status: 'not_found',
|
|
155
|
-
error: 'OAuth flow not found or expired'
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const response = {
|
|
160
|
-
status: flow.status
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
if (flow.status === 'completed' && flow.tokenId) {
|
|
164
|
-
response.tokenId = flow.tokenId;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
if (flow.status === 'failed' && flow.error) {
|
|
168
|
-
response.error = flow.error;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
res.json(response);
|
|
172
|
-
} catch (error) {
|
|
173
|
-
console.error('Error fetching OAuth status:', error);
|
|
174
|
-
res.status(500).json({ error: error.message });
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
// POST /api/oauth/cancel/:stateId - Cancel OAuth flow
|
|
179
|
-
router.post('/cancel/:stateId', (req, res) => {
|
|
180
|
-
try {
|
|
181
|
-
const { stateId } = req.params;
|
|
182
|
-
|
|
183
|
-
// Get port for this state
|
|
184
|
-
const port = stateToPort.get(stateId);
|
|
185
|
-
|
|
186
|
-
// Stop callback server if running
|
|
187
|
-
if (port && isServerRunning(port)) {
|
|
188
|
-
stopCallbackServer(port);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// Cancel the flow
|
|
192
|
-
cancelFlow(stateId);
|
|
193
|
-
|
|
194
|
-
// Clean up state -> port mapping
|
|
195
|
-
stateToPort.delete(stateId);
|
|
196
|
-
|
|
197
|
-
res.json({ success: true });
|
|
198
|
-
} catch (error) {
|
|
199
|
-
console.error('Error cancelling OAuth flow:', error);
|
|
200
|
-
res.status(500).json({ error: error.message });
|
|
201
|
-
}
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
// GET /api/oauth/tokens - List all tokens (masked)
|
|
205
|
-
router.get('/tokens', (req, res) => {
|
|
206
|
-
try {
|
|
207
|
-
const tokens = getAllTokens();
|
|
208
|
-
res.json({ tokens });
|
|
209
|
-
} catch (error) {
|
|
210
|
-
console.error('Error fetching OAuth tokens:', error);
|
|
211
|
-
res.status(500).json({ error: error.message });
|
|
212
|
-
}
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
// GET /api/oauth/tokens/:tokenId - Get single token (masked)
|
|
216
|
-
router.get('/tokens/:tokenId', (req, res) => {
|
|
217
|
-
try {
|
|
218
|
-
const token = getToken(req.params.tokenId);
|
|
219
|
-
if (!token) {
|
|
220
|
-
return res.status(404).json({ error: 'Token not found' });
|
|
221
|
-
}
|
|
222
|
-
// Return masked token info
|
|
223
|
-
const masked = {
|
|
224
|
-
...token,
|
|
225
|
-
accessToken: token.accessToken ? token.accessToken.substring(0, 8) + '...' : null,
|
|
226
|
-
refreshToken: token.refreshToken ? token.refreshToken.substring(0, 8) + '...' : null
|
|
227
|
-
};
|
|
228
|
-
res.json(masked);
|
|
229
|
-
} catch (error) {
|
|
230
|
-
console.error('Error fetching OAuth token:', error);
|
|
231
|
-
res.status(500).json({ error: error.message });
|
|
232
|
-
}
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
// DELETE /api/oauth/tokens/:tokenId - Delete a token
|
|
236
|
-
router.delete('/tokens/:tokenId', (req, res) => {
|
|
237
|
-
try {
|
|
238
|
-
const { tokenId } = req.params;
|
|
239
|
-
const result = deleteToken(tokenId);
|
|
240
|
-
res.json(result);
|
|
241
|
-
} catch (error) {
|
|
242
|
-
if (error.message === 'Token not found') {
|
|
243
|
-
return res.status(404).json({ error: 'Token not found' });
|
|
244
|
-
}
|
|
245
|
-
console.error('Error deleting OAuth token:', error);
|
|
246
|
-
res.status(500).json({ error: error.message });
|
|
247
|
-
}
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
// POST /api/oauth/refresh/:tokenId - Refresh a token
|
|
251
|
-
router.post('/refresh/:tokenId', async (req, res) => {
|
|
252
|
-
try {
|
|
253
|
-
const { tokenId } = req.params;
|
|
254
|
-
const token = getToken(tokenId);
|
|
255
|
-
|
|
256
|
-
if (!token) {
|
|
257
|
-
return res.status(404).json({ error: 'Token not found' });
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
if (!token.refreshToken) {
|
|
261
|
-
return res.status(400).json({ error: 'Token does not have a refresh token' });
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// Refresh the token
|
|
265
|
-
const refreshResult = await refreshToken(token.provider, token.refreshToken);
|
|
266
|
-
|
|
267
|
-
// Calculate new expiry time
|
|
268
|
-
const now = Date.now();
|
|
269
|
-
const expiresAt = refreshResult.expiresIn
|
|
270
|
-
? now + (refreshResult.expiresIn * 1000)
|
|
271
|
-
: null;
|
|
272
|
-
|
|
273
|
-
// Update token in storage
|
|
274
|
-
const updatedToken = updateToken(tokenId, {
|
|
275
|
-
accessToken: refreshResult.accessToken,
|
|
276
|
-
refreshToken: refreshResult.refreshToken || token.refreshToken,
|
|
277
|
-
expiresAt,
|
|
278
|
-
scope: refreshResult.scope || token.scope
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
res.json({
|
|
282
|
-
success: true,
|
|
283
|
-
expiresAt: updatedToken.expiresAt
|
|
284
|
-
});
|
|
285
|
-
} catch (error) {
|
|
286
|
-
if (error.message === 'Token not found') {
|
|
287
|
-
return res.status(404).json({ error: 'Token not found' });
|
|
288
|
-
}
|
|
289
|
-
console.error('Error refreshing OAuth token:', error);
|
|
290
|
-
res.status(500).json({ error: error.message });
|
|
291
|
-
}
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
module.exports = router;
|
|
@@ -1,385 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Command Permissions API 路由
|
|
3
|
-
*
|
|
4
|
-
* 管理 Claude Code / Codex / Gemini CLI 命令执行权限
|
|
5
|
-
*
|
|
6
|
-
* 三个 CLI 工具的权限控制机制:
|
|
7
|
-
*
|
|
8
|
-
* 1. Claude Code:
|
|
9
|
-
* - 配置文件: ~/.claude/settings.json (用户级) 或 .claude/settings.json (项目级)
|
|
10
|
-
* - 权限格式: permissions.allow / permissions.deny 数组
|
|
11
|
-
* - 支持通配符: Bash(npm run *), Read(./src/**)
|
|
12
|
-
*
|
|
13
|
-
* 2. Codex CLI:
|
|
14
|
-
* - 启动参数: --ask-for-approval never/on-request/on-failure
|
|
15
|
-
* - 沙箱模式: --sandbox read-only/workspace-write/danger-full-access
|
|
16
|
-
* - YOLO 模式: --yolo 或 --dangerously-bypass-approvals-and-sandbox
|
|
17
|
-
*
|
|
18
|
-
* 3. Gemini CLI:
|
|
19
|
-
* - 启动参数: --approval-mode default/auto_edit/yolo
|
|
20
|
-
* - 允许工具: --allowed-tools "ShellTool(git status)"
|
|
21
|
-
* - 沙箱: --sandbox / -s
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
const express = require('express');
|
|
25
|
-
const fs = require('fs');
|
|
26
|
-
const path = require('path');
|
|
27
|
-
const os = require('os');
|
|
28
|
-
const permissionTemplatesService = require('../services/permission-templates-service');
|
|
29
|
-
|
|
30
|
-
const router = express.Router();
|
|
31
|
-
|
|
32
|
-
// Claude Code 设置文件路径
|
|
33
|
-
function getClaudeSettingsPath(projectPath) {
|
|
34
|
-
if (projectPath) {
|
|
35
|
-
return path.join(projectPath, '.claude', 'settings.json');
|
|
36
|
-
}
|
|
37
|
-
return path.join(os.homedir(), '.claude', 'settings.json');
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// 读取 Claude Code settings.json
|
|
41
|
-
function readClaudeSettings(projectPath) {
|
|
42
|
-
const settingsPath = getClaudeSettingsPath(projectPath);
|
|
43
|
-
try {
|
|
44
|
-
if (fs.existsSync(settingsPath)) {
|
|
45
|
-
const content = fs.readFileSync(settingsPath, 'utf-8');
|
|
46
|
-
return JSON.parse(content);
|
|
47
|
-
}
|
|
48
|
-
} catch (err) {
|
|
49
|
-
console.error('[Permissions API] Error reading Claude settings:', err);
|
|
50
|
-
}
|
|
51
|
-
return {};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// 保存 Claude Code settings.json
|
|
55
|
-
function saveClaudeSettings(projectPath, settings) {
|
|
56
|
-
const settingsPath = getClaudeSettingsPath(projectPath);
|
|
57
|
-
const settingsDir = path.dirname(settingsPath);
|
|
58
|
-
|
|
59
|
-
try {
|
|
60
|
-
// 确保目录存在
|
|
61
|
-
if (!fs.existsSync(settingsDir)) {
|
|
62
|
-
fs.mkdirSync(settingsDir, { recursive: true });
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// 写入文件
|
|
66
|
-
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf-8');
|
|
67
|
-
|
|
68
|
-
// 验证文件已创建
|
|
69
|
-
if (!fs.existsSync(settingsPath)) {
|
|
70
|
-
throw new Error('文件写入后验证失败,文件未被创建');
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return { success: true, path: settingsPath };
|
|
74
|
-
} catch (err) {
|
|
75
|
-
console.error('[Permissions API] Error saving Claude settings:', err);
|
|
76
|
-
throw new Error(`保存配置文件失败: ${err.message}`);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// 全局 all-allow 状态(内存中)
|
|
81
|
-
let globalAllAllowEnabled = process.env.CLAUDE_ALL_ALLOW === 'true';
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* 获取项目的命令执行权限设置
|
|
85
|
-
* GET /api/permissions
|
|
86
|
-
* Query: projectPath - 项目路径, cliType - CLI 类型 (claude/codex/gemini)
|
|
87
|
-
*/
|
|
88
|
-
router.get('/', (req, res) => {
|
|
89
|
-
try {
|
|
90
|
-
const { projectPath, cliType = 'claude' } = req.query;
|
|
91
|
-
|
|
92
|
-
if (!projectPath) {
|
|
93
|
-
return res.status(400).json({
|
|
94
|
-
success: false,
|
|
95
|
-
message: '缺少 projectPath 参数'
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (!fs.existsSync(projectPath)) {
|
|
100
|
-
return res.status(404).json({
|
|
101
|
-
success: false,
|
|
102
|
-
message: '项目路径不存在'
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// 读取 Claude Code 配置(所有 CLI 工具共用此配置管理)
|
|
107
|
-
const settings = readClaudeSettings(projectPath);
|
|
108
|
-
const permissions = settings.permissions || {};
|
|
109
|
-
|
|
110
|
-
res.json({
|
|
111
|
-
success: true,
|
|
112
|
-
cliType,
|
|
113
|
-
settings: {
|
|
114
|
-
// Claude Code 格式
|
|
115
|
-
allow: permissions.allow || [],
|
|
116
|
-
deny: permissions.deny || [],
|
|
117
|
-
// 兼容旧格式
|
|
118
|
-
allowedCommands: permissions.allow || [],
|
|
119
|
-
denyCommands: permissions.deny || []
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
|
-
} catch (err) {
|
|
123
|
-
console.error('[Permissions API] Get permissions error:', err);
|
|
124
|
-
res.status(500).json({
|
|
125
|
-
success: false,
|
|
126
|
-
message: err.message
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* 保存项目的命令执行权限设置
|
|
133
|
-
* POST /api/permissions
|
|
134
|
-
* Body: { projectPath, settings: { allow, deny } }
|
|
135
|
-
*/
|
|
136
|
-
router.post('/', (req, res) => {
|
|
137
|
-
try {
|
|
138
|
-
const { projectPath, settings: newPermissions } = req.body;
|
|
139
|
-
|
|
140
|
-
if (!projectPath) {
|
|
141
|
-
return res.status(400).json({
|
|
142
|
-
success: false,
|
|
143
|
-
message: '缺少 projectPath 参数'
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if (!fs.existsSync(projectPath)) {
|
|
148
|
-
return res.status(404).json({
|
|
149
|
-
success: false,
|
|
150
|
-
message: '项目路径不存在'
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// 读取现有设置
|
|
155
|
-
const settings = readClaudeSettings(projectPath);
|
|
156
|
-
|
|
157
|
-
// 更新权限设置(使用 Claude Code 的标准格式)
|
|
158
|
-
settings.permissions = {
|
|
159
|
-
allow: newPermissions?.allow || newPermissions?.allowedCommands || [],
|
|
160
|
-
deny: newPermissions?.deny || newPermissions?.denyCommands || []
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
// 保存设置
|
|
164
|
-
const saveResult = saveClaudeSettings(projectPath, settings);
|
|
165
|
-
|
|
166
|
-
res.json({
|
|
167
|
-
success: true,
|
|
168
|
-
message: '权限设置已保存',
|
|
169
|
-
savedTo: '.claude/settings.json',
|
|
170
|
-
fullPath: saveResult.path
|
|
171
|
-
});
|
|
172
|
-
} catch (err) {
|
|
173
|
-
console.error('[Permissions API] Save permissions error:', err);
|
|
174
|
-
res.status(500).json({
|
|
175
|
-
success: false,
|
|
176
|
-
message: err.message
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* 获取全局 all-allow 模式状态
|
|
183
|
-
* GET /api/permissions/all-allow
|
|
184
|
-
*/
|
|
185
|
-
router.get('/all-allow', (req, res) => {
|
|
186
|
-
try {
|
|
187
|
-
res.json({
|
|
188
|
-
success: true,
|
|
189
|
-
enabled: globalAllAllowEnabled,
|
|
190
|
-
note: '此设置通过启动命令参数控制,如 claude --dangerously-skip-permissions'
|
|
191
|
-
});
|
|
192
|
-
} catch (err) {
|
|
193
|
-
console.error('[Permissions API] Get all-allow status error:', err);
|
|
194
|
-
res.status(500).json({
|
|
195
|
-
success: false,
|
|
196
|
-
message: err.message
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* 设置全局 all-allow 模式(运行时)
|
|
203
|
-
* POST /api/permissions/all-allow
|
|
204
|
-
* Body: { enabled }
|
|
205
|
-
*/
|
|
206
|
-
router.post('/all-allow', (req, res) => {
|
|
207
|
-
try {
|
|
208
|
-
const { enabled } = req.body;
|
|
209
|
-
globalAllAllowEnabled = !!enabled;
|
|
210
|
-
|
|
211
|
-
res.json({
|
|
212
|
-
success: true,
|
|
213
|
-
enabled: globalAllAllowEnabled,
|
|
214
|
-
message: enabled ? 'All-Allow 模式已启用' : 'All-Allow 模式已禁用'
|
|
215
|
-
});
|
|
216
|
-
} catch (err) {
|
|
217
|
-
console.error('[Permissions API] Set all-allow status error:', err);
|
|
218
|
-
res.status(500).json({
|
|
219
|
-
success: false,
|
|
220
|
-
message: err.message
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
/**
|
|
226
|
-
* 获取所有权限模版(内置 + 自定义)
|
|
227
|
-
* GET /api/permissions/templates
|
|
228
|
-
*/
|
|
229
|
-
router.get('/templates', (req, res) => {
|
|
230
|
-
try {
|
|
231
|
-
const templates = permissionTemplatesService.getAllTemplates();
|
|
232
|
-
res.json({
|
|
233
|
-
success: true,
|
|
234
|
-
data: templates
|
|
235
|
-
});
|
|
236
|
-
} catch (err) {
|
|
237
|
-
console.error('[Permissions API] Get templates error:', err);
|
|
238
|
-
res.status(500).json({
|
|
239
|
-
success: false,
|
|
240
|
-
message: err.message
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* 获取单个权限模版
|
|
247
|
-
* GET /api/permissions/templates/:id
|
|
248
|
-
*/
|
|
249
|
-
router.get('/templates/:id', (req, res) => {
|
|
250
|
-
try {
|
|
251
|
-
const template = permissionTemplatesService.getTemplateById(req.params.id);
|
|
252
|
-
if (!template) {
|
|
253
|
-
return res.status(404).json({
|
|
254
|
-
success: false,
|
|
255
|
-
message: '模版不存在'
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
res.json({
|
|
259
|
-
success: true,
|
|
260
|
-
data: template
|
|
261
|
-
});
|
|
262
|
-
} catch (err) {
|
|
263
|
-
console.error('[Permissions API] Get template error:', err);
|
|
264
|
-
res.status(500).json({
|
|
265
|
-
success: false,
|
|
266
|
-
message: err.message
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
/**
|
|
272
|
-
* 创建自定义权限模版
|
|
273
|
-
* POST /api/permissions/templates
|
|
274
|
-
*/
|
|
275
|
-
router.post('/templates', (req, res) => {
|
|
276
|
-
try {
|
|
277
|
-
const template = permissionTemplatesService.createTemplate(req.body);
|
|
278
|
-
res.json({
|
|
279
|
-
success: true,
|
|
280
|
-
data: template,
|
|
281
|
-
message: '模版创建成功'
|
|
282
|
-
});
|
|
283
|
-
} catch (err) {
|
|
284
|
-
console.error('[Permissions API] Create template error:', err);
|
|
285
|
-
res.status(400).json({
|
|
286
|
-
success: false,
|
|
287
|
-
message: err.message
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
/**
|
|
293
|
-
* 更新自定义权限模版
|
|
294
|
-
* PUT /api/permissions/templates/:id
|
|
295
|
-
*/
|
|
296
|
-
router.put('/templates/:id', (req, res) => {
|
|
297
|
-
try {
|
|
298
|
-
const template = permissionTemplatesService.updateTemplate(req.params.id, req.body);
|
|
299
|
-
res.json({
|
|
300
|
-
success: true,
|
|
301
|
-
data: template,
|
|
302
|
-
message: '模版更新成功'
|
|
303
|
-
});
|
|
304
|
-
} catch (err) {
|
|
305
|
-
console.error('[Permissions API] Update template error:', err);
|
|
306
|
-
res.status(400).json({
|
|
307
|
-
success: false,
|
|
308
|
-
message: err.message
|
|
309
|
-
});
|
|
310
|
-
}
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
/**
|
|
314
|
-
* 删除自定义权限模版
|
|
315
|
-
* DELETE /api/permissions/templates/:id
|
|
316
|
-
*/
|
|
317
|
-
router.delete('/templates/:id', (req, res) => {
|
|
318
|
-
try {
|
|
319
|
-
permissionTemplatesService.deleteTemplate(req.params.id);
|
|
320
|
-
res.json({
|
|
321
|
-
success: true,
|
|
322
|
-
message: '模版删除成功'
|
|
323
|
-
});
|
|
324
|
-
} catch (err) {
|
|
325
|
-
console.error('[Permissions API] Delete template error:', err);
|
|
326
|
-
res.status(400).json({
|
|
327
|
-
success: false,
|
|
328
|
-
message: err.message
|
|
329
|
-
});
|
|
330
|
-
}
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
/**
|
|
334
|
-
* 获取各 CLI 工具的启动参数配置说明
|
|
335
|
-
* GET /api/permissions/cli-config
|
|
336
|
-
*/
|
|
337
|
-
router.get('/cli-config', (req, res) => {
|
|
338
|
-
try {
|
|
339
|
-
const cliConfigs = {
|
|
340
|
-
claude: {
|
|
341
|
-
name: 'Claude Code',
|
|
342
|
-
configFile: '.claude/settings.json',
|
|
343
|
-
userConfigFile: '~/.claude/settings.json',
|
|
344
|
-
permissionFormat: {
|
|
345
|
-
example: {
|
|
346
|
-
permissions: {
|
|
347
|
-
allow: ['Bash(npm run:*)', 'Read(./src/**)'],
|
|
348
|
-
deny: ['Bash(rm:*)', 'Read(.env)']
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
},
|
|
352
|
-
allAllowFlag: '--dangerously-skip-permissions',
|
|
353
|
-
docs: 'https://docs.anthropic.com/en/docs/claude-code'
|
|
354
|
-
},
|
|
355
|
-
codex: {
|
|
356
|
-
name: 'Codex CLI',
|
|
357
|
-
sandboxModes: ['read-only', 'workspace-write'],
|
|
358
|
-
approvalModes: ['suggest', 'auto-edit'],
|
|
359
|
-
allAllowFlag: '--dangerously-bypass-approvals-and-sandbox',
|
|
360
|
-
example: 'codex --approval-mode auto-edit --sandbox workspace-write "task"',
|
|
361
|
-
docs: 'https://github.com/openai/codex'
|
|
362
|
-
},
|
|
363
|
-
gemini: {
|
|
364
|
-
name: 'Gemini CLI',
|
|
365
|
-
allowedToolsFlag: '--allowedTools "ShellTool(git status)"',
|
|
366
|
-
sandboxFlag: '--sandbox / -s',
|
|
367
|
-
allAllowFlag: '--yolo',
|
|
368
|
-
docs: 'https://github.com/google-gemini/gemini-cli'
|
|
369
|
-
}
|
|
370
|
-
};
|
|
371
|
-
|
|
372
|
-
res.json({
|
|
373
|
-
success: true,
|
|
374
|
-
cliConfigs
|
|
375
|
-
});
|
|
376
|
-
} catch (err) {
|
|
377
|
-
console.error('[Permissions API] Get CLI config error:', err);
|
|
378
|
-
res.status(500).json({
|
|
379
|
-
success: false,
|
|
380
|
-
message: err.message
|
|
381
|
-
});
|
|
382
|
-
}
|
|
383
|
-
});
|
|
384
|
-
|
|
385
|
-
module.exports = router;
|