xiaozuoassistant 0.1.78 → 0.1.79
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 +7 -1
- package/bin/cli.js +91 -52
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -16,7 +16,13 @@ xiaozuoAssistant 是一个**本地优先(Local-first)**的个人 AI 助手
|
|
|
16
16
|
- **项目空间**:管理项目元数据(描述、成员、目录等),为 AI 提供项目上下文(支持多项目关联)。
|
|
17
17
|
- **智能笔记本**:记录笔记和待办事项(Todo),支持关键字自动归档。
|
|
18
18
|
- **更易接入新模型**:统一 LLM(OpenAI 兼容) client 工厂与 provider→baseURL 解析,便于新增 provider。
|
|
19
|
-
- **CLI 管理**:支持 `start/stop/doctor/export/import
|
|
19
|
+
- **CLI 管理**:支持 `start/stop/doctor/export/import`,完美支持 Windows/macOS/Linux 跨平台运行。
|
|
20
|
+
|
|
21
|
+
## 系统要求
|
|
22
|
+
|
|
23
|
+
- **操作系统**: macOS, Linux, Windows (WSL 或 PowerShell/CMD)
|
|
24
|
+
- **Node.js**: v18.0.0 或更高版本
|
|
25
|
+
- **Python**: v3.0+ (某些依赖可能需要)
|
|
20
26
|
|
|
21
27
|
## 快速开始(推荐:npm 全局安装)
|
|
22
28
|
|
package/bin/cli.js
CHANGED
|
@@ -192,69 +192,103 @@ async function isPortOpen(port) {
|
|
|
192
192
|
});
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
-
async function
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
const tryKill = (targetPid, signal) => {
|
|
203
|
-
try {
|
|
204
|
-
process.kill(targetPid, signal);
|
|
205
|
-
return true;
|
|
206
|
-
} catch (e) {
|
|
207
|
-
return false;
|
|
208
|
-
}
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
const killedGroup = tryKill(-pid, 'SIGTERM');
|
|
212
|
-
if (!killedGroup) {
|
|
213
|
-
tryKill(pid, 'SIGTERM');
|
|
195
|
+
async function killProcessTree(pid) {
|
|
196
|
+
if (process.platform === 'win32') {
|
|
197
|
+
try {
|
|
198
|
+
await runCommand('taskkill', ['/pid', pid.toString(), '/T', '/F'], { stdio: 'ignore' });
|
|
199
|
+
return true;
|
|
200
|
+
} catch {
|
|
201
|
+
return false;
|
|
214
202
|
}
|
|
203
|
+
}
|
|
215
204
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
205
|
+
// Unix-like implementation
|
|
206
|
+
const tryKill = (targetPid, signal) => {
|
|
207
|
+
try {
|
|
208
|
+
process.kill(targetPid, signal);
|
|
209
|
+
return true;
|
|
210
|
+
} catch (e) {
|
|
211
|
+
return false;
|
|
219
212
|
}
|
|
213
|
+
};
|
|
220
214
|
|
|
221
|
-
|
|
222
|
-
|
|
215
|
+
tryKill(-pid, 'SIGTERM');
|
|
216
|
+
tryKill(pid, 'SIGTERM');
|
|
217
|
+
|
|
218
|
+
// Wait a bit
|
|
219
|
+
await new Promise(r => setTimeout(r, 500));
|
|
220
|
+
|
|
221
|
+
if (isProcessRunning(pid)) {
|
|
222
|
+
tryKill(-pid, 'SIGKILL');
|
|
223
223
|
tryKill(pid, 'SIGKILL');
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
await waitGone(300);
|
|
229
|
-
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return !isProcessRunning(pid);
|
|
227
|
+
}
|
|
230
228
|
|
|
231
|
-
|
|
232
|
-
|
|
229
|
+
async function stopServer() {
|
|
230
|
+
const pidFile = getPidFilePath();
|
|
231
|
+
const port = getPortFromConfig();
|
|
233
232
|
|
|
234
233
|
const killByPort = async () => {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
234
|
+
if (process.platform === 'win32') {
|
|
235
|
+
// Windows implementation using netstat
|
|
236
|
+
try {
|
|
237
|
+
const netstat = spawn('netstat', ['-ano'], { stdio: ['ignore', 'pipe', 'ignore'] });
|
|
238
|
+
let output = '';
|
|
239
|
+
netstat.stdout.on('data', d => output += d.toString());
|
|
240
|
+
await new Promise(r => netstat.on('close', r));
|
|
241
|
+
|
|
242
|
+
const lines = output.split('\n');
|
|
243
|
+
const pids = new Set();
|
|
244
|
+
for (const line of lines) {
|
|
245
|
+
if (line.includes(`:${port}`)) {
|
|
246
|
+
const parts = line.trim().split(/\s+/);
|
|
247
|
+
const pid = parts[parts.length - 1];
|
|
248
|
+
if (pid && !isNaN(Number(pid)) && Number(pid) > 0) {
|
|
249
|
+
pids.add(Number(pid));
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (pids.size === 0) return false;
|
|
255
|
+
|
|
256
|
+
for (const pid of pids) {
|
|
257
|
+
await killProcessTree(pid);
|
|
258
|
+
}
|
|
259
|
+
return true;
|
|
260
|
+
} catch (e) {
|
|
261
|
+
console.error('[CLI] Windows port kill failed:', e);
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
} else {
|
|
265
|
+
// Unix implementation using lsof
|
|
266
|
+
const lsof = spawn('lsof', ['-ti', `tcp:${port}`], { stdio: ['ignore', 'pipe', 'ignore'] });
|
|
267
|
+
let output = '';
|
|
268
|
+
lsof.stdout.on('data', (d) => { output += d.toString(); });
|
|
269
|
+
await new Promise((resolve) => lsof.on('close', resolve));
|
|
270
|
+
|
|
271
|
+
const pids = output
|
|
272
|
+
.split(/\s+/)
|
|
273
|
+
.map(s => s.trim())
|
|
274
|
+
.filter(Boolean)
|
|
275
|
+
.map(Number)
|
|
276
|
+
.filter(n => Number.isFinite(n) && n > 0);
|
|
277
|
+
|
|
278
|
+
if (pids.length === 0) return false;
|
|
279
|
+
|
|
280
|
+
for (const pid of pids) {
|
|
281
|
+
await killProcessTree(pid);
|
|
282
|
+
}
|
|
283
|
+
return true;
|
|
251
284
|
}
|
|
252
|
-
return true;
|
|
253
285
|
};
|
|
254
286
|
|
|
255
287
|
if (fs.existsSync(pidFile)) {
|
|
256
288
|
const pidStr = fs.readFileSync(pidFile, 'utf-8').trim();
|
|
257
289
|
const pid = Number(pidStr);
|
|
290
|
+
|
|
291
|
+
// ... existing pid checks ...
|
|
258
292
|
if (!Number.isFinite(pid) || pid <= 0) {
|
|
259
293
|
fs.unlinkSync(pidFile);
|
|
260
294
|
console.log('[CLI] 未找到可用的 PID(已清理 pid 文件)。');
|
|
@@ -268,7 +302,7 @@ async function stopServer() {
|
|
|
268
302
|
}
|
|
269
303
|
|
|
270
304
|
console.log(`[CLI] 正在停止服务(PID: ${pid})...`);
|
|
271
|
-
await
|
|
305
|
+
await killProcessTree(pid);
|
|
272
306
|
await killByPort();
|
|
273
307
|
try { fs.unlinkSync(pidFile); } catch (e) {}
|
|
274
308
|
|
|
@@ -276,9 +310,14 @@ async function stopServer() {
|
|
|
276
310
|
console.log('[CLI] ✅ 服务已停止。');
|
|
277
311
|
return;
|
|
278
312
|
}
|
|
279
|
-
|
|
313
|
+
|
|
314
|
+
// Fallback message
|
|
280
315
|
console.error('[CLI] ❌ 停止失败:端口仍在占用。');
|
|
281
|
-
|
|
316
|
+
if (process.platform !== 'win32') {
|
|
317
|
+
console.error(`[CLI] 你可以手动执行:lsof -ti tcp:${port} | xargs kill -9`);
|
|
318
|
+
} else {
|
|
319
|
+
console.error(`[CLI] 你可以手动执行:netstat -ano | findstr :${port} 并 kill 对应 PID`);
|
|
320
|
+
}
|
|
282
321
|
return;
|
|
283
322
|
}
|
|
284
323
|
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "xiaozuoassistant",
|
|
3
3
|
"private": false,
|
|
4
4
|
"description": "Your personal, locally-hosted AI assistant for office productivity.",
|
|
5
|
-
"version": "0.1.
|
|
5
|
+
"version": "0.1.79",
|
|
6
6
|
"author": "mantle.lau",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"repository": {
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"check": "tsc --noEmit",
|
|
40
40
|
"server:dev": "nodemon --watch src --watch config.json --exec tsx src/index.ts",
|
|
41
41
|
"dev": "concurrently \"npm run client:dev\" \"npm run server:dev\"",
|
|
42
|
-
"postinstall": "node scripts/init-app-home.cjs || true
|
|
42
|
+
"postinstall": "node scripts/init-app-home.cjs || true"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@lancedb/lancedb": "0.22.3",
|