@maka/maka-cli 5.17.17 → 5.17.18

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.
Files changed (41) hide show
  1. package/bundle/typescript/package.json +1 -1
  2. package/bundle/typescript/src/commands/game/sideQuest/archetypes.js +1 -1
  3. package/bundle/typescript/src/commands/game/sideQuest/archetypes.js.map +1 -1
  4. package/bundle/typescript/src/commands/game/sideQuest/commands/command.d.ts +16 -0
  5. package/bundle/typescript/src/commands/game/sideQuest/commands/command.js +10 -0
  6. package/bundle/typescript/src/commands/game/sideQuest/commands/command.js.map +1 -1
  7. package/bundle/typescript/src/commands/game/sideQuest/commands/compile.d.ts +27 -0
  8. package/bundle/typescript/src/commands/game/sideQuest/commands/compile.js +8 -1
  9. package/bundle/typescript/src/commands/game/sideQuest/commands/compile.js.map +1 -1
  10. package/bundle/typescript/src/commands/game/sideQuest/commands/crew.d.ts +12 -2
  11. package/bundle/typescript/src/commands/game/sideQuest/commands/crew.js +69 -79
  12. package/bundle/typescript/src/commands/game/sideQuest/commands/crew.js.map +1 -1
  13. package/bundle/typescript/src/commands/game/sideQuest/commands/players.d.ts +4 -6
  14. package/bundle/typescript/src/commands/game/sideQuest/commands/players.js +6 -9
  15. package/bundle/typescript/src/commands/game/sideQuest/commands/players.js.map +1 -1
  16. package/bundle/typescript/src/commands/game/sideQuest/commands/register.d.ts +16 -0
  17. package/bundle/typescript/src/commands/game/sideQuest/commands/register.js +74 -0
  18. package/bundle/typescript/src/commands/game/sideQuest/commands/register.js.map +1 -0
  19. package/bundle/typescript/src/commands/game/sideQuest/commands/thread.d.ts +17 -0
  20. package/bundle/typescript/src/commands/game/sideQuest/commands/thread.js +165 -0
  21. package/bundle/typescript/src/commands/game/sideQuest/commands/thread.js.map +1 -0
  22. package/bundle/typescript/src/commands/game/sideQuest/engine-version.d.ts +1 -1
  23. package/bundle/typescript/src/commands/game/sideQuest/engine-version.js +3 -1
  24. package/bundle/typescript/src/commands/game/sideQuest/engine-version.js.map +1 -1
  25. package/bundle/typescript/src/commands/game/sideQuest/game.d.ts +17 -4
  26. package/bundle/typescript/src/commands/game/sideQuest/game.js +122 -46
  27. package/bundle/typescript/src/commands/game/sideQuest/game.js.map +1 -1
  28. package/bundle/typescript/src/commands/game/sideQuest/models/player.d.ts +9 -1
  29. package/bundle/typescript/src/commands/game/sideQuest/models/player.js +17 -4
  30. package/bundle/typescript/src/commands/game/sideQuest/models/player.js.map +1 -1
  31. package/bundle/typescript/src/commands/game/sideQuest/types/save-file.d.ts +7 -0
  32. package/bundle/typescript/src/commands/game/sideQuest/utilities/shared-run.d.ts +5 -5
  33. package/bundle/typescript/src/commands/game/sideQuest/utilities/shared-run.js +9 -9
  34. package/bundle/typescript/src/commands/game/sideQuest/utilities/shared-run.js.map +1 -1
  35. package/bundle/typescript/src/commands/game/sideQuest/utilities/social.d.ts +1 -30
  36. package/bundle/typescript/src/commands/game/sideQuest/utilities/social.js +4 -89
  37. package/bundle/typescript/src/commands/game/sideQuest/utilities/social.js.map +1 -1
  38. package/package.json +1 -1
  39. package/bundle/typescript/src/commands/game/sideQuest/utilities/crew-ledger.d.ts +0 -21
  40. package/bundle/typescript/src/commands/game/sideQuest/utilities/crew-ledger.js +0 -25
  41. package/bundle/typescript/src/commands/game/sideQuest/utilities/crew-ledger.js.map +0 -1
