@sokeai/cli 1.0.11 → 1.0.13
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 +125 -0
- package/scripts/run.js +81 -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,127 @@ 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
|
+
if (!isLikelyInteractiveInstall()) return;
|
|
266
|
+
|
|
267
|
+
const shimPath = detectSokeCliShimPath();
|
|
268
|
+
if (!shimPath) return;
|
|
269
|
+
|
|
270
|
+
const targets = getPreferredLinkTargetPaths();
|
|
271
|
+
if (targets.length === 0) return;
|
|
272
|
+
|
|
273
|
+
let selectedTarget = null;
|
|
274
|
+
for (const t of targets) {
|
|
275
|
+
const dir = path.dirname(t);
|
|
276
|
+
if (fs.existsSync(dir)) {
|
|
277
|
+
selectedTarget = t;
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if (!selectedTarget) selectedTarget = targets[0];
|
|
282
|
+
|
|
283
|
+
const dir = path.dirname(selectedTarget);
|
|
284
|
+
let dirWritable = false;
|
|
285
|
+
try {
|
|
286
|
+
fs.accessSync(dir, fs.constants.W_OK);
|
|
287
|
+
dirWritable = true;
|
|
288
|
+
} catch (_) {}
|
|
289
|
+
|
|
290
|
+
const question = dirWritable
|
|
291
|
+
? `检测到你可能在 sokeclaw(GUI)里遇到 “command not found: soke-cli”。是否创建链接 ${selectedTarget} 指向 ${shimPath} 以便 GUI 可直接找到?(y/N) `
|
|
292
|
+
: `检测到你可能在 sokeclaw(GUI)里遇到 “command not found: soke-cli”。是否输出一条需要 sudo 的命令来创建链接 ${selectedTarget} 指向 ${shimPath}?(y/N) `;
|
|
293
|
+
|
|
294
|
+
const ok = await promptYesNo(question);
|
|
295
|
+
if (!ok) return;
|
|
296
|
+
|
|
297
|
+
if (dirWritable) {
|
|
298
|
+
const res = ensureSymlink(selectedTarget, shimPath);
|
|
299
|
+
if (res.ok) {
|
|
300
|
+
console.log(`已配置:${selectedTarget} -> ${shimPath}`);
|
|
301
|
+
} else if (res.reason === 'exists_non_symlink') {
|
|
302
|
+
console.log(`跳过:${selectedTarget} 已存在且不是软链接。`);
|
|
303
|
+
} else {
|
|
304
|
+
console.log(`创建软链接失败:${res.reason}`);
|
|
305
|
+
}
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
console.log(`请执行以下命令完成配置(需要 sudo):`);
|
|
310
|
+
console.log(`sudo ln -sf "${shimPath}" "${selectedTarget}"`);
|
|
311
|
+
}
|
|
312
|
+
|
|
191
313
|
// 平台映射
|
|
192
314
|
const platformMap = {
|
|
193
315
|
'darwin': 'darwin',
|
|
@@ -297,6 +419,9 @@ downloadFile(downloadURL, binaryPath)
|
|
|
297
419
|
syncSkillsToWorkclawRegistry();
|
|
298
420
|
} catch (_) {}
|
|
299
421
|
|
|
422
|
+
return maybeAssistGuiPath();
|
|
423
|
+
})
|
|
424
|
+
.then(() => {
|
|
300
425
|
console.log('soke-cli 安装成功!');
|
|
301
426
|
console.log(`二进制文件位置: ${binaryPath}`);
|
|
302
427
|
console.log('\n使用方法:');
|
package/scripts/run.js
CHANGED
|
@@ -1,9 +1,88 @@
|
|
|
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') {
|
|
17
|
+
// 提取现有的系统 PATH
|
|
18
|
+
const sysPath = execSync('sysctl -n getenv PATH 2>/dev/null || echo ""', { encoding: 'utf8' }).trim();
|
|
19
|
+
const currentPath = process.env.PATH || '';
|
|
20
|
+
|
|
21
|
+
// 合并并去重 PATH
|
|
22
|
+
const pathSet = new Set([nodeDir, cliDir, ...currentPath.split(':'), ...sysPath.split(':')].filter(Boolean));
|
|
23
|
+
targetPath = Array.from(pathSet).join(':');
|
|
24
|
+
|
|
25
|
+
console.log(`注入的 PATH: ${targetPath.substring(0, 100)}...`);
|
|
26
|
+
// launchctl setenv 在新版 macOS 中如果不在 login context 下可能提示 Not privileged,
|
|
27
|
+
// 我们改用软链到 GUI 默认 PATH /usr/local/bin 的方式来解决(如果不可写则提示 sudo)
|
|
28
|
+
const binSource = path.join(nodeDir, 'soke-cli');
|
|
29
|
+
const binTarget = '/usr/local/bin/soke-cli';
|
|
30
|
+
|
|
31
|
+
if (fs.existsSync(binSource)) {
|
|
32
|
+
try {
|
|
33
|
+
// 尝试创建软链
|
|
34
|
+
if (fs.existsSync(binTarget)) {
|
|
35
|
+
try { fs.unlinkSync(binTarget); } catch(e) {}
|
|
36
|
+
}
|
|
37
|
+
fs.symlinkSync(binSource, binTarget);
|
|
38
|
+
console.log(`✅ 成功创建软链: ${binTarget} -> ${binSource}`);
|
|
39
|
+
} catch (linkError) {
|
|
40
|
+
console.log(`\n⚠️ 权限不足,无法自动创建 /usr/local/bin 软链。`);
|
|
41
|
+
console.log(`请手动在终端执行以下命令(可能需要输入密码):`);
|
|
42
|
+
console.log(`\n sudo ln -sf "${binSource}" "${binTarget}"\n`);
|
|
43
|
+
|
|
44
|
+
// 如果这里没法创建,sokeclaw 可能还是找不到,提示一下
|
|
45
|
+
console.log(`执行上述命令后,再重新打开 SokeClaw 即可。`);
|
|
46
|
+
process.exit(0);
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
console.log(`⚠️ 未找到源文件: ${binSource}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
console.log('尝试重启 SokeClaw / WorkClaw...');
|
|
53
|
+
try { execSync('killall SokeClaw 2>/dev/null'); } catch(e) {}
|
|
54
|
+
try { execSync('killall WorkClaw 2>/dev/null'); } catch(e) {}
|
|
55
|
+
try { execSync('killall Electron 2>/dev/null'); } catch(e) {}
|
|
56
|
+
|
|
57
|
+
// 等待进程退出
|
|
58
|
+
setTimeout(() => {
|
|
59
|
+
let launched = false;
|
|
60
|
+
if (fs.existsSync('/Applications/SokeClaw.app')) {
|
|
61
|
+
execSync('open "/Applications/SokeClaw.app"');
|
|
62
|
+
launched = true;
|
|
63
|
+
} else if (fs.existsSync('/Applications/WorkClaw.app')) {
|
|
64
|
+
execSync('open "/Applications/WorkClaw.app"');
|
|
65
|
+
launched = true;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (launched) {
|
|
69
|
+
console.log('\n✅ 修复完成!SokeClaw 已重启。现在你应该可以在里面使用 /skill soke-exam 了。');
|
|
70
|
+
} else {
|
|
71
|
+
console.log('\n✅ 环境变量已注入,但未找到 SokeClaw 应用。请手动从“启动台(Launchpad)”或“访达(Finder)”重新打开它。');
|
|
72
|
+
}
|
|
73
|
+
process.exit(0);
|
|
74
|
+
}, 1500);
|
|
75
|
+
return; // 异步等待中
|
|
76
|
+
} else {
|
|
77
|
+
console.log('\n⚠️ 目前 setup-gui-env 仅支持 macOS 平台。Windows/Linux 用户请手动将 Node/npm bin 目录加入系统环境变量。');
|
|
78
|
+
process.exit(0);
|
|
79
|
+
}
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.error('修复过程中出现错误:', error.message);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
7
86
|
// 获取二进制文件路径
|
|
8
87
|
const platform = process.platform;
|
|
9
88
|
const binaryName = platform === 'win32' ? 'soke-cli.exe' : 'soke-cli';
|
|
@@ -50,4 +129,4 @@ child.on('exit', (code, signal) => {
|
|
|
50
129
|
child.on('error', (err) => {
|
|
51
130
|
console.error('执行 soke-cli 时出错:', err.message);
|
|
52
131
|
process.exit(1);
|
|
53
|
-
});
|
|
132
|
+
});
|