@maka/maka-cli 5.175.1 → 5.176.0

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maka/maka-cli",
3
- "version": "5.175.1",
3
+ "version": "5.176.0",
4
4
  "type": "module",
5
5
  "summary": "A command line tool for scaffolding Meteor 3.x applications using either React.",
6
6
  "description": "A command line tool for scaffolding Meteor 3.x applications using React.",
@@ -430,5 +430,12 @@
430
430
  // the purpose. A `hub: true` seed is stripped of hosts, ice and files on
431
431
  // load -- the shipped hub (scene2) no longer stands one over the Bazaar
432
432
  // or the Sim Den.
433
- export const ENGINE_VERSION = '1.50.0';
433
+ // 1.51.0 (2026-09-12): THE HUB LIVES ON THE SITE. The hub seed is served
434
+ // by maka-cli.com (/api/v1/game-hub) and cached like the catalog; the
435
+ // package ships no scene2.json. Save-shape field the server writes
436
+ // back: hubSeedRef {id, version, contentHash} beside the (for one
437
+ // release) still-embedded hubSeed. Hosted hub sessions
438
+ // (createHeadlessHub) run the solo hub loop in the site's process with
439
+ // a save sink; a browser player can enter the hub.
440
+ export const ENGINE_VERSION = '1.51.0';
434
441
  //# sourceMappingURL=engine-version.js.map
