shennian 0.2.5 → 0.2.6

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 (85) hide show
  1. package/package.json +1 -1
  2. package/dist/bin/shennian.d.ts +0 -2
  3. package/dist/bin/shennian.js +0 -2
  4. package/dist/src/agents/adapter.d.ts +0 -35
  5. package/dist/src/agents/adapter.js +0 -19
  6. package/dist/src/agents/claude.d.ts +0 -25
  7. package/dist/src/agents/claude.js +0 -234
  8. package/dist/src/agents/codex.d.ts +0 -49
  9. package/dist/src/agents/codex.js +0 -794
  10. package/dist/src/agents/command-spec.d.ts +0 -36
  11. package/dist/src/agents/command-spec.js +0 -303
  12. package/dist/src/agents/cursor.d.ts +0 -22
  13. package/dist/src/agents/cursor.js +0 -248
  14. package/dist/src/agents/custom.d.ts +0 -30
  15. package/dist/src/agents/custom.js +0 -210
  16. package/dist/src/agents/detect.d.ts +0 -9
  17. package/dist/src/agents/detect.js +0 -46
  18. package/dist/src/agents/gemini.d.ts +0 -17
  19. package/dist/src/agents/gemini.js +0 -172
  20. package/dist/src/agents/model-registry/cache.d.ts +0 -4
  21. package/dist/src/agents/model-registry/cache.js +0 -37
  22. package/dist/src/agents/model-registry/discovery.d.ts +0 -4
  23. package/dist/src/agents/model-registry/discovery.js +0 -161
  24. package/dist/src/agents/model-registry/parsers.d.ts +0 -11
  25. package/dist/src/agents/model-registry/parsers.js +0 -349
  26. package/dist/src/agents/model-registry/runner.d.ts +0 -6
  27. package/dist/src/agents/model-registry/runner.js +0 -29
  28. package/dist/src/agents/model-registry/service.d.ts +0 -6
  29. package/dist/src/agents/model-registry/service.js +0 -71
  30. package/dist/src/agents/model-registry/types.d.ts +0 -30
  31. package/dist/src/agents/model-registry/types.js +0 -8
  32. package/dist/src/agents/model-registry.d.ts +0 -13
  33. package/dist/src/agents/model-registry.js +0 -15
  34. package/dist/src/agents/openclaw.d.ts +0 -20
  35. package/dist/src/agents/openclaw.js +0 -273
  36. package/dist/src/agents/pi.d.ts +0 -51
  37. package/dist/src/agents/pi.js +0 -793
  38. package/dist/src/commands/agent.d.ts +0 -2
  39. package/dist/src/commands/agent.js +0 -131
  40. package/dist/src/commands/daemon.d.ts +0 -35
  41. package/dist/src/commands/daemon.js +0 -612
  42. package/dist/src/commands/pair-qr.d.ts +0 -4
  43. package/dist/src/commands/pair-qr.js +0 -6
  44. package/dist/src/commands/pair.d.ts +0 -8
  45. package/dist/src/commands/pair.js +0 -162
  46. package/dist/src/commands/upgrade.d.ts +0 -5
  47. package/dist/src/commands/upgrade.js +0 -145
  48. package/dist/src/config/index.d.ts +0 -32
  49. package/dist/src/config/index.js +0 -31
  50. package/dist/src/fs/handler.d.ts +0 -26
  51. package/dist/src/fs/handler.js +0 -130
  52. package/dist/src/fs/security.d.ts +0 -2
  53. package/dist/src/fs/security.js +0 -32
  54. package/dist/src/index.d.ts +0 -1
  55. package/dist/src/index.js +0 -277
  56. package/dist/src/log-reporter.d.ts +0 -18
  57. package/dist/src/log-reporter.js +0 -16
  58. package/dist/src/native-fusion/parsers.d.ts +0 -12
  59. package/dist/src/native-fusion/parsers.js +0 -687
  60. package/dist/src/native-fusion/service.d.ts +0 -25
  61. package/dist/src/native-fusion/service.js +0 -176
  62. package/dist/src/native-fusion/state.d.ts +0 -3
  63. package/dist/src/native-fusion/state.js +0 -22
  64. package/dist/src/native-fusion/types.d.ts +0 -24
  65. package/dist/src/native-fusion/types.js +0 -1
  66. package/dist/src/region.d.ts +0 -15
  67. package/dist/src/region.js +0 -99
  68. package/dist/src/relay/client.d.ts +0 -62
  69. package/dist/src/relay/client.js +0 -311
  70. package/dist/src/session/handlers/agents.d.ts +0 -4
  71. package/dist/src/session/handlers/agents.js +0 -55
  72. package/dist/src/session/handlers/chat.d.ts +0 -4
  73. package/dist/src/session/handlers/chat.js +0 -326
  74. package/dist/src/session/handlers/control.d.ts +0 -5
  75. package/dist/src/session/handlers/control.js +0 -57
  76. package/dist/src/session/handlers/fs.d.ts +0 -10
  77. package/dist/src/session/handlers/fs.js +0 -256
  78. package/dist/src/session/manager.d.ts +0 -28
  79. package/dist/src/session/manager.js +0 -174
  80. package/dist/src/session/store.d.ts +0 -7
  81. package/dist/src/session/store.js +0 -46
  82. package/dist/src/session/types.d.ts +0 -35
  83. package/dist/src/session/types.js +0 -4
  84. package/dist/src/upgrade/engine.d.ts +0 -64
  85. package/dist/src/upgrade/engine.js +0 -238
