itismyskillmarket 1.3.37 → 1.3.39
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 +4 -2
- package/dist/chunk-VRXNOGLL.js +2349 -0
- package/dist/electron-entry.d.ts +28 -0
- package/dist/electron-entry.js +7 -0
- package/dist/index.js +146 -2337
- package/electron/icon.ico +0 -0
- package/electron/icon.png +0 -0
- package/electron/main.mjs +214 -0
- package/electron/preload.mjs +18 -0
- package/electron/tray-icon.png +0 -0
- package/electron//345/220/257/345/212/250/346/241/214/351/235/242/345/272/224/347/224/250.bat +3 -0
- package/gui/app.js +38 -5
- package/package.json +60 -3
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* =============================================================================
|
|
3
|
+
* SkillMarket - Electron Main Process
|
|
4
|
+
* =============================================================================
|
|
5
|
+
*
|
|
6
|
+
* 原生桌面应用窗口:
|
|
7
|
+
* - 内嵌 HTTP 服务器(非浏览器模式)
|
|
8
|
+
* - 系统托盘(关闭到托盘)
|
|
9
|
+
* - 原生菜单栏
|
|
10
|
+
* =============================================================================
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { app, BrowserWindow, Tray, Menu, dialog, shell, nativeImage } from 'electron';
|
|
14
|
+
import path from 'path';
|
|
15
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
16
|
+
|
|
17
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
const ROOT = path.resolve(__dirname, '..');
|
|
19
|
+
|
|
20
|
+
let mainWindow = null;
|
|
21
|
+
let tray = null;
|
|
22
|
+
let server = null;
|
|
23
|
+
const PORT = 18770;
|
|
24
|
+
|
|
25
|
+
// 单实例锁
|
|
26
|
+
const gotLock = app.requestSingleInstanceLock();
|
|
27
|
+
if (!gotLock) {
|
|
28
|
+
app.quit();
|
|
29
|
+
} else {
|
|
30
|
+
app.on('second-instance', () => {
|
|
31
|
+
if (mainWindow) {
|
|
32
|
+
if (mainWindow.isMinimized()) mainWindow.restore();
|
|
33
|
+
mainWindow.show();
|
|
34
|
+
mainWindow.focus();
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// 启动内嵌 HTTP 服务器
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
async function startServer(port) {
|
|
44
|
+
const entryPath = pathToFileURL(
|
|
45
|
+
path.join(ROOT, 'dist', 'electron-entry.js')
|
|
46
|
+
).href;
|
|
47
|
+
const { startGuiServer } = await import(entryPath);
|
|
48
|
+
|
|
49
|
+
const srv = startGuiServer(port);
|
|
50
|
+
server = srv;
|
|
51
|
+
|
|
52
|
+
return new Promise((resolve, reject) => {
|
|
53
|
+
srv.on('listening', () => resolve(port));
|
|
54
|
+
srv.on('error', (err) => {
|
|
55
|
+
if (err.code === 'EADDRINUSE') {
|
|
56
|
+
srv.close();
|
|
57
|
+
resolve(startServer(port + 1));
|
|
58
|
+
} else {
|
|
59
|
+
reject(err);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// 创建主窗口
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
function createWindow(port) {
|
|
70
|
+
mainWindow = new BrowserWindow({
|
|
71
|
+
width: 1200,
|
|
72
|
+
height: 800,
|
|
73
|
+
minWidth: 900,
|
|
74
|
+
minHeight: 600,
|
|
75
|
+
title: 'SkillMarket',
|
|
76
|
+
show: false,
|
|
77
|
+
webPreferences: {
|
|
78
|
+
preload: path.join(__dirname, 'preload.mjs'),
|
|
79
|
+
contextIsolation: true,
|
|
80
|
+
nodeIntegration: false,
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
mainWindow.loadURL(`http://127.0.0.1:${port}`);
|
|
85
|
+
|
|
86
|
+
mainWindow.once('ready-to-show', () => mainWindow.show());
|
|
87
|
+
|
|
88
|
+
// 关闭 → 隐藏到托盘
|
|
89
|
+
mainWindow.on('close', (event) => {
|
|
90
|
+
if (!app.isQuitting) {
|
|
91
|
+
event.preventDefault();
|
|
92
|
+
mainWindow.hide();
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
mainWindow.on('closed', () => { mainWindow = null; });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
// 系统托盘
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
|
|
102
|
+
function createTray(port) {
|
|
103
|
+
const iconPath = path.join(ROOT, 'electron', 'tray-icon.png');
|
|
104
|
+
let icon;
|
|
105
|
+
try {
|
|
106
|
+
icon = nativeImage.createFromPath(iconPath).resize({ width: 16, height: 16 });
|
|
107
|
+
} catch {
|
|
108
|
+
icon = nativeImage.createEmpty();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
tray = new Tray(icon);
|
|
112
|
+
tray.setToolTip('SkillMarket');
|
|
113
|
+
|
|
114
|
+
tray.setContextMenu(Menu.buildFromTemplate([
|
|
115
|
+
{
|
|
116
|
+
label: '打开 SkillMarket',
|
|
117
|
+
click: () => {
|
|
118
|
+
if (mainWindow) { mainWindow.show(); mainWindow.focus(); }
|
|
119
|
+
else { createWindow(port); }
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
{ type: 'separator' },
|
|
123
|
+
{
|
|
124
|
+
label: '关于',
|
|
125
|
+
click: () => dialog.showMessageBox({
|
|
126
|
+
type: 'info',
|
|
127
|
+
title: '关于 SkillMarket',
|
|
128
|
+
message: 'SkillMarket v1.3.37',
|
|
129
|
+
detail: 'Cross-platform skill manager for AI coding tools',
|
|
130
|
+
}),
|
|
131
|
+
},
|
|
132
|
+
{ type: 'separator' },
|
|
133
|
+
{
|
|
134
|
+
label: '退出',
|
|
135
|
+
click: () => { app.isQuitting = true; app.quit(); },
|
|
136
|
+
},
|
|
137
|
+
]));
|
|
138
|
+
|
|
139
|
+
tray.on('double-click', () => {
|
|
140
|
+
if (mainWindow) { mainWindow.show(); mainWindow.focus(); }
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
// 应用菜单
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
function createMenu() {
|
|
149
|
+
const isMac = process.platform === 'darwin';
|
|
150
|
+
const template = [
|
|
151
|
+
{
|
|
152
|
+
label: 'SkillMarket',
|
|
153
|
+
submenu: [
|
|
154
|
+
...(isMac ? [{ role: 'about' }, { type: 'separator' }] : []),
|
|
155
|
+
{ role: 'quit' },
|
|
156
|
+
],
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
label: '编辑',
|
|
160
|
+
submenu: [
|
|
161
|
+
{ role: 'undo' }, { role: 'redo' }, { type: 'separator' },
|
|
162
|
+
{ role: 'cut' }, { role: 'copy' }, { role: 'paste' }, { role: 'selectAll' },
|
|
163
|
+
],
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
label: '视图',
|
|
167
|
+
submenu: [
|
|
168
|
+
{ role: 'reload' }, { role: 'forceReload' },
|
|
169
|
+
{ type: 'separator' },
|
|
170
|
+
{ role: 'resetZoom' }, { role: 'zoomIn' }, { role: 'zoomOut' },
|
|
171
|
+
{ type: 'separator' },
|
|
172
|
+
{ role: 'togglefullscreen' },
|
|
173
|
+
],
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
label: '帮助',
|
|
177
|
+
submenu: [
|
|
178
|
+
{ label: 'GitHub', click: () => shell.openExternal('https://github.com/wxc2004/market') },
|
|
179
|
+
{ label: '报告问题', click: () => shell.openExternal('https://github.com/wxc2004/market/issues') },
|
|
180
|
+
],
|
|
181
|
+
},
|
|
182
|
+
];
|
|
183
|
+
Menu.setApplicationMenu(Menu.buildFromTemplate(template));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ---------------------------------------------------------------------------
|
|
187
|
+
// 启动
|
|
188
|
+
// ---------------------------------------------------------------------------
|
|
189
|
+
|
|
190
|
+
app.whenReady().then(async () => {
|
|
191
|
+
try {
|
|
192
|
+
const actualPort = await startServer(PORT);
|
|
193
|
+
createWindow(actualPort);
|
|
194
|
+
try { createTray(actualPort); } catch {}
|
|
195
|
+
createMenu();
|
|
196
|
+
|
|
197
|
+
app.on('activate', () => {
|
|
198
|
+
if (mainWindow === null) createWindow(actualPort);
|
|
199
|
+
else mainWindow.show();
|
|
200
|
+
});
|
|
201
|
+
} catch (err) {
|
|
202
|
+
dialog.showErrorBox('启动失败', err.message);
|
|
203
|
+
app.quit();
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
app.on('window-all-closed', () => {
|
|
208
|
+
if (process.platform !== 'darwin') app.quit();
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
app.on('before-quit', () => {
|
|
212
|
+
app.isQuitting = true;
|
|
213
|
+
if (server) try { server.close(); } catch {}
|
|
214
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* =============================================================================
|
|
3
|
+
* SkillMarket - Electron Preload Script
|
|
4
|
+
* =============================================================================
|
|
5
|
+
*
|
|
6
|
+
* 桥接 Node.js 和渲染进程。通过 contextBridge 安全地暴露 API。
|
|
7
|
+
* =============================================================================
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { contextBridge, ipcRenderer } from 'electron';
|
|
11
|
+
|
|
12
|
+
contextBridge.exposeInMainWorld('electronAPI', {
|
|
13
|
+
// 平台信息
|
|
14
|
+
platform: process.platform,
|
|
15
|
+
|
|
16
|
+
// 获取应用版本
|
|
17
|
+
getVersion: () => ipcRenderer.invoke('get-version'),
|
|
18
|
+
});
|
|
Binary file
|
package/gui/app.js
CHANGED
|
@@ -1933,8 +1933,12 @@ const uploadState = {
|
|
|
1933
1933
|
skillName: '',
|
|
1934
1934
|
file: null,
|
|
1935
1935
|
data: null, // Parsed result from backend
|
|
1936
|
+
tempDir: '', // Temporary directory path from server
|
|
1936
1937
|
};
|
|
1937
1938
|
|
|
1939
|
+
/** 上传文件大小限制:50 MB */
|
|
1940
|
+
const MAX_UPLOAD_SIZE = 50 * 1024 * 1024;
|
|
1941
|
+
|
|
1938
1942
|
/** 初始化 Upload 控件 */
|
|
1939
1943
|
function initializeUploadControls() {
|
|
1940
1944
|
const dropzone = document.getElementById('upload-dropzone');
|
|
@@ -1946,16 +1950,30 @@ function initializeUploadControls() {
|
|
|
1946
1950
|
if (!dropzone) return;
|
|
1947
1951
|
|
|
1948
1952
|
// 文件选择
|
|
1949
|
-
selectBtn.addEventListener('click', () =>
|
|
1953
|
+
selectBtn.addEventListener('click', (e) => {
|
|
1954
|
+
e.stopPropagation(); // 防止冒泡到 dropzone 的 click 事件
|
|
1955
|
+
fileInput.click();
|
|
1956
|
+
});
|
|
1950
1957
|
fileInput.addEventListener('change', (e) => {
|
|
1951
1958
|
if (e.target.files.length > 0) {
|
|
1952
|
-
|
|
1959
|
+
const file = e.target.files[0];
|
|
1960
|
+
if (file.size > MAX_UPLOAD_SIZE) {
|
|
1961
|
+
const sizeMB = (file.size / 1024 / 1024).toFixed(1);
|
|
1962
|
+
showToast(`File too large (${sizeMB} MB). Maximum is 50 MB.`, 'error');
|
|
1963
|
+
e.target.value = '';
|
|
1964
|
+
return;
|
|
1965
|
+
}
|
|
1966
|
+
uploadState.file = file;
|
|
1953
1967
|
submitBtn.disabled = false;
|
|
1954
1968
|
}
|
|
1955
1969
|
});
|
|
1956
1970
|
|
|
1957
|
-
// 拖拽上传
|
|
1958
|
-
dropzone.addEventListener('click', () =>
|
|
1971
|
+
// 拖拽上传 — 仅处理拖拽事件,点击由 Choose File 按钮或单独点击处理
|
|
1972
|
+
dropzone.addEventListener('click', (e) => {
|
|
1973
|
+
// 如果点击的是内部按钮,不重复触发文件选择(由按钮的 stopPropagation 处理)
|
|
1974
|
+
if (e.target.closest('button')) return;
|
|
1975
|
+
fileInput.click();
|
|
1976
|
+
});
|
|
1959
1977
|
|
|
1960
1978
|
dropzone.addEventListener('dragover', (e) => {
|
|
1961
1979
|
e.preventDefault();
|
|
@@ -1970,7 +1988,13 @@ function initializeUploadControls() {
|
|
|
1970
1988
|
e.preventDefault();
|
|
1971
1989
|
dropzone.classList.remove('drag-over');
|
|
1972
1990
|
if (e.dataTransfer.files.length > 0) {
|
|
1973
|
-
|
|
1991
|
+
const file = e.dataTransfer.files[0];
|
|
1992
|
+
if (file.size > MAX_UPLOAD_SIZE) {
|
|
1993
|
+
const sizeMB = (file.size / 1024 / 1024).toFixed(1);
|
|
1994
|
+
showToast(`File too large (${sizeMB} MB). Maximum is 50 MB.`, 'error');
|
|
1995
|
+
return;
|
|
1996
|
+
}
|
|
1997
|
+
uploadState.file = file;
|
|
1974
1998
|
submitBtn.disabled = false;
|
|
1975
1999
|
}
|
|
1976
2000
|
});
|
|
@@ -1981,6 +2005,12 @@ function initializeUploadControls() {
|
|
|
1981
2005
|
showToast(t('upload.errorNoFile'), 'error');
|
|
1982
2006
|
return;
|
|
1983
2007
|
}
|
|
2008
|
+
// 文件大小校验
|
|
2009
|
+
if (uploadState.file.size > MAX_UPLOAD_SIZE) {
|
|
2010
|
+
const sizeMB = (uploadState.file.size / 1024 / 1024).toFixed(1);
|
|
2011
|
+
showToast(`File too large (${sizeMB} MB). Maximum is 50 MB.`, 'error');
|
|
2012
|
+
return;
|
|
2013
|
+
}
|
|
1984
2014
|
// Use skill name override if provided
|
|
1985
2015
|
const override = skillNameInput.value.trim();
|
|
1986
2016
|
handleUpload(uploadState.file, override || undefined);
|
|
@@ -2007,6 +2037,7 @@ function resetUploadView() {
|
|
|
2007
2037
|
uploadState.file = null;
|
|
2008
2038
|
uploadState.data = null;
|
|
2009
2039
|
uploadState.skillName = '';
|
|
2040
|
+
uploadState.tempDir = '';
|
|
2010
2041
|
document.getElementById('upload-phase1').classList.remove('hidden');
|
|
2011
2042
|
document.getElementById('upload-phase2').classList.add('hidden');
|
|
2012
2043
|
document.getElementById('upload-submit-btn').disabled = true;
|
|
@@ -2068,6 +2099,7 @@ async function handleUpload(file, skillNameOverride) {
|
|
|
2068
2099
|
|
|
2069
2100
|
uploadState.data = result;
|
|
2070
2101
|
uploadState.skillName = skillNameOverride || result.skillName;
|
|
2102
|
+
uploadState.tempDir = result.tempDir || '';
|
|
2071
2103
|
|
|
2072
2104
|
// Switch to preview phase
|
|
2073
2105
|
setTimeout(() => {
|
|
@@ -2151,6 +2183,7 @@ async function executeUploadAction(action) {
|
|
|
2151
2183
|
body: JSON.stringify({
|
|
2152
2184
|
skillName: uploadState.skillName,
|
|
2153
2185
|
action: action,
|
|
2186
|
+
tempDir: uploadState.tempDir,
|
|
2154
2187
|
}),
|
|
2155
2188
|
});
|
|
2156
2189
|
|
package/package.json
CHANGED
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "itismyskillmarket",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.39",
|
|
4
4
|
"description": "Cross-platform skill manager for AI coding tools",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"skm": "dist/index.js"
|
|
8
8
|
},
|
|
9
|
+
"main": "electron/main.mjs",
|
|
9
10
|
"scripts": {
|
|
10
11
|
"build": "tsup",
|
|
11
12
|
"dev": "tsup --watch",
|
|
12
13
|
"test": "vitest",
|
|
13
|
-
"build:exe": "node scripts/build-exe.mjs"
|
|
14
|
+
"build:exe": "node scripts/build-exe.mjs",
|
|
15
|
+
"build:installer": "npm run build:exe && ISCC scripts/skillmarket.iss",
|
|
16
|
+
"build:installer:ps": "npm run build:exe && powershell -ExecutionPolicy Bypass -File scripts/install.ps1 -Silent",
|
|
17
|
+
"install:local": "powershell -ExecutionPolicy Bypass -File scripts/install.ps1",
|
|
18
|
+
"uninstall:local": "powershell -ExecutionPolicy Bypass -File scripts/install.ps1 -Uninstall",
|
|
19
|
+
"electron:dev": "npm run build && npx electron electron/main.mjs",
|
|
20
|
+
"electron:build": "npm run build && npx electron-builder build --win"
|
|
14
21
|
},
|
|
15
22
|
"dependencies": {
|
|
16
23
|
"adm-zip": "^0.5.17",
|
|
@@ -19,15 +26,65 @@
|
|
|
19
26
|
"tar": "^7.5.15"
|
|
20
27
|
},
|
|
21
28
|
"devDependencies": {
|
|
29
|
+
"@types/adm-zip": "^0.5.8",
|
|
22
30
|
"@types/fs-extra": "^11.0.4",
|
|
23
31
|
"@yao-pkg/pkg": "^6.19.0",
|
|
32
|
+
"electron": "^33.0.0",
|
|
33
|
+
"electron-builder": "^25.0.0",
|
|
24
34
|
"postject": "^1.0.0-alpha.6",
|
|
25
35
|
"tsup": "^8.0.0",
|
|
26
36
|
"typescript": "^5.3.0",
|
|
27
37
|
"vitest": "^1.2.0"
|
|
28
38
|
},
|
|
39
|
+
"build": {
|
|
40
|
+
"appId": "com.skillmarket.app",
|
|
41
|
+
"productName": "SkillMarket",
|
|
42
|
+
"directories": {
|
|
43
|
+
"output": "release"
|
|
44
|
+
},
|
|
45
|
+
"files": [
|
|
46
|
+
"dist/**/*",
|
|
47
|
+
"electron/**/*",
|
|
48
|
+
"gui/**/*",
|
|
49
|
+
"package.json"
|
|
50
|
+
],
|
|
51
|
+
"win": {
|
|
52
|
+
"target": [
|
|
53
|
+
{
|
|
54
|
+
"target": "nsis",
|
|
55
|
+
"arch": [
|
|
56
|
+
"x64"
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
],
|
|
60
|
+
"icon": "electron/icon.ico"
|
|
61
|
+
},
|
|
62
|
+
"nsis": {
|
|
63
|
+
"oneClick": false,
|
|
64
|
+
"allowToChangeInstallationDirectory": true,
|
|
65
|
+
"createDesktopShortcut": true,
|
|
66
|
+
"createStartMenuShortcut": true,
|
|
67
|
+
"shortcutName": "SkillMarket",
|
|
68
|
+
"uninstallDisplayName": "SkillMarket",
|
|
69
|
+
"installerIcon": "electron/icon.ico",
|
|
70
|
+
"uninstallerIcon": "electron/icon.ico"
|
|
71
|
+
},
|
|
72
|
+
"extraResources": [
|
|
73
|
+
{
|
|
74
|
+
"from": "scripts",
|
|
75
|
+
"to": "scripts",
|
|
76
|
+
"filter": [
|
|
77
|
+
"*.ps1",
|
|
78
|
+
"*.iss"
|
|
79
|
+
]
|
|
80
|
+
}
|
|
81
|
+
],
|
|
82
|
+
"asar": true,
|
|
83
|
+
"compression": "maximum"
|
|
84
|
+
},
|
|
29
85
|
"files": [
|
|
30
86
|
"dist",
|
|
31
|
-
"gui"
|
|
87
|
+
"gui",
|
|
88
|
+
"electron"
|
|
32
89
|
]
|
|
33
90
|
}
|