@caoruhua/open-claude-remote 0.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/LICENSE +21 -0
- package/README.md +449 -0
- package/dist/api/auth-routes.d.ts +4 -0
- package/dist/api/auth-routes.d.ts.map +1 -0
- package/dist/api/auth-routes.js +7 -0
- package/dist/api/auth-routes.js.map +1 -0
- package/dist/api/config-routes.d.ts +4 -0
- package/dist/api/config-routes.d.ts.map +1 -0
- package/dist/api/config-routes.js +180 -0
- package/dist/api/config-routes.js.map +1 -0
- package/dist/api/health-routes.d.ts +3 -0
- package/dist/api/health-routes.d.ts.map +1 -0
- package/dist/api/health-routes.js +9 -0
- package/dist/api/health-routes.js.map +1 -0
- package/dist/api/hook-routes.d.ts +4 -0
- package/dist/api/hook-routes.d.ts.map +1 -0
- package/dist/api/hook-routes.js +32 -0
- package/dist/api/hook-routes.js.map +1 -0
- package/dist/api/instance-routes.d.ts +20 -0
- package/dist/api/instance-routes.d.ts.map +1 -0
- package/dist/api/instance-routes.js +128 -0
- package/dist/api/instance-routes.js.map +1 -0
- package/dist/api/push-routes.d.ts +5 -0
- package/dist/api/push-routes.d.ts.map +1 -0
- package/dist/api/push-routes.js +45 -0
- package/dist/api/push-routes.js.map +1 -0
- package/dist/api/router.d.ts +19 -0
- package/dist/api/router.d.ts.map +1 -0
- package/dist/api/router.js +37 -0
- package/dist/api/router.js.map +1 -0
- package/dist/api/status-routes.d.ts +5 -0
- package/dist/api/status-routes.d.ts.map +1 -0
- package/dist/api/status-routes.js +17 -0
- package/dist/api/status-routes.js.map +1 -0
- package/dist/attach.d.ts +9 -0
- package/dist/attach.d.ts.map +1 -0
- package/dist/attach.js +155 -0
- package/dist/attach.js.map +1 -0
- package/dist/auth/auth-middleware.d.ts +52 -0
- package/dist/auth/auth-middleware.d.ts.map +1 -0
- package/dist/auth/auth-middleware.js +124 -0
- package/dist/auth/auth-middleware.js.map +1 -0
- package/dist/auth/rate-limiter.d.ts +37 -0
- package/dist/auth/rate-limiter.d.ts.map +1 -0
- package/dist/auth/rate-limiter.js +81 -0
- package/dist/auth/rate-limiter.js.map +1 -0
- package/dist/auth/token-generator.d.ts +9 -0
- package/dist/auth/token-generator.d.ts.map +1 -0
- package/dist/auth/token-generator.js +15 -0
- package/dist/auth/token-generator.js.map +1 -0
- package/dist/cli-utils.d.ts +19 -0
- package/dist/cli-utils.d.ts.map +1 -0
- package/dist/cli-utils.js +132 -0
- package/dist/cli-utils.js.map +1 -0
- package/dist/cli.d.ts +21 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +58 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +146 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +329 -0
- package/dist/config.js.map +1 -0
- package/dist/hooks/hook-receiver.d.ts +39 -0
- package/dist/hooks/hook-receiver.d.ts.map +1 -0
- package/dist/hooks/hook-receiver.js +46 -0
- package/dist/hooks/hook-receiver.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +353 -0
- package/dist/index.js.map +1 -0
- package/dist/logger/logger.d.ts +8 -0
- package/dist/logger/logger.d.ts.map +1 -0
- package/dist/logger/logger.js +90 -0
- package/dist/logger/logger.js.map +1 -0
- package/dist/notification/dingtalk-service.d.ts +24 -0
- package/dist/notification/dingtalk-service.d.ts.map +1 -0
- package/dist/notification/dingtalk-service.js +94 -0
- package/dist/notification/dingtalk-service.js.map +1 -0
- package/dist/pty/output-buffer.d.ts +25 -0
- package/dist/pty/output-buffer.d.ts.map +1 -0
- package/dist/pty/output-buffer.js +58 -0
- package/dist/pty/output-buffer.js.map +1 -0
- package/dist/pty/pty-manager.d.ts +45 -0
- package/dist/pty/pty-manager.d.ts.map +1 -0
- package/dist/pty/pty-manager.js +108 -0
- package/dist/pty/pty-manager.js.map +1 -0
- package/dist/pty/types.d.ts +11 -0
- package/dist/pty/types.d.ts.map +1 -0
- package/dist/pty/types.js +2 -0
- package/dist/pty/types.js.map +1 -0
- package/dist/pty/virtual-pty.d.ts +37 -0
- package/dist/pty/virtual-pty.d.ts.map +1 -0
- package/dist/pty/virtual-pty.js +161 -0
- package/dist/pty/virtual-pty.js.map +1 -0
- package/dist/push/push-service.d.ts +87 -0
- package/dist/push/push-service.d.ts.map +1 -0
- package/dist/push/push-service.js +301 -0
- package/dist/push/push-service.js.map +1 -0
- package/dist/registry/instance-registry.d.ts +32 -0
- package/dist/registry/instance-registry.d.ts.map +1 -0
- package/dist/registry/instance-registry.js +115 -0
- package/dist/registry/instance-registry.js.map +1 -0
- package/dist/registry/instance-spawner.d.ts +33 -0
- package/dist/registry/instance-spawner.d.ts.map +1 -0
- package/dist/registry/instance-spawner.js +91 -0
- package/dist/registry/instance-spawner.js.map +1 -0
- package/dist/registry/port-finder.d.ts +8 -0
- package/dist/registry/port-finder.d.ts.map +1 -0
- package/dist/registry/port-finder.js +35 -0
- package/dist/registry/port-finder.js.map +1 -0
- package/dist/registry/shared-token.d.ts +11 -0
- package/dist/registry/shared-token.d.ts.map +1 -0
- package/dist/registry/shared-token.js +82 -0
- package/dist/registry/shared-token.js.map +1 -0
- package/dist/registry/stop-instances.d.ts +27 -0
- package/dist/registry/stop-instances.d.ts.map +1 -0
- package/dist/registry/stop-instances.js +212 -0
- package/dist/registry/stop-instances.js.map +1 -0
- package/dist/session/session-controller.d.ts +58 -0
- package/dist/session/session-controller.d.ts.map +1 -0
- package/dist/session/session-controller.js +273 -0
- package/dist/session/session-controller.js.map +1 -0
- package/dist/terminal/terminal-relay.d.ts +29 -0
- package/dist/terminal/terminal-relay.d.ts.map +1 -0
- package/dist/terminal/terminal-relay.js +106 -0
- package/dist/terminal/terminal-relay.js.map +1 -0
- package/dist/utils/ansi-filter.d.ts +41 -0
- package/dist/utils/ansi-filter.d.ts.map +1 -0
- package/dist/utils/ansi-filter.js +147 -0
- package/dist/utils/ansi-filter.js.map +1 -0
- package/dist/utils/file-lock.d.ts +23 -0
- package/dist/utils/file-lock.d.ts.map +1 -0
- package/dist/utils/file-lock.js +125 -0
- package/dist/utils/file-lock.js.map +1 -0
- package/dist/utils/ip-monitor.d.ts +39 -0
- package/dist/utils/ip-monitor.d.ts.map +1 -0
- package/dist/utils/ip-monitor.js +114 -0
- package/dist/utils/ip-monitor.js.map +1 -0
- package/dist/utils/network.d.ts +19 -0
- package/dist/utils/network.d.ts.map +1 -0
- package/dist/utils/network.js +54 -0
- package/dist/utils/network.js.map +1 -0
- package/dist/utils/pid-file.d.ts +10 -0
- package/dist/utils/pid-file.d.ts.map +1 -0
- package/dist/utils/pid-file.js +30 -0
- package/dist/utils/pid-file.js.map +1 -0
- package/dist/utils/qrcode-banner.d.ts +6 -0
- package/dist/utils/qrcode-banner.d.ts.map +1 -0
- package/dist/utils/qrcode-banner.js +16 -0
- package/dist/utils/qrcode-banner.js.map +1 -0
- package/dist/ws/ws-handler.d.ts +10 -0
- package/dist/ws/ws-handler.d.ts.map +1 -0
- package/dist/ws/ws-handler.js +36 -0
- package/dist/ws/ws-handler.js.map +1 -0
- package/dist/ws/ws-server.d.ts +78 -0
- package/dist/ws/ws-server.d.ts.map +1 -0
- package/dist/ws/ws-server.js +223 -0
- package/dist/ws/ws-server.js.map +1 -0
- package/frontend-dist/assets/index-BKudo1Dw.css +32 -0
- package/frontend-dist/assets/index-BqqB1hYe.js +141 -0
- package/frontend-dist/index.html +15 -0
- package/frontend-dist/sw.js +46 -0
- package/package.json +79 -0
- package/shared-dist/constants.d.ts +10 -0
- package/shared-dist/constants.d.ts.map +1 -0
- package/shared-dist/constants.js +10 -0
- package/shared-dist/constants.js.map +1 -0
- package/shared-dist/defaults.d.ts +30 -0
- package/shared-dist/defaults.d.ts.map +1 -0
- package/shared-dist/defaults.js +31 -0
- package/shared-dist/defaults.js.map +1 -0
- package/shared-dist/index.d.ts +5 -0
- package/shared-dist/index.d.ts.map +1 -0
- package/shared-dist/index.js +5 -0
- package/shared-dist/index.js.map +1 -0
- package/shared-dist/instance.d.ts +25 -0
- package/shared-dist/instance.d.ts.map +1 -0
- package/shared-dist/instance.js +7 -0
- package/shared-dist/instance.js.map +1 -0
- package/shared-dist/question-utils.d.ts +15 -0
- package/shared-dist/question-utils.d.ts.map +1 -0
- package/shared-dist/question-utils.js +24 -0
- package/shared-dist/question-utils.js.map +1 -0
- package/shared-dist/ws-protocol.d.ts +60 -0
- package/shared-dist/ws-protocol.d.ts.map +1 -0
- package/shared-dist/ws-protocol.js +5 -0
- package/shared-dist/ws-protocol.js.map +1 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
import { resolve, dirname } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { existsSync, readdirSync, unlinkSync, readFileSync } from 'node:fs';
|
|
5
|
+
import { homedir } from 'node:os';
|
|
6
|
+
import { randomUUID } from 'node:crypto';
|
|
7
|
+
import express from 'express';
|
|
8
|
+
import cors from 'cors';
|
|
9
|
+
import { CLAUDE_REMOTE_DIR, SETTINGS_DIR } from '../shared-dist/index.js';
|
|
10
|
+
import { loadConfig, createSessionCookieName, createClaudeSettings, extractSettingsFromArgs, saveClaudeSettings, ensureDefaultUserConfig } from './config.js';
|
|
11
|
+
import { AuthModule } from './auth/auth-middleware.js';
|
|
12
|
+
import { PtyManager } from './pty/pty-manager.js';
|
|
13
|
+
import { WsServer } from './ws/ws-server.js';
|
|
14
|
+
import { HookReceiver } from './hooks/hook-receiver.js';
|
|
15
|
+
import { SessionController } from './session/session-controller.js';
|
|
16
|
+
import { TerminalRelay } from './terminal/terminal-relay.js';
|
|
17
|
+
import { createApiRouter } from './api/router.js';
|
|
18
|
+
import { PushService } from './push/push-service.js';
|
|
19
|
+
import { DingtalkService } from './notification/dingtalk-service.js';
|
|
20
|
+
import { logger, setInstanceContext } from './logger/logger.js';
|
|
21
|
+
import { getOrCreateSharedToken } from './registry/shared-token.js';
|
|
22
|
+
import { findAvailablePort } from './registry/port-finder.js';
|
|
23
|
+
import { InstanceRegistryManager } from './registry/instance-registry.js';
|
|
24
|
+
import { InstanceSpawner } from './registry/instance-spawner.js';
|
|
25
|
+
import { IpMonitor } from './utils/ip-monitor.js';
|
|
26
|
+
import { printQRCode } from './utils/qrcode-banner.js';
|
|
27
|
+
export async function startServer(cliOverrides = {}) {
|
|
28
|
+
// 1. Load configuration
|
|
29
|
+
const config = loadConfig(cliOverrides);
|
|
30
|
+
const projectRoot = resolve(dirname(fileURLToPath(import.meta.url)), '../..');
|
|
31
|
+
// 1.5. Ensure default shortcuts/commands in user config
|
|
32
|
+
await ensureDefaultUserConfig();
|
|
33
|
+
// 2. Shared config directory and token
|
|
34
|
+
const sharedConfigDir = resolve(homedir(), CLAUDE_REMOTE_DIR);
|
|
35
|
+
const { token, source: tokenSource } = getOrCreateSharedToken(sharedConfigDir, config.token ?? undefined);
|
|
36
|
+
// 3. Instance ID and registry
|
|
37
|
+
const instanceId = randomUUID();
|
|
38
|
+
const registry = new InstanceRegistryManager(sharedConfigDir);
|
|
39
|
+
// 4. Find available port (auto-increment if preferred port is occupied)
|
|
40
|
+
const actualPort = await findAvailablePort(config.port, config.host);
|
|
41
|
+
// Update config to reflect the actual port (cookie name must match actual port
|
|
42
|
+
// to prevent cross-instance cookie collision when port auto-increments)
|
|
43
|
+
if (actualPort !== config.port) {
|
|
44
|
+
config.sessionCookieName = createSessionCookieName(actualPort);
|
|
45
|
+
config.port = actualPort;
|
|
46
|
+
logger.info({
|
|
47
|
+
actualPort,
|
|
48
|
+
sessionCookieName: config.sessionCookieName,
|
|
49
|
+
}, 'Config updated for actual port');
|
|
50
|
+
}
|
|
51
|
+
// 设置日志实例上下文,后续所有日志自动包含 instancePort 字段
|
|
52
|
+
setInstanceContext(actualPort);
|
|
53
|
+
// 5. Create Express app
|
|
54
|
+
const app = express();
|
|
55
|
+
app.use(express.json());
|
|
56
|
+
app.use(cors({
|
|
57
|
+
origin: (origin, callback) => {
|
|
58
|
+
// Allow same-origin requests (origin is undefined) and requests from LAN
|
|
59
|
+
if (!origin) {
|
|
60
|
+
callback(null, true);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
const url = new URL(origin);
|
|
65
|
+
// 允许同一 host 的任意端口(多实例跨端口访问)
|
|
66
|
+
const allowedHosts = [config.displayIp, 'localhost', '127.0.0.1'];
|
|
67
|
+
if (allowedHosts.includes(url.hostname)) {
|
|
68
|
+
callback(null, true);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// invalid origin
|
|
74
|
+
}
|
|
75
|
+
callback(new Error('Not allowed by CORS'));
|
|
76
|
+
},
|
|
77
|
+
credentials: true,
|
|
78
|
+
}));
|
|
79
|
+
// 6. Create HTTP server
|
|
80
|
+
const httpServer = createServer(app);
|
|
81
|
+
// 7. Setup auth module
|
|
82
|
+
const authModule = new AuthModule({
|
|
83
|
+
token,
|
|
84
|
+
sessionTtlMs: config.sessionTtlMs,
|
|
85
|
+
rateLimitPerMinute: config.authRateLimit,
|
|
86
|
+
cookieName: config.sessionCookieName,
|
|
87
|
+
});
|
|
88
|
+
// 8. Setup Hook receiver
|
|
89
|
+
const hookReceiver = new HookReceiver();
|
|
90
|
+
// 9. Setup Push service
|
|
91
|
+
const pushService = new PushService(sharedConfigDir);
|
|
92
|
+
// 9.5. Setup Dingtalk service (if configured)
|
|
93
|
+
const userConfigPath = resolve(sharedConfigDir, 'config.json');
|
|
94
|
+
let dingtalkService = null;
|
|
95
|
+
try {
|
|
96
|
+
if (existsSync(userConfigPath)) {
|
|
97
|
+
const userConfigContent = readFileSync(userConfigPath, 'utf-8');
|
|
98
|
+
const userConfig = JSON.parse(userConfigContent);
|
|
99
|
+
if (userConfig.dingtalk?.webhookUrl) {
|
|
100
|
+
dingtalkService = new DingtalkService(userConfig.dingtalk.webhookUrl);
|
|
101
|
+
logger.info('Dingtalk notification service initialized');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
logger.warn({ err }, 'Failed to load dingtalk config, skipping');
|
|
107
|
+
}
|
|
108
|
+
// 10. Session controller reference (set after PTY spawn)
|
|
109
|
+
let sessionController = null;
|
|
110
|
+
// 10.5. Create Instance Spawner (for creating new instances via API)
|
|
111
|
+
const instanceSpawner = new InstanceSpawner();
|
|
112
|
+
// 11. Mount REST API (with instance routes)
|
|
113
|
+
app.use('/api', createApiRouter({
|
|
114
|
+
authModule,
|
|
115
|
+
hookReceiver,
|
|
116
|
+
getController: () => sessionController,
|
|
117
|
+
pushService,
|
|
118
|
+
listInstances: () => registry.list(),
|
|
119
|
+
currentInstanceId: instanceId,
|
|
120
|
+
instanceSpawner,
|
|
121
|
+
}));
|
|
122
|
+
// 12. Serve frontend static files (if built)
|
|
123
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
124
|
+
// 发布包中 frontend-dist 在 dist 同级,开发时在 frontend/dist
|
|
125
|
+
const publishFrontendDist = resolve(__dirname, '../frontend-dist');
|
|
126
|
+
const devFrontendDist = resolve(__dirname, '../../frontend/dist');
|
|
127
|
+
const frontendDist = existsSync(publishFrontendDist)
|
|
128
|
+
? publishFrontendDist
|
|
129
|
+
: devFrontendDist;
|
|
130
|
+
if (existsSync(frontendDist)) {
|
|
131
|
+
app.use(express.static(frontendDist));
|
|
132
|
+
// SPA fallback — skip /api and /ws paths to avoid hijacking backend routes
|
|
133
|
+
app.get('*', (req, res, next) => {
|
|
134
|
+
if (req.path.startsWith('/api') || req.path.startsWith('/ws')) {
|
|
135
|
+
return next();
|
|
136
|
+
}
|
|
137
|
+
res.sendFile(resolve(frontendDist, 'index.html'));
|
|
138
|
+
});
|
|
139
|
+
logger.info({ path: frontendDist }, 'Serving frontend static files');
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
logger.warn('Frontend dist not found, skipping static file serving');
|
|
143
|
+
}
|
|
144
|
+
// 13. Create WebSocket server
|
|
145
|
+
const wsServer = new WsServer(httpServer, authModule);
|
|
146
|
+
// 14. Spawn PTY with Claude Code CLI
|
|
147
|
+
const ptyManager = new PtyManager();
|
|
148
|
+
// 15. Terminal Relay (条件启动,headless 模式跳过)
|
|
149
|
+
const noTerminal = process.env.NO_TERMINAL === 'true';
|
|
150
|
+
const relay = (!noTerminal && process.stdin.isTTY) ? new TerminalRelay(ptyManager) : undefined;
|
|
151
|
+
// 16. Create Session Controller (with relay for dynamic master switch)
|
|
152
|
+
sessionController = new SessionController(ptyManager, wsServer, hookReceiver, config.maxBufferLines, relay);
|
|
153
|
+
sessionController.setPushService(pushService);
|
|
154
|
+
if (dingtalkService) {
|
|
155
|
+
sessionController.setDingtalkService(dingtalkService);
|
|
156
|
+
}
|
|
157
|
+
// 17. Spawn Claude Code with instance-specific hook settings
|
|
158
|
+
// 检查用户是否传了 --settings 参数,如果有则合并 hooks
|
|
159
|
+
const extracted = extractSettingsFromArgs(config.claudeArgs);
|
|
160
|
+
let finalArgs;
|
|
161
|
+
// 生成最终的 settings 对象
|
|
162
|
+
const finalSettings = createClaudeSettings(actualPort, extracted?.settingsValue);
|
|
163
|
+
// 保存到文件并通过文件路径传递给 Claude
|
|
164
|
+
const settingsPath = saveClaudeSettings(finalSettings, actualPort, sharedConfigDir);
|
|
165
|
+
if (extracted) {
|
|
166
|
+
finalArgs = [...extracted.otherArgs, '--settings', settingsPath];
|
|
167
|
+
logger.info({ port: actualPort, originalSettingsPath: extracted.settingsPath, savedSettingsPath: settingsPath }, 'Merged user settings with hooks');
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
finalArgs = [...config.claudeArgs, '--settings', settingsPath];
|
|
171
|
+
logger.info({ port: actualPort, savedSettingsPath: settingsPath }, 'Generated Claude settings with instance-specific hook URL');
|
|
172
|
+
}
|
|
173
|
+
ptyManager.spawn({
|
|
174
|
+
command: config.claudeCommand,
|
|
175
|
+
args: finalArgs,
|
|
176
|
+
cwd: config.claudeCwd,
|
|
177
|
+
});
|
|
178
|
+
// Start relay after spawn (if not headless)
|
|
179
|
+
if (relay) {
|
|
180
|
+
relay.start();
|
|
181
|
+
}
|
|
182
|
+
sessionController.setStatus('running');
|
|
183
|
+
// 18. Register instance in shared registry
|
|
184
|
+
registry.register({
|
|
185
|
+
instanceId,
|
|
186
|
+
name: config.instanceName,
|
|
187
|
+
host: config.displayIp,
|
|
188
|
+
port: actualPort,
|
|
189
|
+
pid: process.pid,
|
|
190
|
+
cwd: config.claudeCwd,
|
|
191
|
+
startedAt: new Date().toISOString(),
|
|
192
|
+
headless: noTerminal,
|
|
193
|
+
});
|
|
194
|
+
// 18.5. Clean up stale settings files for dead instances
|
|
195
|
+
try {
|
|
196
|
+
const settingsDir = resolve(sharedConfigDir, SETTINGS_DIR);
|
|
197
|
+
if (existsSync(settingsDir)) {
|
|
198
|
+
const alivePorts = new Set((await registry.list()).map(i => i.port));
|
|
199
|
+
for (const file of readdirSync(settingsDir)) {
|
|
200
|
+
const match = file.match(/^(\d+)\.json$/);
|
|
201
|
+
if (match) {
|
|
202
|
+
const port = parseInt(match[1], 10);
|
|
203
|
+
if (!alivePorts.has(port)) {
|
|
204
|
+
try {
|
|
205
|
+
unlinkSync(resolve(settingsDir, file));
|
|
206
|
+
logger.info({ file, port }, 'Cleaned up stale settings file');
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
// 静默处理:清理失败不影响启动
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
catch (err) {
|
|
217
|
+
logger.debug({ err }, 'Settings cleanup skipped');
|
|
218
|
+
}
|
|
219
|
+
// 18.6. Start IP monitor
|
|
220
|
+
const ipMonitor = new IpMonitor((newIp, oldIp) => {
|
|
221
|
+
const newUrl = `http://${newIp}:${actualPort}`;
|
|
222
|
+
logger.info({ oldIp, newIp, newUrl }, 'IP change detected');
|
|
223
|
+
// Update config
|
|
224
|
+
config.displayIp = newIp;
|
|
225
|
+
// Update registry
|
|
226
|
+
registry.updateHost(instanceId, newIp);
|
|
227
|
+
// Broadcast to all connected clients
|
|
228
|
+
wsServer.broadcast({
|
|
229
|
+
type: 'ip_changed',
|
|
230
|
+
oldIp,
|
|
231
|
+
newIp,
|
|
232
|
+
newUrl,
|
|
233
|
+
});
|
|
234
|
+
}, 30000, 2); // 30s interval, 2 consecutive detections required
|
|
235
|
+
ipMonitor.start(config.displayIp);
|
|
236
|
+
// 19. Unified graceful shutdown
|
|
237
|
+
let shuttingDown = false;
|
|
238
|
+
const shutdown = (exitCode = 0) => {
|
|
239
|
+
if (shuttingDown)
|
|
240
|
+
return;
|
|
241
|
+
shuttingDown = true;
|
|
242
|
+
logger.info({ exitCode }, 'Shutting down...');
|
|
243
|
+
// Unregister from shared registry
|
|
244
|
+
registry.unregister(instanceId);
|
|
245
|
+
if (relay) {
|
|
246
|
+
relay.stop();
|
|
247
|
+
}
|
|
248
|
+
ipMonitor.stop();
|
|
249
|
+
// Pause stdin to remove it as an active event loop handle
|
|
250
|
+
if (!process.stdin.isTTY) {
|
|
251
|
+
process.stdin.pause();
|
|
252
|
+
}
|
|
253
|
+
ptyManager.destroy();
|
|
254
|
+
wsServer.destroy();
|
|
255
|
+
authModule.destroy();
|
|
256
|
+
httpServer.close(() => {
|
|
257
|
+
process.exit(exitCode);
|
|
258
|
+
});
|
|
259
|
+
// Force exit if httpServer.close hangs (ref'd to guarantee it fires)
|
|
260
|
+
setTimeout(() => process.exit(exitCode), 2000);
|
|
261
|
+
};
|
|
262
|
+
// Handle PTY exit → graceful shutdown
|
|
263
|
+
ptyManager.on('exit', (exitCode) => {
|
|
264
|
+
logger.info({ exitCode }, 'Claude Code exited');
|
|
265
|
+
// Give time for WS messages to flush, then shutdown
|
|
266
|
+
setTimeout(() => shutdown(exitCode), 500);
|
|
267
|
+
});
|
|
268
|
+
process.on('SIGINT', () => {
|
|
269
|
+
logger.info('SIGINT received');
|
|
270
|
+
shutdown(0);
|
|
271
|
+
});
|
|
272
|
+
process.on('SIGTERM', () => {
|
|
273
|
+
logger.info('SIGTERM received');
|
|
274
|
+
shutdown(0);
|
|
275
|
+
});
|
|
276
|
+
// When running via pnpm dev (stdin is a pipe, not TTY), detect parent process exit
|
|
277
|
+
// via stdin close event — this fires when concurrently/pnpm terminates
|
|
278
|
+
// Also handle Ctrl+C which may come through stdin in pipe mode
|
|
279
|
+
if (!process.stdin.isTTY) {
|
|
280
|
+
process.stdin.resume();
|
|
281
|
+
// Kitty keyboard protocol CSI u variants for Ctrl+C:
|
|
282
|
+
// Match press/repeat (event type 1 or 2) but not release (3)
|
|
283
|
+
const CTRL_C = '\x03';
|
|
284
|
+
const KITTY_CTRL_C_RE = /\x1b\[99;5(?::(?:[12]))?(?:;\d+)*u/;
|
|
285
|
+
// Use 'readable' event for immediate reads without waiting for newline
|
|
286
|
+
// In pipe mode, 'data' event is line-buffered and waits for Enter
|
|
287
|
+
process.stdin.on('readable', () => {
|
|
288
|
+
let chunk;
|
|
289
|
+
while ((chunk = process.stdin.read()) !== null) {
|
|
290
|
+
const str = chunk.toString();
|
|
291
|
+
logger.info({ hex: chunk.toString('hex'), len: chunk.length, str: str.replace(/\x1b/g, 'ESC') }, 'stdin data received in non-TTY mode');
|
|
292
|
+
// Single Ctrl+C (classic ETX or Kitty protocol) → shutdown immediately
|
|
293
|
+
if (str === CTRL_C || KITTY_CTRL_C_RE.test(str)) {
|
|
294
|
+
logger.info('Ctrl+C detected in pipe mode, initiating shutdown');
|
|
295
|
+
shutdown(0);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
process.stdin.once('close', () => {
|
|
300
|
+
logger.info('stdin pipe closed (parent process exited), initiating shutdown');
|
|
301
|
+
shutdown(0);
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
// 20. Handle port collision (TOCTOU between findAvailablePort and listen)
|
|
305
|
+
httpServer.on('error', (err) => {
|
|
306
|
+
if (err.code === 'EADDRINUSE') {
|
|
307
|
+
logger.error({ port: actualPort, host: config.host }, 'Port is already in use (TOCTOU race). Another process claimed it between port check and listen.');
|
|
308
|
+
process.exit(1);
|
|
309
|
+
}
|
|
310
|
+
logger.error({ err }, 'HTTP server error');
|
|
311
|
+
process.exit(1);
|
|
312
|
+
});
|
|
313
|
+
// 21. Start listening
|
|
314
|
+
httpServer.listen(actualPort, config.host, () => {
|
|
315
|
+
const url = `http://${config.displayIp}:${actualPort}`;
|
|
316
|
+
const isFirstInstance = tokenSource === 'generated';
|
|
317
|
+
process.stderr.write('\n');
|
|
318
|
+
process.stderr.write('╔══════════════════════════════════════════════════╗\n');
|
|
319
|
+
process.stderr.write('║ Claude Code Remote Proxy Started ║\n');
|
|
320
|
+
process.stderr.write('╠══════════════════════════════════════════════════╣\n');
|
|
321
|
+
process.stderr.write(`║ Instance: ${config.instanceName.padEnd(37)}║\n`);
|
|
322
|
+
process.stderr.write(`║ URL: ${url.padEnd(37)}║\n`);
|
|
323
|
+
if (isFirstInstance) {
|
|
324
|
+
// 首次启动(生成了新 Token):完整显示 Token
|
|
325
|
+
const tokenPreview = token.length >= 16
|
|
326
|
+
? `${token.substring(0, 8)}...${token.substring(token.length - 8)}`
|
|
327
|
+
: token;
|
|
328
|
+
process.stderr.write(`║ Token: ${tokenPreview.padEnd(37)}║\n`);
|
|
329
|
+
process.stderr.write('╠══════════════════════════════════════════════════╣\n');
|
|
330
|
+
process.stderr.write(`║ Full Token (copy to phone): ║\n`);
|
|
331
|
+
process.stderr.write(`║ ${token.padEnd(48)}║\n`);
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
// 后续启动(读取共享 Token):简短提示
|
|
335
|
+
process.stderr.write(`║ Token: ${'(shared, see first instance)'.padEnd(37)}║\n`);
|
|
336
|
+
}
|
|
337
|
+
process.stderr.write('╚══════════════════════════════════════════════════╝\n');
|
|
338
|
+
// 每次启动都显示二维码,方便手机扫码连接
|
|
339
|
+
const qrUrl = `${url}?token=${token}`;
|
|
340
|
+
printQRCode(qrUrl);
|
|
341
|
+
process.stderr.write('\n');
|
|
342
|
+
logger.info({ url, host: config.host, port: actualPort, instanceName: config.instanceName, tokenSource }, 'Server started');
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
// Auto-start when run directly (non-CLI mode)
|
|
346
|
+
// CLI mode: cli.ts imports startServer() directly
|
|
347
|
+
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
348
|
+
startServer().catch((err) => {
|
|
349
|
+
logger.error({ err }, 'Failed to start');
|
|
350
|
+
process.exit(1);
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import pino from 'pino';
|
|
2
|
+
export declare let logger: pino.Logger<never, boolean>;
|
|
3
|
+
/**
|
|
4
|
+
* 设置实例上下文,使后续所有日志自动包含 instancePort 字段。
|
|
5
|
+
* 在端口确定后调用一次。ESM live binding 确保所有 importer 自动获取新值。
|
|
6
|
+
*/
|
|
7
|
+
export declare function setInstanceContext(port: number): void;
|
|
8
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/logger/logger.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AAyFxB,eAAO,IAAI,MAAM,6BAAiB,CAAC;AAEnC;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAErD"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import pino from 'pino';
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { mkdirSync } from 'node:fs';
|
|
5
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const projectRoot = resolve(__dirname, '../../..'); // logger/ → src/ → backend/ → root/
|
|
7
|
+
const logDir = process.env.LOG_DIR ?? resolve(projectRoot, 'logs');
|
|
8
|
+
// Ensure log directory exists
|
|
9
|
+
try {
|
|
10
|
+
mkdirSync(logDir, { recursive: true });
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
// ignore - may not have write access in test environment
|
|
14
|
+
}
|
|
15
|
+
const logFilePath = resolve(logDir, 'app.log');
|
|
16
|
+
const errorLogPath = resolve(logDir, 'error.log');
|
|
17
|
+
const isDev = process.env.NODE_ENV !== 'production';
|
|
18
|
+
const isTest = process.env.NODE_ENV === 'test' || process.env.VITEST === 'true';
|
|
19
|
+
const isCli = process.env.CLI_MODE === 'true';
|
|
20
|
+
const isStderrTTY = process.stderr.isTTY === true;
|
|
21
|
+
function createLogger() {
|
|
22
|
+
if (isTest) {
|
|
23
|
+
// Silent logger in test environment
|
|
24
|
+
return pino({ level: 'silent' });
|
|
25
|
+
}
|
|
26
|
+
if (isCli) {
|
|
27
|
+
// CLI mode: only write to files, keep terminal clean
|
|
28
|
+
return pino({
|
|
29
|
+
level: 'info',
|
|
30
|
+
transport: {
|
|
31
|
+
targets: [
|
|
32
|
+
{ target: 'pino/file', options: { destination: logFilePath }, level: 'info' },
|
|
33
|
+
{ target: 'pino/file', options: { destination: errorLogPath }, level: 'error' },
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
if (isDev && isStderrTTY) {
|
|
39
|
+
// Pretty console output in dev (only when stderr is a TTY)
|
|
40
|
+
return pino({
|
|
41
|
+
level: 'debug',
|
|
42
|
+
transport: {
|
|
43
|
+
targets: [
|
|
44
|
+
{
|
|
45
|
+
target: 'pino-pretty',
|
|
46
|
+
options: { destination: 2 }, // stderr — keep stdout clean for PTY relay
|
|
47
|
+
level: 'info',
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
target: 'pino/file',
|
|
51
|
+
options: { destination: logFilePath },
|
|
52
|
+
level: 'info',
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
target: 'pino/file',
|
|
56
|
+
options: { destination: errorLogPath },
|
|
57
|
+
level: 'error',
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
// Production: JSON to files
|
|
64
|
+
return pino({
|
|
65
|
+
level: 'info',
|
|
66
|
+
transport: {
|
|
67
|
+
targets: [
|
|
68
|
+
{
|
|
69
|
+
target: 'pino/file',
|
|
70
|
+
options: { destination: logFilePath },
|
|
71
|
+
level: 'info',
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
target: 'pino/file',
|
|
75
|
+
options: { destination: errorLogPath },
|
|
76
|
+
level: 'error',
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
export let logger = createLogger();
|
|
83
|
+
/**
|
|
84
|
+
* 设置实例上下文,使后续所有日志自动包含 instancePort 字段。
|
|
85
|
+
* 在端口确定后调用一次。ESM live binding 确保所有 importer 自动获取新值。
|
|
86
|
+
*/
|
|
87
|
+
export function setInstanceContext(port) {
|
|
88
|
+
logger = logger.child({ instancePort: port });
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/logger/logger.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEpC,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC1D,MAAM,WAAW,GAAG,OAAO,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC,CAAE,oCAAoC;AACzF,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,OAAO,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;AAEnE,8BAA8B;AAC9B,IAAI,CAAC;IACH,SAAS,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AACzC,CAAC;AAAC,MAAM,CAAC;IACP,yDAAyD;AAC3D,CAAC;AAED,MAAM,WAAW,GAAG,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AAC/C,MAAM,YAAY,GAAG,OAAO,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;AAElD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,CAAC;AACpD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,KAAK,MAAM,CAAC;AAChF,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,MAAM,CAAC;AAC9C,MAAM,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,KAAK,IAAI,CAAC;AAElD,SAAS,YAAY;IACnB,IAAI,MAAM,EAAE,CAAC;QACX,oCAAoC;QACpC,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;IACnC,CAAC;IAED,IAAI,KAAK,EAAE,CAAC;QACV,qDAAqD;QACrD,OAAO,IAAI,CAAC;YACV,KAAK,EAAE,MAAM;YACb,SAAS,EAAE;gBACT,OAAO,EAAE;oBACP,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,EAAE,WAAW,EAAE,WAAW,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE;oBAC7E,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,EAAE,WAAW,EAAE,YAAY,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE;iBAChF;aACF;SACF,CAAC,CAAC;IACL,CAAC;IAED,IAAI,KAAK,IAAI,WAAW,EAAE,CAAC;QACzB,2DAA2D;QAC3D,OAAO,IAAI,CAAC;YACV,KAAK,EAAE,OAAO;YACd,SAAS,EAAE;gBACT,OAAO,EAAE;oBACP;wBACE,MAAM,EAAE,aAAa;wBACrB,OAAO,EAAE,EAAE,WAAW,EAAE,CAAC,EAAE,EAAE,2CAA2C;wBACxE,KAAK,EAAE,MAAM;qBACd;oBACD;wBACE,MAAM,EAAE,WAAW;wBACnB,OAAO,EAAE,EAAE,WAAW,EAAE,WAAW,EAAE;wBACrC,KAAK,EAAE,MAAM;qBACd;oBACD;wBACE,MAAM,EAAE,WAAW;wBACnB,OAAO,EAAE,EAAE,WAAW,EAAE,YAAY,EAAE;wBACtC,KAAK,EAAE,OAAO;qBACf;iBACF;aACF;SACF,CAAC,CAAC;IACL,CAAC;IAED,4BAA4B;IAC5B,OAAO,IAAI,CAAC;QACV,KAAK,EAAE,MAAM;QACb,SAAS,EAAE;YACT,OAAO,EAAE;gBACP;oBACE,MAAM,EAAE,WAAW;oBACnB,OAAO,EAAE,EAAE,WAAW,EAAE,WAAW,EAAE;oBACrC,KAAK,EAAE,MAAM;iBACd;gBACD;oBACE,MAAM,EAAE,WAAW;oBACnB,OAAO,EAAE,EAAE,WAAW,EAAE,YAAY,EAAE;oBACtC,KAAK,EAAE,OAAO;iBACf;aACF;SACF;KACF,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,IAAI,MAAM,GAAG,YAAY,EAAE,CAAC;AAEnC;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC;AAChD,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 钉钉群机器人 Webhook 通知服务
|
|
3
|
+
* 参考文档: https://open.dingtalk.com/document/robots/custom-robot-access
|
|
4
|
+
*/
|
|
5
|
+
export declare class DingtalkService {
|
|
6
|
+
private webhookUrl;
|
|
7
|
+
private validatedUrl;
|
|
8
|
+
constructor(webhookUrl: string);
|
|
9
|
+
/**
|
|
10
|
+
* 验证 webhook URL 是否为合法的钉钉域名
|
|
11
|
+
* 防止 SSRF 攻击
|
|
12
|
+
*/
|
|
13
|
+
private validateWebhookUrl;
|
|
14
|
+
/**
|
|
15
|
+
* 发送钉钉通知
|
|
16
|
+
* 失败时仅记录日志,不抛出异常
|
|
17
|
+
*
|
|
18
|
+
* @param title 消息标题
|
|
19
|
+
* @param tool 工具名称
|
|
20
|
+
* @param message 详细消息
|
|
21
|
+
*/
|
|
22
|
+
sendNotification(title: string, tool: string, message: string): Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=dingtalk-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dingtalk-service.d.ts","sourceRoot":"","sources":["../../src/notification/dingtalk-service.ts"],"names":[],"mappings":"AAQA;;;GAGG;AACH,qBAAa,eAAe;IAGd,OAAO,CAAC,UAAU;IAF9B,OAAO,CAAC,YAAY,CAAuB;gBAEvB,UAAU,EAAE,MAAM;IAItC;;;OAGG;IACH,OAAO,CAAC,kBAAkB;IAiB1B;;;;;;;OAOG;IACG,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAwDpF"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { logger } from '../logger/logger.js';
|
|
2
|
+
/** 钉钉 Webhook 允许的域名 */
|
|
3
|
+
const ALLOWED_HOSTS = ['oapi.dingtalk.com'];
|
|
4
|
+
/** HTTP 请求超时时间(毫秒) */
|
|
5
|
+
const REQUEST_TIMEOUT_MS = 5000;
|
|
6
|
+
/**
|
|
7
|
+
* 钉钉群机器人 Webhook 通知服务
|
|
8
|
+
* 参考文档: https://open.dingtalk.com/document/robots/custom-robot-access
|
|
9
|
+
*/
|
|
10
|
+
export class DingtalkService {
|
|
11
|
+
webhookUrl;
|
|
12
|
+
validatedUrl = null;
|
|
13
|
+
constructor(webhookUrl) {
|
|
14
|
+
this.webhookUrl = webhookUrl;
|
|
15
|
+
this.validatedUrl = this.validateWebhookUrl(webhookUrl);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 验证 webhook URL 是否为合法的钉钉域名
|
|
19
|
+
* 防止 SSRF 攻击
|
|
20
|
+
*/
|
|
21
|
+
validateWebhookUrl(url) {
|
|
22
|
+
if (!url)
|
|
23
|
+
return null;
|
|
24
|
+
try {
|
|
25
|
+
const parsed = new URL(url);
|
|
26
|
+
// 只允许钉钉官方域名
|
|
27
|
+
if (!ALLOWED_HOSTS.includes(parsed.hostname)) {
|
|
28
|
+
logger.warn({ hostname: parsed.hostname }, 'Invalid dingtalk webhook hostname, rejecting');
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
return url;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
logger.warn({ url }, 'Invalid webhook URL format, rejecting');
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 发送钉钉通知
|
|
40
|
+
* 失败时仅记录日志,不抛出异常
|
|
41
|
+
*
|
|
42
|
+
* @param title 消息标题
|
|
43
|
+
* @param tool 工具名称
|
|
44
|
+
* @param message 详细消息
|
|
45
|
+
*/
|
|
46
|
+
async sendNotification(title, tool, message) {
|
|
47
|
+
if (!this.validatedUrl) {
|
|
48
|
+
logger.warn('Dingtalk webhook URL is empty or invalid, skipping notification');
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const payload = {
|
|
52
|
+
msgtype: 'markdown',
|
|
53
|
+
markdown: {
|
|
54
|
+
title,
|
|
55
|
+
text: `### ${title}\n\n**工具**: ${tool}\n\n**消息**: ${message}\n\n---\n请及时处理`,
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
const controller = new AbortController();
|
|
59
|
+
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
60
|
+
try {
|
|
61
|
+
const response = await fetch(this.validatedUrl, {
|
|
62
|
+
method: 'POST',
|
|
63
|
+
headers: {
|
|
64
|
+
'Content-Type': 'application/json',
|
|
65
|
+
},
|
|
66
|
+
body: JSON.stringify(payload),
|
|
67
|
+
signal: controller.signal,
|
|
68
|
+
});
|
|
69
|
+
if (!response.ok) {
|
|
70
|
+
const text = await response.text();
|
|
71
|
+
logger.error({ status: response.status, body: text }, 'Dingtalk notification failed with HTTP error');
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const result = (await response.json());
|
|
75
|
+
if (result.errcode !== 0) {
|
|
76
|
+
logger.error({ errcode: result.errcode, errmsg: result.errmsg }, 'Dingtalk notification failed with API error');
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
logger.info({ title, tool }, 'Dingtalk notification sent successfully');
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
if (err instanceof Error && err.name === 'AbortError') {
|
|
83
|
+
logger.error({ timeout: REQUEST_TIMEOUT_MS }, 'Dingtalk notification timed out');
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
logger.error({ err, title, tool }, 'Dingtalk notification failed with exception');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
clearTimeout(timeout);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=dingtalk-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dingtalk-service.js","sourceRoot":"","sources":["../../src/notification/dingtalk-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ring buffer for PTY output. Stores raw ANSI strings, enforces max line count.
|
|
3
|
+
* Provides monotonically increasing sequence numbers for reconnection sync.
|
|
4
|
+
*/
|
|
5
|
+
export declare class OutputBuffer {
|
|
6
|
+
private lines;
|
|
7
|
+
private partial;
|
|
8
|
+
private seq;
|
|
9
|
+
private readonly maxLines;
|
|
10
|
+
constructor(maxLines?: number);
|
|
11
|
+
get sequenceNumber(): number;
|
|
12
|
+
/**
|
|
13
|
+
* Append raw PTY output data. May contain multiple lines or partial lines.
|
|
14
|
+
*/
|
|
15
|
+
append(data: string): void;
|
|
16
|
+
/**
|
|
17
|
+
* Get full buffered content as a single string (for history_sync).
|
|
18
|
+
*/
|
|
19
|
+
getFullContent(): string;
|
|
20
|
+
/**
|
|
21
|
+
* Clear the buffer.
|
|
22
|
+
*/
|
|
23
|
+
clear(): void;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=output-buffer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"output-buffer.d.ts","sourceRoot":"","sources":["../../src/pty/output-buffer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,KAAK,CAAgB;IAC7B,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,GAAG,CAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;gBAEtB,QAAQ,GAAE,MAAc;IAIpC,IAAI,cAAc,IAAI,MAAM,CAE3B;IAED;;OAEG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAsB1B;;OAEG;IACH,cAAc,IAAI,MAAM;IAYxB;;OAEG;IACH,KAAK,IAAI,IAAI;CAId"}
|