@@ -1,612 +0,0 @@
1
- // @arch docs/architecture/cli/daemon.md#守护进程管理
2
- // @test src/__tests__/daemon-autostart.test.ts
3
- import chalk from 'chalk';
4
- import fs from 'node:fs';
5
- import path from 'node:path';
6
- import os from 'node:os';
7
- import { execSync, spawn } from 'node:child_process';
8
- import { fileURLToPath } from 'node:url';
9
- import { getShennianDir, resolveShennianPath } from '../config/index.js';
10
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
11
- const SHENNIAN_DIR = getShennianDir();
12
- const PID_FILE = resolveShennianPath('daemon.pid');
13
- const LOG_FILE = resolveShennianPath('daemon.log');
14
- const SHENNIAN_SCRIPT = path.resolve(__dirname, '../../bin/shennian.js');
15
- const NODE_EXEC = process.execPath;
16
- function getPlatform() {
17
- const p = os.platform();
18
- if (p === 'darwin' || p === 'linux' || p === 'win32')
19
- return p;
20
- return 'linux';
21
- }
22
- export function isEphemeralCliPath(candidate) {
23
- const normalized = candidate.replace(/\\/g, '/').toLowerCase();
24
- const tmp = os.tmpdir().replace(/\\/g, '/').toLowerCase();
25
- return (normalized.includes('/_npx/') ||
26
- normalized.includes('/npm-cache/_npx/') ||
27
- normalized.includes('/pnpm/dlx/') ||
28
- normalized.startsWith(tmp.endsWith('/') ? tmp : `${tmp}/`));
29
- }
30
- export function resolveServiceLaunchSpec(input) {
31
- if (fs.existsSync(input.scriptPath) && !isEphemeralCliPath(input.scriptPath)) {
32
- return {
33
- command: input.nodeExec,
34
- args: [input.scriptPath, 'start'],
35
- mode: 'direct',
36
- };
37
- }
38
- if (input.shennianCommandPath && !isEphemeralCliPath(input.shennianCommandPath)) {
39
- return {
40
- command: input.shennianCommandPath,
41
- args: ['start'],
42
- mode: 'global-shim',
43
- };
44
- }
45
- if (input.npxPath) {
46
- return {
47
- command: input.npxPath,
48
- args: ['--yes', 'shennian', 'start'],
49
- mode: 'npx',
50
- };
51
- }
52
- return {
53
- command: input.nodeExec,
54
- args: [input.scriptPath, 'start'],
55
- mode: 'direct',
56
- };
57
- }
58
- // ─── PID helpers ────────────────────────────────────────────────────────────
59
- function readPid() {
60
- try {
61
- const raw = fs.readFileSync(PID_FILE, 'utf-8').trim();
62
- const pid = parseInt(raw, 10);
63
- return isNaN(pid) ? null : pid;
64
- }
65
- catch {
66
- return null;
67
- }
68
- }
69
- function isRunning(pid) {
70
- try {
71
- process.kill(pid, 0);
72
- return true;
73
- }
74
- catch {
75
- return false;
76
- }
77
- }
78
- // ─── Service definitions ─────────────────────────────────────────────────────
79
- const LAUNCHD_LABEL = 'com.shennian.agent';
80
- const LAUNCHD_PLIST = path.join(os.homedir(), 'Library/LaunchAgents', `${LAUNCHD_LABEL}.plist`);
81
- const WINDOWS_STARTUP_DIR = path.join(os.homedir(), 'AppData', 'Roaming', 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup');
82
- const WINDOWS_STARTUP_CMD = resolveShennianPath('autostart.cmd');
83
- const WINDOWS_LAUNCHER_VBS = resolveShennianPath('autostart.vbs');
84
- const WINDOWS_TASK_XML = resolveShennianPath('autostart.xml');
85
- const WINDOWS_STARTUP_VBS = path.join(WINDOWS_STARTUP_DIR, 'ShennianAgent.vbs');
86
- function findCommandPath(binary) {
87
- const command = getPlatform() === 'win32' ? `where ${binary}` : `command -v ${binary}`;
88
- try {
89
- const output = execSync(command, {
90
- stdio: ['ignore', 'pipe', 'pipe'],
91
- encoding: 'utf-8',
92
- });
93
- const first = output
94
- .split(/\r?\n/)
95
- .map((line) => line.trim())
96
- .find(Boolean);
97
- return first ?? null;
98
- }
99
- catch {
100
- return null;
101
- }
102
- }
103
- function resolveNpxPath() {
104
- const fileName = getPlatform() === 'win32' ? 'npx.cmd' : 'npx';
105
- const sibling = path.join(path.dirname(NODE_EXEC), fileName);
106
- if (fs.existsSync(sibling))
107
- return sibling;
108
- return findCommandPath('npx');
109
- }
110
- function resolveCurrentServiceLaunchSpec() {
111
- return resolveServiceLaunchSpec({
112
- nodeExec: NODE_EXEC,
113
- scriptPath: SHENNIAN_SCRIPT,
114
- shennianCommandPath: findCommandPath('shennian'),
115
- npxPath: resolveNpxPath(),
116
- });
117
- }
118
- function escapeXml(text) {
119
- return text
120
- .replace(/&/g, '&')
121
- .replace(/</g, '&lt;')
122
- .replace(/>/g, '&gt;')
123
- .replace(/"/g, '&quot;')
124
- .replace(/'/g, '&apos;');
125
- }
126
- function quoteSystemdArg(text) {
127
- return `"${text.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
128
- }
129
- function quoteCmdArg(text) {
130
- return `"${text.replace(/"/g, '""')}"`;
131
- }
132
- function escapeVbsString(text) {
133
- return text.replace(/"/g, '""');
134
- }
135
- function escapeXmlValue(text) {
136
- return escapeXml(text);
137
- }
138
- function isWindowsCmdScript(filePath) {
139
- const ext = path.extname(filePath).toLowerCase();
140
- return ext === '.cmd' || ext === '.bat';
141
- }
142
- export function buildWindowsLauncherCommand(spec, logFile) {
143
- const commandLine = [spec.command, ...spec.args].map(quoteCmdArg).join(' ');
144
- const invocation = isWindowsCmdScript(spec.command) ? `call ${commandLine}` : commandLine;
145
- return [
146
- '@echo off',
147
- 'setlocal',
148
- `${invocation} >> ${quoteCmdArg(logFile)} 2>&1`,
149
- '',
150
- ].join('\r\n');
151
- }
152
- export function buildWindowsStartupVbs(cmdPath) {
153
- return [
154
- 'Set shell = CreateObject("WScript.Shell")',
155
- `shell.Run Chr(34) & "${escapeVbsString(cmdPath)}" & Chr(34), 0, False`,
156
- '',
157
- ].join('\r\n');
158
- }
159
- export function buildWindowsScheduledTaskXml(input) {
160
- const joinedArgs = input.arguments.join(' ');
161
- return `<?xml version="1.0" encoding="UTF-16"?>
162
- <Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
163
- <RegistrationInfo>
164
- <Description>Shennian Agent Daemon</Description>
165
- </RegistrationInfo>
166
- <Triggers>
167
- <LogonTrigger>
168
- <Enabled>true</Enabled>
169
- <UserId>${escapeXmlValue(input.userId)}</UserId>
170
- </LogonTrigger>
171
- <EventTrigger>
172
- <Enabled>true</Enabled>
173
- <Subscription><![CDATA[
174
- <QueryList>
175
- <Query Id="0" Path="System">
176
- <Select Path="System">*[System[Provider[@Name='Microsoft-Windows-Power-Troubleshooter'] and EventID=1]]</Select>
177
- </Query>
178
- </QueryList>]]></Subscription>
179
- </EventTrigger>
180
- </Triggers>
181
- <Principals>
182
- <Principal id="Author">
183
- <UserId>${escapeXmlValue(input.userId)}</UserId>
184
- <LogonType>InteractiveToken</LogonType>
185
- <RunLevel>LeastPrivilege</RunLevel>
186
- </Principal>
187
- </Principals>
188
- <Settings>
189
- <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
190
- <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
191
- <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
192
- <AllowHardTerminate>true</AllowHardTerminate>
193
- <StartWhenAvailable>true</StartWhenAvailable>
194
- <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
195
- <IdleSettings>
196
- <StopOnIdleEnd>false</StopOnIdleEnd>
197
- <RestartOnIdle>false</RestartOnIdle>
198
- </IdleSettings>
199
- <AllowStartOnDemand>true</AllowStartOnDemand>
200
- <Enabled>true</Enabled>
201
- <Hidden>true</Hidden>
202
- <RunOnlyIfIdle>false</RunOnlyIfIdle>
203
- <WakeToRun>false</WakeToRun>
204
- <ExecutionTimeLimit>PT0S</ExecutionTimeLimit>
205
- <Priority>7</Priority>
206
- <RestartOnFailure>
207
- <Interval>PT1M</Interval>
208
- <Count>3</Count>
209
- </RestartOnFailure>
210
- </Settings>
211
- <Actions Context="Author">
212
- <Exec>
213
- <Command>${escapeXmlValue(input.command)}</Command>
214
- <Arguments>${escapeXmlValue(joinedArgs)}</Arguments>
215
- </Exec>
216
- </Actions>
217
- </Task>`;
218
- }
219
- function removeLegacyWindowsTask() {
220
- try {
221
- execSync(`schtasks /delete /tn "${WINDOWS_TASK_NAME}" /f`, { stdio: 'pipe' });
222
- }
223
- catch {
224
- // Ignore missing legacy Task Scheduler entries during migration.
225
- }
226
- }
227
- function removeLegacyWindowsStartupFiles() {
228
- try {
229
- fs.unlinkSync(WINDOWS_STARTUP_VBS);
230
- }
231
- catch {
232
- // Legacy Startup entry may already be absent.
233
- }
234
- }
235
- function getWindowsTaskUserId() {
236
- const domain = process.env.USERDOMAIN?.trim();
237
- const computer = process.env.COMPUTERNAME?.trim();
238
- const username = process.env.USERNAME?.trim() || os.userInfo().username;
239
- if (domain && domain.toUpperCase() !== 'WORKGROUP')
240
- return `${domain}\\${username}`;
241
- if (computer)
242
- return `${computer}\\${username}`;
243
- return username;
244
- }
245
- function buildDetachedLaunchSpec(spec) {
246
- if (getPlatform() === 'win32' && isWindowsCmdScript(spec.command)) {
247
- const commandLine = [spec.command, ...spec.args].map(quoteCmdArg).join(' ');
248
- return {
249
- command: process.env.ComSpec || 'cmd.exe',
250
- args: ['/d', '/s', '/c', `"${commandLine}"`],
251
- windowsVerbatimArguments: true,
252
- };
253
- }
254
- return {
255
- command: spec.command,
256
- args: spec.args,
257
- };
258
- }
259
- function installWindowsScheduledTask() {
260
- const launch = resolveCurrentServiceLaunchSpec();
261
- fs.writeFileSync(WINDOWS_STARTUP_CMD, buildWindowsLauncherCommand(launch, LOG_FILE));
262
- fs.writeFileSync(WINDOWS_LAUNCHER_VBS, buildWindowsStartupVbs(WINDOWS_STARTUP_CMD));
263
- removeLegacyWindowsStartupFiles();
264
- removeLegacyWindowsTask();
265
- const taskXml = buildWindowsScheduledTaskXml({
266
- userId: getWindowsTaskUserId(),
267
- command: path.join(process.env.SystemRoot || 'C:\\Windows', 'System32', 'wscript.exe'),
268
- arguments: [quoteCmdArg(WINDOWS_LAUNCHER_VBS)],
269
- });
270
- fs.writeFileSync(WINDOWS_TASK_XML, taskXml, 'utf8');
271
- try {
272
- execSync(`schtasks /create /tn "${WINDOWS_TASK_NAME}" /xml "${WINDOWS_TASK_XML}" /f`, { stdio: 'pipe' });
273
- execSync(`schtasks /run /tn "${WINDOWS_TASK_NAME}"`, { stdio: 'pipe' });
274
- return true;
275
- }
276
- catch {
277
- return false;
278
- }
279
- }
280
- export function captureEnvForService() {
281
- const keep = [
282
- 'PATH', 'HOME', 'USER', 'LOGNAME', 'SHELL', 'TMPDIR', 'LANG',
283
- 'LC_ALL', 'LC_CTYPE', 'SSH_AUTH_SOCK', 'XDG_CONFIG_HOME', 'XDG_DATA_HOME',
284
- 'TEMP', 'TMP',
285
- 'ANTHROPIC_API_KEY', 'ANTHROPIC_AUTH_TOKEN', 'ANTHROPIC_BASE_URL',
286
- 'OPENAI_API_KEY', 'OPENAI_BASE_URL',
287
- 'GEMINI_API_KEY', 'GOOGLE_API_KEY',
288
- ];
289
- const env = {};
290
- for (const k of keep) {
291
- if (process.env[k])
292
- env[k] = process.env[k];
293
- }
294
- env.HOME ??= os.homedir();
295
- env.PATH ??= '/usr/local/bin:/usr/bin:/bin';
296
- env.USER ??= os.userInfo().username;
297
- if (getPlatform() === 'win32') {
298
- const tempDir = process.env.TEMP || process.env.TMP || os.tmpdir();
299
- env.TMPDIR ??= tempDir;
300
- env.TEMP ??= tempDir;
301
- env.TMP ??= tempDir;
302
- }
303
- else {
304
- env.TMPDIR ??= '/tmp';
305
- }
306
- return env;
307
- }
308
- function buildPlist() {
309
- const env = captureEnvForService();
310
- const launch = resolveCurrentServiceLaunchSpec();
311
- const envEntries = Object.entries(env)
312
- .map(([k, v]) => ` <key>${escapeXml(k)}</key>\n <string>${escapeXml(v)}</string>`)
313
- .join('\n');
314
- const programArgs = [launch.command, ...launch.args]
315
- .map((value) => ` <string>${escapeXml(value)}</string>`)
316
- .join('\n');
317
- return `<?xml version="1.0" encoding="UTF-8"?>
318
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
319
- <plist version="1.0">
320
- <dict>
321
- <key>Label</key>
322
- <string>${LAUNCHD_LABEL}</string>
323
- <key>ProgramArguments</key>
324
- <array>
325
- ${programArgs}
326
- </array>
327
- <key>EnvironmentVariables</key>
328
- <dict>
329
- ${envEntries}
330
- </dict>
331
- <key>RunAtLoad</key>
332
- <true/>
333
- <key>KeepAlive</key>
334
- <true/>
335
- <key>ThrottleInterval</key>
336
- <integer>5</integer>
337
- <key>StandardOutPath</key>
338
- <string>${LOG_FILE}</string>
339
- <key>StandardErrorPath</key>
340
- <string>${LOG_FILE}</string>
341
- </dict>
342
- </plist>`;
343
- }
344
- const SYSTEMD_UNIT = path.join(os.homedir(), '.config/systemd/user/shennian.service');
345
- function buildSystemdUnit() {
346
- const env = captureEnvForService();
347
- const launch = resolveCurrentServiceLaunchSpec();
348
- const envLines = Object.entries(env)
349
- .map(([k, v]) => `Environment=${k}=${v}`)
350
- .join('\n');
351
- const execStart = [launch.command, ...launch.args].map(quoteSystemdArg).join(' ');
352
- return `[Unit]
353
- Description=Shennian Agent Daemon
354
- After=network.target
355
-
356
- [Service]
357
- ${envLines}
358
- ExecStart=${execStart}
359
- Restart=always
360
- RestartSec=10
361
- StandardOutput=append:${LOG_FILE}
362
- StandardError=append:${LOG_FILE}
363
-
364
- [Install]
365
- WantedBy=default.target`;
366
- }
367
- const WINDOWS_TASK_NAME = 'ShennianAgent';
368
- /**
369
- * Save current env vars so the daemon can load them at startup.
370
- * This is critical for launchd/systemd which start with minimal env.
371
- */
372
- export function saveEnvSnapshot() {
373
- fs.mkdirSync(SHENNIAN_DIR, { recursive: true });
374
- const snapshot = {};
375
- for (const [k, v] of Object.entries(process.env)) {
376
- if (v !== undefined)
377
- snapshot[k] = v;
378
- }
379
- fs.writeFileSync(resolveShennianPath('env.json'), JSON.stringify(snapshot, null, 2));
380
- }
381
- // ─── Exported helpers (used by pair.ts) ─────────────────────────────────────
382
- export function startDaemonProcess() {
383
- fs.mkdirSync(SHENNIAN_DIR, { recursive: true });
384
- const pid = readPid();
385
- if (pid !== null && isRunning(pid)) {
386
- console.log(chalk.green(`✓ Background service already running (PID ${pid})`));
387
- return;
388
- }
389
- const logFd = fs.openSync(LOG_FILE, 'a');
390
- const launch = buildDetachedLaunchSpec(resolveCurrentServiceLaunchSpec());
391
- const child = spawn(launch.command, launch.args, {
392
- detached: true,
393
- stdio: ['ignore', logFd, logFd],
394
- env: process.env,
395
- ...(launch.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}),
396
- });
397
- child.unref();
398
- fs.closeSync(logFd);
399
- fs.writeFileSync(PID_FILE, String(child.pid));
400
- console.log(chalk.green(`✓ Background service started (PID ${child.pid})`));
401
- console.log(chalk.gray(` Logs: ${LOG_FILE}`));
402
- }
403
- /**
404
- * Install the platform-native auto-start service.
405
- * Returns true if the service was immediately started (so caller can skip startDaemonProcess).
406
- */
407
- export function installService() {
408
- fs.mkdirSync(SHENNIAN_DIR, { recursive: true });
409
- const platform = getPlatform();
410
- const spec = resolveCurrentServiceLaunchSpec();
411
- if (spec.mode === 'direct' && isEphemeralCliPath(SHENNIAN_SCRIPT)) {
412
- console.warn(chalk.yellow('⚠ Warning: Current CLI path is temporary (npx). Auto-start may not work after reboot.\n'
413
- + ' Run `npm install -g shennian` for reliable auto-start.'));
414
- }
415
- switch (platform) {
416
- case 'darwin': {
417
- const dir = path.dirname(LAUNCHD_PLIST);
418
- fs.mkdirSync(dir, { recursive: true });
419
- fs.writeFileSync(LAUNCHD_PLIST, buildPlist());
420
- try {
421
- // Unload first (ignore errors), then reload - this starts the service immediately
422
- execSync(`launchctl unload "${LAUNCHD_PLIST}" 2>/dev/null; launchctl load -w "${LAUNCHD_PLIST}"`, { stdio: 'pipe' });
423
- return true; // launchd started it; caller must NOT also call startDaemonProcess
424
- }
425
- catch {
426
- try {
427
- execSync(`launchctl load -w "${LAUNCHD_PLIST}"`, { stdio: 'pipe' });
428
- return true;
429
- }
430
- catch {
431
- // launchctl can fail on locked-down shells; caller falls back to manual start below.
432
- }
433
- }
434
- return false;
435
- }
436
- case 'linux': {
437
- const dir = path.dirname(SYSTEMD_UNIT);
438
- fs.mkdirSync(dir, { recursive: true });
439
- fs.writeFileSync(SYSTEMD_UNIT, buildSystemdUnit());
440
- try {
441
- execSync('systemctl --user daemon-reload && systemctl --user enable shennian', { stdio: 'pipe' });
442
- }
443
- catch {
444
- // Some Linux environments lack systemd user services; keep best-effort behavior.
445
- }
446
- // Enable linger so the user systemd session (and thus this service) persists
447
- // across reboots even without an active login session.
448
- try {
449
- execSync(`loginctl enable-linger ${os.userInfo().username}`, { stdio: 'pipe' });
450
- }
451
- catch {
452
- // loginctl is unavailable on some distros/containers; auto-start still works after login.
453
- }
454
- return false; // systemd enable doesn't start immediately; caller should startDaemonProcess
455
- }
456
- case 'win32': {
457
- return installWindowsScheduledTask();
458
- }
459
- }
460
- }
461
- // ─── Subcommand implementations ──────────────────────────────────────────────
462
- function daemonStop() {
463
- const pid = readPid();
464
- if (pid === null) {
465
- console.log(chalk.yellow('⚠ No running background service found'));
466
- return;
467
- }
468
- if (!isRunning(pid)) {
469
- try {
470
- fs.unlinkSync(PID_FILE);
471
- }
472
- catch {
473
- // Stale pid file is already gone.
474
- }
475
- console.log(chalk.yellow('⚠ Service process no longer exists, cleaned up'));
476
- return;
477
- }
478
- try {
479
- process.kill(pid, 'SIGTERM');
480
- try {
481
- fs.unlinkSync(PID_FILE);
482
- }
483
- catch {
484
- // Best-effort cleanup after SIGTERM.
485
- }
486
- console.log(chalk.green(`✓ Background service stopped (PID ${pid})`));
487
- }
488
- catch (err) {
489
- console.error(chalk.red(`✗ Failed to stop: ${err instanceof Error ? err.message : String(err)}`));
490
- }
491
- }
492
- function daemonStatus() {
493
- const pid = readPid();
494
- if (pid === null) {
495
- console.log(chalk.gray('● Background service not running'));
496
- return;
497
- }
498
- if (isRunning(pid)) {
499
- console.log(chalk.green(`● Background service running (PID ${pid})`));
500
- console.log(chalk.gray(` Logs: ${LOG_FILE}`));
501
- }
502
- else {
503
- console.log(chalk.yellow(`● Background service stopped (PID ${pid} is stale)`));
504
- try {
505
- fs.unlinkSync(PID_FILE);
506
- }
507
- catch {
508
- // Another process may have cleaned up the stale pid file first.
509
- }
510
- }
511
- }
512
- function daemonLogs(opts) {
513
- if (!fs.existsSync(LOG_FILE)) {
514
- console.log(chalk.gray('No logs yet'));
515
- return;
516
- }
517
- try {
518
- const out = execSync(os.platform() === 'win32'
519
- ? `powershell Get-Content -Tail ${opts.lines} "${LOG_FILE}"`
520
- : `tail -n ${opts.lines} "${LOG_FILE}"`, { encoding: 'utf-8' });
521
- process.stdout.write(out);
522
- }
523
- catch {
524
- const lines = fs.readFileSync(LOG_FILE, 'utf-8').split('\n').slice(-opts.lines);
525
- console.log(lines.join('\n'));
526
- }
527
- }
528
- function daemonUninstall() {
529
- daemonStop();
530
- const platform = getPlatform();
531
- switch (platform) {
532
- case 'darwin': {
533
- if (fs.existsSync(LAUNCHD_PLIST)) {
534
- try {
535
- execSync(`launchctl unload -w "${LAUNCHD_PLIST}"`, { stdio: 'pipe' });
536
- }
537
- catch {
538
- // Continue removing the plist even if launchctl already forgot about it.
539
- }
540
- fs.unlinkSync(LAUNCHD_PLIST);
541
- console.log(chalk.green('✓ Auto-start uninstalled (launchd)'));
542
- }
543
- break;
544
- }
545
- case 'linux': {
546
- try {
547
- execSync('systemctl --user disable --now shennian', { stdio: 'pipe' });
548
- }
549
- catch {
550
- // Service may already be absent or systemd user services may be unavailable.
551
- }
552
- if (fs.existsSync(SYSTEMD_UNIT)) {
553
- fs.unlinkSync(SYSTEMD_UNIT);
554
- try {
555
- execSync('systemctl --user daemon-reload', { stdio: 'pipe' });
556
- }
557
- catch {
558
- // Ignore reload errors during cleanup.
559
- }
560
- }
561
- console.log(chalk.green('✓ Auto-start uninstalled (systemd)'));
562
- break;
563
- }
564
- case 'win32': {
565
- removeLegacyWindowsTask();
566
- try {
567
- fs.unlinkSync(WINDOWS_STARTUP_CMD);
568
- }
569
- catch {
570
- // Launcher script may already be removed.
571
- }
572
- try {
573
- fs.unlinkSync(WINDOWS_LAUNCHER_VBS);
574
- }
575
- catch {
576
- // Launcher vbs may already be removed.
577
- }
578
- try {
579
- fs.unlinkSync(WINDOWS_TASK_XML);
580
- }
581
- catch {
582
- // Task xml may already be removed.
583
- }
584
- removeLegacyWindowsStartupFiles();
585
- console.log(chalk.green('✓ Auto-start uninstalled (Task Scheduler)'));
586
- break;
587
- }
588
- }
589
- }
590
- // ─── Register command ─────────────────────────────────────────────────────────
591
- export function registerDaemonCommand(program) {
592
- const daemon = program
593
- .command('daemon')
594
- .description('Manage the background service');
595
- daemon
596
- .command('stop')
597
- .description('Stop the background service')
598
- .action(daemonStop);
599
- daemon
600
- .command('status')
601
- .description('Show background service status')
602
- .action(daemonStatus);
603
- daemon
604
- .command('logs')
605
- .description('Show recent logs')
606
- .option('-n, --lines <n>', 'Number of lines', '50')
607
- .action((opts) => daemonLogs({ lines: parseInt(opts.lines, 10) }));
608
- daemon
609
- .command('uninstall')
610
- .description('Uninstall auto-start service')
611
- .action(daemonUninstall);
612
- }
@@ -1,4 +0,0 @@
1
- export declare const PAIR_QR_RENDER_OPTIONS: {
2
- readonly small: false;
3
- };
4
- export declare function buildPairQrPayload(pairToken: string): string;
@@ -1,6 +0,0 @@
1
- // @arch docs/product/PRD.md#手机扫码配对
2
- // @test src/__tests__/pair-qr.test.ts
3
- export const PAIR_QR_RENDER_OPTIONS = { small: false };
4
- export function buildPairQrPayload(pairToken) {
5
- return `pair:${pairToken}`;
6
- }
@@ -1,8 +0,0 @@
1
- import type { Command } from 'commander';
2
- export declare function runPairFlow(opts: {
3
- serverUrl: string;
4
- machineName: string;
5
- force?: boolean;
6
- }): Promise<void>;
7
- export declare function runSmartStart(serverUrl: string | undefined, machineName: string): Promise<void>;
8
- export declare function registerPairCommand(program: Command): void;