openclaw-weiyuan-init 1.0.69 → 1.0.73
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.js +1 -0
- package/lib/commands.js +131 -34
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -14,6 +14,7 @@ program
|
|
|
14
14
|
.description('初始化 weiyuan skill')
|
|
15
15
|
.option('-w, --workspace <path>', '指定工作目录', 'workspace-weiyuan')
|
|
16
16
|
.option('-s, --server <url>', '指定服务器地址', 'https://api.magon.com.cn/api')
|
|
17
|
+
.option('--server-fallbacks <urls>', '备用服务器地址,逗号分隔,如 "https://a/api,https://b/api"')
|
|
17
18
|
.option('-u, --upgrade <url>', '指定升级源地址(含 LATEST_SKILL_VERSION.txt)', 'https://api.magon.com.cn/upgrade')
|
|
18
19
|
.option('-d, --download <url>', '指定下载地址(可覆盖自动版本解析)')
|
|
19
20
|
.option('-i, --invite <token>', '邀请码令牌(包含 server/upgrade/project/code)')
|
package/lib/commands.js
CHANGED
|
@@ -16,7 +16,8 @@ const DEFAULT_CONFIG = {
|
|
|
16
16
|
upgradeBaseUrl: 'https://api.magon.com.cn/upgrade',
|
|
17
17
|
downloadUrl: '',
|
|
18
18
|
serverUrl: 'https://api.magon.com.cn/api',
|
|
19
|
-
identityFile: '.weiyuan'
|
|
19
|
+
identityFile: '.weiyuan',
|
|
20
|
+
serverFallbacks: []
|
|
20
21
|
};
|
|
21
22
|
|
|
22
23
|
const DEFAULT_SKILL_PACKAGE_JSON = {
|
|
@@ -109,18 +110,75 @@ function decodeInviteToken(token) {
|
|
|
109
110
|
}
|
|
110
111
|
}
|
|
111
112
|
|
|
112
|
-
|
|
113
|
-
|
|
113
|
+
function normalizeServerUrl(raw) {
|
|
114
|
+
if (!raw || typeof raw !== 'string') return '';
|
|
114
115
|
try {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
116
|
+
const url = new URL(raw.trim());
|
|
117
|
+
const p = (url.pathname || '/').replace(/\/+$/, '');
|
|
118
|
+
if (!p || p === '/') url.pathname = '/api';
|
|
119
|
+
url.hash = '';
|
|
120
|
+
return url.toString().replace(/\/$/, '');
|
|
119
121
|
} catch (_) {
|
|
122
|
+
return '';
|
|
120
123
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function parseServerFallbacks(raw) {
|
|
127
|
+
if (!raw || typeof raw !== 'string') return [];
|
|
128
|
+
return raw
|
|
129
|
+
.split(',')
|
|
130
|
+
.map((x) => normalizeServerUrl(x))
|
|
131
|
+
.filter(Boolean);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function resolveServerCandidates(options, invite) {
|
|
135
|
+
const envFallbacks = parseServerFallbacks(process.env.WEIYUAN_SERVER_FALLBACKS || '');
|
|
136
|
+
const optionFallbacks = parseServerFallbacks(options.serverFallbacks || '');
|
|
137
|
+
const inviteFallbacks = Array.isArray(invite && invite.apis) ? invite.apis.map((x) => normalizeServerUrl(String(x))) : [];
|
|
138
|
+
const seeds = [
|
|
139
|
+
normalizeServerUrl(options.server || ''),
|
|
140
|
+
normalizeServerUrl(invite && invite.api ? invite.api : ''),
|
|
141
|
+
...inviteFallbacks,
|
|
142
|
+
...optionFallbacks,
|
|
143
|
+
...envFallbacks,
|
|
144
|
+
normalizeServerUrl(DEFAULT_CONFIG.serverUrl)
|
|
145
|
+
].filter(Boolean);
|
|
146
|
+
return [...new Set(seeds)];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function isRetryableGatewayError(error) {
|
|
150
|
+
const text = String((error && (error.stderr || error.stdout || error.message)) || '').toLowerCase();
|
|
151
|
+
return /502|bad gateway|econnreset|etimedout|eai_again|socket hang up|network error/.test(text);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async function sleep(ms) {
|
|
155
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async function runJoin(weiyuanPath, identityPath, projectId, code, fromInit = false) {
|
|
159
|
+
const extra = fromInit ? ['--fromInit'] : [];
|
|
160
|
+
const maxAttempts = 3;
|
|
161
|
+
let lastError = null;
|
|
162
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
163
|
+
try {
|
|
164
|
+
await execFileAsync('npm', ['--prefix', weiyuanPath, 'run', 'weiyuan', '--', 'join', '--identity', identityPath, '--project', projectId, '--code', code, ...extra], {
|
|
165
|
+
cwd: weiyuanPath
|
|
166
|
+
});
|
|
167
|
+
return;
|
|
168
|
+
} catch (_) {
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
await execFileAsync('npx', ['-y', '-p', 'tsx', '-p', 'tweetnacl', 'tsx', path.join(weiyuanPath, 'src', 'cliMain.ts'), 'join', '--identity', identityPath, '--project', projectId, '--code', code, ...extra], {
|
|
172
|
+
cwd: weiyuanPath
|
|
173
|
+
});
|
|
174
|
+
return;
|
|
175
|
+
} catch (error) {
|
|
176
|
+
lastError = error;
|
|
177
|
+
if (i >= maxAttempts - 1 || !isRetryableGatewayError(error)) throw error;
|
|
178
|
+
await sleep(1200 * (i + 1));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (lastError) throw lastError;
|
|
124
182
|
}
|
|
125
183
|
|
|
126
184
|
async function ensureSkillRuntime(weiyuanPath) {
|
|
@@ -136,16 +194,25 @@ async function ensureSkillRuntime(weiyuanPath) {
|
|
|
136
194
|
}
|
|
137
195
|
|
|
138
196
|
async function runCliInit(weiyuanPath, identityPath, serverUrl) {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
197
|
+
const maxAttempts = 3;
|
|
198
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
199
|
+
try {
|
|
200
|
+
await execFileAsync('npm', ['--prefix', weiyuanPath, 'run', 'weiyuan', '--', 'init', '--server', serverUrl, '--out', identityPath], {
|
|
201
|
+
cwd: weiyuanPath
|
|
202
|
+
});
|
|
203
|
+
return true;
|
|
204
|
+
} catch (_) {
|
|
205
|
+
}
|
|
206
|
+
try {
|
|
207
|
+
await execFileAsync('npx', ['-y', '-p', 'tsx', '-p', 'tweetnacl', 'tsx', path.join(weiyuanPath, 'src', 'cliMain.ts'), 'init', '--server', serverUrl, '--out', identityPath], {
|
|
208
|
+
cwd: weiyuanPath
|
|
209
|
+
});
|
|
210
|
+
return true;
|
|
211
|
+
} catch (error) {
|
|
212
|
+
if (i >= maxAttempts - 1 || !isRetryableGatewayError(error)) throw error;
|
|
213
|
+
await sleep(1200 * (i + 1));
|
|
214
|
+
}
|
|
145
215
|
}
|
|
146
|
-
await execFileAsync('npx', ['-y', '-p', 'tsx', '-p', 'tweetnacl', 'tsx', path.join(weiyuanPath, 'src', 'cliMain.ts'), 'init', '--server', serverUrl, '--out', identityPath], {
|
|
147
|
-
cwd: weiyuanPath
|
|
148
|
-
});
|
|
149
216
|
return true;
|
|
150
217
|
}
|
|
151
218
|
|
|
@@ -165,7 +232,8 @@ async function runInit(options) {
|
|
|
165
232
|
const weiyuanPath = path.join(workspacePath, 'weiyuan');
|
|
166
233
|
const upgradeBaseUrl = options.upgrade || DEFAULT_CONFIG.upgradeBaseUrl;
|
|
167
234
|
const requestedDownloadUrl = options.download || DEFAULT_CONFIG.downloadUrl;
|
|
168
|
-
const
|
|
235
|
+
const serverCandidates = resolveServerCandidates(options, invite);
|
|
236
|
+
let serverUrl = serverCandidates[0] || DEFAULT_CONFIG.serverUrl;
|
|
169
237
|
const force = options.force || false;
|
|
170
238
|
const hasInviteJoin = Boolean(options.project && options.code);
|
|
171
239
|
const identityPath = path.join(workspacePath, DEFAULT_CONFIG.identityFile);
|
|
@@ -201,11 +269,16 @@ async function runInit(options) {
|
|
|
201
269
|
|
|
202
270
|
// 2. 检查服务器
|
|
203
271
|
spinner = ora('检查服务器连接...').start();
|
|
204
|
-
|
|
205
|
-
|
|
272
|
+
let selectedServer = "";
|
|
273
|
+
for (const candidate of serverCandidates) {
|
|
274
|
+
const ok = await checkServer(candidate);
|
|
275
|
+
if (ok) { selectedServer = candidate; break; }
|
|
276
|
+
}
|
|
277
|
+
if (selectedServer) {
|
|
278
|
+
serverUrl = selectedServer;
|
|
206
279
|
spinner.succeed(`服务器: ${serverUrl}`);
|
|
207
280
|
} else {
|
|
208
|
-
spinner.warn(
|
|
281
|
+
spinner.warn(`服务器连接失败,继续尝试初始化(候选: ${serverCandidates.join(", ")})`);
|
|
209
282
|
}
|
|
210
283
|
|
|
211
284
|
// 3. 下载文件
|
|
@@ -257,10 +330,21 @@ async function runInit(options) {
|
|
|
257
330
|
// 7. 创建身份文件
|
|
258
331
|
spinner = ora('创建身份文件...').start();
|
|
259
332
|
let identityCreated = false;
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
333
|
+
let initError = null;
|
|
334
|
+
for (const candidate of serverCandidates) {
|
|
335
|
+
serverUrl = candidate;
|
|
336
|
+
try {
|
|
337
|
+
identityCreated = await runCliInit(weiyuanPath, identityPath, serverUrl);
|
|
338
|
+
if (identityCreated) break;
|
|
339
|
+
} catch (error) {
|
|
340
|
+
initError = error;
|
|
341
|
+
identityCreated = await createIdentityFile(identityPath, serverUrl, workspacePath);
|
|
342
|
+
if (identityCreated) break;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
if (!identityCreated && initError) {
|
|
346
|
+
spinner.fail(`身份文件创建失败: ${initError.message}`);
|
|
347
|
+
throw initError;
|
|
264
348
|
}
|
|
265
349
|
if (identityCreated) {
|
|
266
350
|
spinner.succeed(`身份文件: ${DEFAULT_CONFIG.identityFile}`);
|
|
@@ -280,14 +364,27 @@ async function runInit(options) {
|
|
|
280
364
|
|
|
281
365
|
if (options.project && options.code) {
|
|
282
366
|
spinner = ora('自动加入微元项目...').start();
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
367
|
+
let joined = false;
|
|
368
|
+
let joinErr = null;
|
|
369
|
+
for (const candidate of serverCandidates) {
|
|
370
|
+
try {
|
|
371
|
+
const identity = await fs.readJson(identityPath);
|
|
372
|
+
identity.serverBaseUrl = candidate;
|
|
373
|
+
await fs.writeJson(identityPath, identity, { spaces: 2 });
|
|
374
|
+
await runJoin(weiyuanPath, identityPath, options.project, options.code, true);
|
|
375
|
+
joined = true;
|
|
376
|
+
serverUrl = candidate;
|
|
377
|
+
break;
|
|
378
|
+
} catch (error) {
|
|
379
|
+
joinErr = error;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
if (!joined) {
|
|
383
|
+
spinner.fail(`自动入组失败: ${joinErr ? joinErr.message : 'unknown_error'}`);
|
|
384
|
+
throw (joinErr || new Error('join_failed'));
|
|
290
385
|
}
|
|
386
|
+
spinner.succeed(`已加入项目: ${options.project}`);
|
|
387
|
+
console.log(renderFixedMessage(fixedMessages.sceneB, { projectId: options.project }));
|
|
291
388
|
return;
|
|
292
389
|
}
|
|
293
390
|
console.log(fixedMessages.sceneA);
|