@wendongfly/zihi 1.0.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.
Files changed (145) hide show
  1. package/bin/daemon.js +23 -0
  2. package/bin/zihi.js +603 -0
  3. package/dist/admin.html +297 -0
  4. package/dist/attach.js +2 -0
  5. package/dist/chat.html +2254 -0
  6. package/dist/client-dist/socket.io.esm.min.js +7 -0
  7. package/dist/client-dist/socket.io.js +4955 -0
  8. package/dist/client-dist/socket.io.min.js +7 -0
  9. package/dist/client-dist/socket.io.msgpack.min.js +7 -0
  10. package/dist/files.html +722 -0
  11. package/dist/icon.png +0 -0
  12. package/dist/icon.svg +4 -0
  13. package/dist/index.html +976 -0
  14. package/dist/index.js +485 -0
  15. package/dist/lib/ansi_up.js +431 -0
  16. package/dist/lib/xterm/LICENSE +21 -0
  17. package/dist/lib/xterm/README.md +230 -0
  18. package/dist/lib/xterm/css/xterm.css +209 -0
  19. package/dist/lib/xterm/lib/xterm.js +2 -0
  20. package/dist/lib/xterm/lib/xterm.js.map +1 -0
  21. package/dist/lib/xterm/package.json +100 -0
  22. package/dist/lib/xterm/src/browser/AccessibilityManager.ts +300 -0
  23. package/dist/lib/xterm/src/browser/Clipboard.ts +93 -0
  24. package/dist/lib/xterm/src/browser/ColorContrastCache.ts +34 -0
  25. package/dist/lib/xterm/src/browser/Lifecycle.ts +33 -0
  26. package/dist/lib/xterm/src/browser/Linkifier2.ts +416 -0
  27. package/dist/lib/xterm/src/browser/LocalizableStrings.ts +12 -0
  28. package/dist/lib/xterm/src/browser/OscLinkProvider.ts +128 -0
  29. package/dist/lib/xterm/src/browser/RenderDebouncer.ts +83 -0
  30. package/dist/lib/xterm/src/browser/ScreenDprMonitor.ts +72 -0
  31. package/dist/lib/xterm/src/browser/Terminal.ts +1305 -0
  32. package/dist/lib/xterm/src/browser/TimeBasedDebouncer.ts +86 -0
  33. package/dist/lib/xterm/src/browser/Types.d.ts +181 -0
  34. package/dist/lib/xterm/src/browser/Viewport.ts +401 -0
  35. package/dist/lib/xterm/src/browser/decorations/BufferDecorationRenderer.ts +134 -0
  36. package/dist/lib/xterm/src/browser/decorations/ColorZoneStore.ts +117 -0
  37. package/dist/lib/xterm/src/browser/decorations/OverviewRulerRenderer.ts +219 -0
  38. package/dist/lib/xterm/src/browser/input/CompositionHelper.ts +246 -0
  39. package/dist/lib/xterm/src/browser/input/Mouse.ts +54 -0
  40. package/dist/lib/xterm/src/browser/input/MoveToCell.ts +249 -0
  41. package/dist/lib/xterm/src/browser/public/Terminal.ts +260 -0
  42. package/dist/lib/xterm/src/browser/renderer/dom/DomRenderer.ts +506 -0
  43. package/dist/lib/xterm/src/browser/renderer/dom/DomRendererRowFactory.ts +522 -0
  44. package/dist/lib/xterm/src/browser/renderer/dom/WidthCache.ts +157 -0
  45. package/dist/lib/xterm/src/browser/renderer/shared/CellColorResolver.ts +137 -0
  46. package/dist/lib/xterm/src/browser/renderer/shared/CharAtlasCache.ts +96 -0
  47. package/dist/lib/xterm/src/browser/renderer/shared/CharAtlasUtils.ts +75 -0
  48. package/dist/lib/xterm/src/browser/renderer/shared/Constants.ts +14 -0
  49. package/dist/lib/xterm/src/browser/renderer/shared/CursorBlinkStateManager.ts +146 -0
  50. package/dist/lib/xterm/src/browser/renderer/shared/CustomGlyphs.ts +687 -0
  51. package/dist/lib/xterm/src/browser/renderer/shared/DevicePixelObserver.ts +41 -0
  52. package/dist/lib/xterm/src/browser/renderer/shared/README.md +1 -0
  53. package/dist/lib/xterm/src/browser/renderer/shared/RendererUtils.ts +58 -0
  54. package/dist/lib/xterm/src/browser/renderer/shared/SelectionRenderModel.ts +91 -0
  55. package/dist/lib/xterm/src/browser/renderer/shared/TextureAtlas.ts +1082 -0
  56. package/dist/lib/xterm/src/browser/renderer/shared/Types.d.ts +173 -0
  57. package/dist/lib/xterm/src/browser/selection/SelectionModel.ts +144 -0
  58. package/dist/lib/xterm/src/browser/selection/Types.d.ts +15 -0
  59. package/dist/lib/xterm/src/browser/services/CharSizeService.ts +102 -0
  60. package/dist/lib/xterm/src/browser/services/CharacterJoinerService.ts +339 -0
  61. package/dist/lib/xterm/src/browser/services/CoreBrowserService.ts +33 -0
  62. package/dist/lib/xterm/src/browser/services/MouseService.ts +46 -0
  63. package/dist/lib/xterm/src/browser/services/RenderService.ts +284 -0
  64. package/dist/lib/xterm/src/browser/services/SelectionService.ts +1029 -0
  65. package/dist/lib/xterm/src/browser/services/Services.ts +138 -0
  66. package/dist/lib/xterm/src/browser/services/ThemeService.ts +237 -0
  67. package/dist/lib/xterm/src/common/CircularList.ts +241 -0
  68. package/dist/lib/xterm/src/common/Clone.ts +23 -0
  69. package/dist/lib/xterm/src/common/Color.ts +356 -0
  70. package/dist/lib/xterm/src/common/CoreTerminal.ts +284 -0
  71. package/dist/lib/xterm/src/common/EventEmitter.ts +73 -0
  72. package/dist/lib/xterm/src/common/InputHandler.ts +3443 -0
  73. package/dist/lib/xterm/src/common/Lifecycle.ts +108 -0
  74. package/dist/lib/xterm/src/common/MultiKeyMap.ts +42 -0
  75. package/dist/lib/xterm/src/common/Platform.ts +43 -0
  76. package/dist/lib/xterm/src/common/SortedList.ts +118 -0
  77. package/dist/lib/xterm/src/common/TaskQueue.ts +166 -0
  78. package/dist/lib/xterm/src/common/TypedArrayUtils.ts +17 -0
  79. package/dist/lib/xterm/src/common/Types.d.ts +553 -0
  80. package/dist/lib/xterm/src/common/WindowsMode.ts +27 -0
  81. package/dist/lib/xterm/src/common/buffer/AttributeData.ts +196 -0
  82. package/dist/lib/xterm/src/common/buffer/Buffer.ts +654 -0
  83. package/dist/lib/xterm/src/common/buffer/BufferLine.ts +520 -0
  84. package/dist/lib/xterm/src/common/buffer/BufferRange.ts +13 -0
  85. package/dist/lib/xterm/src/common/buffer/BufferReflow.ts +223 -0
  86. package/dist/lib/xterm/src/common/buffer/BufferSet.ts +134 -0
  87. package/dist/lib/xterm/src/common/buffer/CellData.ts +94 -0
  88. package/dist/lib/xterm/src/common/buffer/Constants.ts +149 -0
  89. package/dist/lib/xterm/src/common/buffer/Marker.ts +43 -0
  90. package/dist/lib/xterm/src/common/buffer/Types.d.ts +52 -0
  91. package/dist/lib/xterm/src/common/data/Charsets.ts +256 -0
  92. package/dist/lib/xterm/src/common/data/EscapeSequences.ts +153 -0
  93. package/dist/lib/xterm/src/common/input/Keyboard.ts +398 -0
  94. package/dist/lib/xterm/src/common/input/TextDecoder.ts +346 -0
  95. package/dist/lib/xterm/src/common/input/UnicodeV6.ts +132 -0
  96. package/dist/lib/xterm/src/common/input/WriteBuffer.ts +246 -0
  97. package/dist/lib/xterm/src/common/input/XParseColor.ts +80 -0
  98. package/dist/lib/xterm/src/common/parser/Constants.ts +58 -0
  99. package/dist/lib/xterm/src/common/parser/DcsParser.ts +192 -0
  100. package/dist/lib/xterm/src/common/parser/EscapeSequenceParser.ts +792 -0
  101. package/dist/lib/xterm/src/common/parser/OscParser.ts +238 -0
  102. package/dist/lib/xterm/src/common/parser/Params.ts +229 -0
  103. package/dist/lib/xterm/src/common/parser/Types.d.ts +274 -0
  104. package/dist/lib/xterm/src/common/public/AddonManager.ts +53 -0
  105. package/dist/lib/xterm/src/common/public/BufferApiView.ts +35 -0
  106. package/dist/lib/xterm/src/common/public/BufferLineApiView.ts +29 -0
  107. package/dist/lib/xterm/src/common/public/BufferNamespaceApi.ts +36 -0
  108. package/dist/lib/xterm/src/common/public/ParserApi.ts +37 -0
  109. package/dist/lib/xterm/src/common/public/UnicodeApi.ts +27 -0
  110. package/dist/lib/xterm/src/common/services/BufferService.ts +151 -0
  111. package/dist/lib/xterm/src/common/services/CharsetService.ts +34 -0
  112. package/dist/lib/xterm/src/common/services/CoreMouseService.ts +318 -0
  113. package/dist/lib/xterm/src/common/services/CoreService.ts +87 -0
  114. package/dist/lib/xterm/src/common/services/DecorationService.ts +140 -0
  115. package/dist/lib/xterm/src/common/services/InstantiationService.ts +85 -0
  116. package/dist/lib/xterm/src/common/services/LogService.ts +124 -0
  117. package/dist/lib/xterm/src/common/services/OptionsService.ts +201 -0
  118. package/dist/lib/xterm/src/common/services/OscLinkService.ts +115 -0
  119. package/dist/lib/xterm/src/common/services/ServiceRegistry.ts +49 -0
  120. package/dist/lib/xterm/src/common/services/Services.ts +342 -0
  121. package/dist/lib/xterm/src/common/services/UnicodeService.ts +86 -0
  122. package/dist/lib/xterm/src/headless/Terminal.ts +136 -0
  123. package/dist/lib/xterm/src/headless/public/Terminal.ts +195 -0
  124. package/dist/lib/xterm/typings/xterm.d.ts +1844 -0
  125. package/dist/lib/xterm-fit/LICENSE +19 -0
  126. package/dist/lib/xterm-fit/README.md +24 -0
  127. package/dist/lib/xterm-fit/lib/xterm-addon-fit.js +2 -0
  128. package/dist/lib/xterm-fit/lib/xterm-addon-fit.js.map +1 -0
  129. package/dist/lib/xterm-fit/package.json +26 -0
  130. package/dist/lib/xterm-fit/src/FitAddon.ts +89 -0
  131. package/dist/lib/xterm-fit/typings/xterm-addon-fit.d.ts +55 -0
  132. package/dist/lib/xterm-links/LICENSE +19 -0
  133. package/dist/lib/xterm-links/README.md +21 -0
  134. package/dist/lib/xterm-links/lib/xterm-addon-web-links.js +2 -0
  135. package/dist/lib/xterm-links/lib/xterm-addon-web-links.js.map +1 -0
  136. package/dist/lib/xterm-links/package.json +26 -0
  137. package/dist/lib/xterm-links/src/WebLinkProvider.ts +198 -0
  138. package/dist/lib/xterm-links/src/WebLinksAddon.ts +57 -0
  139. package/dist/lib/xterm-links/typings/xterm-addon-web-links.d.ts +53 -0
  140. package/dist/login.html +163 -0
  141. package/dist/manifest.json +12 -0
  142. package/dist/package.json +1 -0
  143. package/dist/sw.js +127 -0
  144. package/dist/sync.html +816 -0
  145. package/package.json +47 -0
