ones-fetch 1.0.0 → 1.1.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 +6 -1
- package/bin/install.mjs +2 -2
- package/package.json +7 -2
- package/public/icon.ico +0 -0
- package/public/index.html +17 -0
- package/public/launcher.vbs +4 -23
- package/src/server.mjs +33 -0
- package/.claude/settings.local.json +0 -19
- package/public/icon.png +0 -0
package/README.md
CHANGED
package/bin/install.mjs
CHANGED
|
@@ -14,7 +14,7 @@ const projectRoot = join(__dirname, '..');
|
|
|
14
14
|
async function createWindowsShortcut() {
|
|
15
15
|
const desktop = join(homedir(), 'Desktop');
|
|
16
16
|
const shortcutPath = join(desktop, 'ONES 采集工具.lnk');
|
|
17
|
-
const iconPath = join(projectRoot, 'public', 'icon.
|
|
17
|
+
const iconPath = join(projectRoot, 'public', 'icon.ico');
|
|
18
18
|
const vbsLauncher = join(projectRoot, 'public', 'launcher.vbs');
|
|
19
19
|
|
|
20
20
|
// 创建 PowerShell 脚本来生成快捷方式
|
|
@@ -24,7 +24,7 @@ $Shortcut = $WshShell.CreateShortcut("${shortcutPath.replace(/\\/g, '\\\\')}")
|
|
|
24
24
|
$Shortcut.TargetPath = "wscript.exe"
|
|
25
25
|
$Shortcut.Arguments = '"${vbsLauncher.replace(/\\/g, '\\\\')}"'
|
|
26
26
|
$Shortcut.WorkingDirectory = "${projectRoot.replace(/\\/g, '\\\\')}"
|
|
27
|
-
$Shortcut.IconLocation = "${iconPath.replace(/\\/g, '\\\\')}"
|
|
27
|
+
$Shortcut.IconLocation = "${iconPath.replace(/\\/g, '\\\\')},0"
|
|
28
28
|
$Shortcut.Description = "ONES 任务采集工具"
|
|
29
29
|
$Shortcut.Save()
|
|
30
30
|
`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ones-fetch",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "ONES Fetch — Web app for recursive ONES subtask crawling",
|
|
6
6
|
"bin": {
|
|
@@ -13,7 +13,12 @@
|
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"playwright-core": "^1.58.2"
|
|
15
15
|
},
|
|
16
|
-
"keywords": [
|
|
16
|
+
"keywords": [
|
|
17
|
+
"ones",
|
|
18
|
+
"task",
|
|
19
|
+
"crawler",
|
|
20
|
+
"project-management"
|
|
21
|
+
],
|
|
17
22
|
"author": "",
|
|
18
23
|
"license": "MIT"
|
|
19
24
|
}
|
package/public/icon.ico
ADDED
|
Binary file
|
package/public/index.html
CHANGED
|
@@ -99,6 +99,23 @@ let allTasks = [];
|
|
|
99
99
|
let collapsed = new Set();
|
|
100
100
|
let authPollingTimer = null;
|
|
101
101
|
let authContext = { baseUrl: '', teamId: '' };
|
|
102
|
+
let heartbeatTimer = null;
|
|
103
|
+
|
|
104
|
+
// Start heartbeat to keep server alive
|
|
105
|
+
function startHeartbeat() {
|
|
106
|
+
if (heartbeatTimer) clearInterval(heartbeatTimer);
|
|
107
|
+
heartbeatTimer = setInterval(() => {
|
|
108
|
+
fetch('/api/heartbeat').catch(() => {});
|
|
109
|
+
}, 60000); // Every 60 seconds
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Stop heartbeat when page unloads
|
|
113
|
+
window.addEventListener('beforeunload', () => {
|
|
114
|
+
if (heartbeatTimer) clearInterval(heartbeatTimer);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Start heartbeat on page load
|
|
118
|
+
startHeartbeat();
|
|
102
119
|
|
|
103
120
|
function extractContext(text) {
|
|
104
121
|
const match = text.match(/(https?:\/\/[^/\s]+)\/project\/#\/team\/([^/\s]+)\/task\/([A-Za-z0-9]{10,})/);
|
package/public/launcher.vbs
CHANGED
|
@@ -1,34 +1,15 @@
|
|
|
1
1
|
Set WshShell = CreateObject("WScript.Shell")
|
|
2
2
|
Set fso = CreateObject("Scripting.FileSystemObject")
|
|
3
3
|
|
|
4
|
-
'
|
|
4
|
+
' 获取项目根目录
|
|
5
5
|
scriptDir = fso.GetParentFolderName(WScript.ScriptFullName)
|
|
6
|
-
|
|
7
|
-
' 检查 Node.js 是否安装
|
|
8
|
-
On Error Resume Next
|
|
9
|
-
WshShell.Run "node --version", 0, True
|
|
10
|
-
If Err.Number <> 0 Then
|
|
11
|
-
MsgBox "未检测到 Node.js,请先安装 Node.js 18 或更高版本。" & vbCrLf & vbCrLf & "下载地址:https://nodejs.org/", vbCritical, "ONES 采集工具"
|
|
12
|
-
WScript.Quit
|
|
13
|
-
End If
|
|
14
|
-
On Error Goto 0
|
|
15
|
-
|
|
16
|
-
' 检查是否已安装依赖
|
|
17
|
-
If Not fso.FolderExists(scriptDir & "\node_modules") Then
|
|
18
|
-
result = MsgBox("首次运行需要安装依赖(约 30MB),是否继续?", vbYesNo + vbQuestion, "ONES 采集工具")
|
|
19
|
-
If result = vbNo Then
|
|
20
|
-
WScript.Quit
|
|
21
|
-
End If
|
|
22
|
-
|
|
23
|
-
' 显示安装窗口
|
|
24
|
-
WshShell.Run "cmd /c cd /d """ & scriptDir & """ && npm install && pause", 1, True
|
|
25
|
-
End If
|
|
6
|
+
projectRoot = fso.GetParentFolderName(scriptDir)
|
|
26
7
|
|
|
27
8
|
' 启动服务器(后台运行)
|
|
28
|
-
WshShell.Run "cmd /c cd /d """ &
|
|
9
|
+
WshShell.Run "cmd /c cd /d """ & projectRoot & """ && node src/server.mjs", 0, False
|
|
29
10
|
|
|
30
11
|
' 等待服务器启动
|
|
31
|
-
WScript.Sleep
|
|
12
|
+
WScript.Sleep 1500
|
|
32
13
|
|
|
33
14
|
' 打开浏览器
|
|
34
15
|
WshShell.Run "http://localhost:3000", 1
|
package/src/server.mjs
CHANGED
|
@@ -365,6 +365,21 @@ async function handleCrawl(req, res) {
|
|
|
365
365
|
}
|
|
366
366
|
}
|
|
367
367
|
|
|
368
|
+
// Heartbeat tracking
|
|
369
|
+
let lastHeartbeat = Date.now();
|
|
370
|
+
const HEARTBEAT_TIMEOUT = 2 * 60 * 1000; // 2 minutes
|
|
371
|
+
const HEARTBEAT_CHECK_INTERVAL = 30 * 1000; // 30 seconds
|
|
372
|
+
|
|
373
|
+
function startHeartbeatMonitor() {
|
|
374
|
+
setInterval(() => {
|
|
375
|
+
const now = Date.now();
|
|
376
|
+
if (now - lastHeartbeat > HEARTBEAT_TIMEOUT) {
|
|
377
|
+
process.stdout.write('No heartbeat received for 5 minutes, shutting down server...\n');
|
|
378
|
+
process.exit(0);
|
|
379
|
+
}
|
|
380
|
+
}, HEARTBEAT_CHECK_INTERVAL);
|
|
381
|
+
}
|
|
382
|
+
|
|
368
383
|
const server = http.createServer(async (req, res) => {
|
|
369
384
|
if (req.method === 'GET' && req.url === '/') {
|
|
370
385
|
try {
|
|
@@ -376,6 +391,12 @@ const server = http.createServer(async (req, res) => {
|
|
|
376
391
|
}
|
|
377
392
|
return;
|
|
378
393
|
}
|
|
394
|
+
if (req.method === 'GET' && req.url === '/api/heartbeat') {
|
|
395
|
+
lastHeartbeat = Date.now();
|
|
396
|
+
res.writeHead(204);
|
|
397
|
+
res.end();
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
379
400
|
if (req.method === 'GET' && req.url === '/api/auth/status') {
|
|
380
401
|
return handleAuthStatus(req, res);
|
|
381
402
|
}
|
|
@@ -392,4 +413,16 @@ server.listen(PORT, () => {
|
|
|
392
413
|
const url = `http://localhost:${PORT}`;
|
|
393
414
|
process.stdout.write(`ONES Fetch Web UI → ${url}\n`);
|
|
394
415
|
openBrowser(url);
|
|
416
|
+
startHeartbeatMonitor();
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
server.on('error', (err) => {
|
|
420
|
+
if (err.code === 'EADDRINUSE') {
|
|
421
|
+
process.stdout.write(`Port ${PORT} is already in use. Opening browser to existing instance...\n`);
|
|
422
|
+
openBrowser(`http://localhost:${PORT}`);
|
|
423
|
+
process.exit(0);
|
|
424
|
+
} else {
|
|
425
|
+
process.stderr.write(`Server error: ${err.message}\n`);
|
|
426
|
+
process.exit(1);
|
|
427
|
+
}
|
|
395
428
|
});
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"permissions": {
|
|
3
|
-
"allow": [
|
|
4
|
-
"Bash(npm install:*)",
|
|
5
|
-
"Bash(node:*)",
|
|
6
|
-
"Bash(timeout 3 npm start)",
|
|
7
|
-
"Bash(ls:*)",
|
|
8
|
-
"Bash(python process_icon.py)",
|
|
9
|
-
"Bash(python -c \"from PIL import Image; img = Image.open\\('public/icon.png'\\); print\\(f'尺寸: {img.size}, 模式: {img.mode}'\\)\")",
|
|
10
|
-
"Bash(git add:*)",
|
|
11
|
-
"Bash(git reset:*)",
|
|
12
|
-
"Bash(git mv:*)",
|
|
13
|
-
"Bash(gh --version)",
|
|
14
|
-
"Bash(gh repo:*)",
|
|
15
|
-
"Bash(npm whoami:*)",
|
|
16
|
-
"Bash(npm login:*)"
|
|
17
|
-
]
|
|
18
|
-
}
|
|
19
|
-
}
|
package/public/icon.png
DELETED
|
Binary file
|