@@ -6,8 +6,7 @@ import { Room } from './models/_index.js';
6
6
  import { Door } from './models/door.js';
7
7
  import { SAVE_VERSION } from './types/save-file.js';
8
8
  import { serializePlayer, restorePlayer, captureHubOverlay, applyHubOverlay, writeSaveAtomic, deleteSave, restoreItem, readSave, serializeItem, saveSlug, } from './utilities/persistence.js';
9
- import { appendCrewLedger } from './utilities/crew-ledger.js';
10
- import { heartbeatPresence, heartbeatPresenceNow, ghostHireable, syncCrewLedger, claimGhostEarnings } from './utilities/social.js';
9
+ import { heartbeatPresence, heartbeatPresenceNow } from './utilities/social.js';
11
10
  import { pushSave, burnCloudSaveFor } from './utilities/cloud-saves.js';
12
11
  import { DOCWAGON_TIERS, RESUSC_FEE, DEATH_COMP } from './utilities/docwagon.js';
13
12
  import { isFence, LIFESTYLE_TIERS } from './utilities/commerce.js';
@@ -77,7 +76,9 @@ import { ContactsCommand } from './commands/contacts.js';
77
76
  import { JumpCommand } from './commands/jump.js';
78
77
  import { InitiateCommand } from './commands/initiate.js';
79
78
  import { SummonCommand } from './commands/summon.js';
80
- import { CompileCommand } from './commands/compile.js';
79
+ import { CompileCommand, SPRITE_TYPES } from './commands/compile.js';
80
+ import { ThreadCommand } from './commands/thread.js';
81
+ import { RegisterCommand } from './commands/register.js';
81
82
  import { OrderCommand } from './commands/order.js';
82
83
  import { DeployCommand, AgentCommand, RecallCommand } from './commands/companions.js';
83
84
  import { DeckCommand, LoadProgramCommand, UnloadProgramCommand } from './commands/deck.js';
