openclaw-weiyuan-init 1.0.118 → 1.0.127

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 CHANGED
@@ -3,11 +3,12 @@
3
3
  const { program } = require('commander');
4
4
  const chalk = require('chalk');
5
5
  const { runInit, runClean, runStatus } = require('../lib/commands');
6
+ const { version } = require('../package.json');
6
7
 
7
8
  program
8
9
  .name('openclaw-weiyuan-init')
9
10
  .description('OpenClaw Weiyuan Skill 一键初始化工具')
10
- .version('1.0.0');
11
+ .version(version);
11
12
 
12
13
  program
13
14
  .command('init')
package/lib/commands.js CHANGED
@@ -60,6 +60,15 @@ const DEFAULT_SKILL_TSCONFIG = {
60
60
 
61
61
  const REQUIRED_SKILL_ACTIONS = ['project.list', 'task.list_all', 'context.candidates'];
62
62
 
63
+ function cleanCliRawText(raw) {
64
+ if (raw === undefined || raw === null) return '';
65
+ let text = String(raw).replace(/\r|\n/g, ' ').replace(/`/g, '').trim();
66
+ while ((text.startsWith('"') && text.endsWith('"')) || (text.startsWith("'") && text.endsWith("'"))) {
67
+ text = text.slice(1, -1).trim();
68
+ }
69
+ return text;
70
+ }
71
+
63
72
  function bundledSkillMetadataPath(fileName) {
64
73
  return path.join(__dirname, '..', 'workspace-weiyuan', 'weiyuan', fileName);
65
74
  }
@@ -447,7 +456,19 @@ async function persistReleaseNotes(weiyuanPath, notes) {
447
456
 
448
457
  function resolveWorkspacePath(workspaceOption) {
449
458
  const raw = (workspaceOption || DEFAULT_CONFIG.workspaceName).trim();
450
- const target = path.isAbsolute(raw) ? raw : path.join(process.cwd(), raw);
459
+ const cwd = process.cwd();
460
+ const workspaceName = DEFAULT_CONFIG.workspaceName.toLowerCase();
461
+ const cwdBase = path.basename(cwd).toLowerCase();
462
+ const rawParts = raw.replace(/\\/g, '/').split('/').map((x) => String(x || '').trim()).filter(Boolean);
463
+ if (cwdBase === workspaceName) {
464
+ if (!raw || raw === '.' || raw === './' || raw === '.\\') {
465
+ return cwd;
466
+ }
467
+ if (rawParts.length > 0 && rawParts.every((part) => part.toLowerCase() === workspaceName)) {
468
+ return cwd;
469
+ }
470
+ }
471
+ const target = path.isAbsolute(raw) ? raw : path.join(cwd, raw);
451
472
  if (path.basename(target).toLowerCase() === DEFAULT_CONFIG.workspaceName.toLowerCase()) {
452
473
  return target;
453
474
  }
@@ -457,7 +478,7 @@ function resolveWorkspacePath(workspaceOption) {
457
478
  function decodeInviteToken(token) {
458
479
  if (!token || typeof token !== 'string') return null;
459
480
  try {
460
- const normalized = token.replace(/-/g, '+').replace(/_/g, '/');
481
+ const normalized = cleanCliRawText(token).replace(/-/g, '+').replace(/_/g, '/');
461
482
  const pad = normalized.length % 4 === 0 ? '' : '='.repeat(4 - (normalized.length % 4));
462
483
  const raw = Buffer.from(normalized + pad, 'base64').toString('utf8');
463
484
  const parsed = JSON.parse(raw);
@@ -481,9 +502,10 @@ function decodeInviteToken(token) {
481
502
  }
482
503
 
483
504
  function normalizeServerUrl(raw) {
484
- if (!raw || typeof raw !== 'string') return '';
505
+ const cleaned = cleanCliRawText(raw);
506
+ if (!cleaned) return '';
485
507
  try {
486
- const url = new URL(raw.trim());
508
+ const url = new URL(cleaned);
487
509
  const p = (url.pathname || '/').replace(/\/+$/, '');
488
510
  if (!p || p === '/') url.pathname = '/api';
489
511
  url.hash = '';
@@ -493,6 +515,30 @@ function normalizeServerUrl(raw) {
493
515
  }
494
516
  }
495
517
 
518
+ function normalizeUpgradeUrl(raw) {
519
+ const cleaned = cleanCliRawText(raw);
520
+ if (!cleaned) return '';
521
+ try {
522
+ const url = new URL(cleaned);
523
+ url.hash = '';
524
+ return url.toString().replace(/\/+$/, '');
525
+ } catch (_) {
526
+ return '';
527
+ }
528
+ }
529
+
530
+ function normalizeDownloadUrl(raw) {
531
+ const cleaned = cleanCliRawText(raw);
532
+ if (!cleaned) return '';
533
+ try {
534
+ const url = new URL(cleaned);
535
+ url.hash = '';
536
+ return url.toString();
537
+ } catch (_) {
538
+ return '';
539
+ }
540
+ }
541
+
496
542
  function isAgentRegisterServerUrl(raw) {
497
543
  if (!raw || typeof raw !== 'string') return false;
498
544
  try {
@@ -641,6 +687,7 @@ async function printAgentConversationGuide(identityPath, projectId) {
641
687
  async function ensureSkillRuntime(weiyuanPath) {
642
688
  const pkgPath = path.join(weiyuanPath, 'package.json');
643
689
  const tsconfigPath = path.join(weiyuanPath, 'tsconfig.json');
690
+ const bundledManifestPath = bundledSkillMetadataPath('manifest.json');
644
691
  const capabilityCompatContent = `export const CAPABILITY_ACTION = {
645
692
  PROJECT_DELETE: "project.delete",
646
693
  TASK_DELETE: "task.delete",
@@ -656,8 +703,15 @@ async function ensureSkillRuntime(weiyuanPath) {
656
703
 
657
704
  export const CAPABILITY_ACTION_ALLOWLIST = new Set<string>(Object.values(CAPABILITY_ACTION))
658
705
  `;
706
+ let fallbackPackageJson = { ...DEFAULT_SKILL_PACKAGE_JSON };
707
+ try {
708
+ const bundledManifest = await fs.readJson(bundledManifestPath);
709
+ const bundledVersion = String(bundledManifest && bundledManifest.version || '').trim();
710
+ if (bundledVersion) fallbackPackageJson = { ...fallbackPackageJson, version: bundledVersion };
711
+ } catch (_) {
712
+ }
659
713
  if (!await fs.pathExists(pkgPath)) {
660
- await fs.writeJson(pkgPath, DEFAULT_SKILL_PACKAGE_JSON, { spaces: 2 });
714
+ await fs.writeJson(pkgPath, fallbackPackageJson, { spaces: 2 });
661
715
  }
662
716
  if (!await fs.pathExists(tsconfigPath)) {
663
717
  await fs.writeJson(tsconfigPath, DEFAULT_SKILL_TSCONFIG, { spaces: 2 });
@@ -706,10 +760,16 @@ async function runInit(options) {
706
760
  printBanner();
707
761
  const fixedMessages = getFixedMessages();
708
762
  let initIdentityInfo = null;
763
+ options = options || {};
764
+ options.server = cleanCliRawText(options.server || '');
765
+ options.upgrade = cleanCliRawText(options.upgrade || '');
766
+ options.download = cleanCliRawText(options.download || '');
767
+ options.invite = cleanCliRawText(options.invite || '');
768
+ options.project = cleanCliRawText(options.project || '');
769
+ options.code = cleanCliRawText(options.code || '');
709
770
 
710
771
  const invite = decodeInviteToken(options.invite);
711
772
  if (invite) {
712
- options.server = options.server || invite.api;
713
773
  options.upgrade = options.upgrade || invite.upgrade;
714
774
  options.project = options.project || invite.projectId;
715
775
  options.code = options.code || invite.code;
@@ -717,8 +777,8 @@ async function runInit(options) {
717
777
 
718
778
  const workspacePath = resolveWorkspacePath(options.workspace);
719
779
  const weiyuanPath = path.join(workspacePath, 'weiyuan');
720
- const upgradeBaseUrl = options.upgrade || DEFAULT_CONFIG.upgradeBaseUrl;
721
- const requestedDownloadUrl = options.download || DEFAULT_CONFIG.downloadUrl;
780
+ const upgradeBaseUrl = normalizeUpgradeUrl(options.upgrade || DEFAULT_CONFIG.upgradeBaseUrl) || DEFAULT_CONFIG.upgradeBaseUrl;
781
+ const requestedDownloadUrl = normalizeDownloadUrl(options.download || DEFAULT_CONFIG.downloadUrl);
722
782
  const serverCandidates = resolveServerCandidates(options, invite);
723
783
  let serverUrl = serverCandidates[0] || DEFAULT_CONFIG.serverUrl;
724
784
  const force = options.force || false;
@@ -810,42 +870,57 @@ async function runInit(options) {
810
870
  spinner = ora('检测到已有 weiyuan 运行目录,跳过下载与解压...').start();
811
871
  spinner.succeed('已复用现有 weiyuan 目录');
812
872
  } else {
813
- spinner = ora('解析下载包...').start();
814
- const source = await resolvePackageSource({
815
- downloadUrl: requestedDownloadUrl,
816
- upgradeBaseUrl,
817
- spinner
818
- });
819
- spinner.succeed(`下载源: ${source.downloadUrl}`);
820
- printReleaseNotes(source.releaseNotes);
821
-
822
- spinner = ora('下载 weiyuan skill...').start();
823
- const zipPath = path.join(weiyuanPath, source.zipFilename);
824
- const downloadSuccess = await downloadFile(source.downloadUrl, zipPath, spinner, source.sha256 || '');
825
- if (!downloadSuccess) {
826
- spinner.fail('下载失败');
827
- throw new Error('下载失败');
828
- }
829
- spinner.succeed(`下载完成: ${source.zipFilename}`);
830
-
831
- // 4. 解压
832
- spinner = ora('解压文件...').start();
833
- const extractSuccess = await extractZip(zipPath, weiyuanPath);
834
- if (!extractSuccess) {
835
- spinner.fail('解压失败');
836
- throw new Error('解压失败');
837
- }
838
- spinner.succeed('解压完成');
839
-
840
- // 5. 清理
841
- spinner = ora('清理临时文件...').start();
842
- try {
843
- await fs.remove(zipPath);
844
- await persistReleaseNotes(weiyuanPath, source.releaseNotes || null);
845
- spinner.succeed('清理完成');
846
- } catch (error) {
847
- spinner.warn('清理失败,可手动删除');
873
+ const maxDownloadAttempts = 2;
874
+ let downloadCompleted = false;
875
+ let lastDownloadError = null;
876
+ for (let attempt = 1; attempt <= maxDownloadAttempts; attempt += 1) {
877
+ let source = null;
878
+ let zipPath = '';
879
+ try {
880
+ spinner = ora(attempt > 1 ? `重新解析下载包(第 ${attempt} 次)...` : '解析下载包...').start();
881
+ source = await resolvePackageSource({
882
+ downloadUrl: requestedDownloadUrl,
883
+ upgradeBaseUrl,
884
+ spinner
885
+ });
886
+ spinner.succeed(`下载源: ${source.downloadUrl}`);
887
+ printReleaseNotes(source.releaseNotes);
888
+
889
+ spinner = ora(attempt > 1 ? `重新下载 weiyuan skill(第 ${attempt} 次)...` : '下载 weiyuan skill...').start();
890
+ zipPath = path.join(weiyuanPath, source.zipFilename);
891
+ await fs.remove(zipPath).catch(() => {});
892
+ const downloadSuccess = await downloadFile(source.downloadUrl, zipPath, spinner, source.sha256 || '');
893
+ if (!downloadSuccess) throw new Error('下载失败');
894
+ spinner.succeed(`下载完成: ${source.zipFilename}`);
895
+
896
+ spinner = ora('解压文件...').start();
897
+ const extractSuccess = await extractZip(zipPath, weiyuanPath);
898
+ if (!extractSuccess) throw new Error('解压失败');
899
+ spinner.succeed('解压完成');
900
+
901
+ spinner = ora('清理临时文件...').start();
902
+ try {
903
+ await fs.remove(zipPath);
904
+ await persistReleaseNotes(weiyuanPath, source.releaseNotes || null);
905
+ spinner.succeed('清理完成');
906
+ } catch (error) {
907
+ spinner.warn('清理失败,可手动删除');
908
+ }
909
+ downloadCompleted = true;
910
+ lastDownloadError = null;
911
+ break;
912
+ } catch (error) {
913
+ lastDownloadError = error;
914
+ if (zipPath) await fs.remove(zipPath).catch(() => {});
915
+ const msg = String(error && error.message || error || '');
916
+ const canRetry = attempt < maxDownloadAttempts && msg.includes('skill_package_hash_mismatch');
917
+ if (spinner) {
918
+ spinner.warn(canRetry ? '下载包哈希校验未通过,正在重新获取最新版本后重试...' : `下载失败:${msg || 'unknown_error'}`);
919
+ }
920
+ if (!canRetry) throw error;
921
+ }
848
922
  }
923
+ if (!downloadCompleted && lastDownloadError) throw lastDownloadError;
849
924
  }
850
925
 
851
926
  // 6. 准备 CLI 运行时
package/lib/identity.js CHANGED
@@ -1,5 +1,6 @@
1
1
  const fs = require('fs-extra');
2
2
  const os = require('os');
3
+ const path = require('path');
3
4
  const crypto = require('crypto');
4
5
  const axios = require('axios');
5
6
  const naclImport = require('tweetnacl');
@@ -64,12 +65,66 @@ function normalizeBusinessServerUrl(serverUrl) {
64
65
  }
65
66
  }
66
67
 
67
- async function registerIdentity(serverUrl, identity) {
68
+ function applyIdentityProxyMetadata(identity, initInfo) {
69
+ const next = identity && typeof identity === 'object' ? { ...identity } : {};
70
+ const lobsterId = String(next.lobsterId || '').trim();
71
+ const effectiveLobsterId = String(initInfo && initInfo.effectiveLobsterId || '').trim();
72
+ const ownerLobsterId = String(initInfo && initInfo.ownerLobsterId || '').trim();
73
+ const accountModeRaw = String(initInfo && initInfo.accountMode || initInfo && initInfo.mode || '').trim();
74
+ const accountMode = accountModeRaw === 'agent' || accountModeRaw === 'owner' ? accountModeRaw : '';
75
+ const accountTypeRaw = String(initInfo && initInfo.accountType || '').trim().toLowerCase();
76
+ const accountType = accountTypeRaw === 'auto' || accountTypeRaw === 'weiyuan' || accountTypeRaw === 'lob' ? accountTypeRaw : '';
77
+ const displayAccountId = String((initInfo && (initInfo.displayAccountId || initInfo.account)) || '').trim();
78
+ if (effectiveLobsterId && effectiveLobsterId !== lobsterId) next.effectiveLobsterId = effectiveLobsterId;
79
+ else delete next.effectiveLobsterId;
80
+ if (ownerLobsterId && ownerLobsterId !== lobsterId) next.ownerLobsterId = ownerLobsterId;
81
+ else delete next.ownerLobsterId;
82
+ if (accountMode) next.accountMode = accountMode;
83
+ else delete next.accountMode;
84
+ if (accountType) next.accountType = accountType;
85
+ else delete next.accountType;
86
+ if (displayAccountId) next.displayAccountId = displayAccountId;
87
+ else delete next.displayAccountId;
88
+ return next;
89
+ }
90
+
91
+ function normalizeSkillVersionText(raw) {
92
+ const text = String(raw || '').trim().replace(/^v/i, '');
93
+ return text && text !== '0.0.0' ? text : '';
94
+ }
95
+
96
+ async function readLocalSkillVersion(workspacePath) {
97
+ const weiyuanPath = path.join(String(workspacePath || ''), 'weiyuan');
98
+ const candidates = [
99
+ path.join(weiyuanPath, 'package.json'),
100
+ path.join(weiyuanPath, 'release-notes', 'latest.json'),
101
+ path.join(weiyuanPath, 'manifest.json'),
102
+ ];
103
+ for (const file of candidates) {
104
+ try {
105
+ const parsed = await fs.readJson(file);
106
+ const version = normalizeSkillVersionText(parsed && parsed.version);
107
+ if (version) return version;
108
+ } catch (_) {
109
+ }
110
+ }
111
+ try {
112
+ const plain = await fs.readFile(path.join(weiyuanPath, '.version'), 'utf8');
113
+ const version = normalizeSkillVersionText(plain);
114
+ if (version) return version;
115
+ } catch (_) {
116
+ }
117
+ return '';
118
+ }
119
+
120
+ async function registerIdentity(serverUrl, identity, workspacePath) {
121
+ const skillVersion = await readLocalSkillVersion(workspacePath);
68
122
  const body = {
69
123
  lobsterId: identity.lobsterId,
70
124
  publicKeyBase64: identity.publicKeyBase64,
71
125
  identityHash: identity.identityHash,
72
- recoveryHint: identity && identity.meta ? identity.meta.recoveryHint : undefined
126
+ recoveryHint: identity && identity.meta ? identity.meta.recoveryHint : undefined,
127
+ skillVersion: skillVersion || undefined
73
128
  };
74
129
  const timestampMs = String(Date.now());
75
130
  const nonce = `nonce_${Math.random().toString(16).slice(2)}`;
@@ -127,7 +182,7 @@ async function createIdentityFile(identityPath, serverUrl, workspacePath) {
127
182
  recoveryHint: (existing.meta && existing.meta.recoveryHint) || buildRecoveryHint(workspacePath)
128
183
  }
129
184
  };
130
- const initInfo = await registerIdentity(serverUrl, identity);
185
+ const initInfo = await registerIdentity(serverUrl, identity, workspacePath);
131
186
  const serverLobsterId = String(initInfo && initInfo.lobsterId || '').trim();
132
187
  if (serverLobsterId) {
133
188
  identity.lobsterId = serverLobsterId;
@@ -151,7 +206,8 @@ async function createIdentityFile(identityPath, serverUrl, workspacePath) {
151
206
  .filter(([k, v]) => k && Number.isFinite(v))
152
207
  )
153
208
  : {};
154
- await fs.writeJson(identityPath, identity, { spaces: 2 });
209
+ const nextIdentity = applyIdentityProxyMetadata(identity, initInfo);
210
+ await fs.writeJson(identityPath, nextIdentity, { spaces: 2 });
155
211
  return { created: true, initInfo: initInfo || null };
156
212
  }
157
213
  }
@@ -176,7 +232,7 @@ async function createIdentityFile(identityPath, serverUrl, workspacePath) {
176
232
  }
177
233
  };
178
234
 
179
- const initInfo = await registerIdentity(serverUrl, identity);
235
+ const initInfo = await registerIdentity(serverUrl, identity, workspacePath);
180
236
  const serverLobsterId = String(initInfo && initInfo.lobsterId || '').trim();
181
237
  if (serverLobsterId) {
182
238
  identity.lobsterId = serverLobsterId;
@@ -200,7 +256,8 @@ async function createIdentityFile(identityPath, serverUrl, workspacePath) {
200
256
  .filter(([k, v]) => k && Number.isFinite(v))
201
257
  )
202
258
  : {};
203
- await fs.writeJson(identityPath, identity, { spaces: 2 });
259
+ const nextIdentity = applyIdentityProxyMetadata(identity, initInfo);
260
+ await fs.writeJson(identityPath, nextIdentity, { spaces: 2 });
204
261
  return { created: true, initInfo: initInfo || null };
205
262
  } catch (error) {
206
263
  return { created: false, initInfo: null, error: error && error.message ? String(error.message) : 'identity_create_failed' };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-weiyuan-init",
3
- "version": "1.0.118",
3
+ "version": "1.0.127",
4
4
  "description": "OpenClaw Weiyuan Skill 一键初始化工具",
5
5
  "main": "bin/cli.js",
6
6
  "bin": {