@axhub/genie 0.2.8 → 0.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -675
- package/dist/api-docs.html +2 -2
- package/dist/assets/App-CYCCsgwf.js +264 -0
- package/dist/assets/ReviewApp-0srHIXwb.js +1 -0
- package/dist/assets/{_basePickBy-CqJbRZ9y.js → _basePickBy-DVVb07UV.js} +1 -1
- package/dist/assets/{_baseUniq-BS8YH8jO.js → _baseUniq-BtbziL5G.js} +1 -1
- package/dist/assets/{arc-BBmKEN-S.js → arc-BsCC8yBD.js} +1 -1
- package/dist/assets/{architectureDiagram-2XIMDMQ5-N5lcb82R.js → architectureDiagram-2XIMDMQ5-woFp6eNI.js} +1 -1
- package/dist/assets/{blockDiagram-WCTKOSBZ-DTMwHuLn.js → blockDiagram-WCTKOSBZ-ya8VAc2k.js} +1 -1
- package/dist/assets/{c4Diagram-IC4MRINW-BTKlkXI9.js → c4Diagram-IC4MRINW-CY1dZmIZ.js} +1 -1
- package/dist/assets/channel-BMhScXFe.js +1 -0
- package/dist/assets/{chunk-4BX2VUAB-DUdoTxAc.js → chunk-4BX2VUAB-CR1lAd74.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-Bm_92xe4.js → chunk-55IACEB6-CP98WcFC.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-CGW0g62g.js → chunk-FMBD7UC4-D9c7ijAB.js} +1 -1
- package/dist/assets/{chunk-JSJVCQXG-DYkTH3w1.js → chunk-JSJVCQXG-DQAGYOn-.js} +1 -1
- package/dist/assets/{chunk-KX2RTZJC-C9oTlISU.js → chunk-KX2RTZJC-BbTXiDq7.js} +1 -1
- package/dist/assets/{chunk-NQ4KR5QH-CM50ygWP.js → chunk-NQ4KR5QH-BI6AX0dr.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-7dzpYeNJ.js → chunk-QZHKN3VN-DB3V2Ifo.js} +1 -1
- package/dist/assets/{chunk-WL4C6EOR-Cm9nQrsr.js → chunk-WL4C6EOR-DhzTthv6.js} +1 -1
- package/dist/assets/classDiagram-VBA2DB6C-CMIxlWcT.js +1 -0
- package/dist/assets/classDiagram-v2-RAHNMMFH-CMIxlWcT.js +1 -0
- package/dist/assets/clone-BPqOt4r3.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-Ccp_p0JZ.js → cose-bilkent-S5V4N54A-BQ09ZE2j.js} +1 -1
- package/dist/assets/{dagre-KLK3FWXG-fBwTLUp9.js → dagre-KLK3FWXG-Dc2ueD_R.js} +1 -1
- package/dist/assets/{diagram-E7M64L7V-CeNVmFUp.js → diagram-E7M64L7V-DP-LsQoL.js} +1 -1
- package/dist/assets/{diagram-IFDJBPK2-CtavyLGa.js → diagram-IFDJBPK2-Cg6r42cB.js} +1 -1
- package/dist/assets/{diagram-P4PSJMXO-CpQTjQwc.js → diagram-P4PSJMXO-aHsfoUZE.js} +1 -1
- package/dist/assets/{erDiagram-INFDFZHY-B8R5vwhd.js → erDiagram-INFDFZHY-qBXJ4aAz.js} +1 -1
- package/dist/assets/{flowDiagram-PKNHOUZH-BvkVVwIQ.js → flowDiagram-PKNHOUZH-D_13emJM.js} +1 -1
- package/dist/assets/{ganttDiagram-A5KZAMGK-DOu3hSNa.js → ganttDiagram-A5KZAMGK-BvIcOLwz.js} +1 -1
- package/dist/assets/{gitGraphDiagram-K3NZZRJ6-C7zT67YE.js → gitGraphDiagram-K3NZZRJ6-ad0vvNcU.js} +1 -1
- package/dist/assets/{graph-D11wiwHo.js → graph-CeJCMjan.js} +1 -1
- package/dist/assets/{highlighted-body-TPN3WLV5-Babpthg-.js → highlighted-body-TPN3WLV5-B_novwSz.js} +1 -1
- package/dist/assets/index-C514cLyb.js +2 -0
- package/dist/assets/index-h1DBl_g3.css +1 -0
- package/dist/assets/{infoDiagram-LFFYTUFH-BmA7IpQG.js → infoDiagram-LFFYTUFH-lOxAqb3m.js} +1 -1
- package/dist/assets/{ishikawaDiagram-PHBUUO56-BEquZd3E.js → ishikawaDiagram-PHBUUO56-DIr-51gj.js} +1 -1
- package/dist/assets/{journeyDiagram-4ABVD52K-BfemGz7f.js → journeyDiagram-4ABVD52K-CYcIW0ZU.js} +1 -1
- package/dist/assets/{kanban-definition-K7BYSVSG-CWja3mln.js → kanban-definition-K7BYSVSG-C1ZK616a.js} +1 -1
- package/dist/assets/{layout-BLUNf-PJ.js → layout-CI2RM-v6.js} +1 -1
- package/dist/assets/{linear-DukIV_Xv.js → linear-DE7bISck.js} +1 -1
- package/dist/assets/{mermaid-O7DHMXV3-SgtM28qI.js → mermaid-O7DHMXV3-XxAJo8EK.js} +6 -6
- package/dist/assets/{mindmap-definition-YRQLILUH-4UjqXITU.js → mindmap-definition-YRQLILUH-Dz6EFjmn.js} +1 -1
- package/dist/assets/{pieDiagram-SKSYHLDU-8AxqJd0M.js → pieDiagram-SKSYHLDU-DPpEzUed.js} +1 -1
- package/dist/assets/{quadrantDiagram-337W2JSQ-D60m8V8r.js → quadrantDiagram-337W2JSQ-xdoXNet7.js} +1 -1
- package/dist/assets/{requirementDiagram-Z7DCOOCP-zqh9jBVf.js → requirementDiagram-Z7DCOOCP-DUq8H3CL.js} +1 -1
- package/dist/assets/{sankeyDiagram-WA2Y5GQK-CDZILTLI.js → sankeyDiagram-WA2Y5GQK-CmqEUxRu.js} +1 -1
- package/dist/assets/{sequenceDiagram-2WXFIKYE-7BReFd0L.js → sequenceDiagram-2WXFIKYE-DhtXRNiH.js} +1 -1
- package/dist/assets/{stateDiagram-RAJIS63D-HPTVdIG4.js → stateDiagram-RAJIS63D-Dj0HOlbN.js} +1 -1
- package/dist/assets/stateDiagram-v2-FVOUBMTO-C9utf5gv.js +1 -0
- package/dist/assets/{timeline-definition-YZTLITO2-CTVllFgr.js → timeline-definition-YZTLITO2-DUuJzZB5.js} +1 -1
- package/dist/assets/{treemap-KZPCXAKY-BtyxboJZ.js → treemap-KZPCXAKY-DpYBQ0qr.js} +1 -1
- package/dist/assets/vendor-codemirror-CMHSJ_9p.js +9 -0
- package/dist/assets/{vendor-react-Cpt6D04s.js → vendor-react-xmA_f8ig.js} +1 -1
- package/dist/assets/{vennDiagram-LZ73GAT5-D96ZI6Mg.js → vennDiagram-LZ73GAT5-DpePUyOd.js} +1 -1
- package/dist/assets/{xychartDiagram-JWTSCODW-eRk-39YO.js → xychartDiagram-JWTSCODW-Cfp1I4_U.js} +1 -1
- package/dist/index.html +5 -5
- package/package.json +8 -7
- package/server/acp-runtime/client.js +129 -16
- package/server/acp-runtime/index.js +54 -0
- package/server/acp-runtime/registry.js +2 -2
- package/server/acp-runtime/session-store.js +79 -5
- package/server/cli.js +55 -10
- package/server/database/db.js +20 -0
- package/server/external-agent/service.js +24 -6
- package/server/external-agent/ws.js +540 -27
- package/server/index.js +112 -151
- package/server/lan-access/core.js +79 -0
- package/server/lan-access/state.js +102 -0
- package/server/middleware/auth.js +57 -14
- package/server/projects.js +930 -667
- package/server/routes/auth.js +24 -4
- package/server/routes/cli-auth.js +21 -25
- package/server/routes/codex.js +84 -298
- package/server/routes/commands.js +322 -407
- package/server/routes/lan-access.js +231 -0
- package/server/routes/projects.js +154 -158
- package/server/routes/session-core.js +160 -91
- package/server/routes/settings.js +113 -99
- package/server/session-core/eventStore.js +60 -20
- package/server/session-core/providerAdapters.js +75 -38
- package/server/session-core/runtimeState.js +8 -0
- package/server/session-core/sessionListMerge.js +47 -0
- package/shared/conversationEvents.js +174 -15
- package/shared/modelConstants.js +79 -99
- package/dist/assets/App-CTKZtqB1.js +0 -460
- package/dist/assets/ReviewApp-DM6BNAzR.js +0 -1
- package/dist/assets/channel-1oJBvF-0.js +0 -1
- package/dist/assets/classDiagram-VBA2DB6C-d5TeKFM4.js +0 -1
- package/dist/assets/classDiagram-v2-RAHNMMFH-d5TeKFM4.js +0 -1
- package/dist/assets/clone-CinxIlEu.js +0 -1
- package/dist/assets/index-DFxzgWoO.js +0 -2
- package/dist/assets/index-YCFGDVKw.css +0 -1
- package/dist/assets/stateDiagram-v2-FVOUBMTO-DTUf5_gC.js +0 -1
- package/dist/assets/vendor-codemirror-Dz7_EqNA.js +0 -39
- package/server/_legacy-providers/README.md +0 -30
- package/server/_legacy-providers/claude-sdk.js +0 -956
- package/server/_legacy-providers/gemini-cli.js +0 -368
- package/server/_legacy-providers/openai-codex.js +0 -705
- package/server/_legacy-providers/opencode-cli.js +0 -674
- package/server/routes/git.js +0 -1110
- package/server/routes/mcp-utils.js +0 -48
- package/server/routes/mcp.js +0 -536
- package/server/routes/taskmaster.js +0 -1963
- package/server/utils/mcp-detector.js +0 -198
- package/server/utils/taskmaster-websocket.js +0 -129
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
import express from 'express';
|
|
2
|
-
import
|
|
2
|
+
import crypto from 'node:crypto';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import {
|
|
5
|
+
getClaudeSessionMetadata,
|
|
6
|
+
getCodexSessionMetadata,
|
|
7
|
+
getGeminiSessionMetadata,
|
|
8
|
+
getOpencodeSessionMetadata,
|
|
9
|
+
getProjects,
|
|
10
|
+
getProjectsList
|
|
11
|
+
} from '../projects.js';
|
|
3
12
|
import { discoverAllProviders, discoverProvider } from '../session-core/providerDiscovery.js';
|
|
4
13
|
import { getProviderAdapter } from '../session-core/providerAdapters.js';
|
|
5
|
-
import { listAcpSessions } from '../acp-runtime/session-store.js';
|
|
14
|
+
import { findAcpSessionRecord, listAcpSessions } from '../acp-runtime/session-store.js';
|
|
15
|
+
import { mergeSessionLists } from '../session-core/sessionListMerge.js';
|
|
6
16
|
|
|
7
17
|
const router = express.Router();
|
|
8
18
|
|
|
@@ -21,12 +31,138 @@ async function flattenProjectSessions(project) {
|
|
|
21
31
|
];
|
|
22
32
|
|
|
23
33
|
const acpSessions = await listAcpSessions({ projectPath });
|
|
34
|
+
const legacySessions = groups.flatMap(({ provider, items }) => (
|
|
35
|
+
items.map((item) => ({ ...item, provider, __provider: provider, source: item?.source || 'legacy' }))
|
|
36
|
+
));
|
|
37
|
+
const normalizedAcpSessions = acpSessions.map((item) => ({
|
|
38
|
+
...item,
|
|
39
|
+
provider: item.provider,
|
|
40
|
+
__provider: item.provider,
|
|
41
|
+
source: 'acp'
|
|
42
|
+
}));
|
|
24
43
|
|
|
25
|
-
return
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
44
|
+
return mergeSessionLists(legacySessions, normalizedAcpSessions);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function resolveProviderSessionRoute({
|
|
48
|
+
provider,
|
|
49
|
+
sessionId,
|
|
50
|
+
projectList = null
|
|
51
|
+
}) {
|
|
52
|
+
const normalizedProvider = String(provider || '').trim().toLowerCase();
|
|
53
|
+
const normalizedSessionId = String(sessionId || '').trim();
|
|
54
|
+
|
|
55
|
+
if (!normalizedProvider || !normalizedSessionId) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const projects = Array.isArray(projectList) ? projectList : await getProjectsList();
|
|
60
|
+
const normalizeComparableProjectPath = (projectPath) => {
|
|
61
|
+
if (typeof projectPath !== 'string' || !projectPath.trim()) {
|
|
62
|
+
return '';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const withoutWindowsLongPathPrefix = projectPath.startsWith('\\\\?\\')
|
|
66
|
+
? projectPath.slice(4)
|
|
67
|
+
: projectPath;
|
|
68
|
+
|
|
69
|
+
return path.normalize(withoutWindowsLongPathPrefix);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const findProjectByPath = (projectPath) => {
|
|
73
|
+
const normalizedProjectPath = normalizeComparableProjectPath(projectPath);
|
|
74
|
+
if (!normalizedProjectPath) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return projects.find((project) => {
|
|
79
|
+
const candidatePath = project.fullPath || project.path || '';
|
|
80
|
+
return normalizeComparableProjectPath(candidatePath) === normalizedProjectPath;
|
|
81
|
+
}) || null;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const acpRecord = await findAcpSessionRecord(normalizedSessionId, normalizedProvider);
|
|
85
|
+
const acpProject = findProjectByPath(acpRecord?.projectPath || null);
|
|
86
|
+
|
|
87
|
+
let matchedProject = acpProject;
|
|
88
|
+
let resolvedSource = acpRecord ? 'acp' : 'legacy';
|
|
89
|
+
|
|
90
|
+
if (!matchedProject) {
|
|
91
|
+
switch (normalizedProvider) {
|
|
92
|
+
case 'claude': {
|
|
93
|
+
const metadata = await getClaudeSessionMetadata(normalizedSessionId);
|
|
94
|
+
matchedProject = findProjectByPath(metadata?.cwd || null);
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
case 'codex': {
|
|
98
|
+
const metadata = await getCodexSessionMetadata(normalizedSessionId);
|
|
99
|
+
matchedProject = findProjectByPath(metadata?.cwd || null);
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
case 'gemini': {
|
|
103
|
+
const metadata = await getGeminiSessionMetadata(normalizedSessionId);
|
|
104
|
+
const projectHash = String(metadata?.projectHash || '').trim();
|
|
105
|
+
|
|
106
|
+
if (projectHash) {
|
|
107
|
+
matchedProject = projects.find((project) => {
|
|
108
|
+
const projectPath = normalizeComparableProjectPath(project.fullPath || project.path || '');
|
|
109
|
+
if (!projectPath) {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const candidateHash = crypto.createHash('sha256').update(projectPath).digest('hex');
|
|
114
|
+
return candidateHash === projectHash;
|
|
115
|
+
}) || null;
|
|
116
|
+
}
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
case 'opencode': {
|
|
120
|
+
const metadata = await getOpencodeSessionMetadata(normalizedSessionId);
|
|
121
|
+
matchedProject = findProjectByPath(metadata?.cwd || null);
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
default:
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (!matchedProject) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const adapter = getProviderAdapter(normalizedProvider);
|
|
134
|
+
let sessions = [];
|
|
135
|
+
|
|
136
|
+
if (normalizedProvider === 'claude') {
|
|
137
|
+
sessions = await adapter.listSessions({
|
|
138
|
+
projectName: matchedProject.name,
|
|
139
|
+
projectPath: matchedProject.fullPath || matchedProject.path,
|
|
140
|
+
limit: 1000,
|
|
141
|
+
offset: 0
|
|
142
|
+
});
|
|
143
|
+
} else {
|
|
144
|
+
sessions = await adapter.listSessions({
|
|
145
|
+
projectPath: matchedProject.fullPath || matchedProject.path,
|
|
146
|
+
limit: 0
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const matchedSession = (Array.isArray(sessions) ? sessions : []).find((session) => session.id === normalizedSessionId);
|
|
151
|
+
if (!matchedSession) {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
provider: normalizedProvider,
|
|
157
|
+
source: matchedSession.source || resolvedSource,
|
|
158
|
+
session: matchedSession,
|
|
159
|
+
project: {
|
|
160
|
+
name: matchedProject.name,
|
|
161
|
+
fullPath: matchedProject.fullPath || matchedProject.path,
|
|
162
|
+
path: matchedProject.path,
|
|
163
|
+
displayName: matchedProject.displayName || matchedProject.name
|
|
164
|
+
}
|
|
165
|
+
};
|
|
30
166
|
}
|
|
31
167
|
|
|
32
168
|
router.get('/providers', async (req, res) => {
|
|
@@ -68,98 +204,29 @@ router.get('/projects/:projectName/history-index', async (req, res) => {
|
|
|
68
204
|
}
|
|
69
205
|
});
|
|
70
206
|
|
|
71
|
-
router.get('/sessions/:sessionId/resolve', async (req, res) => {
|
|
207
|
+
router.get('/sessions/:provider/:sessionId/resolve', async (req, res) => {
|
|
72
208
|
try {
|
|
73
|
-
const
|
|
209
|
+
const requestedProvider = String(req.params.provider || '').trim().toLowerCase();
|
|
74
210
|
const requestedSessionId = String(req.params.sessionId || '').trim();
|
|
75
|
-
const providerHint = typeof req.query.provider === 'string' ? req.query.provider.trim().toLowerCase() : '';
|
|
76
|
-
|
|
77
|
-
if (!requestedSessionId) {
|
|
78
|
-
return res.status(400).json({ success: false, error: 'Session id is required' });
|
|
79
|
-
}
|
|
80
211
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
'codex',
|
|
84
|
-
'claude',
|
|
85
|
-
'gemini',
|
|
86
|
-
'opencode'
|
|
87
|
-
].filter((provider, index, all) => provider && all.indexOf(provider) === index);
|
|
88
|
-
|
|
89
|
-
let directProjectMatch = null;
|
|
90
|
-
let directMatchSessions = [];
|
|
91
|
-
|
|
92
|
-
for (const project of projects) {
|
|
93
|
-
const flattened = await flattenProjectSessions(project);
|
|
94
|
-
if (flattened.some((session) => session.id === requestedSessionId)) {
|
|
95
|
-
directProjectMatch = project;
|
|
96
|
-
directMatchSessions = flattened;
|
|
97
|
-
break;
|
|
98
|
-
}
|
|
212
|
+
if (!requestedProvider || !requestedSessionId) {
|
|
213
|
+
return res.status(400).json({ success: false, error: 'provider and session id are required' });
|
|
99
214
|
}
|
|
100
215
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
success: true,
|
|
106
|
-
found: true,
|
|
107
|
-
provider: matchedSession.provider,
|
|
108
|
-
source: matchedSession.source || 'legacy',
|
|
109
|
-
session: matchedSession,
|
|
110
|
-
project: {
|
|
111
|
-
name: directProjectMatch.name,
|
|
112
|
-
fullPath: directProjectMatch.fullPath || directProjectMatch.path,
|
|
113
|
-
path: directProjectMatch.path,
|
|
114
|
-
displayName: directProjectMatch.displayName || directProjectMatch.name
|
|
115
|
-
}
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
for (const provider of providerOrder) {
|
|
120
|
-
const adapter = getProviderAdapter(provider);
|
|
121
|
-
|
|
122
|
-
for (const project of projects) {
|
|
123
|
-
let sessions = [];
|
|
124
|
-
|
|
125
|
-
try {
|
|
126
|
-
if (provider === 'claude') {
|
|
127
|
-
const result = await adapter.listSessions({
|
|
128
|
-
projectName: project.name,
|
|
129
|
-
projectPath: project.fullPath || project.path,
|
|
130
|
-
limit: 1000,
|
|
131
|
-
offset: 0
|
|
132
|
-
});
|
|
133
|
-
sessions = Array.isArray(result) ? result : [];
|
|
134
|
-
} else {
|
|
135
|
-
sessions = await adapter.listSessions({ projectPath: project.fullPath || project.path, limit: 0 });
|
|
136
|
-
}
|
|
137
|
-
} catch (error) {
|
|
138
|
-
continue;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const matchedSession = sessions.find((session) => session.id === requestedSessionId);
|
|
142
|
-
if (!matchedSession) {
|
|
143
|
-
continue;
|
|
144
|
-
}
|
|
216
|
+
const result = await resolveProviderSessionRoute({
|
|
217
|
+
provider: requestedProvider,
|
|
218
|
+
sessionId: requestedSessionId
|
|
219
|
+
});
|
|
145
220
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
found: true,
|
|
149
|
-
provider,
|
|
150
|
-
source: matchedSession.source || 'legacy',
|
|
151
|
-
session: matchedSession,
|
|
152
|
-
project: {
|
|
153
|
-
name: project.name,
|
|
154
|
-
fullPath: project.fullPath || project.path,
|
|
155
|
-
path: project.path,
|
|
156
|
-
displayName: project.displayName || project.name
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
}
|
|
221
|
+
if (!result) {
|
|
222
|
+
return res.status(404).json({ success: true, found: false });
|
|
160
223
|
}
|
|
161
224
|
|
|
162
|
-
res.
|
|
225
|
+
res.json({
|
|
226
|
+
success: true,
|
|
227
|
+
found: true,
|
|
228
|
+
...result
|
|
229
|
+
});
|
|
163
230
|
} catch (error) {
|
|
164
231
|
res.status(500).json({ success: false, error: error.message });
|
|
165
232
|
}
|
|
@@ -208,4 +275,6 @@ router.get('/sessions/:provider/:sessionId/events', async (req, res) => {
|
|
|
208
275
|
}
|
|
209
276
|
});
|
|
210
277
|
|
|
278
|
+
export { flattenProjectSessions, resolveProviderSessionRoute };
|
|
279
|
+
|
|
211
280
|
export default router;
|
|
@@ -2,40 +2,50 @@ import express from 'express';
|
|
|
2
2
|
import { apiKeysDb, credentialsDb } from '../database/db.js';
|
|
3
3
|
|
|
4
4
|
const router = express.Router();
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
const DISALLOWED_CREDENTIAL_TYPES = new Set(['github_token']);
|
|
6
|
+
|
|
7
|
+
function parseNumericId(rawValue) {
|
|
8
|
+
const parsed = Number.parseInt(String(rawValue || ''), 10);
|
|
9
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function sanitizeApiKeyRecord(record) {
|
|
13
|
+
const visiblePrefix = String(record?.api_key || '').slice(0, 10);
|
|
14
|
+
return {
|
|
15
|
+
...record,
|
|
16
|
+
api_key: visiblePrefix ? `${visiblePrefix}...` : ''
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function badRequest(res, error) {
|
|
21
|
+
return res.status(400).json({ error });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function notFound(res, error) {
|
|
25
|
+
return res.status(404).json({ error });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
router.get('/api-keys', (req, res) => {
|
|
12
29
|
try {
|
|
13
|
-
const apiKeys = apiKeysDb.getApiKeys(req.user.id);
|
|
14
|
-
|
|
15
|
-
const sanitizedKeys = apiKeys.map(key => ({
|
|
16
|
-
...key,
|
|
17
|
-
api_key: key.api_key.substring(0, 10) + '...'
|
|
18
|
-
}));
|
|
19
|
-
res.json({ apiKeys: sanitizedKeys });
|
|
30
|
+
const apiKeys = apiKeysDb.getApiKeys(req.user.id).map(sanitizeApiKeyRecord);
|
|
31
|
+
res.json({ apiKeys });
|
|
20
32
|
} catch (error) {
|
|
21
33
|
console.error('Error fetching API keys:', error);
|
|
22
34
|
res.status(500).json({ error: 'Failed to fetch API keys' });
|
|
23
35
|
}
|
|
24
36
|
});
|
|
25
37
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if (!keyName || !keyName.trim()) {
|
|
32
|
-
return res.status(400).json({ error: 'Key name is required' });
|
|
33
|
-
}
|
|
38
|
+
router.post('/api-keys', (req, res) => {
|
|
39
|
+
const keyName = String(req.body?.keyName || '').trim();
|
|
40
|
+
if (!keyName) {
|
|
41
|
+
return badRequest(res, 'Key name is required');
|
|
42
|
+
}
|
|
34
43
|
|
|
35
|
-
|
|
44
|
+
try {
|
|
45
|
+
const apiKey = apiKeysDb.createApiKey(req.user.id, keyName);
|
|
36
46
|
res.json({
|
|
37
47
|
success: true,
|
|
38
|
-
apiKey
|
|
48
|
+
apiKey
|
|
39
49
|
});
|
|
40
50
|
} catch (error) {
|
|
41
51
|
console.error('Error creating API key:', error);
|
|
@@ -43,56 +53,54 @@ router.post('/api-keys', async (req, res) => {
|
|
|
43
53
|
}
|
|
44
54
|
});
|
|
45
55
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
56
|
+
router.delete('/api-keys/:keyId', (req, res) => {
|
|
57
|
+
const keyId = parseNumericId(req.params.keyId);
|
|
58
|
+
if (keyId === null) {
|
|
59
|
+
return badRequest(res, 'Invalid API key id');
|
|
60
|
+
}
|
|
51
61
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
res
|
|
62
|
+
try {
|
|
63
|
+
const removed = apiKeysDb.deleteApiKey(req.user.id, keyId);
|
|
64
|
+
if (!removed) {
|
|
65
|
+
return notFound(res, 'API key not found');
|
|
56
66
|
}
|
|
67
|
+
|
|
68
|
+
res.json({ success: true });
|
|
57
69
|
} catch (error) {
|
|
58
70
|
console.error('Error deleting API key:', error);
|
|
59
71
|
res.status(500).json({ error: 'Failed to delete API key' });
|
|
60
72
|
}
|
|
61
73
|
});
|
|
62
74
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const { keyId } = req.params;
|
|
67
|
-
const { isActive } = req.body;
|
|
75
|
+
router.patch('/api-keys/:keyId/toggle', (req, res) => {
|
|
76
|
+
const keyId = parseNumericId(req.params.keyId);
|
|
77
|
+
const isActive = req.body?.isActive;
|
|
68
78
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
79
|
+
if (keyId === null) {
|
|
80
|
+
return badRequest(res, 'Invalid API key id');
|
|
81
|
+
}
|
|
72
82
|
|
|
73
|
-
|
|
83
|
+
if (typeof isActive !== 'boolean') {
|
|
84
|
+
return badRequest(res, 'isActive must be a boolean');
|
|
85
|
+
}
|
|
74
86
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
res
|
|
87
|
+
try {
|
|
88
|
+
const updated = apiKeysDb.toggleApiKey(req.user.id, keyId, isActive);
|
|
89
|
+
if (!updated) {
|
|
90
|
+
return notFound(res, 'API key not found');
|
|
79
91
|
}
|
|
92
|
+
|
|
93
|
+
res.json({ success: true });
|
|
80
94
|
} catch (error) {
|
|
81
95
|
console.error('Error toggling API key:', error);
|
|
82
96
|
res.status(500).json({ error: 'Failed to toggle API key' });
|
|
83
97
|
}
|
|
84
98
|
});
|
|
85
99
|
|
|
86
|
-
|
|
87
|
-
// Generic Credentials Management
|
|
88
|
-
// ===============================
|
|
89
|
-
|
|
90
|
-
// Get all credentials for the authenticated user (optionally filtered by type)
|
|
91
|
-
router.get('/credentials', async (req, res) => {
|
|
100
|
+
router.get('/credentials', (req, res) => {
|
|
92
101
|
try {
|
|
93
|
-
const
|
|
94
|
-
const credentials = credentialsDb.getCredentials(req.user.id,
|
|
95
|
-
// Don't send the actual credential values for security
|
|
102
|
+
const credentialType = typeof req.query.type === 'string' ? req.query.type.trim() : null;
|
|
103
|
+
const credentials = credentialsDb.getCredentials(req.user.id, credentialType || null);
|
|
96
104
|
res.json({ credentials });
|
|
97
105
|
} catch (error) {
|
|
98
106
|
console.error('Error fetching credentials:', error);
|
|
@@ -100,38 +108,40 @@ router.get('/credentials', async (req, res) => {
|
|
|
100
108
|
}
|
|
101
109
|
});
|
|
102
110
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
111
|
+
router.post('/credentials', (req, res) => {
|
|
112
|
+
const credentialName = String(req.body?.credentialName || '').trim();
|
|
113
|
+
const credentialType = String(req.body?.credentialType || '').trim();
|
|
114
|
+
const credentialValue = String(req.body?.credentialValue || '').trim();
|
|
115
|
+
const description = String(req.body?.description || '').trim();
|
|
107
116
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
117
|
+
if (!credentialName) {
|
|
118
|
+
return badRequest(res, 'Credential name is required');
|
|
119
|
+
}
|
|
111
120
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
121
|
+
if (!credentialType) {
|
|
122
|
+
return badRequest(res, 'Credential type is required');
|
|
123
|
+
}
|
|
115
124
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
125
|
+
if (!credentialValue) {
|
|
126
|
+
return badRequest(res, 'Credential value is required');
|
|
127
|
+
}
|
|
119
128
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
129
|
+
if (DISALLOWED_CREDENTIAL_TYPES.has(credentialType.toLowerCase())) {
|
|
130
|
+
return badRequest(res, 'GitHub tokens are no longer supported.');
|
|
131
|
+
}
|
|
123
132
|
|
|
124
|
-
|
|
133
|
+
try {
|
|
134
|
+
const credential = credentialsDb.createCredential(
|
|
125
135
|
req.user.id,
|
|
126
|
-
credentialName
|
|
127
|
-
credentialType
|
|
128
|
-
credentialValue
|
|
129
|
-
description
|
|
136
|
+
credentialName,
|
|
137
|
+
credentialType,
|
|
138
|
+
credentialValue,
|
|
139
|
+
description || null
|
|
130
140
|
);
|
|
131
141
|
|
|
132
142
|
res.json({
|
|
133
143
|
success: true,
|
|
134
|
-
credential
|
|
144
|
+
credential
|
|
135
145
|
});
|
|
136
146
|
} catch (error) {
|
|
137
147
|
console.error('Error creating credential:', error);
|
|
@@ -139,40 +149,44 @@ router.post('/credentials', async (req, res) => {
|
|
|
139
149
|
}
|
|
140
150
|
});
|
|
141
151
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
152
|
+
router.delete('/credentials/:credentialId', (req, res) => {
|
|
153
|
+
const credentialId = parseNumericId(req.params.credentialId);
|
|
154
|
+
if (credentialId === null) {
|
|
155
|
+
return badRequest(res, 'Invalid credential id');
|
|
156
|
+
}
|
|
147
157
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
res
|
|
158
|
+
try {
|
|
159
|
+
const removed = credentialsDb.deleteCredential(req.user.id, credentialId);
|
|
160
|
+
if (!removed) {
|
|
161
|
+
return notFound(res, 'Credential not found');
|
|
152
162
|
}
|
|
163
|
+
|
|
164
|
+
res.json({ success: true });
|
|
153
165
|
} catch (error) {
|
|
154
166
|
console.error('Error deleting credential:', error);
|
|
155
167
|
res.status(500).json({ error: 'Failed to delete credential' });
|
|
156
168
|
}
|
|
157
169
|
});
|
|
158
170
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const { credentialId } = req.params;
|
|
163
|
-
const { isActive } = req.body;
|
|
171
|
+
router.patch('/credentials/:credentialId/toggle', (req, res) => {
|
|
172
|
+
const credentialId = parseNumericId(req.params.credentialId);
|
|
173
|
+
const isActive = req.body?.isActive;
|
|
164
174
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
175
|
+
if (credentialId === null) {
|
|
176
|
+
return badRequest(res, 'Invalid credential id');
|
|
177
|
+
}
|
|
168
178
|
|
|
169
|
-
|
|
179
|
+
if (typeof isActive !== 'boolean') {
|
|
180
|
+
return badRequest(res, 'isActive must be a boolean');
|
|
181
|
+
}
|
|
170
182
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
res
|
|
183
|
+
try {
|
|
184
|
+
const updated = credentialsDb.toggleCredential(req.user.id, credentialId, isActive);
|
|
185
|
+
if (!updated) {
|
|
186
|
+
return notFound(res, 'Credential not found');
|
|
175
187
|
}
|
|
188
|
+
|
|
189
|
+
res.json({ success: true });
|
|
176
190
|
} catch (error) {
|
|
177
191
|
console.error('Error toggling credential:', error);
|
|
178
192
|
res.status(500).json({ error: 'Failed to toggle credential' });
|