deepseek-cowork 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/bin/cli.mjs +91 -0
- package/commands/config.mjs +154 -0
- package/commands/login.mjs +155 -0
- package/commands/logout.mjs +69 -0
- package/commands/open.mjs +91 -0
- package/commands/start.mjs +228 -0
- package/commands/status.mjs +129 -0
- package/commands/stop.mjs +90 -0
- package/index.mjs +66 -0
- package/package.json +60 -0
- package/utils/process.mjs +148 -0
package/bin/cli.mjs
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* DeepSeek Cowork CLI 入口
|
|
5
|
+
*
|
|
6
|
+
* 创建时间: 2026-01-20
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { program } from 'commander';
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
11
|
+
import { dirname, join } from 'path';
|
|
12
|
+
import { readFileSync } from 'fs';
|
|
13
|
+
|
|
14
|
+
// 获取包根目录
|
|
15
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
16
|
+
const __dirname = dirname(__filename);
|
|
17
|
+
const packageRoot = join(__dirname, '..');
|
|
18
|
+
|
|
19
|
+
// 读取 package.json
|
|
20
|
+
const packageJson = JSON.parse(readFileSync(join(packageRoot, 'package.json'), 'utf8'));
|
|
21
|
+
|
|
22
|
+
// 导入命令
|
|
23
|
+
import { startCommand } from '../commands/start.mjs';
|
|
24
|
+
import { stopCommand } from '../commands/stop.mjs';
|
|
25
|
+
import { statusCommand } from '../commands/status.mjs';
|
|
26
|
+
import { configCommand } from '../commands/config.mjs';
|
|
27
|
+
import { openCommand } from '../commands/open.mjs';
|
|
28
|
+
import { loginCommand } from '../commands/login.mjs';
|
|
29
|
+
import { logoutCommand } from '../commands/logout.mjs';
|
|
30
|
+
|
|
31
|
+
// 配置程序
|
|
32
|
+
program
|
|
33
|
+
.name('deepseek-cowork')
|
|
34
|
+
.description('Open-Source Alternative to Claude Cowork - CLI Tool')
|
|
35
|
+
.version(packageJson.version);
|
|
36
|
+
|
|
37
|
+
// 注册命令
|
|
38
|
+
program
|
|
39
|
+
.command('start')
|
|
40
|
+
.description('Start the local service')
|
|
41
|
+
.option('-d, --daemon', 'Run in background as daemon')
|
|
42
|
+
.option('-p, --port <port>', 'HTTP port', '3333')
|
|
43
|
+
.option('--ws-port <port>', 'WebSocket port', '8080')
|
|
44
|
+
.option('-w, --work-dir <path>', 'Working directory')
|
|
45
|
+
.option('--debug', 'Enable debug mode')
|
|
46
|
+
.action(startCommand);
|
|
47
|
+
|
|
48
|
+
program
|
|
49
|
+
.command('stop')
|
|
50
|
+
.description('Stop the local service')
|
|
51
|
+
.action(stopCommand);
|
|
52
|
+
|
|
53
|
+
program
|
|
54
|
+
.command('status')
|
|
55
|
+
.description('Show service status')
|
|
56
|
+
.option('-j, --json', 'Output as JSON')
|
|
57
|
+
.action(statusCommand);
|
|
58
|
+
|
|
59
|
+
program
|
|
60
|
+
.command('config')
|
|
61
|
+
.description('Manage configuration')
|
|
62
|
+
.argument('[action]', 'Action: list, get, set', 'list')
|
|
63
|
+
.argument('[key]', 'Configuration key')
|
|
64
|
+
.argument('[value]', 'Configuration value')
|
|
65
|
+
.action(configCommand);
|
|
66
|
+
|
|
67
|
+
program
|
|
68
|
+
.command('open')
|
|
69
|
+
.description('Open web interface in browser')
|
|
70
|
+
.option('-l, --local', 'Open local interface instead of public website')
|
|
71
|
+
.action(openCommand);
|
|
72
|
+
|
|
73
|
+
program
|
|
74
|
+
.command('login')
|
|
75
|
+
.description('Login with your Happy AI account')
|
|
76
|
+
.option('-s, --secret <secret>', 'Provide secret directly')
|
|
77
|
+
.action(loginCommand);
|
|
78
|
+
|
|
79
|
+
program
|
|
80
|
+
.command('logout')
|
|
81
|
+
.description('Logout from your account')
|
|
82
|
+
.action(logoutCommand);
|
|
83
|
+
|
|
84
|
+
// 默认命令:显示帮助
|
|
85
|
+
program
|
|
86
|
+
.action(() => {
|
|
87
|
+
program.help();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// 解析命令行参数
|
|
91
|
+
program.parse();
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* config 命令 - 管理配置
|
|
3
|
+
*
|
|
4
|
+
* 创建时间: 2026-01-20
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import { getConfig, getUserSettings } from '../index.mjs';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 配置管理命令
|
|
12
|
+
*/
|
|
13
|
+
export async function configCommand(action = 'list', key = null, value = null) {
|
|
14
|
+
try {
|
|
15
|
+
const config = await getConfig();
|
|
16
|
+
const userSettings = await getUserSettings();
|
|
17
|
+
|
|
18
|
+
// 初始化用户设置
|
|
19
|
+
userSettings.initialize();
|
|
20
|
+
|
|
21
|
+
switch (action) {
|
|
22
|
+
case 'list':
|
|
23
|
+
listConfig(userSettings, config);
|
|
24
|
+
break;
|
|
25
|
+
|
|
26
|
+
case 'get':
|
|
27
|
+
if (!key) {
|
|
28
|
+
console.error(chalk.red('Error: Key is required'));
|
|
29
|
+
console.log(chalk.dim('Usage: deepseek-cowork config get <key>'));
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
getConfigValue(userSettings, key);
|
|
33
|
+
break;
|
|
34
|
+
|
|
35
|
+
case 'set':
|
|
36
|
+
if (!key) {
|
|
37
|
+
console.error(chalk.red('Error: Key is required'));
|
|
38
|
+
console.log(chalk.dim('Usage: deepseek-cowork config set <key> <value>'));
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
setConfigValue(userSettings, key, value);
|
|
42
|
+
break;
|
|
43
|
+
|
|
44
|
+
default:
|
|
45
|
+
console.error(chalk.red(`Unknown action: ${action}`));
|
|
46
|
+
console.log(chalk.dim('Available actions: list, get, set'));
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error(chalk.red('Error:'), error.message);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 列出所有配置
|
|
58
|
+
*/
|
|
59
|
+
function listConfig(userSettings, config) {
|
|
60
|
+
const settings = userSettings.getAll();
|
|
61
|
+
|
|
62
|
+
console.log('');
|
|
63
|
+
console.log(chalk.bold('DeepSeek Cowork Configuration'));
|
|
64
|
+
console.log(chalk.dim('─'.repeat(50)));
|
|
65
|
+
|
|
66
|
+
// 服务器配置
|
|
67
|
+
console.log('');
|
|
68
|
+
console.log(chalk.cyan('Server:'));
|
|
69
|
+
console.log(` server.httpPort: ${settings.server?.httpPort || config.DEFAULT_HTTP_PORT}`);
|
|
70
|
+
console.log(` server.wsPort: ${settings.server?.wsPort || config.DEFAULT_WS_PORT}`);
|
|
71
|
+
console.log(` server.autoStart: ${settings.server?.autoStart || false}`);
|
|
72
|
+
|
|
73
|
+
// Happy AI 配置
|
|
74
|
+
console.log('');
|
|
75
|
+
console.log(chalk.cyan('Happy AI:'));
|
|
76
|
+
console.log(` happy.workspaceDir: ${settings.happy?.workspaceDir || '(default)'}`);
|
|
77
|
+
console.log(` happy.permissionMode: ${settings.happy?.permissionMode || 'default'}`);
|
|
78
|
+
console.log(` happy.serverUrl: ${settings.happy?.serverUrl || '(default)'}`);
|
|
79
|
+
console.log(` happy.autoMonitor: ${settings.happy?.autoMonitor !== false}`);
|
|
80
|
+
console.log(` happy.debug: ${settings.happy?.debug || false}`);
|
|
81
|
+
|
|
82
|
+
// Claude Code 配置
|
|
83
|
+
console.log('');
|
|
84
|
+
console.log(chalk.cyan('Claude Code:'));
|
|
85
|
+
const claudeCode = settings.happy?.claudeCode || {};
|
|
86
|
+
console.log(` happy.claudeCode.provider: ${claudeCode.provider || 'anthropic'}`);
|
|
87
|
+
console.log(` happy.claudeCode.baseUrl: ${claudeCode.baseUrl || '(default)'}`);
|
|
88
|
+
console.log(` happy.claudeCode.model: ${claudeCode.model || '(default)'}`);
|
|
89
|
+
console.log(` happy.claudeCode.smallFastModel: ${claudeCode.smallFastModel || '(default)'}`);
|
|
90
|
+
console.log(` happy.claudeCode.timeoutMs: ${claudeCode.timeoutMs || 600000}`);
|
|
91
|
+
|
|
92
|
+
// 路径信息
|
|
93
|
+
console.log('');
|
|
94
|
+
console.log(chalk.cyan('Paths:'));
|
|
95
|
+
console.log(` Data directory: ${config.getDataDir()}`);
|
|
96
|
+
console.log(` Settings file: ${userSettings.getSettingsPath()}`);
|
|
97
|
+
console.log(` Default workspace: ${config.getDefaultWorkspaceDir()}`);
|
|
98
|
+
|
|
99
|
+
console.log('');
|
|
100
|
+
console.log(chalk.dim('To modify a setting:'));
|
|
101
|
+
console.log(chalk.dim(' deepseek-cowork config set <key> <value>'));
|
|
102
|
+
console.log('');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* 获取配置值
|
|
107
|
+
*/
|
|
108
|
+
function getConfigValue(userSettings, key) {
|
|
109
|
+
const value = userSettings.get(key);
|
|
110
|
+
|
|
111
|
+
if (value === undefined) {
|
|
112
|
+
console.log(chalk.yellow(`Key "${key}" is not set`));
|
|
113
|
+
} else if (typeof value === 'object') {
|
|
114
|
+
console.log(JSON.stringify(value, null, 2));
|
|
115
|
+
} else {
|
|
116
|
+
console.log(value);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* 设置配置值
|
|
122
|
+
*/
|
|
123
|
+
function setConfigValue(userSettings, key, value) {
|
|
124
|
+
// 解析值类型
|
|
125
|
+
let parsedValue = value;
|
|
126
|
+
|
|
127
|
+
if (value === 'true') {
|
|
128
|
+
parsedValue = true;
|
|
129
|
+
} else if (value === 'false') {
|
|
130
|
+
parsedValue = false;
|
|
131
|
+
} else if (value === 'null' || value === null) {
|
|
132
|
+
parsedValue = null;
|
|
133
|
+
} else if (!isNaN(value) && value !== '') {
|
|
134
|
+
// 尝试解析为数字
|
|
135
|
+
const num = parseFloat(value);
|
|
136
|
+
if (!isNaN(num)) {
|
|
137
|
+
parsedValue = num;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 保存设置
|
|
142
|
+
userSettings.set(key, parsedValue);
|
|
143
|
+
|
|
144
|
+
console.log(chalk.green('✓'), `Set ${chalk.cyan(key)} = ${chalk.white(JSON.stringify(parsedValue))}`);
|
|
145
|
+
|
|
146
|
+
// 某些设置可能需要重启服务
|
|
147
|
+
const needsRestart = ['server.httpPort', 'server.wsPort', 'happy.serverUrl'];
|
|
148
|
+
if (needsRestart.includes(key)) {
|
|
149
|
+
console.log(chalk.yellow('\nNote: This change will take effect after restarting the service'));
|
|
150
|
+
console.log(chalk.dim(' deepseek-cowork stop && deepseek-cowork start'));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export default configCommand;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* login 命令 - 登录账户
|
|
3
|
+
*
|
|
4
|
+
* 创建时间: 2026-01-20
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import ora from 'ora';
|
|
9
|
+
import { createInterface } from 'readline';
|
|
10
|
+
import { getConfig, getSecureSettings, getUserSettings } from '../index.mjs';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 登录命令
|
|
14
|
+
*/
|
|
15
|
+
export async function loginCommand(options) {
|
|
16
|
+
const spinner = ora();
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const config = await getConfig();
|
|
20
|
+
const secureSettings = await getSecureSettings();
|
|
21
|
+
const userSettings = await getUserSettings();
|
|
22
|
+
|
|
23
|
+
// 初始化设置
|
|
24
|
+
config.initializeDirectories();
|
|
25
|
+
userSettings.initialize();
|
|
26
|
+
await secureSettings.initialize();
|
|
27
|
+
|
|
28
|
+
// 检查是否已登录
|
|
29
|
+
if (secureSettings.hasSecret('happy.secret')) {
|
|
30
|
+
console.log(chalk.yellow('⚠ You are already logged in'));
|
|
31
|
+
console.log('');
|
|
32
|
+
console.log(chalk.dim('To switch accounts, logout first:'));
|
|
33
|
+
console.log(chalk.white(' deepseek-cowork logout'));
|
|
34
|
+
console.log('');
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let secret = options.secret;
|
|
39
|
+
|
|
40
|
+
// 如果没有通过参数提供 secret,提示用户输入
|
|
41
|
+
if (!secret) {
|
|
42
|
+
console.log('');
|
|
43
|
+
console.log(chalk.bold('DeepSeek Cowork Login'));
|
|
44
|
+
console.log(chalk.dim('─'.repeat(40)));
|
|
45
|
+
console.log('');
|
|
46
|
+
console.log('Please enter your Happy AI Secret.');
|
|
47
|
+
console.log(chalk.dim('You can find it at: https://deepseek-cowork.com/account'));
|
|
48
|
+
console.log('');
|
|
49
|
+
|
|
50
|
+
secret = await promptSecret('Secret: ');
|
|
51
|
+
console.log('');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!secret || secret.trim() === '') {
|
|
55
|
+
console.log(chalk.red('Error: Secret is required'));
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 动态导入验证模块
|
|
60
|
+
spinner.start('Validating secret...');
|
|
61
|
+
|
|
62
|
+
const { createRequire } = await import('module');
|
|
63
|
+
const require = createRequire(import.meta.url);
|
|
64
|
+
const SecretGenerator = require(config.getDataDir() + '/../../lib/happy-client/utils/SecretGenerator');
|
|
65
|
+
|
|
66
|
+
// 验证格式
|
|
67
|
+
const validation = SecretGenerator.validateSecretFormat(secret.trim());
|
|
68
|
+
|
|
69
|
+
if (!validation.valid) {
|
|
70
|
+
spinner.fail(`Invalid secret format: ${validation.error}`);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
spinner.text = 'Verifying with server...';
|
|
75
|
+
|
|
76
|
+
// 尝试获取 Token 验证有效性
|
|
77
|
+
try {
|
|
78
|
+
const Auth = require(config.getDataDir() + '/../../lib/happy-client/core/Auth');
|
|
79
|
+
const auth = new Auth();
|
|
80
|
+
const serverUrl = userSettings.get('happy.serverUrl') || 'https://api.deepseek-cowork.com';
|
|
81
|
+
const masterSecret = Buffer.from(validation.normalized, 'base64url');
|
|
82
|
+
|
|
83
|
+
const token = await auth.getToken(masterSecret, serverUrl);
|
|
84
|
+
|
|
85
|
+
if (!token) {
|
|
86
|
+
spinner.fail('Failed to verify secret with server');
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
spinner.text = 'Saving credentials...';
|
|
91
|
+
|
|
92
|
+
// 保存 secret
|
|
93
|
+
secureSettings.setSecret('happy.secret', validation.normalized);
|
|
94
|
+
|
|
95
|
+
// 同步到 ~/.happy/access.key
|
|
96
|
+
const path = await import('path');
|
|
97
|
+
const fs = await import('fs');
|
|
98
|
+
const os = await import('os');
|
|
99
|
+
|
|
100
|
+
const happyHomeDir = path.join(os.homedir(), '.happy');
|
|
101
|
+
const accessKeyPath = path.join(happyHomeDir, 'access.key');
|
|
102
|
+
|
|
103
|
+
if (!fs.existsSync(happyHomeDir)) {
|
|
104
|
+
fs.mkdirSync(happyHomeDir, { recursive: true });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const secretBytes = Buffer.from(validation.normalized, 'base64url');
|
|
108
|
+
const credentials = {
|
|
109
|
+
secret: secretBytes.toString('base64'),
|
|
110
|
+
token: token
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
fs.writeFileSync(accessKeyPath, JSON.stringify(credentials, null, 2), 'utf8');
|
|
114
|
+
|
|
115
|
+
spinner.succeed('Login successful!');
|
|
116
|
+
console.log('');
|
|
117
|
+
console.log(chalk.green('You are now logged in.'));
|
|
118
|
+
console.log('');
|
|
119
|
+
console.log(chalk.cyan('Start the service:'), chalk.white('deepseek-cowork start'));
|
|
120
|
+
console.log(chalk.cyan('Open web interface:'), chalk.white('deepseek-cowork open'));
|
|
121
|
+
console.log('');
|
|
122
|
+
|
|
123
|
+
} catch (error) {
|
|
124
|
+
spinner.fail(`Verification failed: ${error.message}`);
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
} catch (error) {
|
|
129
|
+
spinner.fail(`Login failed: ${error.message}`);
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* 提示用户输入(隐藏输入)
|
|
136
|
+
*/
|
|
137
|
+
async function promptSecret(prompt) {
|
|
138
|
+
return new Promise((resolve) => {
|
|
139
|
+
const rl = createInterface({
|
|
140
|
+
input: process.stdin,
|
|
141
|
+
output: process.stdout
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// 隐藏输入
|
|
145
|
+
process.stdout.write(prompt);
|
|
146
|
+
|
|
147
|
+
// 在 Windows 上可能不支持隐藏输入,使用普通输入
|
|
148
|
+
rl.question('', (answer) => {
|
|
149
|
+
rl.close();
|
|
150
|
+
resolve(answer);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export default loginCommand;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* logout 命令 - 登出账户
|
|
3
|
+
*
|
|
4
|
+
* 创建时间: 2026-01-20
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import ora from 'ora';
|
|
9
|
+
import { existsSync, unlinkSync } from 'fs';
|
|
10
|
+
import { join } from 'path';
|
|
11
|
+
import { homedir } from 'os';
|
|
12
|
+
import { getConfig, getSecureSettings, getUserSettings } from '../index.mjs';
|
|
13
|
+
import { readPidFile, isProcessRunning, killProcess, waitForProcessExit, removePidFile } from '../utils/process.mjs';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 登出命令
|
|
17
|
+
*/
|
|
18
|
+
export async function logoutCommand(options) {
|
|
19
|
+
const spinner = ora('Logging out...').start();
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const config = await getConfig();
|
|
23
|
+
const secureSettings = await getSecureSettings();
|
|
24
|
+
const userSettings = await getUserSettings();
|
|
25
|
+
|
|
26
|
+
// 初始化设置
|
|
27
|
+
userSettings.initialize();
|
|
28
|
+
await secureSettings.initialize();
|
|
29
|
+
|
|
30
|
+
// 检查是否已登录
|
|
31
|
+
if (!secureSettings.hasSecret('happy.secret')) {
|
|
32
|
+
spinner.info('You are not logged in');
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 检查服务是否运行,如果是则先停止
|
|
37
|
+
const pid = readPidFile(config.getPidFilePath());
|
|
38
|
+
if (pid && isProcessRunning(pid)) {
|
|
39
|
+
spinner.text = 'Stopping service...';
|
|
40
|
+
|
|
41
|
+
killProcess(pid, 'SIGTERM');
|
|
42
|
+
await waitForProcessExit(pid, 5000);
|
|
43
|
+
removePidFile(config.getPidFilePath());
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 删除 secret
|
|
47
|
+
spinner.text = 'Removing credentials...';
|
|
48
|
+
secureSettings.deleteSecret('happy.secret');
|
|
49
|
+
secureSettings.deleteSecret('claude.authToken');
|
|
50
|
+
|
|
51
|
+
// 删除 ~/.happy/access.key
|
|
52
|
+
const accessKeyPath = join(homedir(), '.happy', 'access.key');
|
|
53
|
+
if (existsSync(accessKeyPath)) {
|
|
54
|
+
unlinkSync(accessKeyPath);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
spinner.succeed('Logged out successfully');
|
|
58
|
+
console.log('');
|
|
59
|
+
console.log(chalk.dim('Your local data has been preserved.'));
|
|
60
|
+
console.log(chalk.dim('To login again: deepseek-cowork login'));
|
|
61
|
+
console.log('');
|
|
62
|
+
|
|
63
|
+
} catch (error) {
|
|
64
|
+
spinner.fail(`Logout failed: ${error.message}`);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export default logoutCommand;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* open 命令 - 打开 Web 界面
|
|
3
|
+
*
|
|
4
|
+
* 创建时间: 2026-01-20
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import ora from 'ora';
|
|
9
|
+
import open from 'open';
|
|
10
|
+
import { getConfig } from '../index.mjs';
|
|
11
|
+
import { readPidFile, isProcessRunning } from '../utils/process.mjs';
|
|
12
|
+
|
|
13
|
+
// 公域网站地址
|
|
14
|
+
const PUBLIC_URL = 'https://deepseek-cowork.com';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 打开 Web 界面命令
|
|
18
|
+
*/
|
|
19
|
+
export async function openCommand(options) {
|
|
20
|
+
try {
|
|
21
|
+
const config = await getConfig();
|
|
22
|
+
const port = config.DEFAULT_HTTP_PORT;
|
|
23
|
+
|
|
24
|
+
// 检查本地服务是否运行
|
|
25
|
+
const pid = readPidFile(config.getPidFilePath());
|
|
26
|
+
const isRunning = pid && isProcessRunning(pid);
|
|
27
|
+
|
|
28
|
+
let serviceAvailable = false;
|
|
29
|
+
|
|
30
|
+
if (isRunning) {
|
|
31
|
+
// 尝试连接本地服务
|
|
32
|
+
try {
|
|
33
|
+
const response = await fetch(`http://localhost:${port}/api/ping`, {
|
|
34
|
+
signal: AbortSignal.timeout(2000)
|
|
35
|
+
});
|
|
36
|
+
serviceAvailable = response.ok;
|
|
37
|
+
} catch (e) {
|
|
38
|
+
// 服务未响应
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!serviceAvailable) {
|
|
43
|
+
console.log(chalk.yellow('⚠ Local service is not running'));
|
|
44
|
+
console.log('');
|
|
45
|
+
|
|
46
|
+
if (!options.local) {
|
|
47
|
+
// 询问是否启动服务
|
|
48
|
+
console.log('The web interface requires the local service to function properly.');
|
|
49
|
+
console.log('');
|
|
50
|
+
console.log(chalk.cyan('Start the service first:'));
|
|
51
|
+
console.log(chalk.white(' deepseek-cowork start --daemon'));
|
|
52
|
+
console.log('');
|
|
53
|
+
console.log(chalk.dim('Or open the public website without local features:'));
|
|
54
|
+
console.log(chalk.dim(` ${PUBLIC_URL}`));
|
|
55
|
+
console.log('');
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 确定要打开的 URL
|
|
61
|
+
let url;
|
|
62
|
+
|
|
63
|
+
if (options.local) {
|
|
64
|
+
// 打开本地界面(如果部署了本地前端)
|
|
65
|
+
// 目前本地前端使用 Electron,这里打开的是公域网站连接本地服务
|
|
66
|
+
url = `${PUBLIC_URL}?local=true`;
|
|
67
|
+
console.log(chalk.dim('Opening local interface...'));
|
|
68
|
+
} else {
|
|
69
|
+
// 打开公域网站
|
|
70
|
+
url = PUBLIC_URL;
|
|
71
|
+
console.log(chalk.dim('Opening web interface...'));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 打开浏览器
|
|
75
|
+
await open(url);
|
|
76
|
+
|
|
77
|
+
console.log('');
|
|
78
|
+
console.log(chalk.green('✓'), `Opened ${chalk.cyan(url)}`);
|
|
79
|
+
|
|
80
|
+
if (serviceAvailable) {
|
|
81
|
+
console.log(chalk.dim(` Local service: http://localhost:${port}`));
|
|
82
|
+
}
|
|
83
|
+
console.log('');
|
|
84
|
+
|
|
85
|
+
} catch (error) {
|
|
86
|
+
console.error(chalk.red('Error:'), error.message);
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export default openCommand;
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* start 命令 - 启动本地服务
|
|
3
|
+
*
|
|
4
|
+
* 创建时间: 2026-01-20
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import ora from 'ora';
|
|
9
|
+
import { spawn } from 'child_process';
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
11
|
+
import { dirname, join } from 'path';
|
|
12
|
+
import { existsSync, writeFileSync, readFileSync } from 'fs';
|
|
13
|
+
import { getLocalService, getConfig } from '../index.mjs';
|
|
14
|
+
import { checkPort, writePidFile, readPidFile, isProcessRunning } from '../utils/process.mjs';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 启动服务命令
|
|
18
|
+
*/
|
|
19
|
+
export async function startCommand(options) {
|
|
20
|
+
const spinner = ora('Starting DeepSeek Cowork...').start();
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
// 获取配置
|
|
24
|
+
const config = await getConfig();
|
|
25
|
+
const httpPort = parseInt(options.port) || config.DEFAULT_HTTP_PORT;
|
|
26
|
+
const wsPort = parseInt(options.wsPort) || config.DEFAULT_WS_PORT;
|
|
27
|
+
const workDir = options.workDir || null;
|
|
28
|
+
const debug = options.debug || false;
|
|
29
|
+
|
|
30
|
+
// 检查是否已有服务运行
|
|
31
|
+
const existingPid = readPidFile(config.getPidFilePath());
|
|
32
|
+
if (existingPid && isProcessRunning(existingPid)) {
|
|
33
|
+
spinner.fail(`DeepSeek Cowork is already running (PID: ${existingPid})`);
|
|
34
|
+
console.log(chalk.yellow('\nUse `deepseek-cowork status` to check status'));
|
|
35
|
+
console.log(chalk.yellow('Use `deepseek-cowork stop` to stop the service'));
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 检查端口是否可用
|
|
40
|
+
const httpPortAvailable = await checkPort(httpPort);
|
|
41
|
+
if (!httpPortAvailable) {
|
|
42
|
+
spinner.fail(`Port ${httpPort} is already in use`);
|
|
43
|
+
console.log(chalk.yellow(`\nTry using a different port with --port <port>`));
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (options.daemon) {
|
|
48
|
+
// 后台模式运行
|
|
49
|
+
spinner.text = 'Starting in background mode...';
|
|
50
|
+
|
|
51
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
52
|
+
const __dirname = dirname(__filename);
|
|
53
|
+
const startScript = join(__dirname, 'start-daemon.mjs');
|
|
54
|
+
|
|
55
|
+
// 创建后台启动脚本(如果不存在)
|
|
56
|
+
if (!existsSync(startScript)) {
|
|
57
|
+
createDaemonScript(startScript);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 启动后台进程
|
|
61
|
+
const child = spawn(process.execPath, [
|
|
62
|
+
'--no-warnings',
|
|
63
|
+
startScript,
|
|
64
|
+
'--port', httpPort.toString(),
|
|
65
|
+
'--ws-port', wsPort.toString(),
|
|
66
|
+
...(workDir ? ['--work-dir', workDir] : []),
|
|
67
|
+
...(debug ? ['--debug'] : [])
|
|
68
|
+
], {
|
|
69
|
+
detached: true,
|
|
70
|
+
stdio: 'ignore',
|
|
71
|
+
env: { ...process.env, FORCE_COLOR: '1' }
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
child.unref();
|
|
75
|
+
|
|
76
|
+
// 保存 PID
|
|
77
|
+
writePidFile(config.getPidFilePath(), child.pid);
|
|
78
|
+
|
|
79
|
+
// 等待服务启动
|
|
80
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
81
|
+
|
|
82
|
+
// 检查服务是否启动成功
|
|
83
|
+
try {
|
|
84
|
+
const response = await fetch(`http://localhost:${httpPort}/api/ping`);
|
|
85
|
+
if (response.ok) {
|
|
86
|
+
spinner.succeed('DeepSeek Cowork started in background');
|
|
87
|
+
console.log('');
|
|
88
|
+
console.log(chalk.green(` PID: ${child.pid}`));
|
|
89
|
+
console.log(chalk.green(` HTTP: http://localhost:${httpPort}`));
|
|
90
|
+
console.log(chalk.green(` WebSocket: ws://localhost:${wsPort}`));
|
|
91
|
+
console.log('');
|
|
92
|
+
console.log(chalk.cyan(' Open web interface:'), chalk.white('deepseek-cowork open'));
|
|
93
|
+
console.log(chalk.cyan(' Check status: '), chalk.white('deepseek-cowork status'));
|
|
94
|
+
console.log(chalk.cyan(' Stop service: '), chalk.white('deepseek-cowork stop'));
|
|
95
|
+
} else {
|
|
96
|
+
throw new Error('Service not responding');
|
|
97
|
+
}
|
|
98
|
+
} catch (e) {
|
|
99
|
+
spinner.warn('Service started but may not be fully ready');
|
|
100
|
+
console.log(chalk.yellow('\nCheck status with: deepseek-cowork status'));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
} else {
|
|
104
|
+
// 前台模式运行
|
|
105
|
+
spinner.text = 'Initializing services...';
|
|
106
|
+
|
|
107
|
+
const localService = await getLocalService();
|
|
108
|
+
|
|
109
|
+
// 初始化服务
|
|
110
|
+
const initResult = await localService.initialize({
|
|
111
|
+
httpPort,
|
|
112
|
+
wsPort,
|
|
113
|
+
workDir,
|
|
114
|
+
debug
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
if (!initResult.success) {
|
|
118
|
+
spinner.fail(`Initialization failed: ${initResult.error}`);
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
spinner.text = 'Starting HTTP server...';
|
|
123
|
+
|
|
124
|
+
// 启动服务
|
|
125
|
+
const startResult = await localService.start();
|
|
126
|
+
|
|
127
|
+
if (!startResult.success) {
|
|
128
|
+
spinner.fail(`Failed to start: ${startResult.error}`);
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 保存 PID
|
|
133
|
+
writePidFile(config.getPidFilePath(), process.pid);
|
|
134
|
+
|
|
135
|
+
spinner.succeed('DeepSeek Cowork started');
|
|
136
|
+
console.log('');
|
|
137
|
+
console.log(chalk.green(` HTTP: http://localhost:${startResult.httpPort}`));
|
|
138
|
+
console.log(chalk.green(` WebSocket: ws://localhost:${startResult.wsPort}`));
|
|
139
|
+
console.log('');
|
|
140
|
+
console.log(chalk.dim(' Press Ctrl+C to stop'));
|
|
141
|
+
console.log('');
|
|
142
|
+
|
|
143
|
+
// 处理退出信号
|
|
144
|
+
const cleanup = async () => {
|
|
145
|
+
console.log('\n');
|
|
146
|
+
const stopSpinner = ora('Stopping service...').start();
|
|
147
|
+
await localService.stop();
|
|
148
|
+
|
|
149
|
+
// 删除 PID 文件
|
|
150
|
+
const fs = await import('fs');
|
|
151
|
+
try {
|
|
152
|
+
fs.unlinkSync(config.getPidFilePath());
|
|
153
|
+
} catch (e) {
|
|
154
|
+
// 忽略错误
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
stopSpinner.succeed('Service stopped');
|
|
158
|
+
process.exit(0);
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
process.on('SIGINT', cleanup);
|
|
162
|
+
process.on('SIGTERM', cleanup);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
} catch (error) {
|
|
166
|
+
spinner.fail(`Failed to start: ${error.message}`);
|
|
167
|
+
if (options.debug) {
|
|
168
|
+
console.error(error);
|
|
169
|
+
}
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* 创建后台启动脚本
|
|
176
|
+
*/
|
|
177
|
+
function createDaemonScript(scriptPath) {
|
|
178
|
+
const script = `#!/usr/bin/env node
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* DeepSeek Cowork Daemon 启动脚本
|
|
182
|
+
* 自动生成,请勿手动修改
|
|
183
|
+
*/
|
|
184
|
+
|
|
185
|
+
import { fileURLToPath } from 'url';
|
|
186
|
+
import { dirname, join } from 'path';
|
|
187
|
+
|
|
188
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
189
|
+
const __dirname = dirname(__filename);
|
|
190
|
+
|
|
191
|
+
// 解析命令行参数
|
|
192
|
+
const args = process.argv.slice(2);
|
|
193
|
+
const options = {};
|
|
194
|
+
|
|
195
|
+
for (let i = 0; i < args.length; i++) {
|
|
196
|
+
if (args[i] === '--port') {
|
|
197
|
+
options.httpPort = parseInt(args[++i]);
|
|
198
|
+
} else if (args[i] === '--ws-port') {
|
|
199
|
+
options.wsPort = parseInt(args[++i]);
|
|
200
|
+
} else if (args[i] === '--work-dir') {
|
|
201
|
+
options.workDir = args[++i];
|
|
202
|
+
} else if (args[i] === '--debug') {
|
|
203
|
+
options.debug = true;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// 导入并启动服务
|
|
208
|
+
const { getLocalService } = await import('../index.mjs');
|
|
209
|
+
const localService = await getLocalService();
|
|
210
|
+
|
|
211
|
+
async function main() {
|
|
212
|
+
try {
|
|
213
|
+
await localService.initialize(options);
|
|
214
|
+
await localService.start();
|
|
215
|
+
console.log('DeepSeek Cowork daemon started');
|
|
216
|
+
} catch (error) {
|
|
217
|
+
console.error('Failed to start daemon:', error.message);
|
|
218
|
+
process.exit(1);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
main();
|
|
223
|
+
`;
|
|
224
|
+
|
|
225
|
+
writeFileSync(scriptPath, script, 'utf8');
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export default startCommand;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* status 命令 - 显示服务状态
|
|
3
|
+
*
|
|
4
|
+
* 创建时间: 2026-01-20
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import { getConfig } from '../index.mjs';
|
|
9
|
+
import { readPidFile, isProcessRunning } from '../utils/process.mjs';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 状态命令
|
|
13
|
+
*/
|
|
14
|
+
export async function statusCommand(options) {
|
|
15
|
+
try {
|
|
16
|
+
const config = await getConfig();
|
|
17
|
+
const pidPath = config.getPidFilePath();
|
|
18
|
+
const pid = readPidFile(pidPath);
|
|
19
|
+
const port = config.DEFAULT_HTTP_PORT;
|
|
20
|
+
|
|
21
|
+
// 基础状态
|
|
22
|
+
const status = {
|
|
23
|
+
pid: pid,
|
|
24
|
+
running: false,
|
|
25
|
+
httpPort: port,
|
|
26
|
+
wsPort: config.DEFAULT_WS_PORT,
|
|
27
|
+
dataDir: config.getDataDir(),
|
|
28
|
+
service: null
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// 检查进程是否运行
|
|
32
|
+
if (pid && isProcessRunning(pid)) {
|
|
33
|
+
status.running = true;
|
|
34
|
+
|
|
35
|
+
// 尝试获取详细状态
|
|
36
|
+
try {
|
|
37
|
+
const response = await fetch(`http://localhost:${port}/api/status`, {
|
|
38
|
+
signal: AbortSignal.timeout(3000)
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if (response.ok) {
|
|
42
|
+
const data = await response.json();
|
|
43
|
+
status.service = data.status;
|
|
44
|
+
}
|
|
45
|
+
} catch (e) {
|
|
46
|
+
// 服务可能未完全启动或未响应
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// JSON 输出
|
|
51
|
+
if (options.json) {
|
|
52
|
+
console.log(JSON.stringify(status, null, 2));
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 人类可读输出
|
|
57
|
+
console.log('');
|
|
58
|
+
console.log(chalk.bold('DeepSeek Cowork Status'));
|
|
59
|
+
console.log(chalk.dim('─'.repeat(40)));
|
|
60
|
+
|
|
61
|
+
if (status.running) {
|
|
62
|
+
console.log(chalk.green('● Service: Running'));
|
|
63
|
+
console.log(chalk.white(` PID: ${status.pid}`));
|
|
64
|
+
console.log(chalk.white(` HTTP: http://localhost:${status.httpPort}`));
|
|
65
|
+
console.log(chalk.white(` WebSocket: ws://localhost:${status.wsPort}`));
|
|
66
|
+
|
|
67
|
+
if (status.service) {
|
|
68
|
+
console.log('');
|
|
69
|
+
console.log(chalk.dim('Components:'));
|
|
70
|
+
|
|
71
|
+
// HappyService 状态
|
|
72
|
+
if (status.service.happy) {
|
|
73
|
+
const happy = status.service.happy;
|
|
74
|
+
const connStatus = happy.clientConnected ? chalk.green('Connected') : chalk.yellow('Disconnected');
|
|
75
|
+
console.log(` Happy AI: ${connStatus}`);
|
|
76
|
+
|
|
77
|
+
if (happy.eventStatus) {
|
|
78
|
+
console.log(` Event Status: ${happy.eventStatus}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Daemon 状态
|
|
83
|
+
if (status.service.happy?.daemon) {
|
|
84
|
+
const daemon = status.service.happy.daemon;
|
|
85
|
+
const daemonStatus = daemon.running ? chalk.green('Running') : chalk.red('Stopped');
|
|
86
|
+
console.log(` Daemon: ${daemonStatus}`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// BrowserControl 状态
|
|
90
|
+
if (status.service.browserControl) {
|
|
91
|
+
const bcStatus = status.service.browserControl === 'running' ? chalk.green('Running') : chalk.red('Stopped');
|
|
92
|
+
console.log(` Browser: ${bcStatus}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Explorer 状态
|
|
96
|
+
if (status.service.explorer) {
|
|
97
|
+
const expStatus = status.service.explorer === 'running' ? chalk.green('Running') : chalk.red('Stopped');
|
|
98
|
+
console.log(` Explorer: ${expStatus}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
|
+
console.log(chalk.red('○ Service: Not running'));
|
|
103
|
+
|
|
104
|
+
if (pid) {
|
|
105
|
+
console.log(chalk.dim(` (Stale PID file found: ${pid})`));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
console.log('');
|
|
110
|
+
console.log(chalk.dim('Data Directory:'));
|
|
111
|
+
console.log(` ${status.dataDir}`);
|
|
112
|
+
console.log('');
|
|
113
|
+
|
|
114
|
+
// 提示命令
|
|
115
|
+
if (!status.running) {
|
|
116
|
+
console.log(chalk.cyan('Start service:'), chalk.white('deepseek-cowork start'));
|
|
117
|
+
} else {
|
|
118
|
+
console.log(chalk.cyan('Stop service: '), chalk.white('deepseek-cowork stop'));
|
|
119
|
+
console.log(chalk.cyan('Open web UI: '), chalk.white('deepseek-cowork open'));
|
|
120
|
+
}
|
|
121
|
+
console.log('');
|
|
122
|
+
|
|
123
|
+
} catch (error) {
|
|
124
|
+
console.error(chalk.red('Error:'), error.message);
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export default statusCommand;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* stop 命令 - 停止本地服务
|
|
3
|
+
*
|
|
4
|
+
* 创建时间: 2026-01-20
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import ora from 'ora';
|
|
9
|
+
import { getConfig } from '../index.mjs';
|
|
10
|
+
import { readPidFile, removePidFile, isProcessRunning, killProcess, waitForProcessExit } from '../utils/process.mjs';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 停止服务命令
|
|
14
|
+
*/
|
|
15
|
+
export async function stopCommand(options) {
|
|
16
|
+
const spinner = ora('Stopping DeepSeek Cowork...').start();
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const config = await getConfig();
|
|
20
|
+
const pidPath = config.getPidFilePath();
|
|
21
|
+
const pid = readPidFile(pidPath);
|
|
22
|
+
|
|
23
|
+
if (!pid) {
|
|
24
|
+
spinner.info('No running service found');
|
|
25
|
+
removePidFile(pidPath);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!isProcessRunning(pid)) {
|
|
30
|
+
spinner.info('Service is not running (stale PID file removed)');
|
|
31
|
+
removePidFile(pidPath);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 首先尝试通过 API 优雅关闭
|
|
36
|
+
try {
|
|
37
|
+
spinner.text = 'Requesting graceful shutdown...';
|
|
38
|
+
|
|
39
|
+
// 获取端口配置
|
|
40
|
+
const port = config.DEFAULT_HTTP_PORT;
|
|
41
|
+
|
|
42
|
+
const response = await fetch(`http://localhost:${port}/api/status`, {
|
|
43
|
+
method: 'GET',
|
|
44
|
+
signal: AbortSignal.timeout(3000)
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
if (response.ok) {
|
|
48
|
+
// 服务正在运行,尝试优雅关闭
|
|
49
|
+
// 注意:实际的停止 API 需要在服务端实现
|
|
50
|
+
// 这里先直接发送 SIGTERM
|
|
51
|
+
}
|
|
52
|
+
} catch (e) {
|
|
53
|
+
// 服务可能已经不响应,直接终止进程
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 发送 SIGTERM 信号
|
|
57
|
+
spinner.text = 'Sending stop signal...';
|
|
58
|
+
killProcess(pid, 'SIGTERM');
|
|
59
|
+
|
|
60
|
+
// 等待进程退出
|
|
61
|
+
spinner.text = 'Waiting for service to stop...';
|
|
62
|
+
const stopped = await waitForProcessExit(pid, 10000);
|
|
63
|
+
|
|
64
|
+
if (stopped) {
|
|
65
|
+
removePidFile(pidPath);
|
|
66
|
+
spinner.succeed('DeepSeek Cowork stopped');
|
|
67
|
+
} else {
|
|
68
|
+
// 强制终止
|
|
69
|
+
spinner.text = 'Force stopping...';
|
|
70
|
+
killProcess(pid, 'SIGKILL');
|
|
71
|
+
|
|
72
|
+
const forceStopped = await waitForProcessExit(pid, 3000);
|
|
73
|
+
|
|
74
|
+
if (forceStopped) {
|
|
75
|
+
removePidFile(pidPath);
|
|
76
|
+
spinner.succeed('DeepSeek Cowork force stopped');
|
|
77
|
+
} else {
|
|
78
|
+
spinner.fail('Failed to stop service');
|
|
79
|
+
console.log(chalk.yellow(`\nProcess ${pid} may still be running`));
|
|
80
|
+
console.log(chalk.yellow('You may need to kill it manually'));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
} catch (error) {
|
|
85
|
+
spinner.fail(`Failed to stop: ${error.message}`);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export default stopCommand;
|
package/index.mjs
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DeepSeek Cowork CLI 模块入口
|
|
3
|
+
*
|
|
4
|
+
* 用于程序化调用 CLI 功能
|
|
5
|
+
*
|
|
6
|
+
* 创建时间: 2026-01-20
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
import { dirname, join } from 'path';
|
|
11
|
+
|
|
12
|
+
// 获取项目根目录
|
|
13
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
+
const __dirname = dirname(__filename);
|
|
15
|
+
|
|
16
|
+
// 项目根目录(packages/cli 的父目录的父目录)
|
|
17
|
+
export const PROJECT_ROOT = join(__dirname, '../..');
|
|
18
|
+
|
|
19
|
+
// 导出服务模块路径
|
|
20
|
+
export const LOCAL_SERVICE_PATH = join(PROJECT_ROOT, 'lib/local-service');
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 动态导入 local-service 模块
|
|
24
|
+
* 由于 local-service 是 CommonJS 模块,需要动态导入
|
|
25
|
+
*/
|
|
26
|
+
export async function getLocalService() {
|
|
27
|
+
const { createRequire } = await import('module');
|
|
28
|
+
const require = createRequire(import.meta.url);
|
|
29
|
+
return require(LOCAL_SERVICE_PATH);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 获取配置模块
|
|
34
|
+
*/
|
|
35
|
+
export async function getConfig() {
|
|
36
|
+
const { createRequire } = await import('module');
|
|
37
|
+
const require = createRequire(import.meta.url);
|
|
38
|
+
return require(join(LOCAL_SERVICE_PATH, 'config'));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 获取用户设置模块
|
|
43
|
+
*/
|
|
44
|
+
export async function getUserSettings() {
|
|
45
|
+
const { createRequire } = await import('module');
|
|
46
|
+
const require = createRequire(import.meta.url);
|
|
47
|
+
return require(join(LOCAL_SERVICE_PATH, 'user-settings-cli'));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 获取安全设置模块
|
|
52
|
+
*/
|
|
53
|
+
export async function getSecureSettings() {
|
|
54
|
+
const { createRequire } = await import('module');
|
|
55
|
+
const require = createRequire(import.meta.url);
|
|
56
|
+
return require(join(LOCAL_SERVICE_PATH, 'secure-settings-cli'));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export default {
|
|
60
|
+
PROJECT_ROOT,
|
|
61
|
+
LOCAL_SERVICE_PATH,
|
|
62
|
+
getLocalService,
|
|
63
|
+
getConfig,
|
|
64
|
+
getUserSettings,
|
|
65
|
+
getSecureSettings
|
|
66
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "deepseek-cowork",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Open-Source Alternative to Claude Cowork - CLI Tool",
|
|
5
|
+
"author": "imjszhang",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"bin": {
|
|
9
|
+
"deepseek-cowork": "bin/cli.mjs",
|
|
10
|
+
"dsc": "bin/cli.mjs"
|
|
11
|
+
},
|
|
12
|
+
"main": "./index.mjs",
|
|
13
|
+
"files": [
|
|
14
|
+
"bin",
|
|
15
|
+
"commands",
|
|
16
|
+
"index.mjs",
|
|
17
|
+
"utils"
|
|
18
|
+
],
|
|
19
|
+
"keywords": [
|
|
20
|
+
"deepseek",
|
|
21
|
+
"claude-cowork",
|
|
22
|
+
"open-source",
|
|
23
|
+
"ai-assistant",
|
|
24
|
+
"browser-automation",
|
|
25
|
+
"file-management",
|
|
26
|
+
"llm",
|
|
27
|
+
"claude-code",
|
|
28
|
+
"cli"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"start": "node bin/cli.mjs start",
|
|
32
|
+
"dev": "node bin/cli.mjs start --debug",
|
|
33
|
+
"link": "npm link",
|
|
34
|
+
"prepublishOnly": "echo 准备发布...",
|
|
35
|
+
"version:patch": "npm version patch",
|
|
36
|
+
"version:minor": "npm version minor",
|
|
37
|
+
"version:major": "npm version major"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"chalk": "^5.3.0",
|
|
41
|
+
"commander": "^12.1.0",
|
|
42
|
+
"open": "^10.1.0",
|
|
43
|
+
"ora": "^8.0.1"
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=18.0.0"
|
|
47
|
+
},
|
|
48
|
+
"repository": {
|
|
49
|
+
"type": "git",
|
|
50
|
+
"url": "git+https://github.com/imjszhang/deepseek-cowork.git"
|
|
51
|
+
},
|
|
52
|
+
"homepage": "https://deepseek-cowork.com",
|
|
53
|
+
"bugs": {
|
|
54
|
+
"url": "https://github.com/imjszhang/deepseek-cowork/issues"
|
|
55
|
+
},
|
|
56
|
+
"publishConfig": {
|
|
57
|
+
"registry": "https://registry.npmjs.org",
|
|
58
|
+
"access": "public"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 进程管理工具
|
|
3
|
+
*
|
|
4
|
+
* 创建时间: 2026-01-20
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { existsSync, readFileSync, writeFileSync, unlinkSync } from 'fs';
|
|
8
|
+
import { dirname } from 'path';
|
|
9
|
+
import { mkdirSync } from 'fs';
|
|
10
|
+
import net from 'net';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 检查端口是否可用
|
|
14
|
+
* @param {number} port 端口号
|
|
15
|
+
* @returns {Promise<boolean>} 是否可用
|
|
16
|
+
*/
|
|
17
|
+
export async function checkPort(port) {
|
|
18
|
+
return new Promise((resolve) => {
|
|
19
|
+
const server = net.createServer();
|
|
20
|
+
|
|
21
|
+
server.once('error', (err) => {
|
|
22
|
+
if (err.code === 'EADDRINUSE') {
|
|
23
|
+
resolve(false);
|
|
24
|
+
} else {
|
|
25
|
+
resolve(true);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
server.once('listening', () => {
|
|
30
|
+
server.close();
|
|
31
|
+
resolve(true);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
server.listen(port, 'localhost');
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 写入 PID 文件
|
|
40
|
+
* @param {string} pidPath PID 文件路径
|
|
41
|
+
* @param {number} pid 进程 ID
|
|
42
|
+
*/
|
|
43
|
+
export function writePidFile(pidPath, pid) {
|
|
44
|
+
try {
|
|
45
|
+
const dir = dirname(pidPath);
|
|
46
|
+
if (!existsSync(dir)) {
|
|
47
|
+
mkdirSync(dir, { recursive: true });
|
|
48
|
+
}
|
|
49
|
+
writeFileSync(pidPath, pid.toString(), 'utf8');
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error('Failed to write PID file:', error.message);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 读取 PID 文件
|
|
57
|
+
* @param {string} pidPath PID 文件路径
|
|
58
|
+
* @returns {number|null} 进程 ID 或 null
|
|
59
|
+
*/
|
|
60
|
+
export function readPidFile(pidPath) {
|
|
61
|
+
try {
|
|
62
|
+
if (existsSync(pidPath)) {
|
|
63
|
+
const content = readFileSync(pidPath, 'utf8').trim();
|
|
64
|
+
const pid = parseInt(content);
|
|
65
|
+
return isNaN(pid) ? null : pid;
|
|
66
|
+
}
|
|
67
|
+
} catch (error) {
|
|
68
|
+
// 忽略读取错误
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* 删除 PID 文件
|
|
75
|
+
* @param {string} pidPath PID 文件路径
|
|
76
|
+
*/
|
|
77
|
+
export function removePidFile(pidPath) {
|
|
78
|
+
try {
|
|
79
|
+
if (existsSync(pidPath)) {
|
|
80
|
+
unlinkSync(pidPath);
|
|
81
|
+
}
|
|
82
|
+
} catch (error) {
|
|
83
|
+
// 忽略删除错误
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* 检查进程是否运行
|
|
89
|
+
* @param {number} pid 进程 ID
|
|
90
|
+
* @returns {boolean} 是否运行
|
|
91
|
+
*/
|
|
92
|
+
export function isProcessRunning(pid) {
|
|
93
|
+
if (!pid) return false;
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
// 发送信号 0 检查进程是否存在
|
|
97
|
+
process.kill(pid, 0);
|
|
98
|
+
return true;
|
|
99
|
+
} catch (error) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* 终止进程
|
|
106
|
+
* @param {number} pid 进程 ID
|
|
107
|
+
* @param {string} signal 信号(默认 SIGTERM)
|
|
108
|
+
* @returns {boolean} 是否成功
|
|
109
|
+
*/
|
|
110
|
+
export function killProcess(pid, signal = 'SIGTERM') {
|
|
111
|
+
if (!pid) return false;
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
process.kill(pid, signal);
|
|
115
|
+
return true;
|
|
116
|
+
} catch (error) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* 等待进程退出
|
|
123
|
+
* @param {number} pid 进程 ID
|
|
124
|
+
* @param {number} timeout 超时时间(毫秒)
|
|
125
|
+
* @returns {Promise<boolean>} 是否退出
|
|
126
|
+
*/
|
|
127
|
+
export async function waitForProcessExit(pid, timeout = 5000) {
|
|
128
|
+
const startTime = Date.now();
|
|
129
|
+
|
|
130
|
+
while (Date.now() - startTime < timeout) {
|
|
131
|
+
if (!isProcessRunning(pid)) {
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return !isProcessRunning(pid);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export default {
|
|
141
|
+
checkPort,
|
|
142
|
+
writePidFile,
|
|
143
|
+
readPidFile,
|
|
144
|
+
removePidFile,
|
|
145
|
+
isProcessRunning,
|
|
146
|
+
killProcess,
|
|
147
|
+
waitForProcessExit
|
|
148
|
+
};
|