coding-tool-x 3.6.12 → 3.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -33
- package/dist/web/assets/{Analytics-BuBlsbcI.js → Analytics-Dma_j2Rq.js} +1 -1
- package/dist/web/assets/{ConfigTemplates-DQczxuOX.js → ConfigTemplates-C49Y1PLH.js} +1 -1
- package/dist/web/assets/Home-DGGC8H4g.js +1 -0
- package/dist/web/assets/Home-ewgeIhF7.css +1 -0
- package/dist/web/assets/{PluginManager-C8gAlaps.js → PluginManager-B1ECTHyY.js} +1 -1
- package/dist/web/assets/{ProjectList-DL4JK6ci.css → ProjectList-B_L55-l9.css} +1 -1
- package/dist/web/assets/ProjectList-DyiGyT-H.js +1 -0
- package/dist/web/assets/SessionList-CH5GwwiJ.js +1 -0
- package/dist/web/assets/{SkillManager-BDbNbNkK.js → SkillManager-CRyVhjYQ.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-DGGaGW2c.js → WorkspaceManager-CO9tclVt.js} +1 -1
- package/dist/web/assets/{icons-QFXzEgqM.js → icons-Dop5j7Bf.js} +1 -1
- package/dist/web/assets/index-CM26UrnJ.css +1 -0
- package/dist/web/assets/index-EQq56pRa.js +2 -0
- package/dist/web/assets/{naive-ui-kcWDeJwl.js → naive-ui-CVyvdmg1.js} +1 -1
- package/dist/web/index.html +4 -4
- package/package.json +2 -1
- package/src/commands/channels.js +85 -24
- package/src/commands/daemon.js +7 -4
- package/src/commands/doctor.js +17 -8
- package/src/commands/logs.js +48 -6
- package/src/commands/port-config.js +81 -75
- package/src/commands/proxy-control.js +52 -19
- package/src/commands/stats.js +37 -19
- package/src/commands/toggle-proxy.js +131 -53
- package/src/commands/ui.js +3 -4
- package/src/config/default.js +14 -3
- package/src/config/paths.js +99 -11
- package/src/index.js +11 -16
- package/src/server/api/commands.js +1 -1
- package/src/server/api/config-export.js +4 -2
- package/src/server/api/config-registry.js +40 -34
- package/src/server/api/config.js +2 -1
- package/src/server/api/dashboard.js +22 -6
- package/src/server/api/mcp.js +5 -3
- package/src/server/api/pi-channels.js +349 -0
- package/src/server/api/pi-config.js +164 -0
- package/src/server/api/pi-projects.js +76 -0
- package/src/server/api/pi-proxy.js +154 -0
- package/src/server/api/pi-sessions.js +334 -0
- package/src/server/api/pi-statistics.js +40 -0
- package/src/server/api/plugins.js +1 -1
- package/src/server/api/prompts.js +3 -2
- package/src/server/api/skills.js +1 -1
- package/src/server/api/workspaces.js +2 -2
- package/src/server/index.js +41 -0
- package/src/server/pi-proxy-server.js +66 -0
- package/src/server/services/agents-service.js +2 -1
- package/src/server/services/base/proxy-utils.js +1 -0
- package/src/server/services/channel-balance.js +151 -1
- package/src/server/services/channel-scheduler.js +7 -1
- package/src/server/services/commands-service.js +11 -2
- package/src/server/services/config-export-service.js +275 -18
- package/src/server/services/config-registry-service.js +20 -18
- package/src/server/services/config-sync-manager.js +115 -6
- package/src/server/services/config-sync-service.js +4 -2
- package/src/server/services/config-templates-service.js +183 -45
- package/src/server/services/mcp-service.js +217 -33
- package/src/server/services/pi-auth-providers.js +294 -0
- package/src/server/services/pi-channels.js +94 -0
- package/src/server/services/pi-config.js +287 -0
- package/src/server/services/pi-sessions.js +490 -0
- package/src/server/services/pi-settings-manager.js +571 -0
- package/src/server/services/pi-statistics-service.js +317 -0
- package/src/server/services/plugins-service.js +673 -11
- package/src/server/services/prompts-service.js +112 -7
- package/src/server/services/proxy-log-helper.js +1 -0
- package/src/server/services/proxy-runtime.js +5 -1
- package/src/server/services/skill-service.js +13 -3
- package/src/server/services/speed-test.js +9 -3
- package/src/server/services/ui-config.js +50 -12
- package/src/server/services/workspace-service.js +19 -5
- package/src/server/websocket-server.js +7 -1
- package/dist/web/assets/Home-BvAzzWnB.js +0 -1
- package/dist/web/assets/Home-CIua-b-o.css +0 -1
- package/dist/web/assets/ProjectList-nqgi6_cR.js +0 -1
- package/dist/web/assets/SessionList-D8AxMYy6.js +0 -1
- package/dist/web/assets/index-Dsh4Q5Cg.css +0 -1
- package/dist/web/assets/index-y-eEayxR.js +0 -2
- package/src/utils/cli-flags.js +0 -7
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const router = express.Router();
|
|
5
|
+
const {
|
|
6
|
+
startPiProxyServer,
|
|
7
|
+
stopPiProxyServer,
|
|
8
|
+
getPiProxyStatus
|
|
9
|
+
} = require('../pi-proxy-server');
|
|
10
|
+
const {
|
|
11
|
+
getChannels,
|
|
12
|
+
getEnabledChannels,
|
|
13
|
+
markChannelAsRecentlyUsed
|
|
14
|
+
} = require('../services/pi-channels');
|
|
15
|
+
const { getSchedulerState } = require('../services/channel-scheduler');
|
|
16
|
+
const { PATHS, ensureStorageDirMigrated } = require('../../config/paths');
|
|
17
|
+
|
|
18
|
+
function sanitizeChannel(channel) {
|
|
19
|
+
if (!channel) return null;
|
|
20
|
+
return {
|
|
21
|
+
id: channel.id,
|
|
22
|
+
name: channel.name,
|
|
23
|
+
baseUrl: channel.baseUrl,
|
|
24
|
+
websiteUrl: channel.websiteUrl
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function selectLatestEnabledChannel(channels) {
|
|
29
|
+
const enabledChannels = (channels || []).filter(ch => ch.enabled !== false);
|
|
30
|
+
if (enabledChannels.length === 0) return null;
|
|
31
|
+
return enabledChannels.reduce((latest, current) => {
|
|
32
|
+
const latestTs = Number(latest?.updatedAt || latest?.createdAt || 0);
|
|
33
|
+
const currentTs = Number(current?.updatedAt || current?.createdAt || 0);
|
|
34
|
+
return currentTs > latestTs ? current : latest;
|
|
35
|
+
}, enabledChannels[0]);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function saveActiveChannelId(channelId) {
|
|
39
|
+
ensureStorageDirMigrated();
|
|
40
|
+
const filePath = PATHS.activeChannel.pi;
|
|
41
|
+
const dir = path.dirname(filePath);
|
|
42
|
+
if (!fs.existsSync(dir)) {
|
|
43
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
44
|
+
}
|
|
45
|
+
fs.writeFileSync(filePath, JSON.stringify({ activeChannelId: channelId }, null, 2), 'utf8');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function loadActiveChannelId() {
|
|
49
|
+
ensureStorageDirMigrated();
|
|
50
|
+
try {
|
|
51
|
+
if (!fs.existsSync(PATHS.activeChannel.pi)) return null;
|
|
52
|
+
const data = JSON.parse(fs.readFileSync(PATHS.activeChannel.pi, 'utf8'));
|
|
53
|
+
return data.activeChannelId || null;
|
|
54
|
+
} catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function removeActiveChannelFile() {
|
|
60
|
+
ensureStorageDirMigrated();
|
|
61
|
+
if (fs.existsSync(PATHS.activeChannel.pi)) {
|
|
62
|
+
fs.unlinkSync(PATHS.activeChannel.pi);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function resolveActiveChannel(channels, activeChannelId = null) {
|
|
67
|
+
if (!Array.isArray(channels) || channels.length === 0) return null;
|
|
68
|
+
if (activeChannelId) {
|
|
69
|
+
const matched = channels.find(channel => channel.id === activeChannelId);
|
|
70
|
+
if (matched) return matched;
|
|
71
|
+
}
|
|
72
|
+
return selectLatestEnabledChannel(channels)
|
|
73
|
+
|| channels.find(channel => channel.enabled !== false)
|
|
74
|
+
|| channels[0]
|
|
75
|
+
|| null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
router.get('/status', (req, res) => {
|
|
79
|
+
try {
|
|
80
|
+
const proxy = getPiProxyStatus();
|
|
81
|
+
const { channels } = getChannels();
|
|
82
|
+
const enabledChannels = channels.filter(ch => ch.enabled !== false);
|
|
83
|
+
const activeChannel = resolveActiveChannel(channels, loadActiveChannelId());
|
|
84
|
+
res.json({
|
|
85
|
+
proxy,
|
|
86
|
+
config: {
|
|
87
|
+
mode: 'models-yml-provider-config',
|
|
88
|
+
nativeProxyProtocol: false
|
|
89
|
+
},
|
|
90
|
+
activeChannel: sanitizeChannel(activeChannel),
|
|
91
|
+
enabledChannelsCount: enabledChannels.length,
|
|
92
|
+
totalChannelsCount: channels.length
|
|
93
|
+
});
|
|
94
|
+
} catch (error) {
|
|
95
|
+
res.status(500).json({ error: error.message });
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
router.post('/start', async (req, res) => {
|
|
100
|
+
try {
|
|
101
|
+
const enabledChannels = getEnabledChannels();
|
|
102
|
+
if (enabledChannels.length === 0) {
|
|
103
|
+
return res.status(400).json({
|
|
104
|
+
error: 'No enabled OMP channel found. Please create and enable a channel first.'
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
let currentChannel = selectLatestEnabledChannel(enabledChannels) || enabledChannels[0];
|
|
109
|
+
currentChannel = markChannelAsRecentlyUsed(currentChannel.id);
|
|
110
|
+
saveActiveChannelId(currentChannel.id);
|
|
111
|
+
|
|
112
|
+
const proxyResult = await startPiProxyServer();
|
|
113
|
+
if (!proxyResult.success) {
|
|
114
|
+
return res.status(500).json({ error: 'Failed to enable OMP models.yml provider config' });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const { broadcastProxyState, broadcastSchedulerState } = require('../websocket-server');
|
|
118
|
+
const updatedStatus = getPiProxyStatus();
|
|
119
|
+
const { channels: latestChannels } = getChannels();
|
|
120
|
+
const activeChannel = latestChannels.find(channel => channel.id === currentChannel.id) || currentChannel;
|
|
121
|
+
broadcastProxyState('pi', updatedStatus, activeChannel, latestChannels);
|
|
122
|
+
broadcastSchedulerState('pi', getSchedulerState('pi'));
|
|
123
|
+
|
|
124
|
+
res.json({
|
|
125
|
+
success: true,
|
|
126
|
+
port: proxyResult.port,
|
|
127
|
+
activeChannel: sanitizeChannel(activeChannel),
|
|
128
|
+
mode: 'models-yml-provider-config',
|
|
129
|
+
sync: proxyResult.sync || null,
|
|
130
|
+
warnings: proxyResult.warnings || [],
|
|
131
|
+
message: `OMP models.yml provider config enabled, active channel: ${activeChannel.name}`
|
|
132
|
+
});
|
|
133
|
+
} catch (error) {
|
|
134
|
+
console.error('[OMP Proxy] Start failed:', error);
|
|
135
|
+
res.status(500).json({ error: error.message });
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
router.post('/stop', async (req, res) => {
|
|
140
|
+
try {
|
|
141
|
+
const result = await stopPiProxyServer();
|
|
142
|
+
removeActiveChannelFile();
|
|
143
|
+
const { broadcastProxyState, broadcastSchedulerState } = require('../websocket-server');
|
|
144
|
+
const { channels } = getChannels();
|
|
145
|
+
broadcastProxyState('pi', getPiProxyStatus(), null, channels);
|
|
146
|
+
broadcastSchedulerState('pi', getSchedulerState('pi'));
|
|
147
|
+
res.json({ success: true, ...result });
|
|
148
|
+
} catch (error) {
|
|
149
|
+
console.error('[OMP Proxy] Stop failed:', error);
|
|
150
|
+
res.status(500).json({ error: error.message });
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
module.exports = router;
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const router = express.Router();
|
|
3
|
+
const {
|
|
4
|
+
buildLaunchCommand,
|
|
5
|
+
getProjects,
|
|
6
|
+
getSessionsByProject,
|
|
7
|
+
getSessionById,
|
|
8
|
+
getSessionMessages,
|
|
9
|
+
getRecentSessions,
|
|
10
|
+
searchSessions,
|
|
11
|
+
deleteSession,
|
|
12
|
+
forkSession,
|
|
13
|
+
saveSessionOrder,
|
|
14
|
+
isPiInstalled,
|
|
15
|
+
HOME_DIR
|
|
16
|
+
} = require('../services/pi-sessions');
|
|
17
|
+
const { loadAliases } = require('../services/alias');
|
|
18
|
+
const { broadcastLog } = require('../websocket-server');
|
|
19
|
+
|
|
20
|
+
function isNotFoundError(error) {
|
|
21
|
+
if (!error || !error.message) return false;
|
|
22
|
+
return error.message === 'Session not found' || error.message === 'Project not found';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function quoteForShell(value) {
|
|
26
|
+
return `"${String(value).replace(/"/g, '\\"')}"`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = () => {
|
|
30
|
+
router.get('/search/global', (req, res) => {
|
|
31
|
+
try {
|
|
32
|
+
if (!isPiInstalled()) {
|
|
33
|
+
return res.status(404).json({ error: 'OMP CLI not installed' });
|
|
34
|
+
}
|
|
35
|
+
const { keyword } = req.query;
|
|
36
|
+
const parsedContextLength = req.query.context ? parseInt(req.query.context, 10) : 35;
|
|
37
|
+
const contextLength = Number.isFinite(parsedContextLength) ? parsedContextLength : 35;
|
|
38
|
+
if (!keyword) {
|
|
39
|
+
return res.status(400).json({ error: 'Keyword is required' });
|
|
40
|
+
}
|
|
41
|
+
const sessions = searchSessions(keyword, contextLength);
|
|
42
|
+
const totalMatches = sessions.reduce((sum, session) => sum + (session.matchCount || 0), 0);
|
|
43
|
+
res.json({ keyword, totalMatches, sessions, source: 'pi' });
|
|
44
|
+
} catch (err) {
|
|
45
|
+
console.error('[OMP API] Failed to search sessions:', err);
|
|
46
|
+
res.status(500).json({ error: err.message });
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
router.get('/recent/list', (req, res) => {
|
|
51
|
+
try {
|
|
52
|
+
if (!isPiInstalled()) {
|
|
53
|
+
return res.status(404).json({ error: 'OMP CLI not installed' });
|
|
54
|
+
}
|
|
55
|
+
const limit = parseInt(req.query.limit, 10) || 5;
|
|
56
|
+
res.json({ sessions: getRecentSessions(limit), source: 'pi' });
|
|
57
|
+
} catch (err) {
|
|
58
|
+
console.error('[OMP API] Failed to get recent sessions:', err);
|
|
59
|
+
res.status(500).json({ error: err.message });
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
router.get('/:projectName/search', (req, res) => {
|
|
64
|
+
try {
|
|
65
|
+
if (!isPiInstalled()) {
|
|
66
|
+
return res.status(404).json({ error: 'OMP CLI not installed' });
|
|
67
|
+
}
|
|
68
|
+
const { projectName } = req.params;
|
|
69
|
+
const { keyword } = req.query;
|
|
70
|
+
const parsedContextLength = req.query.context ? parseInt(req.query.context, 10) : 35;
|
|
71
|
+
const contextLength = Number.isFinite(parsedContextLength) ? parsedContextLength : 35;
|
|
72
|
+
if (!keyword) {
|
|
73
|
+
return res.status(400).json({ error: 'Keyword is required' });
|
|
74
|
+
}
|
|
75
|
+
const sessions = searchSessions(keyword, contextLength, projectName);
|
|
76
|
+
const totalMatches = sessions.reduce((sum, session) => sum + (session.matchCount || 0), 0);
|
|
77
|
+
res.json({ keyword, totalMatches, sessions });
|
|
78
|
+
} catch (err) {
|
|
79
|
+
console.error('[OMP API] Failed to search project sessions:', err);
|
|
80
|
+
res.status(500).json({ error: err.message });
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
router.get('/:projectName', (req, res) => {
|
|
85
|
+
try {
|
|
86
|
+
if (!isPiInstalled()) {
|
|
87
|
+
return res.status(404).json({ error: 'OMP CLI not installed' });
|
|
88
|
+
}
|
|
89
|
+
const { projectName } = req.params;
|
|
90
|
+
const sessions = getSessionsByProject(projectName);
|
|
91
|
+
const aliases = loadAliases();
|
|
92
|
+
const project = getProjects().find(p => p.name === projectName);
|
|
93
|
+
const totalSize = sessions.reduce((sum, session) => sum + (session.size || 0), 0);
|
|
94
|
+
res.json({
|
|
95
|
+
sessions,
|
|
96
|
+
totalSize,
|
|
97
|
+
aliases,
|
|
98
|
+
projectInfo: {
|
|
99
|
+
name: projectName,
|
|
100
|
+
fullPath: project?.fullPath || project?.path || projectName,
|
|
101
|
+
path: project?.path || projectName,
|
|
102
|
+
displayName: project?.displayName || projectName
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
} catch (err) {
|
|
106
|
+
console.error('[OMP API] Failed to get sessions:', err);
|
|
107
|
+
res.status(500).json({ error: err.message });
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
router.get('/:projectName/:sessionId/status', (req, res) => {
|
|
112
|
+
try {
|
|
113
|
+
if (!isPiInstalled()) {
|
|
114
|
+
return res.status(404).json({ error: 'OMP CLI not installed' });
|
|
115
|
+
}
|
|
116
|
+
const session = getSessionById(req.params.sessionId);
|
|
117
|
+
if (!session) {
|
|
118
|
+
return res.status(404).json({ error: 'Session not found' });
|
|
119
|
+
}
|
|
120
|
+
res.json({
|
|
121
|
+
sessionId: req.params.sessionId,
|
|
122
|
+
lastModified: session.mtime || null,
|
|
123
|
+
size: session.size || 0
|
|
124
|
+
});
|
|
125
|
+
} catch (err) {
|
|
126
|
+
console.error('[OMP API] Failed to get session status:', err);
|
|
127
|
+
res.status(500).json({ error: err.message });
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
router.get('/:projectName/:sessionId/outline', (req, res) => {
|
|
132
|
+
try {
|
|
133
|
+
if (!isPiInstalled()) {
|
|
134
|
+
return res.status(404).json({ error: 'OMP CLI not installed' });
|
|
135
|
+
}
|
|
136
|
+
const session = getSessionById(req.params.sessionId);
|
|
137
|
+
if (!session) {
|
|
138
|
+
return res.status(404).json({ error: 'Session not found' });
|
|
139
|
+
}
|
|
140
|
+
let userMessageNumber = 0;
|
|
141
|
+
const items = [];
|
|
142
|
+
for (const msg of getSessionMessages(req.params.sessionId)) {
|
|
143
|
+
if (msg.type !== 'user') continue;
|
|
144
|
+
const preview = typeof msg.content === 'string' ? msg.content.trim() : '';
|
|
145
|
+
if (!preview) continue;
|
|
146
|
+
userMessageNumber += 1;
|
|
147
|
+
items.push({
|
|
148
|
+
userMessageNumber,
|
|
149
|
+
preview: preview.length > 42 ? `${preview.slice(0, 42)}...` : preview,
|
|
150
|
+
timestamp: msg.timestamp || null
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
res.json({ sessionId: req.params.sessionId, items });
|
|
154
|
+
} catch (err) {
|
|
155
|
+
console.error('[OMP API] Failed to get session outline:', err);
|
|
156
|
+
res.status(500).json({ error: err.message });
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
router.get('/:projectName/:sessionId/messages', (req, res) => {
|
|
161
|
+
try {
|
|
162
|
+
if (!isPiInstalled()) {
|
|
163
|
+
return res.status(404).json({ error: 'OMP CLI not installed' });
|
|
164
|
+
}
|
|
165
|
+
const { sessionId } = req.params;
|
|
166
|
+
const { page = 1, limit = 20, order = 'desc' } = req.query;
|
|
167
|
+
const session = getSessionById(sessionId);
|
|
168
|
+
if (!session) {
|
|
169
|
+
return res.status(404).json({ error: 'Session not found' });
|
|
170
|
+
}
|
|
171
|
+
let userMessageNumber = 0;
|
|
172
|
+
const convertedMessages = getSessionMessages(sessionId).map((message) => {
|
|
173
|
+
if (message.type !== 'user') return message;
|
|
174
|
+
userMessageNumber += 1;
|
|
175
|
+
return { ...message, userMessageNumber };
|
|
176
|
+
});
|
|
177
|
+
const pageNum = parseInt(page, 10) || 1;
|
|
178
|
+
const limitNum = parseInt(limit, 10) || 20;
|
|
179
|
+
const orderedMessages = order === 'desc' ? [...convertedMessages].reverse() : convertedMessages;
|
|
180
|
+
const start = (pageNum - 1) * limitNum;
|
|
181
|
+
const end = start + limitNum;
|
|
182
|
+
res.json({
|
|
183
|
+
messages: orderedMessages.slice(start, end),
|
|
184
|
+
metadata: {
|
|
185
|
+
gitBranch: null,
|
|
186
|
+
gitRepository: null,
|
|
187
|
+
cwd: session?.directory || null,
|
|
188
|
+
provider: session?.provider || '',
|
|
189
|
+
model: session?.model || 'pi'
|
|
190
|
+
},
|
|
191
|
+
pagination: {
|
|
192
|
+
page: pageNum,
|
|
193
|
+
limit: limitNum,
|
|
194
|
+
total: orderedMessages.length,
|
|
195
|
+
hasMore: end < orderedMessages.length
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
} catch (err) {
|
|
199
|
+
console.error('[OMP API] Failed to get session messages:', err);
|
|
200
|
+
res.status(500).json({ error: err.message });
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
router.delete('/:projectName/:sessionId', (req, res) => {
|
|
205
|
+
try {
|
|
206
|
+
if (!isPiInstalled()) {
|
|
207
|
+
return res.status(404).json({ error: 'OMP CLI not installed' });
|
|
208
|
+
}
|
|
209
|
+
res.json(deleteSession(req.params.sessionId));
|
|
210
|
+
} catch (err) {
|
|
211
|
+
if (isNotFoundError(err)) {
|
|
212
|
+
return res.status(404).json({ error: err.message });
|
|
213
|
+
}
|
|
214
|
+
console.error('[OMP API] Failed to delete session:', err);
|
|
215
|
+
res.status(500).json({ error: err.message });
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
router.post('/:projectName/batch-delete', (req, res) => {
|
|
220
|
+
try {
|
|
221
|
+
if (!isPiInstalled()) {
|
|
222
|
+
return res.status(404).json({ error: 'OMP CLI not installed' });
|
|
223
|
+
}
|
|
224
|
+
const { sessionIds } = req.body || {};
|
|
225
|
+
if (!Array.isArray(sessionIds) || sessionIds.length === 0) {
|
|
226
|
+
return res.status(400).json({ error: 'sessionIds must be a non-empty array' });
|
|
227
|
+
}
|
|
228
|
+
const uniqueSessionIds = Array.from(new Set(
|
|
229
|
+
sessionIds
|
|
230
|
+
.filter(sessionId => typeof sessionId === 'string')
|
|
231
|
+
.map(sessionId => sessionId.trim())
|
|
232
|
+
.filter(Boolean)
|
|
233
|
+
));
|
|
234
|
+
if (uniqueSessionIds.length === 0) {
|
|
235
|
+
return res.status(400).json({ error: 'sessionIds must be a non-empty array' });
|
|
236
|
+
}
|
|
237
|
+
const deletedSessionIds = [];
|
|
238
|
+
const failed = [];
|
|
239
|
+
uniqueSessionIds.forEach((sessionId) => {
|
|
240
|
+
try {
|
|
241
|
+
deleteSession(sessionId);
|
|
242
|
+
deletedSessionIds.push(sessionId);
|
|
243
|
+
} catch (err) {
|
|
244
|
+
failed.push({ sessionId, error: err.message });
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
res.json({
|
|
248
|
+
success: failed.length === 0,
|
|
249
|
+
requestedCount: uniqueSessionIds.length,
|
|
250
|
+
deletedCount: deletedSessionIds.length,
|
|
251
|
+
deletedSessionIds,
|
|
252
|
+
failed
|
|
253
|
+
});
|
|
254
|
+
} catch (err) {
|
|
255
|
+
console.error('[OMP API] Failed to batch delete sessions:', err);
|
|
256
|
+
res.status(500).json({ error: err.message });
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
router.post('/:projectName/:sessionId/fork', (req, res) => {
|
|
261
|
+
try {
|
|
262
|
+
if (!isPiInstalled()) {
|
|
263
|
+
return res.status(404).json({ error: 'OMP CLI not installed' });
|
|
264
|
+
}
|
|
265
|
+
res.json(forkSession(req.params.sessionId, req.body || {}));
|
|
266
|
+
} catch (err) {
|
|
267
|
+
if (isNotFoundError(err)) {
|
|
268
|
+
return res.status(404).json({ error: err.message });
|
|
269
|
+
}
|
|
270
|
+
console.error('[OMP API] Failed to fork session:', err);
|
|
271
|
+
res.status(500).json({ error: err.message });
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
router.post('/:projectName/order', (req, res) => {
|
|
276
|
+
try {
|
|
277
|
+
if (!isPiInstalled()) {
|
|
278
|
+
return res.status(404).json({ error: 'OMP CLI not installed' });
|
|
279
|
+
}
|
|
280
|
+
const { order } = req.body || {};
|
|
281
|
+
if (!Array.isArray(order)) {
|
|
282
|
+
return res.status(400).json({ error: 'order must be an array' });
|
|
283
|
+
}
|
|
284
|
+
saveSessionOrder(req.params.projectName, order);
|
|
285
|
+
res.json({ success: true });
|
|
286
|
+
} catch (err) {
|
|
287
|
+
console.error('[OMP API] Failed to save session order:', err);
|
|
288
|
+
res.status(500).json({ error: err.message });
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
router.post('/:projectName/:sessionId/launch', (req, res) => {
|
|
293
|
+
try {
|
|
294
|
+
if (!isPiInstalled()) {
|
|
295
|
+
return res.status(404).json({ error: 'OMP CLI not installed' });
|
|
296
|
+
}
|
|
297
|
+
const { projectName, sessionId } = req.params;
|
|
298
|
+
const session = getSessionById(sessionId);
|
|
299
|
+
if (!session) {
|
|
300
|
+
return res.status(404).json({ error: 'Session not found' });
|
|
301
|
+
}
|
|
302
|
+
const project = getProjects().find(p => p.name === projectName);
|
|
303
|
+
const cwd = session.directory || project?.fullPath || HOME_DIR;
|
|
304
|
+
const command = buildLaunchCommand(sessionId, cwd, req.body || {});
|
|
305
|
+
const copyCommand = `cd ${quoteForShell(cwd)} && ${command}`;
|
|
306
|
+
|
|
307
|
+
broadcastLog({
|
|
308
|
+
type: 'action',
|
|
309
|
+
action: 'launch_pi_session',
|
|
310
|
+
message: `复制 OMP 会话启动命令 ${sessionId.substring(0, 8)}`,
|
|
311
|
+
sessionId,
|
|
312
|
+
tool: 'pi',
|
|
313
|
+
toolType: 'pi',
|
|
314
|
+
source: 'pi',
|
|
315
|
+
timestamp: Date.now()
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
res.json({
|
|
319
|
+
success: true,
|
|
320
|
+
cwd,
|
|
321
|
+
sessionFile: session.filePath,
|
|
322
|
+
sessionId,
|
|
323
|
+
tool: 'pi',
|
|
324
|
+
command,
|
|
325
|
+
copyCommand
|
|
326
|
+
});
|
|
327
|
+
} catch (err) {
|
|
328
|
+
console.error('[OMP API] Failed to prepare launch command:', err);
|
|
329
|
+
res.status(500).json({ error: err.message });
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
return router;
|
|
334
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const router = express.Router();
|
|
3
|
+
const {
|
|
4
|
+
getStatistics,
|
|
5
|
+
getDailyStatistics,
|
|
6
|
+
getTodayStatistics
|
|
7
|
+
} = require('../services/pi-statistics-service');
|
|
8
|
+
|
|
9
|
+
router.get('/summary', (req, res) => {
|
|
10
|
+
try {
|
|
11
|
+
res.json(getStatistics());
|
|
12
|
+
} catch (error) {
|
|
13
|
+
console.error('[OMP] Failed to get statistics:', error);
|
|
14
|
+
res.status(500).json({ error: 'Failed to get statistics' });
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
router.get('/today', (req, res) => {
|
|
19
|
+
try {
|
|
20
|
+
res.json(getTodayStatistics());
|
|
21
|
+
} catch (error) {
|
|
22
|
+
console.error('[OMP] Failed to get today statistics:', error);
|
|
23
|
+
res.status(500).json({ error: 'Failed to get today statistics' });
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
router.get('/daily/:date', (req, res) => {
|
|
28
|
+
try {
|
|
29
|
+
const { date } = req.params;
|
|
30
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) {
|
|
31
|
+
return res.status(400).json({ error: 'Invalid date format. Expected YYYY-MM-DD' });
|
|
32
|
+
}
|
|
33
|
+
res.json(getDailyStatistics(date));
|
|
34
|
+
} catch (error) {
|
|
35
|
+
console.error('[OMP] Failed to get daily statistics:', error);
|
|
36
|
+
res.status(500).json({ error: 'Failed to get daily statistics' });
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
module.exports = router;
|
|
@@ -10,7 +10,7 @@ const { maskToken } = require('../services/oauth-utils');
|
|
|
10
10
|
const { sendApiError } = require('./validation-errors');
|
|
11
11
|
|
|
12
12
|
const router = express.Router();
|
|
13
|
-
const SUPPORTED_PLATFORMS = ['claude', 'codex', 'gemini', 'opencode'];
|
|
13
|
+
const SUPPORTED_PLATFORMS = ['claude', 'codex', 'gemini', 'opencode', 'pi'];
|
|
14
14
|
const pluginServices = new Map();
|
|
15
15
|
|
|
16
16
|
function resolvePlatform(rawPlatform) {
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
const express = require('express');
|
|
6
6
|
const router = express.Router();
|
|
7
7
|
const promptsService = require('../services/prompts-service');
|
|
8
|
+
const PROMPT_FILE_PLATFORMS = ['claude', 'codex', 'gemini', 'opencode'];
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* GET /api/prompts/presets
|
|
@@ -203,7 +204,7 @@ router.get('/platform/:platform', (req, res) => {
|
|
|
203
204
|
try {
|
|
204
205
|
const { platform } = req.params;
|
|
205
206
|
|
|
206
|
-
if (!
|
|
207
|
+
if (!PROMPT_FILE_PLATFORMS.includes(platform)) {
|
|
207
208
|
return res.status(400).json({
|
|
208
209
|
success: false,
|
|
209
210
|
error: `无效的平台: ${platform}`
|
|
@@ -234,7 +235,7 @@ router.post('/import/:platform', (req, res) => {
|
|
|
234
235
|
const { platform } = req.params;
|
|
235
236
|
const { name } = req.body;
|
|
236
237
|
|
|
237
|
-
if (!
|
|
238
|
+
if (!PROMPT_FILE_PLATFORMS.includes(platform)) {
|
|
238
239
|
return res.status(400).json({
|
|
239
240
|
success: false,
|
|
240
241
|
error: `无效的平台: ${platform}`
|
package/src/server/api/skills.js
CHANGED
|
@@ -8,7 +8,7 @@ const { maskToken } = require('../services/oauth-utils');
|
|
|
8
8
|
const { sendApiError } = require('./validation-errors');
|
|
9
9
|
|
|
10
10
|
const router = express.Router();
|
|
11
|
-
const SUPPORTED_PLATFORMS = ['claude', 'codex', 'gemini', 'opencode'];
|
|
11
|
+
const SUPPORTED_PLATFORMS = ['claude', 'codex', 'gemini', 'opencode', 'pi'];
|
|
12
12
|
const skillServices = new Map();
|
|
13
13
|
|
|
14
14
|
function resolvePlatform(rawPlatform) {
|
|
@@ -135,7 +135,7 @@ router.get('/check-git/*', (req, res) => {
|
|
|
135
135
|
|
|
136
136
|
/**
|
|
137
137
|
* GET /api/workspaces/available-projects
|
|
138
|
-
* 获取所有渠道(Claude/Codex/Gemini)的项目并集
|
|
138
|
+
* 获取所有渠道(Claude/Codex/Gemini/OpenCode/OMP)的项目并集
|
|
139
139
|
*/
|
|
140
140
|
router.get('/available-projects', async (req, res) => {
|
|
141
141
|
try {
|
|
@@ -411,7 +411,7 @@ router.delete('/:id/projects/:projectName', (req, res) => {
|
|
|
411
411
|
/**
|
|
412
412
|
* POST /api/workspaces/:id/launch
|
|
413
413
|
* 获取在工作区启动 CLI 工具的命令
|
|
414
|
-
* Body: { tool: 'claude' | 'codex' | 'gemini', projectName?: string }
|
|
414
|
+
* Body: { tool: 'claude' | 'codex' | 'gemini' | 'opencode' | 'pi' | 'omp', projectName?: string }
|
|
415
415
|
*/
|
|
416
416
|
router.post('/:id/launch', (req, res) => {
|
|
417
417
|
try {
|
package/src/server/index.js
CHANGED
|
@@ -23,6 +23,8 @@ const { startProxyServer } = require('./proxy-server');
|
|
|
23
23
|
const { startCodexProxyServer } = require('./codex-proxy-server');
|
|
24
24
|
const { startGeminiProxyServer } = require('./gemini-proxy-server');
|
|
25
25
|
const { startOpenCodeProxyServer, collectProxyModelList } = require('./opencode-proxy-server');
|
|
26
|
+
const { startPiProxyServer } = require('./pi-proxy-server');
|
|
27
|
+
const { createRemoteMutationGuard } = require('./services/network-access');
|
|
26
28
|
const { createApiRequestLogger } = require('./services/request-logger');
|
|
27
29
|
const { inspectWebBuildState, ensureWebDistReady } = require('./services/web-build');
|
|
28
30
|
const { ensureHttpsCredentials } = require('./services/https-cert');
|
|
@@ -148,6 +150,8 @@ async function startServer(port, host = '127.0.0.1', options = {}) {
|
|
|
148
150
|
|
|
149
151
|
const app = express();
|
|
150
152
|
const useHttps = options.useHttps === true || process.argv.includes('--https');
|
|
153
|
+
const lanMode = host === '0.0.0.0';
|
|
154
|
+
const allowRemoteMutation = process.env.CC_TOOL_ALLOW_REMOTE_WRITE === 'true';
|
|
151
155
|
|
|
152
156
|
// Middleware
|
|
153
157
|
app.use(express.json({ limit: '100mb' }));
|
|
@@ -167,6 +171,15 @@ async function startServer(port, host = '127.0.0.1', options = {}) {
|
|
|
167
171
|
// API 请求日志(由 CC_TOOL_LOG_API_REQUESTS=true 环境变量控制,默认关闭)
|
|
168
172
|
app.use('/api', createApiRequestLogger());
|
|
169
173
|
|
|
174
|
+
if (lanMode) {
|
|
175
|
+
app.use('/api', createRemoteMutationGuard({
|
|
176
|
+
enabled: true,
|
|
177
|
+
allowRemoteMutation,
|
|
178
|
+
message: '出于安全考虑,LAN 模式默认仅允许本机执行写操作。可设置 CC_TOOL_ALLOW_REMOTE_WRITE=true 覆盖。'
|
|
179
|
+
}));
|
|
180
|
+
|
|
181
|
+
}
|
|
182
|
+
|
|
170
183
|
// API Routes
|
|
171
184
|
app.use('/api/projects', require('./api/projects')(config));
|
|
172
185
|
app.use('/api/sessions', require('./api/sessions')(config));
|
|
@@ -189,6 +202,14 @@ async function startServer(port, host = '127.0.0.1', options = {}) {
|
|
|
189
202
|
app.use('/api/opencode/proxy', require('./api/opencode-proxy'));
|
|
190
203
|
app.use('/api/opencode/statistics', require('./api/opencode-statistics'));
|
|
191
204
|
|
|
205
|
+
// OMP API Routes
|
|
206
|
+
app.use('/api/pi/projects', require('./api/pi-projects')(config));
|
|
207
|
+
app.use('/api/pi/sessions', require('./api/pi-sessions')(config));
|
|
208
|
+
app.use('/api/pi/channels', require('./api/pi-channels')(config));
|
|
209
|
+
app.use('/api/pi/proxy', require('./api/pi-proxy'));
|
|
210
|
+
app.use('/api/pi/statistics', require('./api/pi-statistics'));
|
|
211
|
+
app.use('/api/pi/config', require('./api/pi-config'));
|
|
212
|
+
|
|
192
213
|
app.use('/api/aliases', require('./api/aliases')());
|
|
193
214
|
app.use('/api/favorites', require('./api/favorites'));
|
|
194
215
|
app.use('/api/ui-config', require('./api/ui-config'));
|
|
@@ -304,6 +325,9 @@ async function startServer(port, host = '127.0.0.1', options = {}) {
|
|
|
304
325
|
console.log(chalk.gray(' [TIP] 首次访问自签名证书时,浏览器可能会提示手动信任本地证书'));
|
|
305
326
|
}
|
|
306
327
|
|
|
328
|
+
if (host === '0.0.0.0' && !allowRemoteMutation) {
|
|
329
|
+
console.log(chalk.yellow(' [LOCK] 已启用 LAN 安全保护:远程写操作默认禁用'));
|
|
330
|
+
}
|
|
307
331
|
// 自动恢复代理状态
|
|
308
332
|
autoRestoreProxies();
|
|
309
333
|
|
|
@@ -425,6 +449,23 @@ function autoRestoreProxies() {
|
|
|
425
449
|
console.error(chalk.red(`[ERROR] OpenCode 代理启动失败: ${err.message}`));
|
|
426
450
|
});
|
|
427
451
|
}
|
|
452
|
+
|
|
453
|
+
// 检查 OMP 受管 provider 配置状态文件
|
|
454
|
+
const piActiveFile = PATHS.activeChannel.pi;
|
|
455
|
+
if (fs.existsSync(piActiveFile)) {
|
|
456
|
+
console.log(chalk.cyan('\n[SYNC] 检测到 OMP 受管渠道状态文件,正在自动恢复...'));
|
|
457
|
+
startPiProxyServer({ preserveStartTime: true })
|
|
458
|
+
.then((result) => {
|
|
459
|
+
if (result.success) {
|
|
460
|
+
console.log(chalk.green(`[OK] OMP models.yml 受管 provider 已自动启用,端口: ${result.port}`));
|
|
461
|
+
} else {
|
|
462
|
+
console.error(chalk.red(`[ERROR] OMP models.yml 受管 provider 恢复失败: ${result.error || 'Unknown error'}`));
|
|
463
|
+
}
|
|
464
|
+
})
|
|
465
|
+
.catch((err) => {
|
|
466
|
+
console.error(chalk.red(`[ERROR] OMP models.yml 受管 provider 恢复失败: ${err.message}`));
|
|
467
|
+
});
|
|
468
|
+
}
|
|
428
469
|
}
|
|
429
470
|
|
|
430
471
|
// 启动时执行健康检查
|