@bitsocial/bitsocial-cli 0.19.88 → 0.19.90

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
@@ -328,13 +328,14 @@ Install a challenge package (npm package name, git URL, tarball URL, or local pa
328
328
 
329
329
  ```
330
330
  USAGE
331
- $ bitsocial challenge add PACKAGE [--pkcOptions.dataPath <value>]
331
+ $ bitsocial challenge add PACKAGE --pkcRpcUrl <value> [--pkcOptions.dataPath <value>]
332
332
 
333
333
  ARGUMENTS
334
334
  PACKAGE Package specifier — anything npm can install (name, name@version, git URL, tarball URL, local path)
335
335
 
336
336
  FLAGS
337
337
  --pkcOptions.dataPath=<value> Data path to install the challenge into
338
+ --pkcRpcUrl=<value> (required) [default: ws://localhost:9138/] URL to PKC RPC
338
339
 
339
340
  DESCRIPTION
340
341
  Install a challenge package (npm package name, git URL, tarball URL, or local path)
@@ -361,13 +362,14 @@ Install a challenge package (npm package name, git URL, tarball URL, or local pa
361
362
 
362
363
  ```
363
364
  USAGE
364
- $ bitsocial challenge i PACKAGE [--pkcOptions.dataPath <value>]
365
+ $ bitsocial challenge i PACKAGE --pkcRpcUrl <value> [--pkcOptions.dataPath <value>]
365
366
 
366
367
  ARGUMENTS
367
368
  PACKAGE Package specifier — anything npm can install (name, name@version, git URL, tarball URL, local path)
368
369
 
369
370
  FLAGS
370
371
  --pkcOptions.dataPath=<value> Data path to install the challenge into
372
+ --pkcRpcUrl=<value> (required) [default: ws://localhost:9138/] URL to PKC RPC
371
373
 
372
374
  DESCRIPTION
373
375
  Install a challenge package (npm package name, git URL, tarball URL, or local path)
@@ -394,13 +396,14 @@ Install a challenge package (npm package name, git URL, tarball URL, or local pa
394
396
 
395
397
  ```
396
398
  USAGE
397
- $ bitsocial challenge install PACKAGE [--pkcOptions.dataPath <value>]
399
+ $ bitsocial challenge install PACKAGE --pkcRpcUrl <value> [--pkcOptions.dataPath <value>]
398
400
 
399
401
  ARGUMENTS
400
402
  PACKAGE Package specifier — anything npm can install (name, name@version, git URL, tarball URL, local path)
401
403
 
402
404
  FLAGS
403
405
  --pkcOptions.dataPath=<value> Data path to install the challenge into
406
+ --pkcRpcUrl=<value> (required) [default: ws://localhost:9138/] URL to PKC RPC
404
407
 
405
408
  DESCRIPTION
406
409
  Install a challenge package (npm package name, git URL, tarball URL, or local path)
@@ -421,7 +424,7 @@ EXAMPLES
421
424
  $ bitsocial challenge install ./my-local-challenge
422
425
  ```
423
426
 
424
- _See code: [src/cli/commands/challenge/install.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.88/src/cli/commands/challenge/install.ts)_
427
+ _See code: [src/cli/commands/challenge/install.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.90/src/cli/commands/challenge/install.ts)_
425
428
 
426
429
  ## `bitsocial challenge list`
427
430
 
@@ -447,7 +450,7 @@ EXAMPLES
447
450
  $ bitsocial challenge list -q
448
451
  ```
449
452
 
450
- _See code: [src/cli/commands/challenge/list.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.88/src/cli/commands/challenge/list.ts)_
453
+ _See code: [src/cli/commands/challenge/list.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.90/src/cli/commands/challenge/list.ts)_
451
454
 
452
455
  ## `bitsocial challenge ls`
453
456
 
@@ -479,13 +482,14 @@ Remove an installed challenge package
479
482
 
480
483
  ```
481
484
  USAGE
482
- $ bitsocial challenge remove NAME [--pkcOptions.dataPath <value>]
485
+ $ bitsocial challenge remove NAME --pkcRpcUrl <value> [--pkcOptions.dataPath <value>]
483
486
 
484
487
  ARGUMENTS
485
488
  NAME The challenge package name (e.g., my-challenge or @scope/my-challenge)
486
489
 
487
490
  FLAGS
488
491
  --pkcOptions.dataPath=<value> Data path where challenges are installed
492
+ --pkcRpcUrl=<value> (required) [default: ws://localhost:9138/] URL to PKC RPC
489
493
 
490
494
  DESCRIPTION
491
495
  Remove an installed challenge package
@@ -501,7 +505,7 @@ EXAMPLES
501
505
  $ bitsocial challenge remove @scope/my-challenge
502
506
  ```
503
507
 
504
- _See code: [src/cli/commands/challenge/remove.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.88/src/cli/commands/challenge/remove.ts)_
508
+ _See code: [src/cli/commands/challenge/remove.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.90/src/cli/commands/challenge/remove.ts)_
505
509
 
506
510
  ## `bitsocial challenge rm NAME`
507
511
 
@@ -509,13 +513,14 @@ Remove an installed challenge package
509
513
 
510
514
  ```
511
515
  USAGE
512
- $ bitsocial challenge rm NAME [--pkcOptions.dataPath <value>]
516
+ $ bitsocial challenge rm NAME --pkcRpcUrl <value> [--pkcOptions.dataPath <value>]
513
517
 
514
518
  ARGUMENTS
515
519
  NAME The challenge package name (e.g., my-challenge or @scope/my-challenge)
516
520
 
517
521
  FLAGS
518
522
  --pkcOptions.dataPath=<value> Data path where challenges are installed
523
+ --pkcRpcUrl=<value> (required) [default: ws://localhost:9138/] URL to PKC RPC
519
524
 
520
525
  DESCRIPTION
521
526
  Remove an installed challenge package
@@ -537,13 +542,14 @@ Remove an installed challenge package
537
542
 
538
543
  ```
539
544
  USAGE
540
- $ bitsocial challenge un NAME [--pkcOptions.dataPath <value>]
545
+ $ bitsocial challenge un NAME --pkcRpcUrl <value> [--pkcOptions.dataPath <value>]
541
546
 
542
547
  ARGUMENTS
543
548
  NAME The challenge package name (e.g., my-challenge or @scope/my-challenge)
544
549
 
545
550
  FLAGS
546
551
  --pkcOptions.dataPath=<value> Data path where challenges are installed
552
+ --pkcRpcUrl=<value> (required) [default: ws://localhost:9138/] URL to PKC RPC
547
553
 
548
554
  DESCRIPTION
549
555
  Remove an installed challenge package
@@ -565,13 +571,14 @@ Remove an installed challenge package
565
571
 
566
572
  ```
567
573
  USAGE
568
- $ bitsocial challenge uninstall NAME [--pkcOptions.dataPath <value>]
574
+ $ bitsocial challenge uninstall NAME --pkcRpcUrl <value> [--pkcOptions.dataPath <value>]
569
575
 
570
576
  ARGUMENTS
571
577
  NAME The challenge package name (e.g., my-challenge or @scope/my-challenge)
572
578
 
573
579
  FLAGS
574
580
  --pkcOptions.dataPath=<value> Data path where challenges are installed
581
+ --pkcRpcUrl=<value> (required) [default: ws://localhost:9138/] URL to PKC RPC
575
582
 
576
583
  DESCRIPTION
577
584
  Remove an installed challenge package
@@ -615,7 +622,7 @@ EXAMPLES
615
622
  $ bitsocial community create --jsonFile ./create-options.json
616
623
  ```
617
624
 
618
- _See code: [src/cli/commands/community/create.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.88/src/cli/commands/community/create.ts)_
625
+ _See code: [src/cli/commands/community/create.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.90/src/cli/commands/community/create.ts)_
619
626
 
620
627
  ## `bitsocial community delete ADDRESSES`
621
628
 
@@ -640,7 +647,7 @@ EXAMPLES
640
647
  $ bitsocial community delete 12D3KooWG3XbzoVyAE6Y9vHZKF64Yuuu4TjdgQKedk14iYmTEPWu
641
648
  ```
642
649
 
643
- _See code: [src/cli/commands/community/delete.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.88/src/cli/commands/community/delete.ts)_
650
+ _See code: [src/cli/commands/community/delete.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.90/src/cli/commands/community/delete.ts)_
644
651
 
645
652
  ## `bitsocial community edit ADDRESS`
646
653
 
@@ -710,7 +717,7 @@ EXAMPLES
710
717
  $ bitsocial community edit bitsocial.bso --jsonFile ./edit-options.json
711
718
  ```
712
719
 
713
- _See code: [src/cli/commands/community/edit.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.88/src/cli/commands/community/edit.ts)_
720
+ _See code: [src/cli/commands/community/edit.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.90/src/cli/commands/community/edit.ts)_
714
721
 
715
722
  ## `bitsocial community export [ADDRESS]`
716
723
 
@@ -751,7 +758,7 @@ EXAMPLES
751
758
  $ bitsocial community export --publicKey 12D3KooWG3XbzoVyAE6Y9vHZKF64Yuuu4TjdgQKedk14iYmTEPWu
752
759
  ```
753
760
 
754
- _See code: [src/cli/commands/community/export.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.88/src/cli/commands/community/export.ts)_
761
+ _See code: [src/cli/commands/community/export.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.90/src/cli/commands/community/export.ts)_
755
762
 
756
763
  ## `bitsocial community get [ADDRESS]`
757
764
 
@@ -782,7 +789,7 @@ EXAMPLES
782
789
  $ bitsocial community get --publicKey 12D3KooWG3XbzoVyAE6Y9vHZKF64Yuuu4TjdgQKedk14iYmTEPWu
783
790
  ```
784
791
 
785
- _See code: [src/cli/commands/community/get.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.88/src/cli/commands/community/get.ts)_
792
+ _See code: [src/cli/commands/community/get.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.90/src/cli/commands/community/get.ts)_
786
793
 
787
794
  ## `bitsocial community list`
788
795
 
@@ -805,7 +812,7 @@ EXAMPLES
805
812
  $ bitsocial community list
806
813
  ```
807
814
 
808
- _See code: [src/cli/commands/community/list.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.88/src/cli/commands/community/list.ts)_
815
+ _See code: [src/cli/commands/community/list.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.90/src/cli/commands/community/list.ts)_
809
816
 
810
817
  ## `bitsocial community start ADDRESSES`
811
818
 
@@ -839,7 +846,7 @@ EXAMPLES
839
846
  $ bitsocial community start $(bitsocial community list -q) --concurrency 1
840
847
  ```
841
848
 
842
- _See code: [src/cli/commands/community/start.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.88/src/cli/commands/community/start.ts)_
849
+ _See code: [src/cli/commands/community/start.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.90/src/cli/commands/community/start.ts)_
843
850
 
844
851
  ## `bitsocial community stop ADDRESSES`
845
852
 
@@ -864,7 +871,7 @@ EXAMPLES
864
871
  $ bitsocial community stop Qmb99crTbSUfKXamXwZBe829Vf6w5w5TktPkb6WstC9RFW
865
872
  ```
866
873
 
867
- _See code: [src/cli/commands/community/stop.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.88/src/cli/commands/community/stop.ts)_
874
+ _See code: [src/cli/commands/community/stop.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.90/src/cli/commands/community/stop.ts)_
868
875
 
869
876
  ## `bitsocial daemon`
870
877
 
@@ -915,7 +922,7 @@ EXAMPLES
915
922
  $ bitsocial daemon --no-allowPrivateKeyExport
916
923
  ```
917
924
 
918
- _See code: [src/cli/commands/daemon.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.88/src/cli/commands/daemon.ts)_
925
+ _See code: [src/cli/commands/daemon.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.90/src/cli/commands/daemon.ts)_
919
926
 
920
927
  ## `bitsocial help [COMMAND]`
921
928
 
@@ -981,7 +988,7 @@ EXAMPLES
981
988
  $ bitsocial logs --stdout -f
982
989
  ```
983
990
 
984
- _See code: [src/cli/commands/logs.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.88/src/cli/commands/logs.ts)_
991
+ _See code: [src/cli/commands/logs.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.90/src/cli/commands/logs.ts)_
985
992
 
986
993
  ## `bitsocial update check`
987
994
 
@@ -998,7 +1005,7 @@ EXAMPLES
998
1005
  $ bitsocial update check
999
1006
  ```
1000
1007
 
1001
- _See code: [src/cli/commands/update/check.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.88/src/cli/commands/update/check.ts)_
1008
+ _See code: [src/cli/commands/update/check.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.90/src/cli/commands/update/check.ts)_
1002
1009
 
1003
1010
  ## `bitsocial update install [VERSION]`
1004
1011
 
@@ -1030,7 +1037,7 @@ EXAMPLES
1030
1037
  $ bitsocial update install --no-restart-daemons
1031
1038
  ```
1032
1039
 
1033
- _See code: [src/cli/commands/update/install.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.88/src/cli/commands/update/install.ts)_
1040
+ _See code: [src/cli/commands/update/install.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.90/src/cli/commands/update/install.ts)_
1034
1041
 
1035
1042
  ## `bitsocial update versions`
1036
1043
 
@@ -1052,7 +1059,7 @@ EXAMPLES
1052
1059
  $ bitsocial update versions --limit 5
1053
1060
  ```
1054
1061
 
1055
- _See code: [src/cli/commands/update/versions.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.88/src/cli/commands/update/versions.ts)_
1062
+ _See code: [src/cli/commands/update/versions.ts](https://github.com/bitsocialnet/bitsocial-cli/blob/v0.19.90/src/cli/commands/update/versions.ts)_
1056
1063
  <!-- commandsstop -->
1057
1064
 
1058
1065
  ## Contribution
@@ -22,4 +22,24 @@ export declare function runNpmPack(packageSpec: string, destDir: string): Promis
22
22
  export declare function runNpmInstall(challengeDir: string): Promise<void>;
23
23
  export declare function verifyNativeModuleAbi(challengeDir: string): Promise<void>;
24
24
  export declare function formatChallengeNameVersion(challenge: Pick<InstalledChallenge, "name" | "version">): string;
25
+ /**
26
+ * HTTP url of a daemon's challenge-reload endpoint, derived from its --pkcRpcUrl. The endpoint
27
+ * is served by the same http server as the RPC socket, and its local-only variant requires the
28
+ * request to come from the loopback interface — so a wildcard bind is dialed as loopback rather
29
+ * than as 0.0.0.0.
30
+ */
31
+ export declare function challengeReloadUrlFromPkcRpcUrl(pkcRpcUrl: string): string | undefined;
32
+ /** How long install/remove wait for a daemon to finish reloading before giving up. */
33
+ export declare const CHALLENGE_RELOAD_TIMEOUT_MS = 30000;
34
+ /**
35
+ * Ask the daemon at `pkcRpcUrl` to reload its challenge packages, so an install/remove takes
36
+ * effect without a restart. Returns whether a daemon actually reloaded — best-effort, since
37
+ * no daemon running is not an error.
38
+ *
39
+ * The request is bounded: a daemon that accepts the connection but never answers (busy, wedged,
40
+ * or something else listening on the port) would otherwise hold the CLI for undici's 300s
41
+ * default. Aborting only ends our wait — the daemon may still finish the reload.
42
+ */
43
+ export declare function reloadChallengesInDaemon(pkcRpcUrl: string | URL, timeoutMs?: number): Promise<boolean>;
44
+ export declare function hashChallengePackageContents(challengeDir: string): Promise<string>;
25
45
  export declare function loadChallengesIntoPKC(dataPath?: string): Promise<InstalledChallenge[]>;
@@ -1,4 +1,5 @@
1
1
  import path from "path";
2
+ import { createHash } from "node:crypto";
2
3
  import { pathToFileURL } from "node:url";
3
4
  import fs from "fs/promises";
4
5
  import { execFileSync, spawn } from "child_process";
@@ -288,11 +289,114 @@ export async function verifyNativeModuleAbi(challengeDir) {
288
289
  export function formatChallengeNameVersion(challenge) {
289
290
  return challenge.version && challenge.version !== "unknown" ? `${challenge.name}@${challenge.version}` : challenge.name;
290
291
  }
292
+ /**
293
+ * HTTP url of a daemon's challenge-reload endpoint, derived from its --pkcRpcUrl. The endpoint
294
+ * is served by the same http server as the RPC socket, and its local-only variant requires the
295
+ * request to come from the loopback interface — so a wildcard bind is dialed as loopback rather
296
+ * than as 0.0.0.0.
297
+ */
298
+ export function challengeReloadUrlFromPkcRpcUrl(pkcRpcUrl) {
299
+ let url;
300
+ try {
301
+ url = new URL(pkcRpcUrl);
302
+ }
303
+ catch {
304
+ return undefined;
305
+ }
306
+ if (!url.port)
307
+ return undefined;
308
+ // URL.hostname keeps the brackets around an IPv6 literal — strip them so the host can be
309
+ // compared and re-bracketed exactly once
310
+ const hostname = url.hostname.replace(/^\[|\]$/g, "");
311
+ const host = hostname === "0.0.0.0" || hostname === "::" ? "127.0.0.1" : hostname;
312
+ return `http://${host.includes(":") ? `[${host}]` : host}:${url.port}/api/challenges/reload`;
313
+ }
314
+ /** How long install/remove wait for a daemon to finish reloading before giving up. */
315
+ export const CHALLENGE_RELOAD_TIMEOUT_MS = 30000;
316
+ /**
317
+ * Ask the daemon at `pkcRpcUrl` to reload its challenge packages, so an install/remove takes
318
+ * effect without a restart. Returns whether a daemon actually reloaded — best-effort, since
319
+ * no daemon running is not an error.
320
+ *
321
+ * The request is bounded: a daemon that accepts the connection but never answers (busy, wedged,
322
+ * or something else listening on the port) would otherwise hold the CLI for undici's 300s
323
+ * default. Aborting only ends our wait — the daemon may still finish the reload.
324
+ */
325
+ export async function reloadChallengesInDaemon(pkcRpcUrl, timeoutMs = CHALLENGE_RELOAD_TIMEOUT_MS) {
326
+ const reloadUrl = challengeReloadUrlFromPkcRpcUrl(pkcRpcUrl.toString());
327
+ if (!reloadUrl)
328
+ return false;
329
+ try {
330
+ const res = await fetch(reloadUrl, { method: "POST", signal: AbortSignal.timeout(timeoutMs) });
331
+ return res.ok;
332
+ }
333
+ catch {
334
+ return false; // daemon not running or not answering, that's fine
335
+ }
336
+ }
337
+ // Hash of a challenge package's own source, used as the import cache key (see
338
+ // loadChallengesIntoPKC). Only node_modules is skipped: dependencies are pinned by the
339
+ // install that produced this package dir, and hashing them would make every reload walk
340
+ // the entire dependency tree. Dot-prefixed files and directories are hashed — pkg.main
341
+ // may point into one (".dist/index.js"), and skipping them left that entry out of the key,
342
+ // so a same-version replacement kept the old import URL and served the stale module.
343
+ export async function hashChallengePackageContents(challengeDir) {
344
+ const hash = createHash("sha256");
345
+ const walk = async (dir, relativeDir) => {
346
+ const entries = await fs.readdir(dir, { withFileTypes: true });
347
+ // Sort so the digest does not depend on readdir order
348
+ entries.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
349
+ for (const entry of entries) {
350
+ if (entry.name === "node_modules")
351
+ continue;
352
+ const absolutePath = path.join(dir, entry.name);
353
+ const relativePath = relativeDir ? `${relativeDir}/${entry.name}` : entry.name;
354
+ if (entry.isDirectory()) {
355
+ await walk(absolutePath, relativePath);
356
+ }
357
+ else if (entry.isFile()) {
358
+ // Frame each record: feeding the path and the raw bytes back to back makes the
359
+ // stream ambiguous, so {index.js:"A", j:"Z"} and {index.js:"AjZ"} would hash
360
+ // identically and a changed entry could reuse the previous cache key
361
+ const contents = await fs.readFile(absolutePath);
362
+ const fileDigest = createHash("sha256").update(contents).digest("hex");
363
+ hash.update(`${relativePath}\0${fileDigest}\0`);
364
+ }
365
+ }
366
+ };
367
+ await walk(challengeDir, "");
368
+ return hash.digest("hex").slice(0, 16);
369
+ }
370
+ /**
371
+ * What each challenge name held in PKC.challenges before this process first registered a package
372
+ * under it. PKC.challenges *is* pkc-js's own registry, so a package named after a built-in
373
+ * ("question") shadows it — unregistering has to hand the built-in back rather than delete the
374
+ * key. `hadPrevious: false` means the name was ours alone and can be deleted.
375
+ *
376
+ * A process serves one data path (the daemon passes its own), so this is not keyed by data path.
377
+ */
378
+ const shadowedChallenges = new Map();
291
379
  export async function loadChallengesIntoPKC(dataPath) {
292
380
  const challenges = await listInstalledChallenges(dataPath);
381
+ const PKC = await import("@pkcprotocol/pkc-js");
382
+ const registry = PKC.default.challenges;
383
+ // Hand back any name whose package is no longer installed, so `challenge remove` takes effect
384
+ // without a restart. A package that is still installed but fails to import keeps its
385
+ // previously loaded factory: it is excluded from the returned list either way, and dropping a
386
+ // working challenge because its replacement is broken would take the community's publication
387
+ // flow down instead of leaving it on known-good code.
388
+ const installedNames = new Set(challenges.map((challenge) => challenge.name));
389
+ for (const [name, original] of shadowedChallenges) {
390
+ if (installedNames.has(name))
391
+ continue;
392
+ if (original.hadPrevious)
393
+ registry[name] = original.previous;
394
+ else
395
+ delete registry[name];
396
+ shadowedChallenges.delete(name);
397
+ }
293
398
  if (challenges.length === 0)
294
399
  return [];
295
- const PKC = await import("@pkcprotocol/pkc-js");
296
400
  const loaded = [];
297
401
  for (const challenge of challenges) {
298
402
  try {
@@ -300,9 +404,26 @@ export async function loadChallengesIntoPKC(dataPath) {
300
404
  // Resolve the entry point
301
405
  const entryPoint = pkg.main || "index.js";
302
406
  const entryPath = path.resolve(challenge.path, entryPoint);
303
- const imported = await import(pathToFileURL(entryPath).href);
407
+ // Node caches ESM modules by URL, and `challenge install` swaps a new build onto
408
+ // the same destination path. Importing the bare path would therefore hand back
409
+ // the module evaluated before the upgrade, so the registry would keep serving the
410
+ // old factory while metadata reports the new version (issue #124). Keying the URL
411
+ // on the package contents re-evaluates the entry whenever the package changes and
412
+ // stays byte-identical (so cached, so factory-identical) when it does not.
413
+ //
414
+ // Only the entry module is re-evaluated: relative imports inside the package do not
415
+ // inherit the query, so a multi-file package graph would keep its stale submodules.
416
+ // Challenge packages are expected to ship a bundled entry point.
417
+ const entryUrl = pathToFileURL(entryPath);
418
+ entryUrl.searchParams.set("bitsocialChallengeContent", await hashChallengePackageContents(challenge.path));
419
+ const imported = await import(entryUrl.href);
304
420
  const factory = imported.default || imported;
305
- PKC.default.challenges[challenge.name] = factory;
421
+ // Remember what this name held before we first took it over, so `challenge remove`
422
+ // can hand it back instead of leaving a dead key (or deleting a pkc-js built-in)
423
+ if (!shadowedChallenges.has(challenge.name)) {
424
+ shadowedChallenges.set(challenge.name, { hadPrevious: challenge.name in registry, previous: registry[challenge.name] });
425
+ }
426
+ registry[challenge.name] = factory;
306
427
  loaded.push(challenge);
307
428
  }
308
429
  catch (err) {
@@ -1,5 +1,5 @@
1
- import { Command } from "@oclif/core";
2
- export default class Install extends Command {
1
+ import { BaseCommand } from "../../base-command.js";
2
+ export default class Install extends BaseCommand {
3
3
  static description: string;
4
4
  static aliases: string[];
5
5
  static args: {
@@ -1,10 +1,11 @@
1
- import { Args, Flags, Command } from "@oclif/core";
1
+ import { Args, Flags } from "@oclif/core";
2
+ import { BaseCommand } from "../../base-command.js";
2
3
  import path from "path";
3
4
  import fs from "fs/promises";
4
5
  import decompress from "decompress";
5
6
  import defaults from "../../../common-utils/defaults.js";
6
- import { ensureNpmAvailable, ensureChallengesDir, challengeNameToDir, readChallengePackageJson, runNpmPack, runNpmInstall, verifyNativeModuleAbi } from "../../../challenge-packages/challenge-utils.js";
7
- export default class Install extends Command {
7
+ import { ensureNpmAvailable, ensureChallengesDir, challengeNameToDir, readChallengePackageJson, runNpmPack, runNpmInstall, verifyNativeModuleAbi, reloadChallengesInDaemon } from "../../../challenge-packages/challenge-utils.js";
8
+ export default class Install extends BaseCommand {
8
9
  static description = "Install a challenge package (npm package name, git URL, tarball URL, or local path)";
9
10
  static aliases = ["challenge:i", "challenge:add"];
10
11
  static args = {
@@ -111,12 +112,10 @@ export default class Install extends Command {
111
112
  const version = pkg.version ? `@${pkg.version}` : "";
112
113
  const elapsedSeconds = Math.max(1, Math.round((Date.now() - startTime) / 1000));
113
114
  this.log(`${alreadyExists ? "changed" : "added"} ${pkg.name}${version} in ${elapsedSeconds}s`);
114
- // 10. Best-effort reload via daemon
115
- try {
116
- await fetch("http://localhost:9138/api/challenges/reload", { method: "POST" });
117
- }
118
- catch {
119
- // daemon not running, that's fine
115
+ // 10. Best-effort reload in the daemon at --pkcRpcUrl, so the install takes effect
116
+ // without a restart
117
+ if (await reloadChallengesInDaemon(flags.pkcRpcUrl)) {
118
+ this.log(`reloaded ${pkg.name}${version} in the daemon at ${flags.pkcRpcUrl}`);
120
119
  }
121
120
  }
122
121
  finally {
@@ -1,5 +1,5 @@
1
- import { Command } from "@oclif/core";
2
- export default class Remove extends Command {
1
+ import { BaseCommand } from "../../base-command.js";
2
+ export default class Remove extends BaseCommand {
3
3
  static description: string;
4
4
  static aliases: string[];
5
5
  static args: {
@@ -1,9 +1,10 @@
1
- import { Args, Flags, Command } from "@oclif/core";
1
+ import { Args, Flags } from "@oclif/core";
2
+ import { BaseCommand } from "../../base-command.js";
2
3
  import fs from "fs/promises";
3
4
  import path from "path";
4
5
  import defaults from "../../../common-utils/defaults.js";
5
- import { getChallengesDir, challengeNameToDir, readChallengePackageJson } from "../../../challenge-packages/challenge-utils.js";
6
- export default class Remove extends Command {
6
+ import { getChallengesDir, challengeNameToDir, readChallengePackageJson, reloadChallengesInDaemon } from "../../../challenge-packages/challenge-utils.js";
7
+ export default class Remove extends BaseCommand {
7
8
  static description = "Remove an installed challenge package";
8
9
  static aliases = ["challenge:uninstall", "challenge:rm", "challenge:un"];
9
10
  static args = {
@@ -60,12 +61,8 @@ export default class Remove extends Command {
60
61
  }
61
62
  }
62
63
  this.log(`removed ${args.name}${version}`);
63
- // Best-effort reload via daemon
64
- try {
65
- await fetch("http://localhost:9138/api/challenges/reload", { method: "POST" });
66
- }
67
- catch {
68
- // daemon not running, that's fine
69
- }
64
+ // Best-effort reload in the daemon at --pkcRpcUrl, so the removal takes effect
65
+ // without a restart
66
+ await reloadChallengesInDaemon(flags.pkcRpcUrl);
70
67
  }
71
68
  }
@@ -195,6 +195,15 @@
195
195
  "bitsocial challenge install ./my-local-challenge"
196
196
  ],
197
197
  "flags": {
198
+ "pkcRpcUrl": {
199
+ "name": "pkcRpcUrl",
200
+ "required": true,
201
+ "summary": "URL to PKC RPC",
202
+ "default": "ws://localhost:9138/",
203
+ "hasDynamicHelp": false,
204
+ "multiple": false,
205
+ "type": "option"
206
+ },
198
207
  "pkcOptions.dataPath": {
199
208
  "description": "Data path to install the challenge into",
200
209
  "name": "pkcOptions.dataPath",
@@ -284,6 +293,15 @@
284
293
  "bitsocial challenge remove @scope/my-challenge"
285
294
  ],
286
295
  "flags": {
296
+ "pkcRpcUrl": {
297
+ "name": "pkcRpcUrl",
298
+ "required": true,
299
+ "summary": "URL to PKC RPC",
300
+ "default": "ws://localhost:9138/",
301
+ "hasDynamicHelp": false,
302
+ "multiple": false,
303
+ "type": "option"
304
+ },
287
305
  "pkcOptions.dataPath": {
288
306
  "description": "Data path where challenges are installed",
289
307
  "name": "pkcOptions.dataPath",
@@ -885,5 +903,5 @@
885
903
  ]
886
904
  }
887
905
  },
888
- "version": "0.19.88"
906
+ "version": "0.19.90"
889
907
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bitsocial/bitsocial-cli",
3
- "version": "0.19.88",
3
+ "version": "0.19.90",
4
4
  "description": "Command line interface to Bitsocial API",
5
5
  "types": "./dist/index.d.ts",
6
6
  "homepage": "https://github.com/bitsocialnet/bitsocial-cli",
@@ -119,7 +119,7 @@
119
119
  "@oclif/plugin-help": "6.2.36",
120
120
  "@oclif/plugin-not-found": "3.2.73",
121
121
  "@oclif/table": "0.5.1",
122
- "@pkcprotocol/pkc-js": "0.0.77",
122
+ "@pkcprotocol/pkc-js": "0.0.82",
123
123
  "dataobject-parser": "1.2.22",
124
124
  "decompress": "4.2.1",
125
125
  "env-paths": "2.2.1",