codewave-openclaw-installer 2.1.9 → 2.1.11

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/check.mjs CHANGED
@@ -433,7 +433,7 @@ async function main() {
433
433
 
434
434
  // CLI 工具
435
435
  checks['razel-py-cli'] = await checkCli('razel-py-cli', {
436
- installCmd: 'pip3 install razel-py-cli && razel-py-cli init',
436
+ installCmd: 'pipx install razel-py-cli && razel-py-cli init',
437
437
  });
438
438
  checks['razel-cli'] = await checkCli('razel-cli', {
439
439
  installCmd: 'npm install -g @lcap-delivery/razel-cli@latest && razel-cli init',
@@ -198,6 +198,131 @@ export function classifyPythonInstallFailure(stderr = '') {
198
198
  };
199
199
  }
200
200
 
201
+ export function getCoreBootstrapPlan(platform = process.platform) {
202
+ if (platform === 'win32') {
203
+ const wingetArgs = (packageId) => [
204
+ 'install',
205
+ '-e',
206
+ '--id',
207
+ packageId,
208
+ '--accept-package-agreements',
209
+ '--accept-source-agreements',
210
+ ];
211
+
212
+ return [
213
+ {
214
+ key: 'nodejs',
215
+ optional: false,
216
+ checkCommands: ['node', 'npm'],
217
+ resultKeys: ['node', 'npm'],
218
+ install: { cmd: 'winget', args: wingetArgs('OpenJS.NodeJS.LTS') },
219
+ },
220
+ {
221
+ key: 'python3',
222
+ optional: false,
223
+ checkCommands: ['python3', 'python', 'py'],
224
+ resultKeys: ['python3', 'pip'],
225
+ install: { cmd: 'winget', args: wingetArgs('Python.Python.3.13') },
226
+ },
227
+ {
228
+ key: 'jq',
229
+ optional: false,
230
+ checkCommands: ['jq'],
231
+ resultKeys: ['jq'],
232
+ install: { cmd: 'winget', args: wingetArgs('jqlang.jq') },
233
+ },
234
+ {
235
+ key: 'pandoc',
236
+ optional: true,
237
+ checkCommands: ['pandoc'],
238
+ resultKeys: ['pandoc'],
239
+ install: { cmd: 'winget', args: wingetArgs('JohnMacFarlane.Pandoc') },
240
+ },
241
+ ];
242
+ }
243
+
244
+ if (platform === 'darwin') {
245
+ const brewArgs = (formula) => ['install', formula];
246
+ return [
247
+ {
248
+ key: 'nodejs',
249
+ optional: false,
250
+ checkCommands: ['node', 'npm'],
251
+ resultKeys: ['node', 'npm'],
252
+ install: { cmd: 'brew', args: brewArgs('node') },
253
+ },
254
+ {
255
+ key: 'python3',
256
+ optional: false,
257
+ checkCommands: ['python3'],
258
+ resultKeys: ['python3', 'pip'],
259
+ install: { cmd: 'brew', args: brewArgs('python') },
260
+ },
261
+ {
262
+ key: 'pkg-config',
263
+ optional: false,
264
+ checkCommands: ['pkg-config'],
265
+ resultKeys: ['pkg-config'],
266
+ install: { cmd: 'brew', args: brewArgs('pkg-config') },
267
+ },
268
+ {
269
+ key: 'cairo',
270
+ optional: false,
271
+ checkCommands: ['pkg-config'],
272
+ verifyArgs: ['--exists', 'cairo'],
273
+ resultKeys: ['cairo'],
274
+ install: { cmd: 'brew', args: brewArgs('cairo') },
275
+ },
276
+ {
277
+ key: 'jq',
278
+ optional: false,
279
+ checkCommands: ['jq'],
280
+ resultKeys: ['jq'],
281
+ install: { cmd: 'brew', args: brewArgs('jq') },
282
+ },
283
+ {
284
+ key: 'pandoc',
285
+ optional: true,
286
+ checkCommands: ['pandoc'],
287
+ resultKeys: ['pandoc'],
288
+ install: { cmd: 'brew', args: brewArgs('pandoc') },
289
+ },
290
+ ];
291
+ }
292
+
293
+ return [];
294
+ }
295
+
296
+ export function getPipxBootstrapPlan({
297
+ platform = process.platform,
298
+ hasBrew = false,
299
+ pythonCommand = 'python3',
300
+ pythonLauncher = null,
301
+ } = {}) {
302
+ if (platform === 'win32') {
303
+ const launcher = pythonLauncher || pythonCommand || 'py';
304
+ return {
305
+ install: { cmd: launcher, args: ['-m', 'pip', 'install', '--user', 'pipx'] },
306
+ ensurePath: { cmd: launcher, args: ['-m', 'pipx', 'ensurepath'] },
307
+ invoke: { cmd: launcher, args: ['-m', 'pipx'] },
308
+ };
309
+ }
310
+
311
+ if (platform === 'darwin' && hasBrew) {
312
+ return {
313
+ install: { cmd: 'brew', args: ['install', 'pipx'] },
314
+ ensurePath: { cmd: 'pipx', args: ['ensurepath'] },
315
+ invoke: { cmd: 'pipx', args: [] },
316
+ };
317
+ }
318
+
319
+ return {
320
+ install: { cmd: pythonCommand, args: ['-m', 'pip', 'install', '--user', 'pipx'] },
321
+ ensurePath: { cmd: pythonCommand, args: ['-m', 'pipx', 'ensurepath'] },
322
+ invoke: { cmd: pythonCommand, args: ['-m', 'pipx'] },
323
+ };
324
+ }
325
+
201
326
  function joinForPlatform(platform, root, candidate) {
202
327
  if (platform === 'win32') {
203
328
  return path.win32.join(root, candidate);
@@ -288,6 +413,10 @@ export function buildRazelConfigBootstrap({
288
413
  razelConfig = {},
289
414
  env = process.env,
290
415
  hasOpenclawLlm = false,
416
+ availableCommands = {
417
+ 'razel-cli': true,
418
+ 'razel-py-cli': true,
419
+ },
291
420
  } = {}) {
292
421
  const writes = [];
293
422
  const missing = [];
@@ -298,6 +427,7 @@ export function buildRazelConfigBootstrap({
298
427
  }
299
428
  writes.push({ cli, key, value });
300
429
  };
430
+ const hasCommand = (cli) => availableCommands[cli] !== false;
301
431
 
302
432
  queueWrite('razel-cli', 'popo_app_id', env.POPO_APP_ID);
303
433
  queueWrite('razel-cli', 'popo_app_secret', env.POPO_APP_SECRET);
@@ -311,11 +441,14 @@ export function buildRazelConfigBootstrap({
311
441
  if (!hasPopoAfterBootstrap) {
312
442
  missing.push({
313
443
  group: 'popo',
314
- title: 'PoPo 认证缺失',
315
- commands: [
316
- 'razel-cli config set popo_app_id <your-app-id>',
317
- 'razel-cli config set popo_app_secret <your-app-secret>',
318
- ],
444
+ title: hasCommand('razel-cli') ? 'PoPo 认证缺失' : 'PoPo 认证缺失(当前未安装 razel-cli)',
445
+ interactive: hasCommand('razel-cli'),
446
+ commands: hasCommand('razel-cli')
447
+ ? [
448
+ 'razel-cli config set popo_app_id <your-app-id>',
449
+ 'razel-cli config set popo_app_secret <your-app-secret>',
450
+ ]
451
+ : [],
319
452
  });
320
453
  }
321
454
 
@@ -329,11 +462,14 @@ export function buildRazelConfigBootstrap({
329
462
  if (!hasLlmAfterBootstrap) {
330
463
  missing.push({
331
464
  group: 'llm',
332
- title: 'LLM 配置缺失',
333
- commands: [
334
- 'razel-py-cli config set llm_base_url <your-llm-base-url>',
335
- 'razel-py-cli config set llm_api_key <your-llm-api-key>',
336
- ],
465
+ title: hasCommand('razel-py-cli') ? 'LLM 配置缺失' : 'LLM 配置缺失(当前未安装 razel-py-cli)',
466
+ interactive: hasCommand('razel-py-cli'),
467
+ commands: hasCommand('razel-py-cli')
468
+ ? [
469
+ 'razel-py-cli config set llm_base_url <your-llm-base-url>',
470
+ 'razel-py-cli config set llm_api_key <your-llm-api-key>',
471
+ ]
472
+ : [],
337
473
  });
338
474
  }
339
475
 
@@ -350,12 +486,15 @@ export function buildRazelConfigBootstrap({
350
486
  if (!hasAsrAfterBootstrap) {
351
487
  missing.push({
352
488
  group: 'asr',
353
- title: 'ASR Provider 缺失',
354
- commands: [
355
- 'razel-py-cli config set dashscope_api_key <your-dashscope-key>',
356
- 'razel-py-cli config set aigw_base_url <your-aigw-base-url>',
357
- 'razel-py-cli config set aigw_api_key <your-aigw-api-key>',
358
- ],
489
+ title: hasCommand('razel-py-cli') ? 'ASR Provider 缺失' : 'ASR Provider 缺失(当前未安装 razel-py-cli)',
490
+ interactive: hasCommand('razel-py-cli'),
491
+ commands: hasCommand('razel-py-cli')
492
+ ? [
493
+ 'razel-py-cli config set dashscope_api_key <your-dashscope-key>',
494
+ 'razel-py-cli config set aigw_base_url <your-aigw-base-url>',
495
+ 'razel-py-cli config set aigw_api_key <your-aigw-api-key>',
496
+ ]
497
+ : [],
359
498
  });
360
499
  }
361
500
 
package/bin/install.mjs CHANGED
@@ -16,7 +16,9 @@ import {
16
16
  createInstallContext,
17
17
  detectDesktopAppInstallMatches,
18
18
  evaluateChannelDetection,
19
+ getCoreBootstrapPlan,
19
20
  getDesktopAppCandidates,
21
+ getPipxBootstrapPlan,
20
22
  resolvePromptDefault,
21
23
  shouldDryRun,
22
24
  } from './install-lib.mjs';
@@ -34,10 +36,25 @@ const dim = (s) => `\x1b[97m${s}\x1b[0m`;
34
36
 
35
37
  const isWin = process.platform === 'win32';
36
38
  const NPM = 'npm';
37
- const PIP = isWin ? 'pip' : 'pip3';
38
39
  const DRY_RUN = shouldDryRun(process.argv);
39
40
  const OPENCLAW_CONFIG_PATH = join(homedir(), '.openclaw', 'openclaw.json');
40
41
 
42
+ function prependPathOnce(candidate) {
43
+ if (!candidate) return;
44
+ const delimiter = isWin ? ';' : ':';
45
+ const segments = (process.env.PATH || '').split(delimiter).filter(Boolean);
46
+ if (segments.includes(candidate)) {
47
+ return;
48
+ }
49
+ process.env.PATH = [candidate, ...segments].join(delimiter);
50
+ }
51
+
52
+ function refreshLocalToolPaths() {
53
+ prependPathOnce(isWin
54
+ ? path.win32.join(homedir(), '.local', 'bin')
55
+ : path.posix.join(homedir(), '.local', 'bin'));
56
+ }
57
+
41
58
  function resolveExecutable(cmd) {
42
59
  if (!isWin) {
43
60
  return cmd;
@@ -344,24 +361,155 @@ async function ensureCommand(command, installArgs, resultKey, context, initArgs
344
361
  contextResult(context, resultKey, 'ok');
345
362
  }
346
363
 
347
- async function installPythonCli(context) {
348
- const hasPython = await commandExists('python3');
349
- const hasPython313 = await commandExists('python3.13');
350
- const hasPip = await commandExists(PIP);
351
- const hasPipx = await commandExists('pipx');
352
-
353
- if (!hasPython) {
354
- console.log(yellow(` ⚠️ 缺少 python3,请先安装 Python 3`));
355
- contextResult(context, 'python3', hasPython ? 'reused' : 'missing');
356
- contextResult(context, 'pip', hasPip ? 'reused' : 'missing');
357
- contextResult(context, 'pipx', hasPipx ? 'reused' : 'missing');
358
- contextResult(context, 'razel-py-cli', 'skipped (missing python3)');
359
- return;
364
+ async function commandExistsAny(commands = []) {
365
+ for (const command of commands) {
366
+ if (await commandExists(command)) {
367
+ return command;
368
+ }
369
+ }
370
+ return null;
371
+ }
372
+
373
+ async function installCoreDependency(spec, context) {
374
+ const verifySpec = async () => {
375
+ if (spec.verifyArgs?.length) {
376
+ if (!(await commandExists(spec.checkCommands[0]))) {
377
+ return false;
378
+ }
379
+ return (await run(spec.checkCommands[0], spec.verifyArgs, { stdio: 'ignore' })) === 0;
380
+ }
381
+
382
+ const present = [];
383
+ for (const command of spec.checkCommands) {
384
+ if (await commandExists(command)) {
385
+ present.push(command);
386
+ }
387
+ }
388
+ return spec.key === 'python3' ? present.length > 0 : present.length === spec.checkCommands.length;
389
+ };
390
+
391
+ const hasDependency = await verifySpec();
392
+ if (hasDependency) {
393
+ for (const resultKey of spec.resultKeys) {
394
+ contextResult(context, resultKey, 'reused');
395
+ }
396
+ return true;
397
+ }
398
+
399
+ if (!(await commandExists(spec.install.cmd))) {
400
+ const status = spec.optional ? `optional missing (${spec.install.cmd} missing)` : `blocked (${spec.install.cmd} missing)`;
401
+ console.log(yellow(` ⚠️ 缺少 ${spec.install.cmd},无法自动安装 ${spec.key}`));
402
+ for (const resultKey of spec.resultKeys) {
403
+ contextResult(context, resultKey, status);
404
+ }
405
+ return false;
406
+ }
407
+
408
+ console.log(dim(` 正在安装 ${spec.key}...`));
409
+ const installCode = await run(spec.install.cmd, spec.install.args);
410
+ if (installCode !== 0) {
411
+ const status = spec.optional ? 'optional install failed' : 'failed';
412
+ console.log(yellow(` ⚠️ ${spec.key} 自动安装失败`));
413
+ for (const resultKey of spec.resultKeys) {
414
+ contextResult(context, resultKey, status);
415
+ }
416
+ return false;
417
+ }
418
+
419
+ const installOk = await verifySpec();
420
+ const status = installOk ? 'ok' : (spec.optional ? 'optional verify failed' : 'installed (verify failed)');
421
+ if (installOk) {
422
+ console.log(green(` ✅ ${spec.key} 安装成功`));
423
+ } else {
424
+ console.log(yellow(` ⚠️ ${spec.key} 安装后未通过命令校验`));
360
425
  }
426
+ for (const resultKey of spec.resultKeys) {
427
+ contextResult(context, resultKey, status);
428
+ }
429
+ return installOk;
430
+ }
361
431
 
362
- contextResult(context, 'python3', 'reused');
363
- contextResult(context, 'pip', 'reused');
364
- contextResult(context, 'pipx', hasPipx ? 'reused' : 'missing');
432
+ async function resolvePythonRuntime() {
433
+ const python313 = await commandExists('python3.13');
434
+ if (!isWin && python313) {
435
+ return { pythonCommand: 'python3.13', pythonLauncher: null, pipCommand: 'pip3' };
436
+ }
437
+
438
+ const python3 = await commandExists('python3');
439
+ const python = await commandExists('python');
440
+ const py = await commandExists('py');
441
+ const pip3 = await commandExists('pip3');
442
+ const pip = await commandExists('pip');
443
+
444
+ if (isWin) {
445
+ return {
446
+ pythonCommand: python || python3 ? (python ? 'python' : 'python3') : (py ? 'py' : null),
447
+ pythonLauncher: py ? 'py' : null,
448
+ pipCommand: pip ? 'pip' : (pip3 ? 'pip3' : null),
449
+ };
450
+ }
451
+
452
+ return {
453
+ pythonCommand: python3 ? 'python3' : (python ? 'python' : null),
454
+ pythonLauncher: null,
455
+ pipCommand: pip3 ? 'pip3' : (pip ? 'pip' : null),
456
+ };
457
+ }
458
+
459
+ async function ensurePipx(context) {
460
+ if (await commandExists('pipx')) {
461
+ contextResult(context, 'pipx', 'reused');
462
+ return {
463
+ available: true,
464
+ invoke: { cmd: 'pipx', args: [] },
465
+ pythonCommand: (await resolvePythonRuntime()).pythonCommand,
466
+ };
467
+ }
468
+
469
+ const runtime = await resolvePythonRuntime();
470
+ if (!runtime.pythonCommand && !runtime.pythonLauncher) {
471
+ console.log(yellow(' ⚠️ 缺少 Python 运行时,无法安装 pipx'));
472
+ contextResult(context, 'pipx', 'blocked (missing python3)');
473
+ return { available: false, invoke: null, pythonCommand: null };
474
+ }
475
+
476
+ const pipxPlan = getPipxBootstrapPlan({
477
+ platform: process.platform,
478
+ hasBrew: await commandExists('brew'),
479
+ pythonCommand: runtime.pythonCommand || 'python3',
480
+ pythonLauncher: runtime.pythonLauncher,
481
+ });
482
+
483
+ if (!(await commandExists(pipxPlan.install.cmd))) {
484
+ console.log(yellow(` ⚠️ 缺少 ${pipxPlan.install.cmd},无法自动安装 pipx`));
485
+ contextResult(context, 'pipx', `blocked (${pipxPlan.install.cmd} missing)`);
486
+ return { available: false, invoke: null, pythonCommand: runtime.pythonCommand };
487
+ }
488
+
489
+ console.log(dim(' 正在安装 pipx...'));
490
+ const installCode = await run(pipxPlan.install.cmd, pipxPlan.install.args);
491
+ if (installCode !== 0) {
492
+ if (runtime.pipCommand) {
493
+ const classification = classifyPythonInstallFailure('externally-managed-environment');
494
+ console.log(dim(` 提示: ${classification.message}`));
495
+ }
496
+ contextResult(context, 'pipx', 'failed');
497
+ return { available: false, invoke: null, pythonCommand: runtime.pythonCommand };
498
+ }
499
+
500
+ await run(pipxPlan.ensurePath.cmd, pipxPlan.ensurePath.args);
501
+ refreshLocalToolPaths();
502
+ const pipxAvailable = await commandExists('pipx');
503
+ contextResult(context, 'pipx', pipxAvailable ? 'ok' : 'ok (path refresh may be needed)');
504
+ return {
505
+ available: true,
506
+ invoke: pipxAvailable ? { cmd: 'pipx', args: [] } : pipxPlan.invoke,
507
+ pythonCommand: runtime.pythonCommand,
508
+ };
509
+ }
510
+
511
+ async function installPythonCli(context) {
512
+ const pipxState = await ensurePipx(context);
365
513
 
366
514
  if (await commandExists('razel-py-cli')) {
367
515
  console.log(dim(' ↺ 已检测到 razel-py-cli,跳过安装'));
@@ -369,33 +517,32 @@ async function installPythonCli(context) {
369
517
  return;
370
518
  }
371
519
 
372
- if (!hasPipx) {
373
- console.log(yellow(' ⚠️ 未检测到 pipx,建议先安装 pipx 后再安装 razel-py-cli'));
374
- if (hasPip) {
375
- const classification = classifyPythonInstallFailure('externally-managed-environment');
376
- console.log(dim(` 提示: ${classification.message}`));
377
- }
520
+ if (!pipxState.available || !pipxState.invoke) {
521
+ console.log(yellow(' ⚠️ 未检测到 pipx,无法继续安装 razel-py-cli'));
378
522
  contextResult(context, 'razel-py-cli', 'blocked (pipx missing)');
379
523
  return;
380
524
  }
381
525
 
382
- const pipxPython = hasPython313 ? 'python3.13' : 'python3';
383
- if (hasPython313) {
384
- console.log(dim(' 已检测到 python3.13,pipx 将优先使用 python3.13 安装 razel-py-cli'));
385
- } else {
386
- console.log(dim(' 未检测到 python3.13,pipx 将使用默认 python3 安装 razel-py-cli'));
526
+ const pipxArgs = [...pipxState.invoke.args, 'install'];
527
+ if (pipxState.pythonCommand) {
528
+ pipxArgs.push('--python', pipxState.pythonCommand);
387
529
  }
530
+ pipxArgs.push('razel-py-cli');
388
531
 
389
- const pipxCode = await run('pipx', ['install', '--python', pipxPython, 'razel-py-cli']);
532
+ const pipxCode = await run(pipxState.invoke.cmd, pipxArgs);
390
533
  if (pipxCode !== 0) {
391
- console.log(yellow(` ⚠️ razel-py-cli 通过 pipx 安装失败 (python=${pipxPython})`));
534
+ console.log(yellow(` ⚠️ razel-py-cli 通过 pipx 安装失败${pipxState.pythonCommand ? ` (python=${pipxState.pythonCommand})` : ''}`));
392
535
  contextResult(context, 'razel-py-cli', 'failed (pipx)');
393
536
  return;
394
537
  }
395
538
 
396
539
  console.log(green(' ✅ razel-py-cli 通过 pipx 安装成功'));
540
+ refreshLocalToolPaths();
397
541
  console.log(dim(' 正在初始化...'));
398
- const initCode = await run('razel-py-cli', ['init']);
542
+ let initCode = await run('razel-py-cli', ['init']);
543
+ if (initCode !== 0 && pipxState.invoke) {
544
+ initCode = await run(pipxState.invoke.cmd, [...pipxState.invoke.args, 'run', 'razel-py-cli', 'init']);
545
+ }
399
546
  contextResult(context, 'razel-py-cli', initCode === 0 ? 'ok' : 'installed via pipx (init failed)');
400
547
  }
401
548
 
@@ -426,6 +573,10 @@ function printConfigGuidance(missing) {
426
573
  console.log(yellow(' 仍有配置项缺失,可直接使用以下命令继续补齐:'));
427
574
  for (const item of missing) {
428
575
  console.log(dim(` • ${item.title}`));
576
+ if (item.commands.length === 0) {
577
+ console.log(dim(' 当前缺少对应 CLI,暂时无法在配置向导中写入;请先完成核心依赖安装。'));
578
+ continue;
579
+ }
429
580
  for (const command of item.commands) {
430
581
  console.log(cyan(` ${command}`));
431
582
  }
@@ -477,7 +628,7 @@ async function collectInteractiveConfigWrites(missing, options = {}) {
477
628
  };
478
629
 
479
630
  const writes = [];
480
- const hasGroup = (group) => missing.some((item) => item.group === group);
631
+ const hasGroup = (group) => missing.some((item) => item.group === group && item.interactive !== false);
481
632
 
482
633
  console.log(dim(' 检测到关键配置缺失,可现在交互配置(支持跳过)'));
483
634
  const shouldConfigure = await askYesNo(' 现在进入配置向导吗?[y/N] ', true);
@@ -828,6 +979,10 @@ async function runPreflight(context) {
828
979
  }
829
980
 
830
981
  async function runCoreInstall(context) {
982
+ const corePlan = getCoreBootstrapPlan(process.platform);
983
+ for (const spec of corePlan) {
984
+ await installCoreDependency(spec, context);
985
+ }
831
986
  await installPythonCli(context);
832
987
  await ensureCommand('razel-cli', [NPM, 'install', '-g', '@lcap-delivery/razel-cli@latest'], 'razel-cli', context, ['init']);
833
988
  }
@@ -836,10 +991,15 @@ async function runConfigBootstrap(context) {
836
991
  const razelConfigPath = join(homedir(), '.razel', 'config.json');
837
992
  const razelConfig = readJson(razelConfigPath) || {};
838
993
  const openclawConfig = readJson(join(homedir(), '.openclaw', 'openclaw.json'));
994
+ const availableCommands = {
995
+ 'razel-cli': await commandExists('razel-cli'),
996
+ 'razel-py-cli': await commandExists('razel-py-cli'),
997
+ };
839
998
  const plan = buildRazelConfigBootstrap({
840
999
  razelConfig,
841
1000
  env: process.env,
842
1001
  hasOpenclawLlm: Boolean(openclawConfig?.providers || openclawConfig?.model || openclawConfig?.models),
1002
+ availableCommands,
843
1003
  });
844
1004
 
845
1005
  if (plan.writes.length === 0 && plan.missing.length === 0) {
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "codewave-openclaw-installer",
3
- "version": "2.1.9",
3
+ "version": "2.1.11",
4
4
  "description": "网易智企 CodeWave OpenClaw 扩展:Skills + CLI 依赖一键安装",
5
5
  "type": "module",
6
6
  "main": "index.js",
7
7
  "bin": {
8
+ "codewave-openclaw-installer": "bin/install.mjs",
8
9
  "codewave-install": "bin/install.mjs",
9
10
  "codewave-check": "bin/check.mjs"
10
11
  },