@@ -12,7 +12,8 @@ import { findCatalogItem, itemFromCatalog, loadCatalog, augFor, spellFor, adeptP
12
12
  import { SKILL_GROUPS } from '../skill-groups.js';
13
13
  import { serializePlayer, serializeItem } from '../utilities/persistence.js';
14
14
  import { canonicalLifestyleTier, LIFESTYLE_TIERS, canonicalLifestyleOption, lifestyleOptionAllowed, LIFESTYLE_OPTIONS, } from '../utilities/commerce.js';
15
- import { SAVE_VERSION } from '../types/save-file.js';
15
+ import { SAVE_VERSION, DUAL_WRITE_HUB_SEED } from '../types/save-file.js';
16
+ import { hubSeedRefOf } from '../utilities/hub-seed.js';
16
17
  /**
17
18
  * THE ENGINE-SIDE CHARACTER FACTORY -- one payload in, one bootable save
18
19
  * out, so the maka-cli.com web generator and the CLI cannot produce
@@ -527,32 +528,25 @@ export function buildFromChargen(payload) {
527
528
  return { player, garage, lifestyleTier, lifestyleOptions };
528
529
  }
529
530
  /**
530
- * The payload's save. `hubSeed` is the SHIPPED scene2 seed by ruling --
531
- * it is embedded rather than referenced so a resume never depends on
532
- * scene2.json still existing or still matching.
533
- */
534
- /**
535
- * THE SHIPPED HUB SEED -- the default home for a character born without one.
531
+ * The payload's save, born into `hubSeed`.
536
532
  *
537
- * The CLI offers this same scene (scenes/scene2.json, the primary; scene1 is
538
- * the smoke test) to a new character it creates itself, so a WEB-born runner
539
- * defaulting to it starts exactly where a CLI-born one does. This is what
540
- * lets the web's finalize call the factory with no hubSeed at all -- the one
541
- * argument it could never produce, since scene generation lives CLI-side.
533
+ * THE HUB SEED IS AN ARGUMENT NOW, AND A REQUIRED ONE (2026-09-12). The
534
+ * shipped scene2.json is gone: the hub lives on maka-cli.com
535
+ * (utilities/hub-seed.ts fetches and caches it for a terminal; the site
536
+ * reads its own GameHubSeeds collection for a web birth), and a factory
537
+ * that quietly reached for a bundled copy would be the second source of
538
+ * truth this migration removes. Nothing in this repo creates a
539
+ * character without first holding the served seed.
542
540
  *
543
- * A LITERAL require, deliberately: the pkg binary traces literal require()
544
- * paths only -- a computed path ships a binary with the seed missing
545
- * (the blessed-terminfo lesson, recorded in the packaging notes).
541
+ * The save records the seed by REFERENCE (hubSeedRef, from the stamp
542
+ * the server put on it) and, for one release, still embeds it
543
+ * (hubSeed) so a binary from before the migration can resume the
544
+ * runner -- see ISaveFileV1.hubSeed and DUAL_WRITE_HUB_SEED in game.ts.
546
545
  */
547
- import { createRequire } from 'module';
548
- const requireSeed = createRequire(import.meta.url);
549
- function shippedHubSeed() {
550
- return requireSeed('../scenes/scene2.json');
551
- }
552
546
  export function createNewCharacterSave(payload, hubSeed) {
553
- return createNewCharacterSaveWithSeed(payload, hubSeed ?? shippedHubSeed());
554
- }
555
- function createNewCharacterSaveWithSeed(payload, hubSeed) {
547
+ if (!hubSeed || typeof hubSeed !== 'object' || !Array.isArray(hubSeed.rooms)) {
548
+ throw new ChargenPayloadError('A runner needs a home district: createNewCharacterSave requires the served hub seed.');
549
+ }
556
550
  const { player, garage, lifestyleTier, lifestyleOptions } = buildFromChargen(payload);
557
551
  // WHAT A BIRTH CERTIFICATE CARRIES, AND WHAT IT DOES NOT (2026-09-05).
558
552
  // Game.applyResumeState restores the PLAYER, applies the LIFESTYLE and
@@ -578,7 +572,8 @@ function createNewCharacterSaveWithSeed(payload, hubSeed) {
578
572
  lifestyleTier,
579
573
  hubName: hubSeed.name ?? 'the hub',
580
574
  },
581
- hubSeed,
575
+ hubSeedRef: hubSeedRefOf(hubSeed),
576
+ ...(DUAL_WRITE_HUB_SEED ? { hubSeed } : {}),
582
577
  player: serializePlayer(player),
583
578
  game: {
584
579
  lifestyleTier, lifestyleOptions, rentDebt: 0, tier: 1, contacts: [],
@@ -18,7 +18,8 @@ import { Category, Size, Rating } from './types/shared/item-enum.js';
18
18
  import { Item } from './models/item.js';
19
19
  import { Room, Container } from './models/_index.js';
20
20
  import { Door } from './models/door.js';
21
- import { SAVE_VERSION } from './types/save-file.js';
21
+ import { SAVE_VERSION, DUAL_WRITE_HUB_SEED } from './types/save-file.js';
22
+ import { hubSeedRefOf } from './utilities/hub-seed.js';
22
23
  import { serializePlayer, restorePlayer, captureHubOverlay, applyHubOverlay, applyHomeBlock, writeSaveAtomic, deleteSave, restoreItem, readSave, serializeItem, saveSlug, spillPathFor, } from './utilities/persistence.js';
23
24
  import { heartbeatPresence, heartbeatPresenceNow } from './utilities/social.js';
24
25
  import { presenceBeatAllowed } from './utilities/presence-gate.js';
@@ -665,13 +666,15 @@ export default class Game {
665
666
  * push one right now and say so.
666
667
  */
667
668
  async pushPresenceNow() {
668
- if (this._anonymous || this._bench)
669
+ // A hosted hub never dials the site from inside the site: the box's
670
+ // own `maka login`, if any, is not this player's.
671
+ if (this._anonymous || this._bench || this._hostedHub)
669
672
  return 'offline';
670
673
  return heartbeatPresenceNow(this.presencePayload());
671
674
  }
672
675
  startPresenceHeartbeat() {
673
- if (this._anonymous || this._bench)
674
- return; // account-less, or a bench: never on the street
676
+ if (this._anonymous || this._bench || this._hostedHub)
677
+ return; // account-less, a bench, or hosted on the site: never on the street
675
678
  if (this._presenceTimer)
676
679
  return;
677
680
  this.beatPresence();
@@ -1285,7 +1288,12 @@ export default class Game {
1285
1288
  _saveFilePath; // from options.saveFilePath; unset = never save
1286
1289
  _dead = false; // set in close(); suppresses any later save
1287
1290
  _resume; // present when this session continues a save
1288
- _hubSeed; // the seed embedded into every save
1291
+ _hubSeed; // the served hub seed this session plays (its stamp is the save's hubSeedRef)
1292
+ /** A HUB HOSTED ON THE SITE (IMainGameConfig.hostedHub): no presence,
1293
+ * no DDP hub link, no auto-draft, and every save beat goes to the
1294
+ * sink instead of a file. */
1295
+ _hostedHub = false;
1296
+ _saveSink;
1289
1297
  /**
1290
1298
  * The one correct runtime NPC spawn (mirrors scene-factory's seed
1291
1299
  * path): construct with a scene-bound registry, add to the scene
@@ -2898,6 +2906,8 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
2898
2906
  this._useAi = options.useAi;
2899
2907
  this._logFilePath = options.logFilePath;
2900
2908
  this._saveFilePath = options.saveFilePath;
2909
+ this._hostedHub = options.hostedHub === true;
2910
+ this._saveSink = options.saveSink;
2901
2911
  this._owner = options.owner;
2902
2912
  this._anonymous = options.anonymousRunner === true;
2903
2913
  this._bench = options.reproBench === true;
@@ -3027,9 +3037,13 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
3027
3037
  // and the DDP hub link (run invites, shared sessions -- Phase C)
3028
3038
  // warms up behind them. All fire-and-forget.
3029
3039
  this.startPresenceHeartbeat();
3030
- void import('./utilities/shared-run.js')
3031
- .then(m => m.ensureHubLink(this))
3032
- .catch(() => undefined);
3040
+ // NOT FROM A HOSTED HUB: the DDP hub link is a terminal's line to
3041
+ // the site, and this game already runs inside it (2026-09-12).
3042
+ if (!this._hostedHub) {
3043
+ void import('./utilities/shared-run.js')
3044
+ .then(m => m.ensureHubLink(this))
3045
+ .catch(() => undefined);
3046
+ }
3033
3047
  // The next run starts cooking the moment you're home (player
3034
3048
  // request): generation takes 20-40s, and waiting for the player
3035
3049
  // to CALL first meant "give me a bit" every time. Draft now --
@@ -3043,7 +3057,10 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
3043
3057
  // player explicitly calls Krow (or takes from the jobs board),
3044
3058
  // never automatically on load. Solo drafts on the player's own
3045
3059
  // key keep cooking at arrival as always.
3046
- if (!this._resume && this.livePartyFingerprint() === '' && this.canDraftNextScene().allowed) {
3060
+ // NEVER FROM A HOSTED HUB (2026-09-12): on the site a draft is a
3061
+ // generation on the site's own key, and it happens only when the
3062
+ // player calls Krow -- not on every browser entry.
3063
+ if (!this._resume && !this._hostedHub && this.livePartyFingerprint() === '' && this.canDraftNextScene().allowed) {
3047
3064
  this.draftNextSceneInBackground();
3048
3065
  }
3049
3066
  }
@@ -3073,7 +3090,7 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
3073
3090
  // Krow's "1 new message" ping lands mid-session as usual.
3074
3091
  // Same live-seat gate as the fresh-start draft above: crew work
3075
3092
  // is commissioned by CALLING, never by loading in.
3076
- if (!this._pendingRunSeed && this.livePartyFingerprint() === '' && this.canDraftNextScene().allowed) {
3093
+ if (!this._pendingRunSeed && !this._hostedHub && this.livePartyFingerprint() === '' && this.canDraftNextScene().allowed) {
3077
3094
  this.draftNextSceneInBackground();
3078
3095
  }
3079
3096
  }
@@ -3385,7 +3402,7 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
3385
3402
  // GUARDS BEFORE THE LOGGER: the "move" beat fires from any room change,
3386
3403
  // including ones outside a logging session (harnesses, benches). A
3387
3404
  // no-op beat must cost nothing and touch nothing.
3388
- if (!this.initialized || this._dead || !this._saveFilePath)
3405
+ if (!this.initialized || this._dead || (!this._saveFilePath && !this._saveSink))
3389
3406
  return;
3390
3407
  if (!this.hasHub || this.scene !== this._hubScene || !this._hubSeed)
3391
3408
  return;
@@ -3394,6 +3411,25 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
3394
3411
  if (this.scene.isPlayerExchangeActive())
3395
3412
  return;
3396
3413
  const logger = Logger.getInstance();
3414
+ // A HOSTED HUB SAVES INTO THE SINK (2026-09-12): the site writes the
3415
+ // GameSaves doc itself. No file, no spill, no push queue -- those are
3416
+ // a terminal's answer to a network it does not own. The sink's
3417
+ // promise is not awaited here (a beat must not block a command);
3418
+ // flushBoundSave awaits it for quit/evict.
3419
+ if (this._saveSink && !this._saveFilePath) {
3420
+ try {
3421
+ const save = this.buildSaveFile(reason);
3422
+ this._lastSinkWrite = Promise.resolve(this._saveSink(save, reason))
3423
+ .catch(err => logger.write(`Saved (${reason}) -> sink FAILED: ${err instanceof Error ? err.message : String(err)}`));
3424
+ logger.write(`Saved (${reason}) -> sink.`);
3425
+ }
3426
+ catch (err) {
3427
+ logger.write(`requestSave(${reason}) failed to build: ${err instanceof Error ? err.message : String(err)}`);
3428
+ }
3429
+ return;
3430
+ }
3431
+ if (!this._saveFilePath)
3432
+ return;
3397
3433
  try {
3398
3434
  const save = this.buildSaveFile(reason);
3399
3435
  // Carry the cloud-sync stamp FORWARD through local writes: the
@@ -3453,6 +3489,13 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
3453
3489
  * nothing to seal and must never be made to wait on a network.
3454
3490
  */
3455
3491
  async flushBoundSave(reason) {
3492
+ // A HOSTED HUB: one final beat into the sink, awaited -- the caller
3493
+ // (quit, evict, drain) needs the write to have landed.
3494
+ if (this._saveSink && !this._saveFilePath) {
3495
+ this.requestSave(reason);
3496
+ await this._lastSinkWrite;
3497
+ return true;
3498
+ }
3456
3499
  if (!this.boundRunner)
3457
3500
  return true;
3458
3501
  // Take a final beat first, so the thing we seal is the state the
@@ -3490,7 +3533,12 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
3490
3533
  lifestyleTier: this.lifestyleTier,
3491
3534
  hubName: this._hubName,
3492
3535
  },
3493
- hubSeed: this._hubSeed,
3536
+ // BY REFERENCE (engine 1.51.0): the stamp maka-cli.com put on the
3537
+ // seed this session plays. An unstamped seed (a hand-written
3538
+ // fixture, a scene handed straight to Game.getInstance) records no
3539
+ // ref; the dual-written embed below still carries it this release.
3540
+ hubSeedRef: hubSeedRefOf(this._hubSeed),
3541
+ ...(DUAL_WRITE_HUB_SEED ? { hubSeed: this._hubSeed } : {}),
3494
3542
  player: serializePlayer(this.player),
3495
3543
  game: {
3496
3544
  location: this.hubLocationSnapshot(),
@@ -5784,6 +5832,37 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
5784
5832
  }
5785
5833
  return Game.instance;
5786
5834
  }
5835
+ /**
5836
+ * A HUB HOSTED ON maka-cli.com (headless.ts createHeadlessHub): the
5837
+ * same boot as a terminal resume -- a Game over the served hub seed
5838
+ * continuing `save` -- but NEVER through the singleton: one server
5839
+ * process hosts many runners' hubs at once, and Game.instance is the
5840
+ * one terminal's game. Pair with initEngine({ hostedHub: true, ... }).
5841
+ */
5842
+ static forHostedHub(player, hubSeed, save, conditionSink) {
5843
+ const game = new Game(player, hubSeed, conditionSink);
5844
+ game._resume = save;
5845
+ return game;
5846
+ }
5847
+ /** Standing in the hub (as opposed to out on a run). */
5848
+ get onHubScene() {
5849
+ return this._hubScene !== undefined && this.scene === this._hubScene;
5850
+ }
5851
+ /**
5852
+ * THE SAVE AS IT STANDS, for a hosting server's quit/evict/drain --
5853
+ * the same file requestSave would write, or undefined when there is
5854
+ * nothing to save (out on a run, dead, down). The pending run rides
5855
+ * the last hub save, exactly as it does when a terminal quits mid-job.
5856
+ */
5857
+ snapshotSave(reason) {
5858
+ if (!this.initialized || this._dead || !this.onHubScene || !this._hubSeed)
5859
+ return undefined;
5860
+ if (this.player.isDown() || this.player.isUnconscious())
5861
+ return undefined;
5862
+ return this.buildSaveFile(reason);
5863
+ }
5864
+ /** The last sink write in flight, awaited by flushBoundSave. */
5865
+ _lastSinkWrite;
5787
5866
  /**
5788
5867
  * The verb the LAST handleInput actually ran, after typo correction
5789
5868
  * ("sjeet" -> "sheet"). ui.ts routes menu output to the Mechanics