@sokeai/cli 1.0.11 → 1.0.14
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/package.json +1 -1
- package/scripts/install.js +106 -0
- package/scripts/run.js +90 -2
package/package.json
CHANGED
package/scripts/install.js
CHANGED
|
@@ -3,6 +3,7 @@ const path = require('path');
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const https = require('https');
|
|
5
5
|
const { execSync } = require('child_process');
|
|
6
|
+
const readline = require('readline');
|
|
6
7
|
|
|
7
8
|
const platform = os.platform();
|
|
8
9
|
const arch = os.arch();
|
|
@@ -188,6 +189,108 @@ function syncSkillsToWorkclawRegistry() {
|
|
|
188
189
|
} catch (_) {}
|
|
189
190
|
}
|
|
190
191
|
|
|
192
|
+
function isLikelyInteractiveInstall() {
|
|
193
|
+
if (!process.stdin.isTTY) return false;
|
|
194
|
+
if (!process.stdout.isTTY) return false;
|
|
195
|
+
if (process.env.CI) return false;
|
|
196
|
+
if (process.env.npm_config_yes === 'true') return false;
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function promptYesNo(message) {
|
|
201
|
+
return new Promise((resolve) => {
|
|
202
|
+
const rl = readline.createInterface({
|
|
203
|
+
input: process.stdin,
|
|
204
|
+
output: process.stdout
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
rl.question(message, (answer) => {
|
|
208
|
+
rl.close();
|
|
209
|
+
const normalized = String(answer || '').trim().toLowerCase();
|
|
210
|
+
resolve(normalized === 'y' || normalized === 'yes');
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function detectNpmGlobalBinDir() {
|
|
216
|
+
const prefix = process.env.npm_config_prefix;
|
|
217
|
+
if (typeof prefix === 'string' && prefix.length > 0) {
|
|
218
|
+
const binDir = platform === 'win32' ? prefix : path.join(prefix, 'bin');
|
|
219
|
+
return binDir;
|
|
220
|
+
}
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function detectSokeCliShimPath() {
|
|
225
|
+
const binDir = detectNpmGlobalBinDir();
|
|
226
|
+
if (!binDir) return null;
|
|
227
|
+
|
|
228
|
+
const candidates =
|
|
229
|
+
platform === 'win32'
|
|
230
|
+
? [path.join(binDir, 'soke-cli.cmd'), path.join(binDir, 'soke-cli.exe')]
|
|
231
|
+
: [path.join(binDir, 'soke-cli')];
|
|
232
|
+
|
|
233
|
+
for (const candidate of candidates) {
|
|
234
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
235
|
+
}
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function getPreferredLinkTargetPaths() {
|
|
240
|
+
if (platform === 'win32') return [];
|
|
241
|
+
return ['/usr/local/bin/soke-cli', '/opt/homebrew/bin/soke-cli'];
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function ensureSymlink(targetPath, sourcePath) {
|
|
245
|
+
try {
|
|
246
|
+
const existing = fs.lstatSync(targetPath);
|
|
247
|
+
if (existing.isSymbolicLink()) {
|
|
248
|
+
const currentTarget = fs.readlinkSync(targetPath);
|
|
249
|
+
if (currentTarget === sourcePath) return { ok: true, changed: false };
|
|
250
|
+
fs.unlinkSync(targetPath);
|
|
251
|
+
} else {
|
|
252
|
+
return { ok: false, changed: false, reason: 'exists_non_symlink' };
|
|
253
|
+
}
|
|
254
|
+
} catch (_) {}
|
|
255
|
+
|
|
256
|
+
try {
|
|
257
|
+
fs.symlinkSync(sourcePath, targetPath);
|
|
258
|
+
return { ok: true, changed: true };
|
|
259
|
+
} catch (err) {
|
|
260
|
+
return { ok: false, changed: false, reason: err && err.message ? err.message : 'failed' };
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async function maybeAssistGuiPath() {
|
|
265
|
+
const platform = os.platform();
|
|
266
|
+
const binDir = path.join(__dirname, '..', 'bin');
|
|
267
|
+
const binaryName = platform === 'win32' ? 'soke-cli.exe' : 'soke-cli';
|
|
268
|
+
const binaryPath = path.join(binDir, binaryName);
|
|
269
|
+
|
|
270
|
+
if (!fs.existsSync(binaryPath)) return Promise.resolve();
|
|
271
|
+
|
|
272
|
+
// 我们不再弹窗询问,而是直接以静默模式尝试执行 setup-gui-env
|
|
273
|
+
// 这会尝试创建软链,如果没权限,它会默默失败(不会报错打断安装)
|
|
274
|
+
// 用户如果后面遇到 command not found,仍可以手动执行 soke-cli setup-gui-env
|
|
275
|
+
return new Promise((resolve) => {
|
|
276
|
+
try {
|
|
277
|
+
const runJsPath = path.join(__dirname, 'run.js');
|
|
278
|
+
const { spawn } = require('child_process');
|
|
279
|
+
const child = spawn(process.execPath, [runJsPath, 'setup-gui-env', '--silent'], {
|
|
280
|
+
stdio: 'ignore', // 静默执行,不污染 npm install 输出
|
|
281
|
+
detached: true
|
|
282
|
+
});
|
|
283
|
+
child.on('error', () => resolve());
|
|
284
|
+
child.on('exit', () => resolve());
|
|
285
|
+
|
|
286
|
+
// 避免卡住
|
|
287
|
+
setTimeout(() => resolve(), 3000);
|
|
288
|
+
} catch (e) {
|
|
289
|
+
resolve();
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
191
294
|
// 平台映射
|
|
192
295
|
const platformMap = {
|
|
193
296
|
'darwin': 'darwin',
|
|
@@ -297,6 +400,9 @@ downloadFile(downloadURL, binaryPath)
|
|
|
297
400
|
syncSkillsToWorkclawRegistry();
|
|
298
401
|
} catch (_) {}
|
|
299
402
|
|
|
403
|
+
return maybeAssistGuiPath();
|
|
404
|
+
})
|
|
405
|
+
.then(() => {
|
|
300
406
|
console.log('soke-cli 安装成功!');
|
|
301
407
|
console.log(`二进制文件位置: ${binaryPath}`);
|
|
302
408
|
console.log('\n使用方法:');
|
package/scripts/run.js
CHANGED
|
@@ -1,9 +1,97 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
const { spawn } = require('child_process');
|
|
3
|
+
const { spawn, execSync } = require('child_process');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
|
|
7
|
+
// 特殊命令拦截:修复 sokeclaw 的 GUI PATH 问题
|
|
8
|
+
if (process.argv[2] === 'setup-gui-env') {
|
|
9
|
+
console.log('正在为 SokeClaw GUI 注入环境变量 PATH...');
|
|
10
|
+
try {
|
|
11
|
+
// 获取 node 和当前脚本的绝对路径目录,这通常也是 soke-cli 所在的 bin 目录
|
|
12
|
+
const nodeDir = path.dirname(process.execPath);
|
|
13
|
+
const cliDir = path.dirname(process.argv[1]);
|
|
14
|
+
|
|
15
|
+
let targetPath = '';
|
|
16
|
+
if (process.platform === 'darwin' || process.platform === 'linux') {
|
|
17
|
+
const binSource = path.join(nodeDir, 'soke-cli');
|
|
18
|
+
const binTarget = '/usr/local/bin/soke-cli';
|
|
19
|
+
|
|
20
|
+
if (fs.existsSync(binSource)) {
|
|
21
|
+
try {
|
|
22
|
+
// 尝试创建软链
|
|
23
|
+
if (fs.existsSync(binTarget)) {
|
|
24
|
+
try { fs.unlinkSync(binTarget); } catch(e) {}
|
|
25
|
+
}
|
|
26
|
+
fs.symlinkSync(binSource, binTarget);
|
|
27
|
+
console.log(`✅ 成功创建软链: ${binTarget} -> ${binSource}`);
|
|
28
|
+
} catch (linkError) {
|
|
29
|
+
console.log(`\n⚠️ 权限不足,无法自动创建 /usr/local/bin 软链。`);
|
|
30
|
+
console.log(`请手动在终端执行以下命令(可能需要输入密码):`);
|
|
31
|
+
console.log(`\n sudo ln -sf "${binSource}" "${binTarget}"\n`);
|
|
32
|
+
|
|
33
|
+
if (process.argv[3] !== '--silent') {
|
|
34
|
+
console.log(`执行上述命令后,再重新打开客户端即可。`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
} else {
|
|
38
|
+
console.log(`⚠️ 未找到源文件: ${binSource}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (process.argv[3] === '--silent') {
|
|
42
|
+
process.exit(0);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
console.log('尝试重启 SokeClaw / WorkClaw...');
|
|
46
|
+
try { execSync('killall SokeClaw 2>/dev/null'); } catch(e) {}
|
|
47
|
+
try { execSync('killall WorkClaw 2>/dev/null'); } catch(e) {}
|
|
48
|
+
try { execSync('killall Electron 2>/dev/null'); } catch(e) {}
|
|
49
|
+
|
|
50
|
+
// 等待进程退出
|
|
51
|
+
setTimeout(() => {
|
|
52
|
+
let launched = false;
|
|
53
|
+
if (process.platform === 'darwin') {
|
|
54
|
+
if (fs.existsSync('/Applications/SokeClaw.app')) {
|
|
55
|
+
execSync('open "/Applications/SokeClaw.app"');
|
|
56
|
+
launched = true;
|
|
57
|
+
} else if (fs.existsSync('/Applications/WorkClaw.app')) {
|
|
58
|
+
execSync('open "/Applications/WorkClaw.app"');
|
|
59
|
+
launched = true;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (launched) {
|
|
64
|
+
console.log('\n✅ 修复完成!SokeClaw 已重启。现在你应该可以在里面使用 /skill soke-exam 了。');
|
|
65
|
+
} else {
|
|
66
|
+
console.log('\n✅ 环境变量已注入,但未自动重启应用。请手动从“启动台(Launchpad)”或“访达(Finder)”重新打开它。');
|
|
67
|
+
}
|
|
68
|
+
process.exit(0);
|
|
69
|
+
}, 1500);
|
|
70
|
+
return; // 异步等待中
|
|
71
|
+
} else if (process.platform === 'win32') {
|
|
72
|
+
console.log('检测到当前为 Windows 系统。');
|
|
73
|
+
|
|
74
|
+
if (process.argv[3] === '--silent') {
|
|
75
|
+
process.exit(0);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
console.log('正在尝试重启 SokeClaw / WorkClaw...');
|
|
79
|
+
try { execSync('taskkill /F /IM SokeClaw.exe 2>nul'); } catch(e) {}
|
|
80
|
+
try { execSync('taskkill /F /IM WorkClaw.exe 2>nul'); } catch(e) {}
|
|
81
|
+
try { execSync('taskkill /F /IM Electron.exe 2>nul'); } catch(e) {}
|
|
82
|
+
|
|
83
|
+
console.log('\n✅ Windows 环境下的全局包通常已在 PATH 中。客户端已被关闭,请手动重新打开它即可生效。');
|
|
84
|
+
process.exit(0);
|
|
85
|
+
} else {
|
|
86
|
+
console.log('\n⚠️ 目前 setup-gui-env 主要支持 macOS/Linux/Windows。');
|
|
87
|
+
process.exit(0);
|
|
88
|
+
}
|
|
89
|
+
} catch (error) {
|
|
90
|
+
console.error('修复过程中出现错误:', error.message);
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
7
95
|
// 获取二进制文件路径
|
|
8
96
|
const platform = process.platform;
|
|
9
97
|
const binaryName = platform === 'win32' ? 'soke-cli.exe' : 'soke-cli';
|
|
@@ -50,4 +138,4 @@ child.on('exit', (code, signal) => {
|
|
|
50
138
|
child.on('error', (err) => {
|
|
51
139
|
console.error('执行 soke-cli 时出错:', err.message);
|
|
52
140
|
process.exit(1);
|
|
53
|
-
});
|
|
141
|
+
});
|