package/bin/daemon.js ADDED
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * zihi 守护进程:写入 PID 并启动 server
4
+ */
5
+ import { writeFileSync, unlinkSync } from 'fs';
6
+ import { join, dirname } from 'path';
7
+ import { fileURLToPath } from 'url';
8
+ import { homedir } from 'os';
9
+
10
+ const __dirname = dirname(fileURLToPath(import.meta.url));
11
+ const pidFile = join(homedir(), '.zihi', 'daemon.pid');
12
+
13
+ // 写入自身 PID
14
+ writeFileSync(pidFile, String(process.pid));
15
+
16
+ // 退出时清理 PID 文件
17
+ function cleanup() { try { unlinkSync(pidFile); } catch {} }
18
+ process.on('exit', cleanup);
19
+ process.on('SIGTERM', () => process.exit(0));
20
+ process.on('SIGINT', () => process.exit(0));
21
+
22
+ // 启动 server
23
+ await import('../dist/index.js');
package/bin/zihi.js ADDED
@@ -0,0 +1,603 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from 'child_process';
3
+ import { fileURLToPath } from 'url';
4
+ import { dirname, join } from 'path';
5
+ import { mkdirSync, openSync, readFileSync, writeFileSync, unlinkSync } from 'fs';
6
+ import { homedir } from 'os';
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = dirname(__filename);
10
+
11
+ // 解析 -p / --port 参数
12
+ const args = process.argv.slice(2);
13
+ let cmd = null;
14
+ for (let i = 0; i < args.length; i++) {
15
+ if ((args[i] === '-p' || args[i] === '--port') && args[i + 1]) {
16
+ process.env.PORT = args[i + 1];
17
+ args.splice(i, 2); i--;
18
+ } else if (args[i].startsWith('--port=')) {
19
+ process.env.PORT = args[i].split('=')[1];
20
+ args.splice(i, 1); i--;
21
+ } else if (!cmd && !args[i].startsWith('-')) {
22
+ cmd = args[i];
23
+ }
24
+ }
25
+ if (!cmd) cmd = args[0];
26
+
27
+ const configDir = join(homedir(), '.zihi');
28
+ const pidFile = join(configDir, 'daemon.pid');
29
+ const logFile = join(configDir, 'daemon.log');
30
+
31
+ // ── 工具函数 ──────────────────────────────────────────────
32
+
33
+ function readPid() {
34
+ try { return parseInt(readFileSync(pidFile, 'utf8').trim(), 10); }
35
+ catch { return null; }
36
+ }
37
+
38
+ function isRunning(pid) {
39
+ try { process.kill(pid, 0); return true; }
40
+ catch { return false; }
41
+ }
42
+
43
+ function stopDaemon() {
44
+ const pid = readPid();
45
+ if (!pid) { console.log('[zihi] 没有正在运行的后台进程'); return false; }
46
+ if (!isRunning(pid)) {
47
+ try { unlinkSync(pidFile); } catch {}
48
+ console.log('[zihi] 进程已不存在,已清理 PID 文件');
49
+ return false;
50
+ }
51
+ process.kill(pid, 'SIGTERM');
52
+ try { unlinkSync(pidFile); } catch {}
53
+ console.log(`[zihi] 已停止后台进程 (PID ${pid})`);
54
+ return true;
55
+ }
56
+
57
+ function startDaemon() {
58
+ mkdirSync(configDir, { recursive: true });
59
+ const out = openSync(logFile, 'a');
60
+ // 启动守护进程(daemon.js),由它管理 server 子进程
61
+ const child = spawn(process.execPath, [join(__dirname, 'daemon.js')], {
62
+ detached: true,
63
+ stdio: ['ignore', out, out],
64
+ cwd: process.cwd(),
65
+ env: { ...process.env },
66
+ windowsHide: true,
67
+ });
68
+ child.unref();
69
+ console.log(`[zihi] 已在后台启动 (PID ${child.pid})`);
70
+
71
+ // 等待 server 启动,显示日志
72
+ let waited = 0;
73
+ const poll = setInterval(() => {
74
+ waited += 500;
75
+ try {
76
+ const content = readFileSync(logFile, 'utf8');
77
+ const lines = content.split('\n');
78
+ // 找到最新的启动信息
79
+ let startIdx = -1;
80
+ for (let i = lines.length - 1; i >= 0; i--) { if (lines[i].includes('━━━━')) { startIdx = i; break; } }
81
+ if (startIdx >= 0) {
82
+ clearInterval(poll);
83
+ // 显示从启动横幅开始的日志
84
+ const recent = lines.slice(Math.max(0, startIdx - 1));
85
+ console.log(recent.join('\n'));
86
+ process.exit(0);
87
+ }
88
+ } catch {}
89
+ if (waited > 10000) {
90
+ clearInterval(poll);
91
+ console.log(`[zihi] 停止: zihi stop`);
92
+ console.log(`[zihi] 日志: zihi log`);
93
+ console.log(`[zihi] 管理: http://localhost:${process.env.PORT || 12300}/admin`);
94
+ process.exit(0);
95
+ }
96
+ }, 500);
97
+ }
98
+
99
+ // ── 命令分发 ──────────────────────────────────────────────
100
+
101
+ if (cmd === 'attach') {
102
+ process.argv.splice(2, 1);
103
+ await import('../dist/attach.js');
104
+
105
+ } else if (cmd === 'stop') {
106
+ stopDaemon();
107
+
108
+ } else if (cmd === 'restart') {
109
+ stopDaemon();
110
+ await new Promise(r => setTimeout(r, 500));
111
+ startDaemon();
112
+ process.exit(0);
113
+
114
+ } else if (cmd === 'status') {
115
+ const pkgVersion = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.url)), '../package.json'), 'utf8')).version;
116
+ const pid = readPid();
117
+ if (pid && isRunning(pid)) {
118
+ console.log(`[zihi] v${pkgVersion} 运行中 (PID ${pid})`);
119
+ } else {
120
+ console.log(`[zihi] v${pkgVersion} 未运行`);
121
+ }
122
+
123
+ } else if (cmd === 'log' || cmd === 'logs') {
124
+ try {
125
+ const content = readFileSync(logFile, 'utf8');
126
+ const lines = content.split('\n');
127
+ const n = parseInt(process.argv[3], 10) || 50;
128
+ console.log(lines.slice(-n).join('\n'));
129
+ } catch {
130
+ console.log('[zihi] 日志文件不存在');
131
+ }
132
+
133
+ } else if (cmd === 'exclusive') {
134
+ // 独占模式开关
135
+ const { existsSync } = await import('fs');
136
+ const usersFile = join(configDir, 'users.json');
137
+ function loadCfg() {
138
+ try { if (existsSync(usersFile)) return JSON.parse(readFileSync(usersFile, 'utf8')); } catch {}
139
+ return { users: {} };
140
+ }
141
+ function saveCfg(data) {
142
+ mkdirSync(configDir, { recursive: true });
143
+ writeFileSync(usersFile, JSON.stringify(data, null, 2), { mode: 0o600 });
144
+ }
145
+
146
+ const sub = args[args.indexOf('exclusive') + 1] || process.argv[3];
147
+ if (sub === 'on' || sub === 'enable') {
148
+ const data = loadCfg();
149
+ data.exclusive = true;
150
+ saveCfg(data);
151
+ console.log('[zihi] 独占模式已开启(同一时间只允许一个账号登录)');
152
+ } else if (sub === 'off' || sub === 'disable') {
153
+ const data = loadCfg();
154
+ data.exclusive = false;
155
+ saveCfg(data);
156
+ console.log('[zihi] 独占模式已关闭(允许多人同时登录)');
157
+ } else {
158
+ const data = loadCfg();
159
+ const mode = data.exclusive ? '开启' : '关闭';
160
+ console.log(`[zihi] 独占模式: ${mode}`);
161
+ console.log(`\n用法:
162
+ zihi exclusive on 开启独占模式(同一时间只允许一个账号)
163
+ zihi exclusive off 关闭独占模式(允许多人同时登录)`);
164
+ }
165
+
166
+ } else if (cmd === 'user') {
167
+ // 用户管理:直接读写 ~/.zihi/users.json,不依赖 src/
168
+ const { existsSync } = await import('fs');
169
+ const usersFile = join(configDir, 'users.json');
170
+
171
+ function loadUsersFile() {
172
+ try {
173
+ if (existsSync(usersFile)) return JSON.parse(readFileSync(usersFile, 'utf8'));
174
+ } catch {}
175
+ return { users: {} };
176
+ }
177
+ function saveUsersFile(data) {
178
+ mkdirSync(configDir, { recursive: true });
179
+ writeFileSync(usersFile, JSON.stringify(data, null, 2), { mode: 0o600 });
180
+ }
181
+
182
+ const sub = args[args.indexOf('user') + 1] || process.argv[3];
183
+ if (sub === 'list' || sub === 'ls') {
184
+ const data = loadUsersFile();
185
+ const entries = Object.entries(data.users || {});
186
+ if (entries.length === 0) {
187
+ console.log('[zihi] 还没有用户,使用 zihi user add <密码> <名称> <目录> 添加');
188
+ } else {
189
+ console.log('\n 密码\t\t名称\t\t目录');
190
+ console.log(' ────\t\t────\t\t────');
191
+ for (const [pwd, info] of entries) {
192
+ const masked = pwd.slice(0, 2) + '*'.repeat(pwd.length - 2);
193
+ console.log(` ${masked}\t\t${info.name}\t\t${info.dir}`);
194
+ }
195
+ console.log();
196
+ }
197
+ } else if (sub === 'add') {
198
+ const pwd = process.argv[4];
199
+ const name = process.argv[5];
200
+ const dir = process.argv[6];
201
+ if (!pwd || !name || !dir) {
202
+ console.log('用法: zihi user add <密码> <名称> <目录>');
203
+ console.log('示例: zihi user add 1234 张三 D:\\project\\app1');
204
+ process.exit(1);
205
+ }
206
+ mkdirSync(dir, { recursive: true });
207
+ // 为用户创建独立的 git 配置(隔离凭据)
208
+ const gitconfigPath = join(dir, '.gitconfig');
209
+ const gitcredPath = join(dir, '.git-credentials');
210
+ if (!existsSync(gitconfigPath)) {
211
+ writeFileSync(gitconfigPath, `[credential]\n\thelper = store --file ${gitcredPath.replace(/\\/g, '/')}\n[user]\n\tname = ${name}\n`, { mode: 0o600 });
212
+ console.log(`[zihi] 已创建 git 配置: ${gitconfigPath}`);
213
+ }
214
+ const data = loadUsersFile();
215
+ data.users[pwd] = { name, dir };
216
+ saveUsersFile(data);
217
+ console.log(`[zihi] 已添加用户 "${name}" (目录: ${dir})`);
218
+ } else if (sub === 'remove' || sub === 'rm') {
219
+ const pwd = process.argv[4];
220
+ if (!pwd) {
221
+ console.log('用法: zihi user remove <密码>');
222
+ process.exit(1);
223
+ }
224
+ const data = loadUsersFile();
225
+ if (data.users[pwd]) {
226
+ delete data.users[pwd];
227
+ saveUsersFile(data);
228
+ console.log(`[zihi] 已删除用户`);
229
+ } else {
230
+ console.log('[zihi] 未找到该密码对应的用户');
231
+ }
232
+ } else {
233
+ console.log(`
234
+ 用户管理命令:
235
+ zihi user list 列出所有用户
236
+ zihi user add <密码> <名称> <目录> 添加用户
237
+ zihi user remove <密码> 删除用户
238
+
239
+ 示例:
240
+ zihi user add 1234 张三 D:\\project\\app1
241
+ zihi user add 5678 李四 D:\\project\\app2
242
+ zihi user list
243
+ zihi user remove 1234
244
+ `);
245
+ }
246
+
247
+ } else if (cmd === 'ssh') {
248
+ // SSH 账号管理:为 zihi 用户创建系统 SSH 账号,隔离目录权限
249
+ const { existsSync } = await import('fs');
250
+ const { platform } = await import('os');
251
+ const usersFile = join(configDir, 'users.json');
252
+ const isWindows = platform() === 'win32';
253
+
254
+ function loadUsersFile() {
255
+ try { if (existsSync(usersFile)) return JSON.parse(readFileSync(usersFile, 'utf8')); } catch {}
256
+ return { users: {} };
257
+ }
258
+ function saveUsersFile(data) {
259
+ mkdirSync(configDir, { recursive: true });
260
+ writeFileSync(usersFile, JSON.stringify(data, null, 2), { mode: 0o600 });
261
+ }
262
+
263
+ function run(cmd, opts = {}) {
264
+ try {
265
+ return execSync(cmd, { encoding: 'utf8', stdio: opts.silent ? 'pipe' : 'inherit', ...opts }).trim();
266
+ } catch (e) {
267
+ if (!opts.ignoreError) { console.error(`[zihi] 命令失败: ${cmd}\n${e.stderr || e.message}`); process.exit(1); }
268
+ return '';
269
+ }
270
+ }
271
+
272
+ function findUserByName(name) {
273
+ const data = loadUsersFile();
274
+ for (const [pwd, info] of Object.entries(data.users || {})) {
275
+ if (info.name === name) return { pwd, ...info };
276
+ }
277
+ return null;
278
+ }
279
+
280
+ const sub = args[args.indexOf('ssh') + 1] || process.argv[3];
281
+
282
+ if (sub === 'add' || sub === 'setup') {
283
+ const myName = process.argv[4];
284
+ const sshPass = process.argv[5];
285
+ if (!myName) {
286
+ console.log('用法: zihi ssh add <zihi用户名> [SSH密码]');
287
+ console.log('示例: zihi ssh add 张三 mypassword');
288
+ console.log('\n将为 zihi 用户创建系统 SSH 账号,绑定到该用户的项目目录');
289
+ process.exit(1);
290
+ }
291
+
292
+ const user = findUserByName(myName);
293
+ if (!user) { console.log(`[zihi] 未找到 zihi 用户 "${myName}",请先 zihi user add`); process.exit(1); }
294
+
295
+ const dir = user.dir;
296
+ // SSH 用户名:zihi-<name> (ASCII 化)
297
+ const sshUser = 'zihi-' + myName.replace(/[^a-zA-Z0-9_-]/g, () => Math.random().toString(36)[2]);
298
+ const sshPassword = sshPass || Math.random().toString(36).slice(2, 10) + 'A1!';
299
+
300
+ console.log(`[zihi] 创建 SSH 账号...`);
301
+ console.log(` zihi 用户: ${myName}`);
302
+ console.log(` 项目目录: ${dir}`);
303
+ console.log(` SSH 用户: ${sshUser}`);
304
+
305
+ if (isWindows) {
306
+ // ── Windows ──
307
+ // 创建本地用户
308
+ run(`net user "${sshUser}" "${sshPassword}" /add /y`, { silent: true, ignoreError: true });
309
+ run(`net user "${sshUser}" "${sshPassword}" /y`, { silent: true, ignoreError: true });
310
+
311
+ // 设置目录权限
312
+ mkdirSync(dir, { recursive: true });
313
+ // 上级目录:允许穿越(Traverse)但不允许列目录
314
+ const parent = join(dir, '..');
315
+ run(`icacls "${parent}" /grant "${sshUser}:(RC,S,X,RA,REA)"`, { silent: true, ignoreError: true });
316
+ // 用户自己的目录:完全控制
317
+ run(`icacls "${dir}" /grant "${sshUser}:(OI)(CI)F"`, { silent: true });
318
+
319
+ // 在用户 home 下创建 project 符号链接
320
+ const userHome = `C:\\Users\\${sshUser}`;
321
+ if (!existsSync(userHome)) mkdirSync(userHome, { recursive: true });
322
+ const link = join(userHome, 'project');
323
+ if (!existsSync(link)) {
324
+ run(`cmd /c mklink /D "${link}" "${dir}"`, { silent: true, ignoreError: true });
325
+ }
326
+
327
+ // 确保 OpenSSH Server 已启动
328
+ run('powershell -Command "Start-Service sshd 2>$null; Set-Service -Name sshd -StartupType Automatic 2>$null"', { silent: true, ignoreError: true });
329
+
330
+ } else {
331
+ // ── Linux / Ubuntu ──
332
+ // 创建系统用户,home 指向项目目录
333
+ run(`id "${sshUser}" 2>/dev/null || useradd -m -d "/home/${sshUser}" -s /bin/bash "${sshUser}"`, { silent: true, ignoreError: true });
334
+ run(`echo "${sshUser}:${sshPassword}" | chpasswd`, { silent: true });
335
+
336
+ mkdirSync(dir, { recursive: true });
337
+
338
+ // 在用户 home 下创建 project 符号链接
339
+ const homeDir = `/home/${sshUser}`;
340
+ const link = `${homeDir}/project`;
341
+ run(`ln -sfn "${dir}" "${link}"`, { silent: true, ignoreError: true });
342
+ run(`chown -h ${sshUser}:${sshUser} "${link}"`, { silent: true, ignoreError: true });
343
+
344
+ // 项目目录:设置为该用户所有
345
+ run(`chown -R ${sshUser}:${sshUser} "${dir}"`, { silent: true, ignoreError: true });
346
+
347
+ // 上级目录权限:移除 others 的读和执行
348
+ const parent = join(dir, '..');
349
+ run(`chmod 711 "${parent}"`, { silent: true, ignoreError: true });
350
+ // 用户自己的目录
351
+ run(`chmod 700 "${dir}"`, { silent: true, ignoreError: true });
352
+
353
+ // 确保 SSH 服务运行
354
+ run('systemctl enable ssh 2>/dev/null; systemctl start ssh 2>/dev/null || service ssh start 2>/dev/null', { silent: true, ignoreError: true });
355
+ }
356
+
357
+ // 保存 SSH 映射到 users.json
358
+ const data = loadUsersFile();
359
+ if (data.users[user.pwd]) {
360
+ data.users[user.pwd].sshUser = sshUser;
361
+ saveUsersFile(data);
362
+ }
363
+
364
+ console.log(`\n ✓ SSH 账号创建成功!`);
365
+ console.log(` ┌─────────────────────────────────`);
366
+ console.log(` │ SSH 用户名: ${sshUser}`);
367
+ console.log(` │ SSH 密码: ${sshPassword}`);
368
+ console.log(` │ 项目目录: ${dir}`);
369
+ console.log(` └─────────────────────────────────`);
370
+ console.log(`\n VS Code 连接方式:`);
371
+ console.log(` 1. 安装 Remote-SSH 扩展`);
372
+ console.log(` 2. 按 F1 → "Remote-SSH: Connect to Host"`);
373
+ console.log(` 3. 输入: ${sshUser}@<服务器IP>`);
374
+ console.log(` 4. 打开目录: ~/project`);
375
+
376
+ } else if (sub === 'list' || sub === 'ls') {
377
+ const data = loadUsersFile();
378
+ const entries = Object.entries(data.users || {}).filter(([, info]) => info.sshUser);
379
+ if (entries.length === 0) {
380
+ console.log('[zihi] 还没有 SSH 账号,使用 zihi ssh add <用户名> 创建');
381
+ } else {
382
+ console.log('\n zihi用户\tSSH账号\t\t\t目录');
383
+ console.log(' ────────\t───────\t\t\t────');
384
+ for (const [, info] of entries) {
385
+ console.log(` ${info.name}\t\t${info.sshUser}\t\t${info.dir}`);
386
+ }
387
+ console.log();
388
+ }
389
+
390
+ } else if (sub === 'remove' || sub === 'rm') {
391
+ const myName = process.argv[4];
392
+ if (!myName) { console.log('用法: zihi ssh remove <zihi用户名>'); process.exit(1); }
393
+
394
+ const user = findUserByName(myName);
395
+ if (!user || !user.sshUser) { console.log(`[zihi] 未找到 "${myName}" 的 SSH 账号`); process.exit(1); }
396
+
397
+ const sshUser = user.sshUser;
398
+ console.log(`[zihi] 删除 SSH 账号: ${sshUser}`);
399
+
400
+ if (isWindows) {
401
+ run(`net user "${sshUser}" /delete`, { silent: true, ignoreError: true });
402
+ } else {
403
+ run(`userdel "${sshUser}" 2>/dev/null`, { silent: true, ignoreError: true });
404
+ // 不删除 home,避免误删项目数据
405
+ }
406
+
407
+ const data = loadUsersFile();
408
+ if (data.users[user.pwd]) {
409
+ delete data.users[user.pwd].sshUser;
410
+ saveUsersFile(data);
411
+ }
412
+ console.log(`[zihi] 已删除 SSH 账号 "${sshUser}"(项目目录保留)`);
413
+
414
+ } else {
415
+ console.log(`
416
+ SSH 账号管理:为 zihi 用户创建独立的 SSH 开发账号
417
+
418
+ zihi ssh add <用户名> [密码] 创建 SSH 账号(绑定到 zihi 用户的项目目录)
419
+ zihi ssh list 列出所有 SSH 账号
420
+ zihi ssh remove <用户名> 删除 SSH 账号(保留项目目录)
421
+
422
+ 示例:
423
+ zihi user add 1234 张三 /data/projects/zhangsan # 先添加 zihi 用户
424
+ zihi ssh add 张三 sshpass123 # 再创建 SSH 账号
425
+ zihi ssh list # 查看所有 SSH 账号
426
+
427
+ 员工使用 VS Code Remote-SSH 连接后打开 ~/project 即可开发。
428
+ 每个用户只能访问自己的项目目录,无法查看其他用户的文件。
429
+ `);
430
+ }
431
+
432
+ } else if (cmd === 'kick') {
433
+ // 踢出会话中的用户
434
+ const { createRequire } = await import('module');
435
+ const require = createRequire(import.meta.url);
436
+ const { io } = require('socket.io-client');
437
+ const { createInterface } = await import('readline');
438
+
439
+ const SERVER = process.env.ZIHI_SERVER || 'http://localhost:12300';
440
+ let token;
441
+ try {
442
+ token = readFileSync(join(configDir, 'token'), 'utf8').trim();
443
+ } catch {
444
+ console.log('[zihi] 未找到 token,请先启动服务器');
445
+ process.exit(1);
446
+ }
447
+
448
+ const socket = io(SERVER, { auth: { token }, reconnection: false });
449
+
450
+ socket.on('connect_error', (err) => {
451
+ console.log(`[zihi] 连接失败: ${err.message}`);
452
+ process.exit(1);
453
+ });
454
+
455
+ socket.on('connect', () => {
456
+ // 获取所有会话
457
+ socket.emit('sessions', null, (sessions) => {
458
+ // sessions 事件可能不存在,用 HTTP 回退
459
+ });
460
+
461
+ // 通过 HTTP 获取会话列表
462
+ fetch(`${SERVER}/api/sessions?token=${token}`)
463
+ .then(r => r.json())
464
+ .then(async (sessions) => {
465
+ const alive = sessions.filter(s => s.alive);
466
+ if (alive.length === 0) {
467
+ console.log('[zihi] 没有活跃的会话');
468
+ socket.disconnect();
469
+ process.exit(0);
470
+ }
471
+
472
+ // 收集所有会话的在线用户
473
+ const allViewers = [];
474
+ for (const session of alive) {
475
+ const result = await new Promise(resolve => {
476
+ socket.emit('join', session.id);
477
+ socket.emit('list-viewers', { sessionId: session.id }, resolve);
478
+ });
479
+ if (result.ok) {
480
+ for (const v of result.viewers) {
481
+ allViewers.push({ ...v, sessionId: session.id, sessionTitle: session.title });
482
+ }
483
+ }
484
+ }
485
+
486
+ if (allViewers.length === 0) {
487
+ console.log('[zihi] 没有在线用户');
488
+ socket.disconnect();
489
+ process.exit(0);
490
+ }
491
+
492
+ // 去重(同一个 socket 可能只在一个会话中)
493
+ const unique = [...new Map(allViewers.map(v => [v.socketId, v])).values()];
494
+
495
+ console.log('\n 在线用户:');
496
+ console.log(' ────────────────────────────────────────');
497
+ unique.forEach((v, i) => {
498
+ const ctrl = v.isController ? ' [控制中]' : '';
499
+ console.log(` [${i + 1}] ${v.userName} (${v.role})${ctrl} 会话: ${v.sessionTitle}`);
500
+ });
501
+ console.log();
502
+
503
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
504
+ rl.question(' 输入序号踢出用户(0 取消): ', (answer) => {
505
+ rl.close();
506
+ const idx = parseInt(answer, 10);
507
+ if (!idx || idx < 1 || idx > unique.length) {
508
+ console.log('[zihi] 已取消');
509
+ socket.disconnect();
510
+ process.exit(0);
511
+ }
512
+ const target = unique[idx - 1];
513
+ socket.emit('kick-user', { socketId: target.socketId, sessionId: target.sessionId }, (res) => {
514
+ if (res.ok) {
515
+ console.log(`[zihi] 已踢出用户 "${res.name}"`);
516
+ } else {
517
+ console.log(`[zihi] 踢出失败: ${res.error}`);
518
+ }
519
+ socket.disconnect();
520
+ process.exit(0);
521
+ });
522
+ });
523
+ })
524
+ .catch((err) => {
525
+ console.log(`[zihi] 获取会话失败: ${err.message}`);
526
+ socket.disconnect();
527
+ process.exit(1);
528
+ });
529
+ });
530
+
531
+ } else if (cmd === 'help' || cmd === '--help' || cmd === '-h') {
532
+ console.log(`
533
+ zihi — 基于 Web 的终端共享工具
534
+ 通过局域网或 Tailscale 在手机/平板上控制电脑终端,无需中继服务器。
535
+
536
+ 服务器:
537
+ zihi 前台启动服务器
538
+ zihi start 前台启动服务器
539
+ zihi -p <port> 指定监听端口(默认 12300)
540
+ zihi -d, --daemon 后台启动服务器(日志写入 ~/.zihi/daemon.log)
541
+ zihi stop 停止后台服务器
542
+ zihi restart 重启后台服务器
543
+ zihi status 查看后台运行状态
544
+ zihi log [N] 查看后台日志(最后 N 行,默认 50)
545
+
546
+ 用户管理:
547
+ zihi user list 列出所有用户
548
+ zihi user add <密码> <名称> <目录> 添加用户
549
+ zihi user remove <密码> 删除用户
550
+ zihi exclusive on 开启独占模式(一次只能一个账号)
551
+ zihi exclusive off 关闭独占模式(多人同时使用,默认)
552
+
553
+ SSH 开发:
554
+ zihi ssh add <用户名> [密码] 为 zihi 用户创建 SSH 账号
555
+ zihi ssh list 列出所有 SSH 账号
556
+ zihi ssh remove <用户名> 删除 SSH 账号
557
+
558
+ 远程连接:
559
+ zihi attach 列出活跃会话,交互式选择后附加
560
+ zihi attach <id> 直接附加到指定会话
561
+ zihi attach --new 创建新会话并附加
562
+ zihi attach --password <密码> 用指定用户身份连接(看到该用户的会话)
563
+ zihi kick 列出在线用户,交互式选择后踢出
564
+
565
+ 快捷键(attach 模式):
566
+ Ctrl+] 分离终端(退出但不关闭会话)
567
+
568
+ 选项:
569
+ -p, --port <port> 监听端口(默认 12300)
570
+
571
+ 环境变量:
572
+ PORT 监听端口(默认 12300)
573
+ HOST 监听地址(默认 0.0.0.0)
574
+ ZIHI_AUTO_ATTACH 启动时自动创建会话(默认 1,设 0 关闭)
575
+ ZIHI_CWD 会话工作目录(默认当前目录)
576
+ ZIHI_SERVER attach 连接的服务器地址(默认 http://localhost:12300)
577
+
578
+ 配置目录:~/.zihi/
579
+ token 认证令牌(自动生成)
580
+ password 登录密码(自动生成,可手动修改)
581
+ users.json 独占用户配置(zihi user 命令管理)
582
+ roles.json 多用户角色配置(可选)
583
+ sessions.json 会话持久化数据
584
+ daemon.pid 后台进程 PID
585
+ daemon.log 后台运行日志
586
+
587
+ 示例:
588
+ zihi # 启动后扫描二维码即可在手机上操作
589
+ zihi -p 8080 # 指定 8080 端口启动
590
+ zihi -p 8080 -d # 8080 端口后台启动
591
+ ZIHI_CWD=/project zihi # 指定会话工作目录
592
+ zihi attach # 从另一台机器连接到会话
593
+ zihi user add 1234 张三 D:\\project # 添加用户
594
+ `);
595
+
596
+ } else if (cmd === '-d' || cmd === '--daemon' || cmd === 'daemon') {
597
+ startDaemon();
598
+ process.exit(0);
599
+
600
+ } else {
601
+ // 默认 / 'start':前台运行
602
+ await import('../dist/index.js');
603
+ }