@bitsocial/bitsocial-cli 0.19.64 → 0.19.66

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,12 +298,19 @@ $ 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)
313
+ * [`bitsocial community export [ADDRESS]`](#bitsocial-community-export-address)
307
314
  * [`bitsocial community get [ADDRESS]`](#bitsocial-community-get-address)
308
315
  * [`bitsocial community list`](#bitsocial-community-list)
309
316
  * [`bitsocial community start ADDRESSES`](#bitsocial-community-start-addresses)
@@ -315,6 +322,72 @@ $ bitsocial community edit mysub.bso '--roles["author-address.bso"]' null
315
322
  * [`bitsocial update install [VERSION]`](#bitsocial-update-install-version)
316
323
  * [`bitsocial update versions`](#bitsocial-update-versions)
317
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
+
318
391
  ## `bitsocial challenge install PACKAGE`
319
392
 
320
393
  Install a challenge package (npm package name, git URL, tarball URL, or local path)
@@ -332,6 +405,10 @@ FLAGS
332
405
  DESCRIPTION
333
406
  Install a challenge package (npm package name, git URL, tarball URL, or local path)
334
407
 
408
+ ALIASES
409
+ $ bitsocial challenge i
410
+ $ bitsocial challenge add
411
+
335
412
  EXAMPLES
336
413
  $ bitsocial challenge install @bitsocial/mintpass-challenge
337
414
 
@@ -344,7 +421,7 @@ EXAMPLES
344
421
  $ bitsocial challenge install ./my-local-challenge
345
422
  ```
346
423
 
347
- _See code: [src/cli/commands/challenge/install.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.64/src/cli/commands/challenge/install.ts)_
424
+ _See code: [src/cli/commands/challenge/install.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.66/src/cli/commands/challenge/install.ts)_
348
425
 
349
426
  ## `bitsocial challenge list`
350
427
 
@@ -361,13 +438,40 @@ FLAGS
361
438
  DESCRIPTION
362
439
  List installed challenge packages
363
440
 
441
+ ALIASES
442
+ $ bitsocial challenge ls
443
+
364
444
  EXAMPLES
365
445
  $ bitsocial challenge list
366
446
 
367
447
  $ bitsocial challenge list -q
368
448
  ```
369
449
 
370
- _See code: [src/cli/commands/challenge/list.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.64/src/cli/commands/challenge/list.ts)_
450
+ _See code: [src/cli/commands/challenge/list.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.66/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
+ ```
371
475
 
372
476
  ## `bitsocial challenge remove NAME`
373
477
 
@@ -386,13 +490,102 @@ FLAGS
386
490
  DESCRIPTION
387
491
  Remove an installed challenge package
388
492
 
493
+ ALIASES
494
+ $ bitsocial challenge uninstall
495
+ $ bitsocial challenge rm
496
+ $ bitsocial challenge un
497
+
389
498
  EXAMPLES
390
499
  $ bitsocial challenge remove my-challenge
391
500
 
392
501
  $ bitsocial challenge remove @scope/my-challenge
393
502
  ```
394
503
 
395
- _See code: [src/cli/commands/challenge/remove.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.64/src/cli/commands/challenge/remove.ts)_
504
+ _See code: [src/cli/commands/challenge/remove.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.66/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
+
556
+ EXAMPLES
557
+ $ bitsocial challenge remove my-challenge
558
+
559
+ $ bitsocial challenge remove @scope/my-challenge
560
+ ```
561
+
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
+ ```
396
589
 
397
590
  ## `bitsocial community create`
398
591
 
@@ -422,7 +615,7 @@ EXAMPLES
422
615
  $ bitsocial community create --jsonFile ./create-options.json
423
616
  ```
424
617
 
425
- _See code: [src/cli/commands/community/create.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.64/src/cli/commands/community/create.ts)_
618
+ _See code: [src/cli/commands/community/create.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.66/src/cli/commands/community/create.ts)_
426
619
 
427
620
  ## `bitsocial community delete ADDRESSES`
428
621
 
@@ -447,7 +640,7 @@ EXAMPLES
447
640
  $ bitsocial community delete 12D3KooWG3XbzoVyAE6Y9vHZKF64Yuuu4TjdgQKedk14iYmTEPWu
448
641
  ```
449
642
 
450
- _See code: [src/cli/commands/community/delete.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.64/src/cli/commands/community/delete.ts)_
643
+ _See code: [src/cli/commands/community/delete.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.66/src/cli/commands/community/delete.ts)_
451
644
 
452
645
  ## `bitsocial community edit ADDRESS`
453
646
 
@@ -517,7 +710,48 @@ EXAMPLES
517
710
  $ bitsocial community edit bitsocial.bso --jsonFile ./edit-options.json
518
711
  ```
519
712
 
520
- _See code: [src/cli/commands/community/edit.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.64/src/cli/commands/community/edit.ts)_
713
+ _See code: [src/cli/commands/community/edit.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.66/src/cli/commands/community/edit.ts)_
714
+
715
+ ## `bitsocial community export [ADDRESS]`
716
+
717
+ Export a local community to a SQLite snapshot file. The export runs on the RPC server (daemon); once finished the snapshot is downloaded and its sha256 checksum is verified. Pass --includePrivateKey to produce a restorable backup that keeps the community's address.
718
+
719
+ ```
720
+ USAGE
721
+ $ bitsocial community export [ADDRESS] --pkcRpcUrl <value> [--name <value>] [--publicKey <value>] [-o <value>]
722
+ [--includePrivateKey] [--force] [-q]
723
+
724
+ ARGUMENTS
725
+ [ADDRESS] Address of the community to export
726
+
727
+ FLAGS
728
+ -o, --path=<value> Destination file for the downloaded snapshot (default:
729
+ <dataPath>/exports/<address>_<datetime>.sqlite)
730
+ -q, --quiet Suppress progress output; only print the path of the downloaded snapshot
731
+ --force Overwrite the destination file if it already exists
732
+ --includePrivateKey Ask the RPC server to include the community signer's private key in the export. Required for
733
+ a restorable backup that keeps the same community address. The daemon may refuse (see
734
+ `bitsocial daemon --no-allowPrivateKeyExport`)
735
+ --name=<value> Name of the community to export
736
+ --pkcRpcUrl=<value> (required) [default: ws://localhost:9138/] URL to PKC RPC
737
+ --publicKey=<value> Public key of the community to export
738
+
739
+ DESCRIPTION
740
+ Export a local community to a SQLite snapshot file. The export runs on the RPC server (daemon); once finished the
741
+ snapshot is downloaded and its sha256 checksum is verified. Pass --includePrivateKey to produce a restorable backup
742
+ that keeps the community's address.
743
+
744
+ EXAMPLES
745
+ $ bitsocial community export plebmusic.bso
746
+
747
+ $ bitsocial community export plebmusic.bso --includePrivateKey -o ./backups/plebmusic.sqlite
748
+
749
+ $ bitsocial community export --name my-community
750
+
751
+ $ bitsocial community export --publicKey 12D3KooWG3XbzoVyAE6Y9vHZKF64Yuuu4TjdgQKedk14iYmTEPWu
752
+ ```
753
+
754
+ _See code: [src/cli/commands/community/export.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.66/src/cli/commands/community/export.ts)_
521
755
 
522
756
  ## `bitsocial community get [ADDRESS]`
523
757
 
@@ -548,7 +782,7 @@ EXAMPLES
548
782
  $ bitsocial community get --publicKey 12D3KooWG3XbzoVyAE6Y9vHZKF64Yuuu4TjdgQKedk14iYmTEPWu
549
783
  ```
550
784
 
551
- _See code: [src/cli/commands/community/get.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.64/src/cli/commands/community/get.ts)_
785
+ _See code: [src/cli/commands/community/get.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.66/src/cli/commands/community/get.ts)_
552
786
 
553
787
  ## `bitsocial community list`
554
788
 
@@ -571,7 +805,7 @@ EXAMPLES
571
805
  $ bitsocial community list
572
806
  ```
573
807
 
574
- _See code: [src/cli/commands/community/list.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.64/src/cli/commands/community/list.ts)_
808
+ _See code: [src/cli/commands/community/list.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.66/src/cli/commands/community/list.ts)_
575
809
 
576
810
  ## `bitsocial community start ADDRESSES`
577
811
 
@@ -605,7 +839,7 @@ EXAMPLES
605
839
  $ bitsocial community start $(bitsocial community list -q) --concurrency 1
606
840
  ```
607
841
 
608
- _See code: [src/cli/commands/community/start.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.64/src/cli/commands/community/start.ts)_
842
+ _See code: [src/cli/commands/community/start.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.66/src/cli/commands/community/start.ts)_
609
843
 
610
844
  ## `bitsocial community stop ADDRESSES`
611
845
 
@@ -630,7 +864,7 @@ EXAMPLES
630
864
  $ bitsocial community stop Qmb99crTbSUfKXamXwZBe829Vf6w5w5TktPkb6WstC9RFW
631
865
  ```
632
866
 
633
- _See code: [src/cli/commands/community/stop.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.64/src/cli/commands/community/stop.ts)_
867
+ _See code: [src/cli/commands/community/stop.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.66/src/cli/commands/community/stop.ts)_
634
868
 
635
869
  ## `bitsocial daemon`
636
870
 
@@ -639,8 +873,12 @@ Run a network-connected Bitsocial node. Once the daemon is running you can creat
639
873
  ```
640
874
  USAGE
641
875
  $ bitsocial daemon --pkcRpcUrl <value> --logPath <value> [--chainProviderUrls <value>...]
876
+ [--allowPrivateKeyExport]
642
877
 
643
878
  FLAGS
879
+ --[no-]allowPrivateKeyExport Allow RPC clients to request community exports that include the community signer's
880
+ private key (`bitsocial community export --includePrivateKey`). Disable with
881
+ --no-allowPrivateKeyExport when exposing the RPC to untrusted clients
644
882
  --chainProviderUrls=<value>... [default:
645
883
  https://eth.drpc.org,https://ethereum.publicnode.com,https://ethereum-rpc.publicnode.c
646
884
  om,https://rpc.mevblocker.io,https://1rpc.io/eth,https://eth-pokt.nodies.app] RPC
@@ -669,9 +907,11 @@ EXAMPLES
669
907
  $ bitsocial daemon --pkcOptions.kuboRpcClientsOptions[0] https://remoteipfsnode.com
670
908
 
671
909
  $ bitsocial daemon --chainProviderUrls https://mainnet.infura.io/v3/YOUR_KEY
910
+
911
+ $ bitsocial daemon --no-allowPrivateKeyExport
672
912
  ```
673
913
 
674
- _See code: [src/cli/commands/daemon.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.64/src/cli/commands/daemon.ts)_
914
+ _See code: [src/cli/commands/daemon.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.66/src/cli/commands/daemon.ts)_
675
915
 
676
916
  ## `bitsocial help [COMMAND]`
677
917
 
@@ -737,7 +977,7 @@ EXAMPLES
737
977
  $ bitsocial logs --stdout -f
738
978
  ```
739
979
 
740
- _See code: [src/cli/commands/logs.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.64/src/cli/commands/logs.ts)_
980
+ _See code: [src/cli/commands/logs.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.66/src/cli/commands/logs.ts)_
741
981
 
742
982
  ## `bitsocial update check`
743
983
 
@@ -754,7 +994,7 @@ EXAMPLES
754
994
  $ bitsocial update check
755
995
  ```
756
996
 
757
- _See code: [src/cli/commands/update/check.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.64/src/cli/commands/update/check.ts)_
997
+ _See code: [src/cli/commands/update/check.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.66/src/cli/commands/update/check.ts)_
758
998
 
759
999
  ## `bitsocial update install [VERSION]`
760
1000
 
@@ -786,7 +1026,7 @@ EXAMPLES
786
1026
  $ bitsocial update install --no-restart-daemons
787
1027
  ```
788
1028
 
789
- _See code: [src/cli/commands/update/install.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.64/src/cli/commands/update/install.ts)_
1029
+ _See code: [src/cli/commands/update/install.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.66/src/cli/commands/update/install.ts)_
790
1030
 
791
1031
  ## `bitsocial update versions`
792
1032
 
@@ -808,7 +1048,7 @@ EXAMPLES
808
1048
  $ bitsocial update versions --limit 5
809
1049
  ```
810
1050
 
811
- _See code: [src/cli/commands/update/versions.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.64/src/cli/commands/update/versions.ts)_
1051
+ _See code: [src/cli/commands/update/versions.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.66/src/cli/commands/update/versions.ts)_
812
1052
  <!-- commandsstop -->
813
1053
 
814
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 +1,8 @@
1
- export declare function printBanner(): void;
1
+ interface RenderBannerOptions {
2
+ env?: Record<string, string | undefined>;
3
+ forceColor?: boolean;
4
+ stdoutIsTTY?: boolean;
5
+ }
6
+ export declare function renderBanner(options?: RenderBannerOptions): string;
7
+ export declare function printBanner(options?: RenderBannerOptions): void;
8
+ export {};
@@ -5,8 +5,8 @@
5
5
  //
6
6
  // COLORS is the paint map. Each character corresponds 1:1 to the character
7
7
  // at the same position in SHAPE:
8
- // B = blue (#1a4fd0) — the sphere
9
- // S = silver (#e5e7eb) — the rings and the "Bitsocial" text
8
+ // B = blue accent — the sphere
9
+ // S = default foreground — the rings and the "Bitsocial" text
10
10
  // . = no color (pass the glyph through as-is; use this for spaces)
11
11
  //
12
12
  // To retouch the art, find a glyph in SHAPE, then flip the character at the
@@ -15,7 +15,8 @@
15
15
  //
16
16
  // Both grids MUST have the same number of rows. Each row in COLORS must be at
17
17
  // least as wide as the corresponding SHAPE row (extra chars are ignored).
18
- // Palette sourced from bitsocialnet/bitsocial-web/about/tailwind.config.ts.
18
+ // Use the terminal's default foreground for the wordmark/rings so the banner
19
+ // stays readable on both light and dark terminal themes.
19
20
  const SHAPE = [
20
21
  " ⢀⣴⣿⣿⣦⡀ ",
21
22
  " ⣾⣿⠁⠈⣿⣷⡀ ",
@@ -56,39 +57,45 @@ const COLORS = [
56
57
  "...............SSSSSSSS......................................................................................",
57
58
  "................SSSSSS......................................................................................."
58
59
  ];
59
- const BLUE = "\x1b[38;2;26;79;208m";
60
- const SILVER = "\x1b[38;2;229;231;235m";
61
- const RESET = "\x1b[0m";
60
+ const BLUE = "\x1b[94m";
61
+ const DEFAULT_FOREGROUND = "\x1b[39m";
62
62
  function paint(shape, colors) {
63
63
  let out = "";
64
- let current = ".";
64
+ let blueActive = false;
65
65
  for (let i = 0; i < shape.length; i++) {
66
66
  const glyph = shape[i];
67
67
  const want = colors[i] ?? ".";
68
- if (want !== current) {
69
- if (current !== ".")
70
- out += RESET;
71
- if (want === "B")
72
- out += BLUE;
73
- else if (want === "S")
74
- out += SILVER;
75
- current = want;
68
+ const wantBlue = want === "B";
69
+ if (wantBlue !== blueActive) {
70
+ out += wantBlue ? BLUE : DEFAULT_FOREGROUND;
71
+ blueActive = wantBlue;
76
72
  }
77
73
  out += glyph;
78
74
  }
79
- if (current !== ".")
80
- out += RESET;
75
+ if (blueActive)
76
+ out += DEFAULT_FOREGROUND;
81
77
  return out;
82
78
  }
83
- function supportsColor() {
84
- if (process.env["NO_COLOR"])
79
+ function envForcesColor(value) {
80
+ if (value === undefined)
85
81
  return false;
86
- if (process.env["FORCE_COLOR"])
82
+ return value !== "0" && value.toLowerCase() !== "false";
83
+ }
84
+ function supportsColor(options = {}) {
85
+ const env = options.env ?? process.env;
86
+ if (env["NO_COLOR"] !== undefined)
87
+ return false;
88
+ if (options.forceColor)
87
89
  return true;
88
- return Boolean(process.stdout.isTTY);
90
+ if (env["FORCE_COLOR"] !== undefined)
91
+ return envForcesColor(env["FORCE_COLOR"]);
92
+ return Boolean(options.stdoutIsTTY ?? process.stdout.isTTY);
89
93
  }
90
- export function printBanner() {
91
- const useColor = supportsColor();
94
+ export function renderBanner(options = {}) {
95
+ const useColor = supportsColor(options);
92
96
  const lines = SHAPE.map((row, i) => (useColor ? paint(row, COLORS[i] ?? "") : row));
93
- process.stdout.write(lines.join("\n") + "\n\n");
97
+ return lines.join("\n") + "\n\n";
98
+ }
99
+ export function printBanner(options = {}) {
100
+ process.stdout.write(renderBanner(options));
94
101
  }
@@ -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
  }