@lightharu/krouter 1.8.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/LICENSE +679 -0
- package/README.md +238 -0
- package/dist-web/assets/index-CM4-0adf.css +1 -0
- package/dist-web/assets/index-DCslvfUR.js +139 -0
- package/dist-web/favicon.svg +9 -0
- package/dist-web/icon.svg +9 -0
- package/dist-web/index.html +19 -0
- package/out-server/main/kiroAuthSync.js +249 -0
- package/out-server/main/kproxy/certManager.js +262 -0
- package/out-server/main/kproxy/index.js +254 -0
- package/out-server/main/kproxy/mitmProxy.js +475 -0
- package/out-server/main/kproxy/types.js +23 -0
- package/out-server/main/proxy/accountPool.js +543 -0
- package/out-server/main/proxy/clientConfig.js +596 -0
- package/out-server/main/proxy/index.js +25 -0
- package/out-server/main/proxy/kiroApi.js +1996 -0
- package/out-server/main/proxy/logger.js +407 -0
- package/out-server/main/proxy/modelCatalog.js +75 -0
- package/out-server/main/proxy/promptCacheTracker.js +301 -0
- package/out-server/main/proxy/proxyServer.js +3543 -0
- package/out-server/main/proxy/selfSignedCert.js +179 -0
- package/out-server/main/proxy/systemProxy.js +250 -0
- package/out-server/main/proxy/tokenCounter.js +164 -0
- package/out-server/main/proxy/toolNameRegistry.js +57 -0
- package/out-server/main/proxy/translator.js +1084 -0
- package/out-server/main/proxy/types.js +3 -0
- package/out-server/main/registration/browser-identity.js +184 -0
- package/out-server/main/registration/chainProxy.js +349 -0
- package/out-server/main/registration/config.js +58 -0
- package/out-server/main/registration/email-service.js +801 -0
- package/out-server/main/registration/fingerprint.js +352 -0
- package/out-server/main/registration/http-utils.js +148 -0
- package/out-server/main/registration/jwe.js +74 -0
- package/out-server/main/registration/names.js +142 -0
- package/out-server/main/registration/proton-mail-window.js +339 -0
- package/out-server/main/registration/registrar.js +1715 -0
- package/out-server/main/registration/tlsClientPool.js +70 -0
- package/out-server/main/registration/xxtea.js +161 -0
- package/out-server/main/runtimePaths.js +19 -0
- package/out-server/main/utils/redact.js +95 -0
- package/out-server/server/index.js +1272 -0
- package/out-server/server/services/accountExtras.js +105 -0
- package/out-server/server/services/accountProfileHydration.js +95 -0
- package/out-server/server/services/authFlows.js +509 -0
- package/out-server/server/services/dashboardTunnel.js +315 -0
- package/out-server/server/services/diagnostics.js +326 -0
- package/out-server/server/services/kiroAccounts.js +431 -0
- package/out-server/server/services/kiroSettings.js +260 -0
- package/out-server/server/services/kproxyRuntime.js +264 -0
- package/out-server/server/services/localKiroCredentials.js +320 -0
- package/out-server/server/services/machineIdRuntime.js +327 -0
- package/out-server/server/services/protonBrowserRuntime.js +724 -0
- package/out-server/server/services/proxyRuntime.js +523 -0
- package/out-server/server/services/registrationRuntime.js +106 -0
- package/out-server/server/store.js +266 -0
- package/package.json +113 -0
- package/resources/tls-client-xgo-1.14.0-windows-amd64.dll +0 -0
- package/scripts/kiro-manager-cli.cjs +3 -0
- package/scripts/krouter-cli.cjs +509 -0
- package/src/renderer/src/assets/krouter-logo.svg +11 -0
- package/src/renderer/src/assets/krouter-mark.svg +9 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.WebStore = void 0;
|
|
7
|
+
exports.hashPassword = hashPassword;
|
|
8
|
+
exports.verifyPassword = verifyPassword;
|
|
9
|
+
const fs_1 = require("fs");
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
12
|
+
const SENSITIVE_KEY_RE = /^(accessToken|refreshToken|csrfToken|clientSecret|password|apiKey|key|token|secret)$/i;
|
|
13
|
+
const ENCRYPTED_MARKER = '__kiroWebEncrypted';
|
|
14
|
+
function dataDir() {
|
|
15
|
+
return path_1.default.resolve(process.env.KROUTER_DATA_DIR || process.env.KAM_DATA_DIR || process.env.KIRO_WEB_DATA_DIR || '.web-data');
|
|
16
|
+
}
|
|
17
|
+
function storePath() {
|
|
18
|
+
return path_1.default.join(dataDir(), 'store.json');
|
|
19
|
+
}
|
|
20
|
+
function encryptionKey() {
|
|
21
|
+
const configured = process.env.APP_ENCRYPTION_KEY || 'development-only-change-me';
|
|
22
|
+
return crypto_1.default.createHash('sha256').update(configured).digest();
|
|
23
|
+
}
|
|
24
|
+
function hashSessionId(sessionId) {
|
|
25
|
+
const secret = process.env.SESSION_SECRET || 'development-session-secret';
|
|
26
|
+
return crypto_1.default.createHmac('sha256', secret).update(sessionId).digest('hex');
|
|
27
|
+
}
|
|
28
|
+
function encryptString(value) {
|
|
29
|
+
const iv = crypto_1.default.randomBytes(12);
|
|
30
|
+
const cipher = crypto_1.default.createCipheriv('aes-256-gcm', encryptionKey(), iv);
|
|
31
|
+
const encrypted = Buffer.concat([cipher.update(value, 'utf8'), cipher.final()]);
|
|
32
|
+
const tag = cipher.getAuthTag();
|
|
33
|
+
return {
|
|
34
|
+
[ENCRYPTED_MARKER]: true,
|
|
35
|
+
v: 1,
|
|
36
|
+
iv: iv.toString('base64'),
|
|
37
|
+
tag: tag.toString('base64'),
|
|
38
|
+
data: encrypted.toString('base64')
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function decryptString(value) {
|
|
42
|
+
const decipher = crypto_1.default.createDecipheriv('aes-256-gcm', encryptionKey(), Buffer.from(String(value.iv), 'base64'));
|
|
43
|
+
decipher.setAuthTag(Buffer.from(String(value.tag), 'base64'));
|
|
44
|
+
const decrypted = Buffer.concat([
|
|
45
|
+
decipher.update(Buffer.from(String(value.data), 'base64')),
|
|
46
|
+
decipher.final()
|
|
47
|
+
]);
|
|
48
|
+
return decrypted.toString('utf8');
|
|
49
|
+
}
|
|
50
|
+
function protect(value, keyName) {
|
|
51
|
+
if (value === null || value === undefined)
|
|
52
|
+
return value;
|
|
53
|
+
if (typeof value === 'string' && keyName && SENSITIVE_KEY_RE.test(keyName)) {
|
|
54
|
+
return encryptString(value);
|
|
55
|
+
}
|
|
56
|
+
if (typeof value !== 'object')
|
|
57
|
+
return value;
|
|
58
|
+
if (Array.isArray(value))
|
|
59
|
+
return value.map((item) => protect(item));
|
|
60
|
+
const record = value;
|
|
61
|
+
if (record[ENCRYPTED_MARKER])
|
|
62
|
+
return value;
|
|
63
|
+
return Object.fromEntries(Object.entries(record).map(([key, child]) => [key, protect(child, key)]));
|
|
64
|
+
}
|
|
65
|
+
function unprotect(value) {
|
|
66
|
+
if (value === null || value === undefined || typeof value !== 'object')
|
|
67
|
+
return value;
|
|
68
|
+
if (Array.isArray(value))
|
|
69
|
+
return value.map((item) => unprotect(item));
|
|
70
|
+
const record = value;
|
|
71
|
+
if (record[ENCRYPTED_MARKER])
|
|
72
|
+
return decryptString(record);
|
|
73
|
+
return Object.fromEntries(Object.entries(record).map(([key, child]) => [key, unprotect(child)]));
|
|
74
|
+
}
|
|
75
|
+
function defaultStore() {
|
|
76
|
+
return {
|
|
77
|
+
version: 1,
|
|
78
|
+
users: [],
|
|
79
|
+
sessions: [],
|
|
80
|
+
accountDataByUser: {},
|
|
81
|
+
settingsByUser: {},
|
|
82
|
+
proxyStateByUser: {},
|
|
83
|
+
auditEvents: []
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function hashPassword(password, salt = crypto_1.default.randomBytes(16).toString('hex')) {
|
|
87
|
+
const hash = crypto_1.default.pbkdf2Sync(password, salt, 120000, 32, 'sha256').toString('hex');
|
|
88
|
+
return { hash, salt };
|
|
89
|
+
}
|
|
90
|
+
function verifyPassword(password, user) {
|
|
91
|
+
const { hash } = hashPassword(password, user.passwordSalt);
|
|
92
|
+
return crypto_1.default.timingSafeEqual(Buffer.from(hash, 'hex'), Buffer.from(user.passwordHash, 'hex'));
|
|
93
|
+
}
|
|
94
|
+
class WebStore {
|
|
95
|
+
data = defaultStore();
|
|
96
|
+
loaded = false;
|
|
97
|
+
async load() {
|
|
98
|
+
if (this.loaded)
|
|
99
|
+
return;
|
|
100
|
+
await fs_1.promises.mkdir(dataDir(), { recursive: true });
|
|
101
|
+
try {
|
|
102
|
+
const raw = await fs_1.promises.readFile(storePath(), 'utf8');
|
|
103
|
+
this.data = { ...defaultStore(), ...JSON.parse(raw) };
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
if (error.code !== 'ENOENT')
|
|
107
|
+
throw error;
|
|
108
|
+
this.data = defaultStore();
|
|
109
|
+
await this.save();
|
|
110
|
+
}
|
|
111
|
+
this.loaded = true;
|
|
112
|
+
await this.ensureConfiguredAdminUser();
|
|
113
|
+
this.pruneExpiredSessions();
|
|
114
|
+
await this.save();
|
|
115
|
+
}
|
|
116
|
+
snapshot() {
|
|
117
|
+
return this.data;
|
|
118
|
+
}
|
|
119
|
+
async save() {
|
|
120
|
+
await fs_1.promises.mkdir(dataDir(), { recursive: true });
|
|
121
|
+
await fs_1.promises.writeFile(storePath(), JSON.stringify(this.data, null, 2), 'utf8');
|
|
122
|
+
}
|
|
123
|
+
isSetupRequired() {
|
|
124
|
+
return this.data.users.length === 0;
|
|
125
|
+
}
|
|
126
|
+
static generateAdminPassword() {
|
|
127
|
+
const alphabet = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789';
|
|
128
|
+
const bytes = crypto_1.default.randomBytes(18);
|
|
129
|
+
let value = '';
|
|
130
|
+
for (const byte of bytes)
|
|
131
|
+
value += alphabet[byte % alphabet.length];
|
|
132
|
+
return `kr-${value.slice(0, 6)}-${value.slice(6, 12)}-${value.slice(12, 18)}`;
|
|
133
|
+
}
|
|
134
|
+
adminEmailFromEnv() {
|
|
135
|
+
return process.env.KROUTER_ADMIN_EMAIL
|
|
136
|
+
|| process.env.KAM_ADMIN_EMAIL
|
|
137
|
+
|| process.env.ADMIN_EMAIL
|
|
138
|
+
|| 'admin@krouter.local';
|
|
139
|
+
}
|
|
140
|
+
configuredAdminPassword() {
|
|
141
|
+
return process.env.KROUTER_ADMIN_PASSWORD
|
|
142
|
+
|| process.env.KAM_ADMIN_PASSWORD
|
|
143
|
+
|| process.env.ADMIN_PASSWORD;
|
|
144
|
+
}
|
|
145
|
+
async ensureConfiguredAdminUser() {
|
|
146
|
+
if (this.data.users.length > 0)
|
|
147
|
+
return;
|
|
148
|
+
const password = this.configuredAdminPassword();
|
|
149
|
+
if (!password)
|
|
150
|
+
return;
|
|
151
|
+
await this.createInitialAdmin({ email: this.adminEmailFromEnv(), password });
|
|
152
|
+
}
|
|
153
|
+
async createInitialAdmin(input) {
|
|
154
|
+
if (this.data.users.length > 0)
|
|
155
|
+
throw new Error('Krouter is already set up');
|
|
156
|
+
const password = String(input.password || '');
|
|
157
|
+
if (password.length < 8)
|
|
158
|
+
throw new Error('Password must be at least 8 characters');
|
|
159
|
+
const email = String(input.email || this.adminEmailFromEnv()).trim() || 'admin@krouter.local';
|
|
160
|
+
const { hash, salt } = hashPassword(password);
|
|
161
|
+
const user = {
|
|
162
|
+
id: crypto_1.default.randomUUID(),
|
|
163
|
+
email,
|
|
164
|
+
name: input.name || 'Admin',
|
|
165
|
+
role: 'admin',
|
|
166
|
+
passwordHash: hash,
|
|
167
|
+
passwordSalt: salt,
|
|
168
|
+
createdAt: Date.now()
|
|
169
|
+
};
|
|
170
|
+
this.data.users.push(user);
|
|
171
|
+
await this.save();
|
|
172
|
+
return user;
|
|
173
|
+
}
|
|
174
|
+
findUserByEmail(email) {
|
|
175
|
+
return this.data.users.find((user) => user.email.toLowerCase() === email.toLowerCase());
|
|
176
|
+
}
|
|
177
|
+
getUsers() {
|
|
178
|
+
return [...this.data.users];
|
|
179
|
+
}
|
|
180
|
+
findUserBySession(sessionId) {
|
|
181
|
+
if (!sessionId)
|
|
182
|
+
return undefined;
|
|
183
|
+
const idHash = hashSessionId(sessionId);
|
|
184
|
+
const session = this.data.sessions.find((item) => item.idHash === idHash && item.expiresAt > Date.now());
|
|
185
|
+
if (!session)
|
|
186
|
+
return undefined;
|
|
187
|
+
return this.data.users.find((user) => user.id === session.userId);
|
|
188
|
+
}
|
|
189
|
+
async createSession(userId) {
|
|
190
|
+
const id = crypto_1.default.randomBytes(32).toString('base64url');
|
|
191
|
+
const expiresAt = Date.now() + 1000 * 60 * 60 * 24 * 7;
|
|
192
|
+
this.data.sessions.push({
|
|
193
|
+
idHash: hashSessionId(id),
|
|
194
|
+
userId,
|
|
195
|
+
expiresAt,
|
|
196
|
+
createdAt: Date.now()
|
|
197
|
+
});
|
|
198
|
+
await this.save();
|
|
199
|
+
return { id, expiresAt };
|
|
200
|
+
}
|
|
201
|
+
async deleteSession(sessionId) {
|
|
202
|
+
if (!sessionId)
|
|
203
|
+
return;
|
|
204
|
+
const idHash = hashSessionId(sessionId);
|
|
205
|
+
this.data.sessions = this.data.sessions.filter((session) => session.idHash !== idHash);
|
|
206
|
+
await this.save();
|
|
207
|
+
}
|
|
208
|
+
getAccountData(userId) {
|
|
209
|
+
return unprotect(this.data.accountDataByUser[userId] || null);
|
|
210
|
+
}
|
|
211
|
+
async setAccountData(userId, accountData) {
|
|
212
|
+
this.data.accountDataByUser[userId] = protect(accountData);
|
|
213
|
+
await this.save();
|
|
214
|
+
}
|
|
215
|
+
getUserSettings(userId) {
|
|
216
|
+
const settings = this.data.settingsByUser[userId];
|
|
217
|
+
if (!settings) {
|
|
218
|
+
this.data.settingsByUser[userId] = {};
|
|
219
|
+
return this.data.settingsByUser[userId];
|
|
220
|
+
}
|
|
221
|
+
return settings;
|
|
222
|
+
}
|
|
223
|
+
async setUserSetting(userId, key, value) {
|
|
224
|
+
const settings = this.getUserSettings(userId);
|
|
225
|
+
settings[key] = protect(value);
|
|
226
|
+
await this.save();
|
|
227
|
+
}
|
|
228
|
+
getUserSetting(userId, key, fallback) {
|
|
229
|
+
const settings = this.getUserSettings(userId);
|
|
230
|
+
if (!(key in settings))
|
|
231
|
+
return fallback;
|
|
232
|
+
return unprotect(settings[key]);
|
|
233
|
+
}
|
|
234
|
+
getProxyState(userId) {
|
|
235
|
+
if (!this.data.proxyStateByUser[userId]) {
|
|
236
|
+
this.data.proxyStateByUser[userId] = {};
|
|
237
|
+
}
|
|
238
|
+
return this.data.proxyStateByUser[userId];
|
|
239
|
+
}
|
|
240
|
+
async updateProxyState(userId, patch) {
|
|
241
|
+
const current = this.getProxyState(userId);
|
|
242
|
+
Object.assign(current, protect(patch));
|
|
243
|
+
await this.save();
|
|
244
|
+
return unprotect(current);
|
|
245
|
+
}
|
|
246
|
+
async audit(userId, type, data) {
|
|
247
|
+
this.data.auditEvents.push({ ts: Date.now(), userId, type, data: protect(data) });
|
|
248
|
+
if (this.data.auditEvents.length > 1000)
|
|
249
|
+
this.data.auditEvents.splice(0, this.data.auditEvents.length - 1000);
|
|
250
|
+
await this.save();
|
|
251
|
+
}
|
|
252
|
+
getAuditEvents(userId) {
|
|
253
|
+
return this.data.auditEvents
|
|
254
|
+
.filter((event) => event.userId === userId)
|
|
255
|
+
.map((event) => ({
|
|
256
|
+
ts: event.ts,
|
|
257
|
+
type: event.type,
|
|
258
|
+
data: unprotect(event.data)
|
|
259
|
+
}));
|
|
260
|
+
}
|
|
261
|
+
pruneExpiredSessions() {
|
|
262
|
+
const now = Date.now();
|
|
263
|
+
this.data.sessions = this.data.sessions.filter((session) => session.expiresAt > now);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
exports.WebStore = WebStore;
|
package/package.json
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lightharu/krouter",
|
|
3
|
+
"version": "1.8.0",
|
|
4
|
+
"description": "Krouter - Web dashboard and CLI router for Kiro accounts, quota balancing, and API proxy",
|
|
5
|
+
"main": "./out-server/server/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"krouter": "scripts/krouter-cli.cjs"
|
|
8
|
+
},
|
|
9
|
+
"author": "LightHaru",
|
|
10
|
+
"license": "AGPL-3.0",
|
|
11
|
+
"homepage": "https://github.com/LightHaru/Krouter",
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/LightHaru/Krouter.git"
|
|
15
|
+
},
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/LightHaru/Krouter/issues"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"krouter",
|
|
21
|
+
"kiro",
|
|
22
|
+
"openclaw",
|
|
23
|
+
"api-proxy",
|
|
24
|
+
"openai-compatible",
|
|
25
|
+
"cli"
|
|
26
|
+
],
|
|
27
|
+
"files": [
|
|
28
|
+
"dist-web/",
|
|
29
|
+
"out-server/",
|
|
30
|
+
"scripts/krouter-cli.cjs",
|
|
31
|
+
"scripts/kiro-manager-cli.cjs",
|
|
32
|
+
"resources/tls-client-xgo-1.14.0-windows-amd64.dll",
|
|
33
|
+
"src/renderer/src/assets/krouter-logo.svg",
|
|
34
|
+
"src/renderer/src/assets/krouter-mark.svg",
|
|
35
|
+
"README.md",
|
|
36
|
+
"LICENSE"
|
|
37
|
+
],
|
|
38
|
+
"scripts": {
|
|
39
|
+
"format": "prettier --write .",
|
|
40
|
+
"lint": "eslint --cache .",
|
|
41
|
+
"typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false",
|
|
42
|
+
"typecheck:web": "tsc --noEmit -p tsconfig.web.json --composite false",
|
|
43
|
+
"typecheck:server": "tsc --noEmit -p tsconfig.server.json",
|
|
44
|
+
"typecheck": "npm run typecheck:node && npm run typecheck:web",
|
|
45
|
+
"start": "electron-vite preview",
|
|
46
|
+
"dev": "chcp 65001 >nul && electron-vite dev",
|
|
47
|
+
"build": "npm run typecheck && electron-vite build",
|
|
48
|
+
"dev:web": "vite --config vite.web.config.ts",
|
|
49
|
+
"dev:api": "tsc -w -p tsconfig.server.json",
|
|
50
|
+
"build:web": "vite build --config vite.web.config.ts",
|
|
51
|
+
"build:api": "tsc -p tsconfig.server.json",
|
|
52
|
+
"clean:fullstack": "node -e \"const fs=require('fs'); for (const p of ['dist-web','out-server']) fs.rmSync(p,{recursive:true,force:true})\"",
|
|
53
|
+
"build:fullstack": "npm run clean:fullstack && npm run typecheck:web && npm run typecheck:server && npm run build:web && npm run build:api",
|
|
54
|
+
"start:api": "node out-server/server/index.js --api-only",
|
|
55
|
+
"start:backend": "node out-server/server/index.js --api-only",
|
|
56
|
+
"start:fullstack": "node out-server/server/index.js",
|
|
57
|
+
"cli": "node scripts/krouter-cli.cjs",
|
|
58
|
+
"prepack": "npm run build:fullstack",
|
|
59
|
+
"build:unpack": "npm run build && electron-builder --dir",
|
|
60
|
+
"build:win": "npm run build && electron-builder --win",
|
|
61
|
+
"build:mac": "electron-vite build && electron-builder --mac",
|
|
62
|
+
"build:linux": "electron-vite build && electron-builder --linux",
|
|
63
|
+
"test:e2e": "node test/e2e-fullsuite/run.mjs",
|
|
64
|
+
"test:e2e:only": "node test/e2e-fullsuite/run.mjs --only",
|
|
65
|
+
"test:e2e:install": "playwright install chromium"
|
|
66
|
+
},
|
|
67
|
+
"dependencies": {
|
|
68
|
+
"@electron-toolkit/preload": "^3.0.2",
|
|
69
|
+
"@electron-toolkit/utils": "^4.0.0",
|
|
70
|
+
"@tailwindcss/vite": "^4.1.17",
|
|
71
|
+
"@tanstack/react-virtual": "^3.13.12",
|
|
72
|
+
"cbor-x": "^1.6.0",
|
|
73
|
+
"class-variance-authority": "^0.7.1",
|
|
74
|
+
"clsx": "^2.1.1",
|
|
75
|
+
"electron-store": "^11.0.2",
|
|
76
|
+
"electron-updater": "^6.3.9",
|
|
77
|
+
"framer-motion": "^12.40.0",
|
|
78
|
+
"js-tiktoken": "^1.0.21",
|
|
79
|
+
"lucide-react": "^0.555.0",
|
|
80
|
+
"node-forge": "^1.3.3",
|
|
81
|
+
"socks": "^2.8.9",
|
|
82
|
+
"tailwind-merge": "^3.4.0",
|
|
83
|
+
"tailwindcss": "^4.1.17",
|
|
84
|
+
"tlsclientwrapper": "^4.2.0",
|
|
85
|
+
"undici": "^7.19.2",
|
|
86
|
+
"uuid": "^13.0.0",
|
|
87
|
+
"zustand": "^5.0.9"
|
|
88
|
+
},
|
|
89
|
+
"devDependencies": {
|
|
90
|
+
"@electron-toolkit/eslint-config-prettier": "^3.0.0",
|
|
91
|
+
"@electron-toolkit/eslint-config-ts": "^3.1.0",
|
|
92
|
+
"@electron-toolkit/tsconfig": "^2.0.0",
|
|
93
|
+
"@types/node": "^22.18.6",
|
|
94
|
+
"@types/node-forge": "^1.3.14",
|
|
95
|
+
"@types/react": "^19.1.13",
|
|
96
|
+
"@types/react-dom": "^19.1.9",
|
|
97
|
+
"@types/uuid": "^10.0.0",
|
|
98
|
+
"@vitejs/plugin-react": "^5.0.3",
|
|
99
|
+
"electron": "^38.1.2",
|
|
100
|
+
"electron-builder": "^25.1.8",
|
|
101
|
+
"electron-vite": "^4.0.1",
|
|
102
|
+
"eslint": "^9.36.0",
|
|
103
|
+
"eslint-plugin-react": "^7.37.5",
|
|
104
|
+
"eslint-plugin-react-hooks": "^5.2.0",
|
|
105
|
+
"eslint-plugin-react-refresh": "^0.4.20",
|
|
106
|
+
"playwright": "^1.60.0",
|
|
107
|
+
"prettier": "^3.6.2",
|
|
108
|
+
"react": "^19.1.1",
|
|
109
|
+
"react-dom": "^19.1.1",
|
|
110
|
+
"typescript": "^5.9.2",
|
|
111
|
+
"vite": "^7.1.6"
|
|
112
|
+
}
|
|
113
|
+
}
|
|
Binary file
|