@@ -227,9 +228,9 @@ export default class Game {
227
228
  // talent pool churns; whoever you HIRED is the persistent part.
228
229
  crewForHire = [];
229
230
  // PRESENCE (social layer, Phase B): a hub heartbeat announces this
230
- // runner to the street's player list on maka-cli.com -- and carries
231
- // the "hireable while away" ghost opt-in. Only beats while the scene
232
- // IS the hub (the run is nobody's business); silent no-op logged out.
231
+ // runner to the street's player list on maka-cli.com. Only beats
232
+ // while the scene IS the hub (the run is nobody's business); silent
233
+ // no-op logged out.
233
234
  _presenceTimer;
234
235
  static PRESENCE_TICK_MS = 60_000;
235
236
  presencePayload() {
@@ -237,7 +238,6 @@ export default class Game {
237
238
  playerName: this.player.name,
238
239
  slug: saveSlug(this.player.name),
239
240
  hubName: this._hubName,
240
- ghostHireable: ghostHireable(),
241
241
  // ONLINE means the LINK is live (player ruling): commlink equipped
242
242
  // and broadcasting. Pull the link or run silent -> off the street.
243
243
  linkUp: this.player.equipment.head?.commlink != null && !this.player.runningSilent,
@@ -294,23 +294,6 @@ export default class Game {
294
294
  this._presenceTimer = setInterval(() => this.beatPresence(), Game.PRESENCE_TICK_MS);
295
295
  this._presenceTimer.unref();
296
296
  }
297
- /** Ghost earnings home delivery: what YOUR runner made riding in
298
- * other players' crews since last time. Fire-and-forget from hub
299
- * arrival beats; pays onto the job credstick when anything landed. */
300
- async collectGhostEarnings() {
301
- try {
302
- const claimed = await claimGhostEarnings(saveSlug(this.player.name));
303
- if (!claimed || this.scene !== this._hubScene)
304
- return;
305
- const paid = this.scene.payRunner(claimed.total, 'ghost earnings');
306
- if (!paid)
307
- return;
308
- const jobs = claimed.entries.map(e => `"${e.job}" with ${e.hiredBy} (${e.amount}¥)`).join(', ');
309
- Logger.getInstance().logWithColor(`While you were gone, your runner rode other crews: ${jobs}. ${paid.net}¥ certified lands on your stick.`, 'green');
310
- this.updateStatus();
311
- }
312
- catch { /* offline -- next homecoming */ }
313
- }
314
297
  static HIRE_NAMES = [
315
298
  'Jax', 'Sable', 'Vex', 'Rook', 'Nyx', 'Cinder', 'Halo', 'Grifter',
316
299
  'Static', 'Mercy', 'Tinder', 'Chrome Marie', 'Deacon', 'Fetch', 'Loa',
@@ -460,9 +443,6 @@ export default class Game {
460
443
  const cut = Math.floor(gross * (member.cutPct * scale) / 100);
461
444
  taken += cut;
462
445
  parts.push(`${member.name} ${member.cutPct}% (${cut}¥)`);
463
- if (member.ghost) {
464
- appendCrewLedger({ slug: member.ghost.slug, owner: member.ghost.owner, amount: cut, job: this.currentJobName ?? 'a job', when: new Date().toISOString() });
465
- }
466
446
  }
467
447
  return { share: gross - taken, line: `Crew cut: ${parts.join(', ')}.` };
468
448
  }
@@ -732,6 +712,9 @@ export default class Game {
732
712
  // death both remove the entry; persistent state (the frame, the deck)
733
713
  // lives on Items in the player's possession, never here.
734
714
  companions = [];
715
+ /** A registered sprite folded for travel, waiting to re-shell at the
716
+ * destination (the drone _pendingRedeploys school). */
717
+ _pendingSpriteRedeploy;
735
718
  // Drones deployed at travel time re-shell on arrival ("the rotors spin
736
719
  // back up at the curb") -- tracked as their items across the swap.
737
720
  _pendingRedeploys = [];
@@ -933,7 +916,15 @@ export default class Game {
933
916
  lines.push(`Your bound spirit thins into the rain -- the summons doesn't outlast the ride.`);
934
917
  }
935
918
  else if (entry.kind === 'sprite') {
936
- lines.push(`Your sprite decompiles into clean static -- the thread doesn't outlast the ride.`);
919
+ // A REGISTERED sprite persists (register.ts): it folds into
920
+ // your PAN for the ride and re-shells at the curb.
921
+ if (entry.registered && entry.spriteType) {
922
+ this._pendingSpriteRedeploy = { spriteType: entry.spriteType, level: entry.force, tasks: entry.services ?? 0 };
923
+ lines.push(`${entry.npc.name} folds into your PAN for the ride -- registered code travels with you.`);
924
+ }
925
+ else {
926
+ lines.push(`Your sprite decompiles into clean static -- the thread doesn't outlast the ride.`);
927
+ }
937
928
  }
938
929
  else if (entry.kind === 'drone') {
939
930
  lines.push(`The ${entry.boundDevice?.name ?? 'drone'} autopilots home to your hand for the ride.`);
@@ -960,8 +951,42 @@ export default class Game {
960
951
  if (shell)
961
952
  lines.push(`The ${item.name}'s rotors spin back up at the curb.`);
962
953
  }
954
+ // A REGISTERED sprite re-shells from the PAN (register.ts).
955
+ if (this._pendingSpriteRedeploy) {
956
+ const spec = this._pendingSpriteRedeploy;
957
+ this._pendingSpriteRedeploy = undefined;
958
+ const line = this.respawnRegisteredSprite(spec);
959
+ if (line)
960
+ lines.push(line);
961
+ }
963
962
  return lines;
964
963
  }
964
+ /** Re-shell a registered sprite (travel arrival, or a resumed save).
965
+ * Returns the arrival line, or undefined when the shell can't land
966
+ * (name collision -- vanishingly rare). */
967
+ respawnRegisteredSprite(spec) {
968
+ const type = SPRITE_TYPES[spec.spriteType];
969
+ if (!type)
970
+ return undefined;
971
+ const shell = this.spawnCompanionShell({
972
+ name: `${type.name} Sprite (Level ${spec.level})`,
973
+ description: `A registered ${type.name.toLowerCase()} sprite riding ${this.player.name}'s PAN -- ${type.blurb}. It serves until its ${spec.tasks} task${spec.tasks === 1 ? '' : 's'} are spent.`,
974
+ kind: 'sprite',
975
+ combat: type.combat(spec.level),
976
+ room: this.player.currentLocation,
977
+ plane: 'matrix',
978
+ force: spec.level,
979
+ services: spec.tasks,
980
+ });
981
+ if (!shell)
982
+ return undefined;
983
+ const entry = this.companions.find(c => c.npc === shell);
984
+ if (entry) {
985
+ entry.registered = true;
986
+ entry.spriteType = spec.spriteType;
987
+ }
988
+ return `${shell.name} unfolds from your PAN, luminous and patient -- ${spec.tasks} task${spec.tasks === 1 ? '' : 's'} standing.`;
989
+ }
965
990
  /**
966
991
  * Records a real transaction with a hub NPC: first contact opens the
967
992
  * relationship at Loyalty 1 (a safeguard -- the hub cast is pre-seeded
@@ -1582,6 +1607,8 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
1582
1607
  CommandFactory.registerCommand('conjure', SummonCommand);
1583
1608
  CommandFactory.registerCommand('compile', CompileCommand);
1584
1609
  CommandFactory.registerCommand('decompile', CompileCommand);
1610
+ CommandFactory.registerCommand('thread', ThreadCommand);
1611
+ CommandFactory.registerCommand('register', RegisterCommand);
1585
1612
  // "At" spots (utilities/spots.ts): walk/move/approach are one
1586
1613
  // crossing; run and sprint are the fast modes (combat semantics in
1587
1614
  // the spot-move phase).
@@ -1768,16 +1795,12 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
1768
1795
  // First deal of the for-hire board (rotates each homecoming).
1769
1796
  this.rollHireCandidates();
1770
1797
  // The street learns you're home (social layer): presence beats,
1771
- // unsynced ghost earnings go up, and anything YOUR runner earned
1772
- // riding other crews comes down. All fire-and-forget. The DDP hub
1773
- // link (run invites, shared sessions -- Phase C) warms up behind
1774
- // them.
1798
+ // and the DDP hub link (run invites, shared sessions -- Phase C)
1799
+ // warms up behind them. All fire-and-forget.
1775
1800
  this.startPresenceHeartbeat();
1776
1801
  void import('./utilities/shared-run.js')
1777
1802
  .then(m => m.ensureHubLink(this))
1778
1803
  .catch(() => undefined);
1779
- void syncCrewLedger();
1780
- void this.collectGhostEarnings();
1781
1804
  // The next run starts cooking the moment you're home (player
1782
1805
  // request): generation takes 20-40s, and waiting for the player
1783
1806
  // to CALL first meant "give me a bit" every time. Draft now --
@@ -1825,6 +1848,15 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
1825
1848
  if (this._hubScene && this.scene === this._hubScene) {
1826
1849
  this.requestSave('hub-arrival');
1827
1850
  }
1851
+ // A REGISTERED sprite from the save re-shells at the cot (the
1852
+ // technomancer's persistent companion -- register.ts).
1853
+ if (this._pendingSpriteRedeploy && this.scene === this._hubScene) {
1854
+ const spec = this._pendingSpriteRedeploy;
1855
+ this._pendingSpriteRedeploy = undefined;
1856
+ const line = this.respawnRegisteredSprite(spec);
1857
+ if (line)
1858
+ Logger.getInstance().logWithColor(line, 'cyan');
1859
+ }
1828
1860
  if (this._resume) {
1829
1861
  const pendingNote = this._pendingRunSeed
1830
1862
  ? hint(`\n\n${FIXER_NAME}'s job is still on offer: "${this._pendingRunSeed.name}". "call taxi" when you're ready, or "call ${FIXER_NAME}" to hear it again.`)
@@ -1889,7 +1921,16 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
1889
1921
  this.docwagon = save.game.docwagon ? { ...save.game.docwagon } : undefined;
1890
1922
  // THE CREW rides the save; the deprecated one-slot enlistedAlly
1891
1923
  // migrates in once (old saves) and is never written again.
1892
- this.crew = (save.game.crew ?? []).map(m => ({ ...m, gear: [...m.gear] }));
1924
+ // GHOST MIGRATION (mechanic removed 2026-08-23): hired AI copies of
1925
+ // other players' runners dissolve on load -- live seats (liveOnly)
1926
+ // and ordinary hires ride on unchanged.
1927
+ this.crew = (save.game.crew ?? [])
1928
+ .filter(m => !(m.ghost && !m.ghost.liveOnly))
1929
+ .map(m => ({ ...m, gear: [...m.gear] }));
1930
+ // The registered sprite stages for its re-shell at hub arrival.
1931
+ if (save.game.registeredSprite) {
1932
+ this._pendingSpriteRedeploy = { ...save.game.registeredSprite };
1933
+ }
1893
1934
  if (this.crew.length === 0 && save.game.enlistedAlly) {
1894
1935
  const legacy = save.game.enlistedAlly;
1895
1936
  this.crew.push({
@@ -1991,6 +2032,14 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
1991
2032
  contacts: [...this.contacts.entries()],
1992
2033
  procurements: [...this.pendingProcurements],
1993
2034
  docwagon: this.docwagon ? { ...this.docwagon } : undefined,
2035
+ registeredSprite: (() => {
2036
+ // A live registered sprite rides the save; a travel-folded one
2037
+ // is in the redeploy stash. Either way it survives the session.
2038
+ const live = this.companions.find(c => c.kind === 'sprite' && c.registered && c.spriteType);
2039
+ if (live)
2040
+ return { spriteType: live.spriteType, level: live.force, tasks: live.services ?? 0 };
2041
+ return this._pendingSpriteRedeploy ? { ...this._pendingSpriteRedeploy } : undefined;
2042
+ })(),
1994
2043
  crew: this.crew.map(m => ({ ...m, gear: [...m.gear] })),
1995
2044
  commandRefusals: { ...this.commandRefusals },
1996
2045
  fencedCategories: [...this.fencedCategories],
@@ -2191,11 +2240,8 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
2191
2240
  // homecoming lines (obituaries, write-back) rode in above. Fresh
2192
2241
  // faces on the for-hire board each homecoming.
2193
2242
  this.rollHireCandidates();
2194
- // Social settlement: presence resumes, ghost cuts sync up, earnings
2195
- // come home.
2243
+ // Social settlement: the street hears you're home.
2196
2244
  this.beatPresence();
2197
- void syncCrewLedger();
2198
- void this.collectGhostEarnings();
2199
2245
  const allyLine = crewLines.length > 0 ? `\n${crewLines.join('\n')}` : '';
2200
2246
  // DOCWAGON's year ticks in homecomings (12 = a street year, player
2201
2247
  // ruling): the contract lapses quietly unless renewed at Doc's.
@@ -3190,9 +3236,9 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
3190
3236
  { title: 'party', entries: [['crew', 'party'], ['hire'], ['dismiss'], ['train'], ['order'], ['lead'], ['command'], ['deploy'], ['recall']] },
3191
3237
  { title: 'magic', entries: [['spells'], ['cast'], ['summon', 'conjure'], ['project'], ['return'], ['assense']] },
3192
3238
  { title: 'matrix', entries: [['jack'], ['deck'], ['load'], ['unload'], ['hack'], ['pan'], ['silent'], ['reboot'], ['agent'], ['drone'], ['jump', 'rig']] },
3193
- // The Emerged get their own shelf (player request: compile was
3194
- // buried mid-matrix). Threading/complex forms join here when built.
3195
- { title: 'resonance', entries: [['compile', 'decompile']] },
3239
+ // The Emerged get their own shelf (player request: "there MUST be
3240
+ // resonance" -- matrix is the place, Resonance is the talent).
3241
+ { title: 'resonance', entries: [['compile', 'decompile'], ['thread'], ['register']] },
3196
3242
  { title: 'social', entries: [['say'], ['talk-to'], ['call'], ['end-call', 'end', 'hangup'], ['accept'], ['decline', 'deny', 'refuse'], ['haggle', 'negotiate'], ['persuade', 'convince'], ['ask'], ['contacts']] },
3197
3243
  { title: 'street', entries: [['browse', 'buy'], ['sell'], ['catalog'], ['source', 'procure'], ['work', 'gig'], ['perform'], ['palm'], ['docwagon'], ['treat'], ['eat'], ['drink'], ['lifestyle'], ['pay']] },
3198
3244
  { title: 'barriers', entries: [['solve'], ['pick'], ['bluff'], ['unlock'], ['lock']] },
@@ -3235,14 +3281,44 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
3235
3281
  }
3236
3282
  return lines.join('\n');
3237
3283
  }
3238
- // "help <command>": one description, aliases resolved to their primary.
3239
- const allVerbs = [...covered, ...uncategorized];
3240
- const pickedVerb = fuzzyPickName(query, allVerbs);
3284
+ // "help <command>" (and "help <command> <sub>"): the command's
3285
+ // one-liner plus a TIDY subcommand table when it declares one
3286
+ // (player ruling: structured rows, not a paragraph; a second word
3287
+ // drills into one row).
3288
+ const words = query.split(/\s+/);
3289
+ const pickedVerb = fuzzyPickName(words[0], [...covered, ...uncategorized])
3290
+ ?? fuzzyPickName(query, [...covered, ...uncategorized]);
3241
3291
  if (pickedVerb) {
3242
3292
  const entry = Game.HELP_CATEGORIES.flatMap(c => c.entries).find(e => e.includes(pickedVerb));
3243
3293
  const primary = entry?.[0] ?? pickedVerb;
3244
3294
  const aliases = entry?.slice(1) ?? [];
3245
- return `${primary}${aliases.length > 0 ? ` (${aliases.join(', ')})` : ''}: ${this.describeVerb(primary)}`;
3295
+ const CommandClass = CommandFactory.getAllRegisteredCommands()[primary];
3296
+ const subs = CommandClass?.getSubcommands?.();
3297
+ const title = `${primary}${aliases.length > 0 ? ` (${aliases.join(', ')})` : ''}`;
3298
+ if (subs && subs.length > 0) {
3299
+ // Drill-down: "help crew hire" -> just the hire row, long form.
3300
+ const subQuery = words.slice(1).join(' ');
3301
+ if (subQuery) {
3302
+ // A row's key: the word after the primary verb when usage
3303
+ // starts with it, else its first word.
3304
+ const subKey = (s) => {
3305
+ const t = s.usage.split(/\\s+/);
3306
+ return (t[0] === primary ? t[1] ?? t[0] : t[0]).replace(/[<>|]/g, '');
3307
+ };
3308
+ const pickedSub = fuzzyPickName(subQuery, subs.map(subKey));
3309
+ const sub = pickedSub ? subs.find(s => subKey(s) === pickedSub) : undefined;
3310
+ if (sub)
3311
+ return `${sub.usage}\n ${sub.long ?? sub.short}`;
3312
+ return `${title}: no subcommand matching "${subQuery}".\n${subs.map(s => ` ${s.usage.padEnd(24)} ${s.short}`).join('\n')}`;
3313
+ }
3314
+ const lines = [`${title}: ${this.describeVerb(primary).split('. ')[0]}.`];
3315
+ for (const s of subs)
3316
+ lines.push(` ${s.usage.padEnd(24)} ${s.short}`);
3317
+ lines.push('');
3318
+ lines.push(`"help ${primary} <sub>" drills into one.`);
3319
+ return lines.join('\n');
3320
+ }
3321
+ return `${title}: ${this.describeVerb(primary)}`;
3246
3322
  }
3247
3323
  return `No command or category called "${query}". Try plain "help".`;
3248
3324
  }