@bitsocial/bitsocial-cli 0.19.65 → 0.19.67

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/README.md CHANGED
@@ -298,9 +298,15 @@ $ bitsocial community edit mysub.bso '--roles["author-address.bso"]' null
298
298
  ## Commands
299
299
 
300
300
  <!-- commands -->
301
+ * [`bitsocial challenge add PACKAGE`](#bitsocial-challenge-add-package)
302
+ * [`bitsocial challenge i PACKAGE`](#bitsocial-challenge-i-package)
301
303
  * [`bitsocial challenge install PACKAGE`](#bitsocial-challenge-install-package)
302
304
  * [`bitsocial challenge list`](#bitsocial-challenge-list)
305
+ * [`bitsocial challenge ls`](#bitsocial-challenge-ls)
303
306
  * [`bitsocial challenge remove NAME`](#bitsocial-challenge-remove-name)
307
+ * [`bitsocial challenge rm NAME`](#bitsocial-challenge-rm-name)
308
+ * [`bitsocial challenge un NAME`](#bitsocial-challenge-un-name)
309
+ * [`bitsocial challenge uninstall NAME`](#bitsocial-challenge-uninstall-name)
304
310
  * [`bitsocial community create`](#bitsocial-community-create)
305
311
  * [`bitsocial community delete ADDRESSES`](#bitsocial-community-delete-addresses)
306
312
  * [`bitsocial community edit ADDRESS`](#bitsocial-community-edit-address)
@@ -316,6 +322,72 @@ $ bitsocial community edit mysub.bso '--roles["author-address.bso"]' null
316
322
  * [`bitsocial update install [VERSION]`](#bitsocial-update-install-version)
317
323
  * [`bitsocial update versions`](#bitsocial-update-versions)
318
324
 
325
+ ## `bitsocial challenge add PACKAGE`
326
+
327
+ Install a challenge package (npm package name, git URL, tarball URL, or local path)
328
+
329
+ ```
330
+ USAGE
331
+ $ bitsocial challenge add PACKAGE [--pkcOptions.dataPath <value>]
332
+
333
+ ARGUMENTS
334
+ PACKAGE Package specifier — anything npm can install (name, name@version, git URL, tarball URL, local path)
335
+
336
+ FLAGS
337
+ --pkcOptions.dataPath=<value> Data path to install the challenge into
338
+
339
+ DESCRIPTION
340
+ Install a challenge package (npm package name, git URL, tarball URL, or local path)
341
+
342
+ ALIASES
343
+ $ bitsocial challenge i
344
+ $ bitsocial challenge add
345
+
346
+ EXAMPLES
347
+ $ bitsocial challenge install @bitsocial/mintpass-challenge
348
+
349
+ $ bitsocial challenge install @bitsocial/mintpass-challenge@1.0.0
350
+
351
+ $ bitsocial challenge install github:user/repo
352
+
353
+ $ bitsocial challenge install https://example.com/my-challenge-1.0.0.tar.gz
354
+
355
+ $ bitsocial challenge install ./my-local-challenge
356
+ ```
357
+
358
+ ## `bitsocial challenge i PACKAGE`
359
+
360
+ Install a challenge package (npm package name, git URL, tarball URL, or local path)
361
+
362
+ ```
363
+ USAGE
364
+ $ bitsocial challenge i PACKAGE [--pkcOptions.dataPath <value>]
365
+
366
+ ARGUMENTS
367
+ PACKAGE Package specifier — anything npm can install (name, name@version, git URL, tarball URL, local path)
368
+
369
+ FLAGS
370
+ --pkcOptions.dataPath=<value> Data path to install the challenge into
371
+
372
+ DESCRIPTION
373
+ Install a challenge package (npm package name, git URL, tarball URL, or local path)
374
+
375
+ ALIASES
376
+ $ bitsocial challenge i
377
+ $ bitsocial challenge add
378
+
379
+ EXAMPLES
380
+ $ bitsocial challenge install @bitsocial/mintpass-challenge
381
+
382
+ $ bitsocial challenge install @bitsocial/mintpass-challenge@1.0.0
383
+
384
+ $ bitsocial challenge install github:user/repo
385
+
386
+ $ bitsocial challenge install https://example.com/my-challenge-1.0.0.tar.gz
387
+
388
+ $ bitsocial challenge install ./my-local-challenge
389
+ ```
390
+
319
391
  ## `bitsocial challenge install PACKAGE`
320
392
 
321
393
  Install a challenge package (npm package name, git URL, tarball URL, or local path)
@@ -333,6 +405,10 @@ FLAGS
333
405
  DESCRIPTION
334
406
  Install a challenge package (npm package name, git URL, tarball URL, or local path)
335
407
 
408
+ ALIASES
409
+ $ bitsocial challenge i
410
+ $ bitsocial challenge add
411
+
336
412
  EXAMPLES
337
413
  $ bitsocial challenge install @bitsocial/mintpass-challenge
338
414
 
@@ -345,7 +421,7 @@ EXAMPLES
345
421
  $ bitsocial challenge install ./my-local-challenge
346
422
  ```
347
423
 
348
- _See code: [src/cli/commands/challenge/install.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.65/src/cli/commands/challenge/install.ts)_
424
+ _See code: [src/cli/commands/challenge/install.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.67/src/cli/commands/challenge/install.ts)_
349
425
 
350
426
  ## `bitsocial challenge list`
351
427
 
@@ -362,13 +438,40 @@ FLAGS
362
438
  DESCRIPTION
363
439
  List installed challenge packages
364
440
 
441
+ ALIASES
442
+ $ bitsocial challenge ls
443
+
365
444
  EXAMPLES
366
445
  $ bitsocial challenge list
367
446
 
368
447
  $ bitsocial challenge list -q
369
448
  ```
370
449
 
371
- _See code: [src/cli/commands/challenge/list.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.65/src/cli/commands/challenge/list.ts)_
450
+ _See code: [src/cli/commands/challenge/list.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.67/src/cli/commands/challenge/list.ts)_
451
+
452
+ ## `bitsocial challenge ls`
453
+
454
+ List installed challenge packages
455
+
456
+ ```
457
+ USAGE
458
+ $ bitsocial challenge ls [-q] [--pkcOptions.dataPath <value>]
459
+
460
+ FLAGS
461
+ -q, --quiet Only display challenge names
462
+ --pkcOptions.dataPath=<value> Data path where challenges are installed
463
+
464
+ DESCRIPTION
465
+ List installed challenge packages
466
+
467
+ ALIASES
468
+ $ bitsocial challenge ls
469
+
470
+ EXAMPLES
471
+ $ bitsocial challenge list
472
+
473
+ $ bitsocial challenge list -q
474
+ ```
372
475
 
373
476
  ## `bitsocial challenge remove NAME`
374
477
 
@@ -387,13 +490,102 @@ FLAGS
387
490
  DESCRIPTION
388
491
  Remove an installed challenge package
389
492
 
493
+ ALIASES
494
+ $ bitsocial challenge uninstall
495
+ $ bitsocial challenge rm
496
+ $ bitsocial challenge un
497
+
498
+ EXAMPLES
499
+ $ bitsocial challenge remove my-challenge
500
+
501
+ $ bitsocial challenge remove @scope/my-challenge
502
+ ```
503
+
504
+ _See code: [src/cli/commands/challenge/remove.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.67/src/cli/commands/challenge/remove.ts)_
505
+
506
+ ## `bitsocial challenge rm NAME`
507
+
508
+ Remove an installed challenge package
509
+
510
+ ```
511
+ USAGE
512
+ $ bitsocial challenge rm NAME [--pkcOptions.dataPath <value>]
513
+
514
+ ARGUMENTS
515
+ NAME The challenge package name (e.g., my-challenge or @scope/my-challenge)
516
+
517
+ FLAGS
518
+ --pkcOptions.dataPath=<value> Data path where challenges are installed
519
+
520
+ DESCRIPTION
521
+ Remove an installed challenge package
522
+
523
+ ALIASES
524
+ $ bitsocial challenge uninstall
525
+ $ bitsocial challenge rm
526
+ $ bitsocial challenge un
527
+
528
+ EXAMPLES
529
+ $ bitsocial challenge remove my-challenge
530
+
531
+ $ bitsocial challenge remove @scope/my-challenge
532
+ ```
533
+
534
+ ## `bitsocial challenge un NAME`
535
+
536
+ Remove an installed challenge package
537
+
538
+ ```
539
+ USAGE
540
+ $ bitsocial challenge un NAME [--pkcOptions.dataPath <value>]
541
+
542
+ ARGUMENTS
543
+ NAME The challenge package name (e.g., my-challenge or @scope/my-challenge)
544
+
545
+ FLAGS
546
+ --pkcOptions.dataPath=<value> Data path where challenges are installed
547
+
548
+ DESCRIPTION
549
+ Remove an installed challenge package
550
+
551
+ ALIASES
552
+ $ bitsocial challenge uninstall
553
+ $ bitsocial challenge rm
554
+ $ bitsocial challenge un
555
+
390
556
  EXAMPLES
391
557
  $ bitsocial challenge remove my-challenge
392
558
 
393
559
  $ bitsocial challenge remove @scope/my-challenge
394
560
  ```
395
561
 
396
- _See code: [src/cli/commands/challenge/remove.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.65/src/cli/commands/challenge/remove.ts)_
562
+ ## `bitsocial challenge uninstall NAME`
563
+
564
+ Remove an installed challenge package
565
+
566
+ ```
567
+ USAGE
568
+ $ bitsocial challenge uninstall NAME [--pkcOptions.dataPath <value>]
569
+
570
+ ARGUMENTS
571
+ NAME The challenge package name (e.g., my-challenge or @scope/my-challenge)
572
+
573
+ FLAGS
574
+ --pkcOptions.dataPath=<value> Data path where challenges are installed
575
+
576
+ DESCRIPTION
577
+ Remove an installed challenge package
578
+
579
+ ALIASES
580
+ $ bitsocial challenge uninstall
581
+ $ bitsocial challenge rm
582
+ $ bitsocial challenge un
583
+
584
+ EXAMPLES
585
+ $ bitsocial challenge remove my-challenge
586
+
587
+ $ bitsocial challenge remove @scope/my-challenge
588
+ ```
397
589
 
398
590
  ## `bitsocial community create`
399
591
 
@@ -423,7 +615,7 @@ EXAMPLES
423
615
  $ bitsocial community create --jsonFile ./create-options.json
424
616
  ```
425
617
 
426
- _See code: [src/cli/commands/community/create.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.65/src/cli/commands/community/create.ts)_
618
+ _See code: [src/cli/commands/community/create.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.67/src/cli/commands/community/create.ts)_
427
619
 
428
620
  ## `bitsocial community delete ADDRESSES`
429
621
 
@@ -448,7 +640,7 @@ EXAMPLES
448
640
  $ bitsocial community delete 12D3KooWG3XbzoVyAE6Y9vHZKF64Yuuu4TjdgQKedk14iYmTEPWu
449
641
  ```
450
642
 
451
- _See code: [src/cli/commands/community/delete.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.65/src/cli/commands/community/delete.ts)_
643
+ _See code: [src/cli/commands/community/delete.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.67/src/cli/commands/community/delete.ts)_
452
644
 
453
645
  ## `bitsocial community edit ADDRESS`
454
646
 
@@ -518,7 +710,7 @@ EXAMPLES
518
710
  $ bitsocial community edit bitsocial.bso --jsonFile ./edit-options.json
519
711
  ```
520
712
 
521
- _See code: [src/cli/commands/community/edit.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.65/src/cli/commands/community/edit.ts)_
713
+ _See code: [src/cli/commands/community/edit.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.67/src/cli/commands/community/edit.ts)_
522
714
 
523
715
  ## `bitsocial community export [ADDRESS]`
524
716
 
@@ -559,7 +751,7 @@ EXAMPLES
559
751
  $ bitsocial community export --publicKey 12D3KooWG3XbzoVyAE6Y9vHZKF64Yuuu4TjdgQKedk14iYmTEPWu
560
752
  ```
561
753
 
562
- _See code: [src/cli/commands/community/export.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.65/src/cli/commands/community/export.ts)_
754
+ _See code: [src/cli/commands/community/export.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.67/src/cli/commands/community/export.ts)_
563
755
 
564
756
  ## `bitsocial community get [ADDRESS]`
565
757
 
@@ -590,7 +782,7 @@ EXAMPLES
590
782
  $ bitsocial community get --publicKey 12D3KooWG3XbzoVyAE6Y9vHZKF64Yuuu4TjdgQKedk14iYmTEPWu
591
783
  ```
592
784
 
593
- _See code: [src/cli/commands/community/get.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.65/src/cli/commands/community/get.ts)_
785
+ _See code: [src/cli/commands/community/get.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.67/src/cli/commands/community/get.ts)_
594
786
 
595
787
  ## `bitsocial community list`
596
788
 
@@ -613,7 +805,7 @@ EXAMPLES
613
805
  $ bitsocial community list
614
806
  ```
615
807
 
616
- _See code: [src/cli/commands/community/list.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.65/src/cli/commands/community/list.ts)_
808
+ _See code: [src/cli/commands/community/list.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.67/src/cli/commands/community/list.ts)_
617
809
 
618
810
  ## `bitsocial community start ADDRESSES`
619
811
 
@@ -647,7 +839,7 @@ EXAMPLES
647
839
  $ bitsocial community start $(bitsocial community list -q) --concurrency 1
648
840
  ```
649
841
 
650
- _See code: [src/cli/commands/community/start.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.65/src/cli/commands/community/start.ts)_
842
+ _See code: [src/cli/commands/community/start.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.67/src/cli/commands/community/start.ts)_
651
843
 
652
844
  ## `bitsocial community stop ADDRESSES`
653
845
 
@@ -672,7 +864,7 @@ EXAMPLES
672
864
  $ bitsocial community stop Qmb99crTbSUfKXamXwZBe829Vf6w5w5TktPkb6WstC9RFW
673
865
  ```
674
866
 
675
- _See code: [src/cli/commands/community/stop.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.65/src/cli/commands/community/stop.ts)_
867
+ _See code: [src/cli/commands/community/stop.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.67/src/cli/commands/community/stop.ts)_
676
868
 
677
869
  ## `bitsocial daemon`
678
870
 
@@ -719,7 +911,7 @@ EXAMPLES
719
911
  $ bitsocial daemon --no-allowPrivateKeyExport
720
912
  ```
721
913
 
722
- _See code: [src/cli/commands/daemon.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.65/src/cli/commands/daemon.ts)_
914
+ _See code: [src/cli/commands/daemon.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.67/src/cli/commands/daemon.ts)_
723
915
 
724
916
  ## `bitsocial help [COMMAND]`
725
917
 
@@ -785,7 +977,7 @@ EXAMPLES
785
977
  $ bitsocial logs --stdout -f
786
978
  ```
787
979
 
788
- _See code: [src/cli/commands/logs.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.65/src/cli/commands/logs.ts)_
980
+ _See code: [src/cli/commands/logs.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.67/src/cli/commands/logs.ts)_
789
981
 
790
982
  ## `bitsocial update check`
791
983
 
@@ -802,7 +994,7 @@ EXAMPLES
802
994
  $ bitsocial update check
803
995
  ```
804
996
 
805
- _See code: [src/cli/commands/update/check.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.65/src/cli/commands/update/check.ts)_
997
+ _See code: [src/cli/commands/update/check.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.67/src/cli/commands/update/check.ts)_
806
998
 
807
999
  ## `bitsocial update install [VERSION]`
808
1000
 
@@ -834,7 +1026,7 @@ EXAMPLES
834
1026
  $ bitsocial update install --no-restart-daemons
835
1027
  ```
836
1028
 
837
- _See code: [src/cli/commands/update/install.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.65/src/cli/commands/update/install.ts)_
1029
+ _See code: [src/cli/commands/update/install.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.67/src/cli/commands/update/install.ts)_
838
1030
 
839
1031
  ## `bitsocial update versions`
840
1032
 
@@ -856,7 +1048,7 @@ EXAMPLES
856
1048
  $ bitsocial update versions --limit 5
857
1049
  ```
858
1050
 
859
- _See code: [src/cli/commands/update/versions.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.65/src/cli/commands/update/versions.ts)_
1051
+ _See code: [src/cli/commands/update/versions.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.67/src/cli/commands/update/versions.ts)_
860
1052
  <!-- commandsstop -->
861
1053
 
862
1054
  ## Contribution
@@ -21,4 +21,5 @@ export declare function ensureNpmAvailable(): Promise<void>;
21
21
  export declare function runNpmPack(packageSpec: string, destDir: string): Promise<string>;
22
22
  export declare function runNpmInstall(challengeDir: string): Promise<void>;
23
23
  export declare function verifyNativeModuleAbi(challengeDir: string): Promise<void>;
24
- export declare function loadChallengesIntoPKC(dataPath?: string): Promise<string[]>;
24
+ export declare function formatChallengeNameVersion(challenge: Pick<InstalledChallenge, "name" | "version">): string;
25
+ export declare function loadChallengesIntoPKC(dataPath?: string): Promise<InstalledChallenge[]>;
@@ -285,12 +285,15 @@ export async function verifyNativeModuleAbi(challengeDir) {
285
285
  `Ensure the challenge package was built for this Node.js version.`);
286
286
  }
287
287
  }
288
+ export function formatChallengeNameVersion(challenge) {
289
+ return challenge.version && challenge.version !== "unknown" ? `${challenge.name}@${challenge.version}` : challenge.name;
290
+ }
288
291
  export async function loadChallengesIntoPKC(dataPath) {
289
292
  const challenges = await listInstalledChallenges(dataPath);
290
293
  if (challenges.length === 0)
291
294
  return [];
292
295
  const PKC = await import("@pkcprotocol/pkc-js");
293
- const loadedNames = [];
296
+ const loaded = [];
294
297
  for (const challenge of challenges) {
295
298
  try {
296
299
  const pkg = await readChallengePackageJson(challenge.path);
@@ -300,11 +303,11 @@ export async function loadChallengesIntoPKC(dataPath) {
300
303
  const imported = await import(pathToFileURL(entryPath).href);
301
304
  const factory = imported.default || imported;
302
305
  PKC.default.challenges[challenge.name] = factory;
303
- loadedNames.push(challenge.name);
306
+ loaded.push(challenge);
304
307
  }
305
308
  catch (err) {
306
309
  console.error(`Failed to load challenge "${challenge.name}":`, err);
307
310
  }
308
311
  }
309
- return loadedNames;
312
+ return loaded;
310
313
  }
@@ -1,6 +1,7 @@
1
1
  import { Command } from "@oclif/core";
2
2
  export default class Install extends Command {
3
3
  static description: string;
4
+ static aliases: string[];
4
5
  static args: {
5
6
  package: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
6
7
  };
@@ -6,6 +6,7 @@ import defaults from "../../../common-utils/defaults.js";
6
6
  import { ensureNpmAvailable, ensureChallengesDir, challengeNameToDir, readChallengePackageJson, runNpmPack, runNpmInstall, verifyNativeModuleAbi } from "../../../challenge-packages/challenge-utils.js";
7
7
  export default class Install extends Command {
8
8
  static description = "Install a challenge package (npm package name, git URL, tarball URL, or local path)";
9
+ static aliases = ["challenge:i", "challenge:add"];
9
10
  static args = {
10
11
  package: Args.string({
11
12
  description: "Package specifier — anything npm can install (name, name@version, git URL, tarball URL, local path)",
@@ -26,9 +27,9 @@ export default class Install extends Command {
26
27
  "bitsocial challenge install ./my-local-challenge"
27
28
  ];
28
29
  async run() {
30
+ const startTime = Date.now();
29
31
  const { args, flags } = await this.parse(Install);
30
32
  const dataPath = flags["pkcOptions.dataPath"] || defaults.PKC_DATA_PATH;
31
- this.log("Installing challenge package — this may take a few minutes...");
32
33
  // 1. Check npm is available
33
34
  await ensureNpmAvailable();
34
35
  // 2. Use npm pack to download the package as a tarball
@@ -68,7 +69,16 @@ export default class Install extends Command {
68
69
  }
69
70
  // 5. Read package info
70
71
  const pkg = await readChallengePackageJson(pkgDir);
71
- // 6. Check not already installed
72
+ // 6. Run npm install in the temp dir, so a failed install never
73
+ // touches an existing working installation of the same package
74
+ process.stderr.write(`[challenge-install] starting npm install in ${pkgDir}\n`);
75
+ await runNpmInstall(pkgDir);
76
+ process.stderr.write("[challenge-install] npm install completed\n");
77
+ // 7. Verify native modules are ABI-compatible (still in the temp dir,
78
+ // so failure needs no rollback — the temp dir is cleaned up below)
79
+ await verifyNativeModuleAbi(pkgDir);
80
+ // 8. Swap the verified package into the challenges dir, replacing any
81
+ // existing installation (idempotent, like npm install)
72
82
  const challengesDir = await ensureChallengesDir(dataPath);
73
83
  const destDir = challengeNameToDir(challengesDir, pkg.name);
74
84
  let alreadyExists = true;
@@ -78,44 +88,30 @@ export default class Install extends Command {
78
88
  catch {
79
89
  alreadyExists = false;
80
90
  }
81
- if (alreadyExists) {
82
- this.error(`Challenge "${pkg.name}" is already installed. Remove it first with: bitsocial challenge remove ${pkg.name}`);
83
- }
84
- // 7. Move to challenges dir
91
+ // Move any existing install aside (same filesystem — tmpDir lives under
92
+ // dataPath) so it can be restored if the final rename fails
93
+ const backupDir = path.join(tmpDir, "previous-install");
94
+ if (alreadyExists)
95
+ await fs.rename(destDir, backupDir);
85
96
  if (pkg.name.startsWith("@")) {
86
97
  // Ensure scope dir exists for scoped packages
87
98
  const scopeDir = path.dirname(destDir);
88
99
  await fs.mkdir(scopeDir, { recursive: true });
89
100
  }
90
- await fs.rename(pkgDir, destDir);
91
- // 8. Run npm install
92
- process.stderr.write(`[challenge-install] starting npm install in ${destDir}\n`);
93
- await runNpmInstall(destDir);
94
- process.stderr.write("[challenge-install] npm install completed\n");
95
- // 9. Verify native modules are ABI-compatible
96
101
  try {
97
- await verifyNativeModuleAbi(destDir);
102
+ await fs.rename(pkgDir, destDir);
98
103
  }
99
104
  catch (err) {
100
- // Roll back the installation on ABI mismatch
101
- await fs.rm(destDir, { recursive: true, force: true });
102
- if (pkg.name.startsWith("@")) {
103
- const scopeDir = path.dirname(destDir);
104
- try {
105
- const entries = await fs.readdir(scopeDir);
106
- if (entries.length === 0)
107
- await fs.rmdir(scopeDir);
108
- }
109
- catch {
110
- // ignore
111
- }
112
- }
113
- this.error(err instanceof Error ? err.message : String(err));
105
+ // Restore the previous installation before surfacing the error
106
+ if (alreadyExists)
107
+ await fs.rename(backupDir, destDir);
108
+ throw err;
114
109
  }
115
- // 10. Print success
110
+ // 9. Print success (npm-style)
116
111
  const version = pkg.version ? `@${pkg.version}` : "";
117
- this.log(`Installed challenge '${pkg.name}${version}'`);
118
- // 11. Best-effort reload via daemon
112
+ const elapsedSeconds = Math.max(1, Math.round((Date.now() - startTime) / 1000));
113
+ this.log(`${alreadyExists ? "changed" : "added"} ${pkg.name}${version} in ${elapsedSeconds}s`);
114
+ // 10. Best-effort reload via daemon
119
115
  try {
120
116
  await fetch("http://localhost:9138/api/challenges/reload", { method: "POST" });
121
117
  }
@@ -124,7 +120,7 @@ export default class Install extends Command {
124
120
  }
125
121
  }
126
122
  finally {
127
- // 12. Clean up temp dir
123
+ // 11. Clean up temp dir (includes the previous-install backup, if any)
128
124
  await fs.rm(tmpDir, { recursive: true, force: true });
129
125
  }
130
126
  }
@@ -1,6 +1,7 @@
1
1
  import { Command } from "@oclif/core";
2
2
  export default class List extends Command {
3
3
  static description: string;
4
+ static aliases: string[];
4
5
  static flags: {
5
6
  quiet: import("@oclif/core/interfaces").BooleanFlag<boolean>;
6
7
  "pkcOptions.dataPath": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
@@ -1,10 +1,11 @@
1
1
  import { Flags, Command } from "@oclif/core";
2
2
  import { EOL } from "os";
3
- import { printTable } from "@oclif/table";
3
+ import path from "path";
4
4
  import defaults from "../../../common-utils/defaults.js";
5
- import { listInstalledChallenges } from "../../../challenge-packages/challenge-utils.js";
5
+ import { getChallengesDir, listInstalledChallenges, formatChallengeNameVersion } from "../../../challenge-packages/challenge-utils.js";
6
6
  export default class List extends Command {
7
7
  static description = "List installed challenge packages";
8
+ static aliases = ["challenge:ls"];
8
9
  static flags = {
9
10
  quiet: Flags.boolean({ char: "q", summary: "Only display challenge names" }),
10
11
  "pkcOptions.dataPath": Flags.directory({
@@ -16,7 +17,8 @@ export default class List extends Command {
16
17
  async run() {
17
18
  const { flags } = await this.parse(List);
18
19
  const dataPath = flags["pkcOptions.dataPath"] || defaults.PKC_DATA_PATH;
19
- const challenges = await listInstalledChallenges(dataPath);
20
+ // Sort alphabetically like npm ls (readdir order is filesystem-dependent)
21
+ const challenges = (await listInstalledChallenges(dataPath)).sort((a, b) => a.name.localeCompare(b.name));
20
22
  if (challenges.length === 0) {
21
23
  this.log("No challenge packages installed.");
22
24
  return;
@@ -25,12 +27,11 @@ export default class List extends Command {
25
27
  this.log(challenges.map((c) => c.name).join(EOL));
26
28
  }
27
29
  else {
28
- printTable({
29
- data: challenges.map((c) => ({
30
- name: c.name,
31
- version: c.version,
32
- description: c.description
33
- }))
30
+ // npm-ls-style tree: challenges dir header, then name@version entries
31
+ this.log(path.resolve(getChallengesDir(dataPath)));
32
+ challenges.forEach((c, i) => {
33
+ const branch = i === challenges.length - 1 ? "└── " : "├── ";
34
+ this.log(`${branch}${formatChallengeNameVersion(c)}`);
34
35
  });
35
36
  }
36
37
  }
@@ -1,6 +1,7 @@
1
1
  import { Command } from "@oclif/core";
2
2
  export default class Remove extends Command {
3
3
  static description: string;
4
+ static aliases: string[];
4
5
  static args: {
5
6
  name: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
6
7
  };
@@ -2,9 +2,10 @@ import { Args, Flags, Command } from "@oclif/core";
2
2
  import fs from "fs/promises";
3
3
  import path from "path";
4
4
  import defaults from "../../../common-utils/defaults.js";
5
- import { getChallengesDir, challengeNameToDir } from "../../../challenge-packages/challenge-utils.js";
5
+ import { getChallengesDir, challengeNameToDir, readChallengePackageJson } from "../../../challenge-packages/challenge-utils.js";
6
6
  export default class Remove extends Command {
7
7
  static description = "Remove an installed challenge package";
8
+ static aliases = ["challenge:uninstall", "challenge:rm", "challenge:un"];
8
9
  static args = {
9
10
  name: Args.string({
10
11
  description: "The challenge package name (e.g., my-challenge or @scope/my-challenge)",
@@ -33,6 +34,16 @@ export default class Remove extends Command {
33
34
  catch {
34
35
  this.error(`Challenge "${args.name}" is not installed.`);
35
36
  }
37
+ // Read the installed version for the success message (best-effort)
38
+ let version = "";
39
+ try {
40
+ const pkg = await readChallengePackageJson(challengeDir);
41
+ if (pkg.version)
42
+ version = `@${pkg.version}`;
43
+ }
44
+ catch {
45
+ // unreadable package.json — report the name only
46
+ }
36
47
  // Remove the challenge directory
37
48
  await fs.rm(challengeDir, { recursive: true, force: true });
38
49
  // Clean up empty @scope/ dir for scoped packages
@@ -48,7 +59,7 @@ export default class Remove extends Command {
48
59
  // ignore
49
60
  }
50
61
  }
51
- this.log(`Removed challenge '${args.name}'`);
62
+ this.log(`removed ${args.name}${version}`);
52
63
  // Best-effort reload via daemon
53
64
  try {
54
65
  await fetch("http://localhost:9138/api/challenges/reload", { method: "POST" });