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.
Files changed (3) hide show
  1. package/README.md +7 -1
  2. package/bin/cli.js +91 -52
  3. 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 stopServer() {
196
- const pidFile = getPidFilePath();
197
- const port = getPortFromConfig();
198
-
199
- const killPid = async (pid) => {
200
- const waitGone = async (ms) => new Promise(r => setTimeout(r, ms));
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
- for (let i = 0; i < 10; i++) {
217
- if (!isProcessRunning(pid)) return true;
218
- await waitGone(300);
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
- const killedGroupHard = tryKill(-pid, 'SIGKILL');
222
- if (!killedGroupHard) {
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
- for (let i = 0; i < 10; i++) {
227
- if (!isProcessRunning(pid)) return true;
228
- await waitGone(300);
229
- }
224
+ }
225
+
226
+ return !isProcessRunning(pid);
227
+ }
230
228
 
231
- return !isProcessRunning(pid);
232
- };
229
+ async function stopServer() {
230
+ const pidFile = getPidFilePath();
231
+ const port = getPortFromConfig();
233
232
 
234
233
  const killByPort = async () => {
235
- const lsof = spawn('lsof', ['-ti', `tcp:${port}`], { stdio: ['ignore', 'pipe', 'ignore'] });
236
- let output = '';
237
- lsof.stdout.on('data', (d) => { output += d.toString(); });
238
- await new Promise((resolve) => lsof.on('close', resolve));
239
-
240
- const pids = output
241
- .split(/\s+/)
242
- .map(s => s.trim())
243
- .filter(Boolean)
244
- .map(Number)
245
- .filter(n => Number.isFinite(n) && n > 0);
246
-
247
- if (pids.length === 0) return false;
248
-
249
- for (const pid of pids) {
250
- await killPid(pid);
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 killPid(pid);
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
- console.error(`[CLI] 你可以手动执行:lsof -ti tcp:${port} | xargs kill -9`);
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.78",
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 && cp node_modules/@lancedb/lancedb-darwin-x64/lancedb.darwin-x64.node node_modules/@lancedb/lancedb/dist/ 2>/dev/null || true && npm rebuild better-sqlite3"
42
+ "postinstall": "node scripts/init-app-home.cjs || true"
43
43
  },
44
44
  "dependencies": {
45
45
  "@lancedb/lancedb": "0.22.3",