@maka/maka-cli 5.382.0 → 5.383.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.382.0",
3
+ "version": "5.383.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.",
@@ -1,7 +1,7 @@
1
1
  // src/commands/command-registry.ts
2
2
  import { CommandFactory } from '../factories/command-factory.js';
3
3
  import { Logger } from '../utilities/logger.js';
4
- import { authorizeNpcAction } from '../utilities/npc-authorization.js';
4
+ import { authorizeNpcAction, clientHoldsTheMeet } from '../utilities/npc-authorization.js';
5
5
  export class CommandRegistry {
6
6
  scene;
7
7
  constructor(scene) {
@@ -74,6 +74,14 @@ export class CommandRegistry {
74
74
  return { args, refusal: verdict.reason ?? `That is not yours to do.` };
75
75
  }
76
76
  }
77
+ // The meet's Johnson keeps his seat (npc-authorization.ts).
78
+ if (declaring.isMovement() && !this.scene.isHumanControlled(actor)) {
79
+ const held = clientHoldsTheMeet(this.scene, actor);
80
+ if (held) {
81
+ Logger.getInstance().write(`Refused "${commandString.trim()}" from ${actor.name}: the client keeps his seat.`);
82
+ return { args, refusal: held };
83
+ }
84
+ }
77
85
  return { command, args };
78
86
  }
79
87
  }
@@ -46,6 +46,16 @@ export class Command {
46
46
  // whether or not it also remembered to say so here.
47
47
  return this.humanOnly ? 'human-only' : this.npcPolicy;
48
48
  }
49
+ /**
50
+ * THIS VERB MOVES THE ACTOR'S OWN BODY -- across the room (move, run,
51
+ * sprint, fly), out of it (go) or after someone (follow). Read by
52
+ * CommandRegistry.parse for AI-driven actors only: a Johnson holding a
53
+ * meet keeps his seat (npc-authorization.ts clientHoldsTheMeet).
54
+ */
55
+ static movesActor = false;
56
+ static isMovement() {
57
+ return this.movesActor;
58
+ }
49
59
  static verb;
50
60
  static description; // Make this static
51
61
  actor;
@@ -124,6 +124,7 @@ export function dragFollowers(scene, mover, origin, logger) {
124
124
  }
125
125
  }
126
126
  export class FollowCommand extends Command {
127
+ static movesActor = true;
127
128
  static verb = 'follow';
128
129
  static description = 'Tail another runner: "follow <name>" and their room changes take you with them. Bare "follow" (or "unfollow") peels off. It breaks on its own if you get caught in a fight, fall behind, or they leave the run. Same room, same plane, real players only -- NPC companions already heel by nature ("order" sends them).';
129
130
  /** The scene's other humans -- the only legal targets. */
@@ -96,6 +96,7 @@ export class GoCommand extends Command {
96
96
  return false;
97
97
  return GoCommand.HEELS_ON[c.kind](plane);
98
98
  }
99
+ static movesActor = true;
99
100
  static verb = 'go';
100
101
  static description = 'Move in the specified direction ("go back" retraces your last move). Fleeing someone you just fought hands them one free parting shot. Jacked in, the grid sees no distance: "go <any room>" jumps your persona straight there ("map" lights the icons). Inside a host there is no distance to cross at all -- "exit" first.';
101
102
  /** See MoveCommand.execute: going through a door is a walk too, and
@@ -1,7 +1,7 @@
1
1
  import { Command } from './command.js';
2
2
  import { hint } from '../utilities/hints.js';
3
3
  import { capitalCase } from 'change-case';
4
- import { planeTintItemName, objectiveMark } from '../utilities/planes.js';
4
+ import { planeTintItemName, jobMark } from '../utilities/planes.js';
5
5
  import { dataDevices } from '../utilities/data-library.js';
6
6
  /**
7
7
  * On-demand inventory panel, replacing the always-on blessed sidebar box
@@ -76,7 +76,7 @@ export class InventoryCommand extends Command {
76
76
  const tinted = item.plane === 'meat'
77
77
  ? rarityTint(item.rating, planeTintItemName(item, 'meat'))
78
78
  : planeTintItemName(item, 'meat');
79
- let line = ` ${CARRIED_MARK}${objectiveMark(this.scene, item.name)}${tinted}`;
79
+ let line = ` ${CARRIED_MARK}${jobMark(this.scene, item)}${tinted}`;
80
80
  // x N for a stack, the same shape the currency tag below uses.
81
81
  if (item.quantity > 1)
82
82
  line += ` �${item.quantity}`;
@@ -130,7 +130,7 @@ export class InventoryCommand extends Command {
130
130
  if (libraries.length > 0) {
131
131
  lines.push('', `{light-blue-fg}On your devices:{/light-blue-fg}`);
132
132
  for (const device of libraries) {
133
- lines.push(` ${device.name}: ${device.files.map(f => `${objectiveMark(this.scene, f.name)}${planeTintItemName(f, 'meat')}`).join(', ')}`);
133
+ lines.push(` ${device.name}: ${device.files.map(f => `${jobMark(this.scene, f)}${planeTintItemName(f, 'meat')}`).join(', ')}`);
134
134
  }
135
135
  const legend = hint(`("transfer <file> to <device>" moves one; "transfer <file> to chip" writes it to physical media.)`);
136
136
  if (legend)
@@ -45,6 +45,7 @@ import { onTheWall, midClimbRefusal } from '../utilities/climb-state.js';
45
45
  * verb that leaves a room.
46
46
  */
47
47
  export class MoveCommand extends Command {
48
+ static movesActor = true;
48
49
  static verb = 'walk';
49
50
  static description = 'Cross the room to a named spot or person: "move to bar", "move to Brick" (also "walk"/"approach"; "run to"/"sprint to" are the fast crossings). "move south 2" paces you across the room by cells instead -- use "go" to leave the room entirely. Bare "move" lists the room\'s spots. You must share a spot with someone to talk or trade with them.';
50
51
  /** Which movement mode this registration answers to (see subclasses). */
@@ -1,6 +1,6 @@
1
1
  import { Command } from './command.js';
2
2
  import { hint } from '../utilities/hints.js';
3
- import { planeTintItemName, emphasizeFind, objectiveMark, fileReach } from '../utilities/planes.js';
3
+ import { planeTintItemName, emphasizeFind, jobMark, fileReach } from '../utilities/planes.js';
4
4
  import { rollPool, formatRoll } from '../utilities/dice.js';
5
5
  import { perceptionTest, matrixSearchTest, matrixSearchSeconds, MATRIX_SEARCH } from '../utilities/perception.js';
6
6
  import { gridVicinity } from '../utilities/grid-reach.js';
@@ -194,7 +194,7 @@ export class SearchCommand extends Command {
194
194
  if (files.length === 0) {
195
195
  return `The archive is indexed and EMPTY -- ${hostLabel(where)} keeps nothing worth the trip. ${clock}`;
196
196
  }
197
- const list = files.map(i => `${objectiveMark(this.scene, i.name)}${emphasizeFind(planeTintItemName(i, actor.plane))}`).join(', ');
197
+ const list = files.map(i => `${jobMark(this.scene, i)}${emphasizeFind(planeTintItemName(i, actor.plane))}`).join(', ');
198
198
  const seal = where.hostCracked
199
199
  ? hint(` ("take" them -- the seal is already broken.)`)
200
200
  : hint(` (sealed in the host's data vault: "hack" the node to break the seal, then "take".)`);
@@ -206,7 +206,7 @@ export class SearchCommand extends Command {
206
206
  const sealedElsewhere = files.length > 0 && where.hasNode;
207
207
  return `Nothing loose on ${where.name}'s grid. ${clock}${sealedElsewhere ? hint(` (${hostLabel(where)} keeps its files INSIDE -- "enter" it and search from in there.)`) : ''}`;
208
208
  }
209
- const list = loose.map(i => `${objectiveMark(this.scene, i.name)}${emphasizeFind(planeTintItemName(i, actor.plane))}`).join(', ');
209
+ const list = loose.map(i => `${jobMark(this.scene, i)}${emphasizeFind(planeTintItemName(i, actor.plane))}`).join(', ');
210
210
  return `Loose on ${where.name}'s grid: ${list}. ${clock}${hint(` ("take" rides them out on your deck.)`)}`;
211
211
  }
212
212
  async execute(_args) {
@@ -303,7 +303,7 @@ export class SearchCommand extends Command {
303
303
  // Plain lowercase for the log/AI history; the SCREEN list is tinted
304
304
  // per plane (see planeTintItemName) so paydata reads as data.
305
305
  const itemsList = found.map(i => i.name.toLowerCase()).join(', ');
306
- const displayList = found.map(i => `${objectiveMark(this.scene, i.name)}${emphasizeFind(planeTintItemName(i, this.actor.plane))}`).join(', ');
306
+ const displayList = found.map(i => `${jobMark(this.scene, i)}${emphasizeFind(planeTintItemName(i, this.actor.plane))}`).join(', ');
307
307
  this.logger.write(`SearchCommand: ${this.actor.name} found the following items: ${itemsList}`);
308
308
  this.actor.performAction('search', itemsList);
309
309
  description += `You search the area and find: ${displayList}`;
@@ -153,7 +153,8 @@ export class TalkCommand extends Command {
153
153
  if (insideWithMe.includes(target)) {
154
154
  await speaker.hear(speaker, `(to ${target.name}) ${message}`);
155
155
  await target.hear(speaker, `(privately) ${message}`);
156
- return `You speak to ${target.name} inside the host: "${message}"`;
156
+ // The echo above IS the confirmation (see the meat branch below).
157
+ return '';
157
158
  }
158
159
  // "At" spots: conversation needs shared ground (see
159
160
  // utilities/spots.ts) -- the bartender doesn't lean across the room.
@@ -174,7 +175,12 @@ export class TalkCommand extends Command {
174
175
  // Face time with the run's CLIENT is THE MEET (or the wrap-up) --
175
176
  // see Game.onClientInteraction (terms / lump-sum settlement).
176
177
  const clientBeat = this.game?.onClientInteraction(target.name);
177
- return `You talk to ${target.name} privately: "${message}"${clientBeat ? `\n${clientBeat}` : ''}`;
178
+ // ONE LINE PER THING SAID (86LjwvK7xSbXhWXGi: "this repeated text can
179
+ // be removed: You talk to Mr. Vellum privately: ..."). The speaker's
180
+ // own hear() above already printed "Maka: (to Mr. Vellum) ...", so a
181
+ // reply quoting the same words back was the same sentence twice. The
182
+ // reply carries only what the echo cannot: the meet's beat.
183
+ return clientBeat ?? '';
178
184
  }
179
185
  }
180
186
  //# sourceMappingURL=talk.js.map
@@ -3319,5 +3319,20 @@
3319
3319
  // hub boot (reaching a save that already slid) and after a shared
3320
3320
  // run's homecoming. A move that cannot land now is retried next time;
3321
3321
  // a session with no districtSeedLoader never moves.
3322
- export const ENGINE_VERSION = '1.194.0';
3322
+ // 1.195.0 (2026-10-04): THE MEET, CLEANED UP (four live-table reports).
3323
+ // - The run's client holds his seat until the job is settled: his AI's
3324
+ // move/approach/run/sprint/fly/go/follow are refused with a reason
3325
+ // (npc-authorization.ts clientHoldsTheMeet; Command.movesActor), except
3326
+ // inside a fight (CWMrB2aNyL4hhrjyG).
3327
+ // - A body nudged onto an exact cell (shoulderAside, yieldDoorway) still
3328
+ // hears and is heard: earshot reads an exact cell as a place, so the
3329
+ // shoved Johnson is no longer deaf to `say` (xurFSiqNtRNbXA88A).
3330
+ // - talk-to/tell no longer quote the words back under the speaker's own
3331
+ // echo (86LjwvK7xSbXhWXGi).
3332
+ // - `inv`/`search` mark job leads with ☆ -- a page spelling a scene
3333
+ // device's code, or a device's own key -- beside the objective's ★
3334
+ // (nfrBW27eRRxstJ8uC).
3335
+ // - The HUD's holstered line drops its `-- "brandish"` tip
3336
+ // (MkxamPxmsSyonR3wC).
3337
+ export const ENGINE_VERSION = '1.195.0';
3323
3338
  //# sourceMappingURL=engine-version.js.map
@@ -9273,8 +9273,12 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
9273
9273
  ? ` {red-fg}[JAMMED -- reboot]{/red-fg}`
9274
9274
  : weaponItem.ammo === 0 ? ` {red-fg}[DRY]{/red-fg}` : ` [${weaponItem.ammo} rds]`)
9275
9275
  : '';
9276
+ // NO VERB COACHING IN THE HUD (MkxamPxmsSyonR3wC: "get rid of this
9277
+ // line in the status bar: -- "brandish""). The status bar is state;
9278
+ // the `brandish` hint already rides the equip/holster replies and
9279
+ // `equipment`, where it answers the moment the question comes up.
9276
9280
  if (player.plane === 'meat' && weaponItem && !player.weaponDrawn) {
9277
- return `{white-fg}Holstered: ${weaponItem.name}${ammoNote}${hint(' -- "brandish"')}{/white-fg}`;
9281
+ return `{white-fg}Holstered: ${weaponItem.name}${ammoNote}{/white-fg}`;
9278
9282
  }
9279
9283
  // White, not gray: gray is ANSI bright-black and illegible on
9280
9284
  // stock dark palettes -- the same ruling that moved the HUD
@@ -2613,7 +2613,9 @@ staff, so codes, passphrases, and lock combinations are exactly the thing you we
2613
2613
  THEM to solve -- say so plainly when they ask, and send them to find it rather than
2614
2614
  handing them a number you'd be making up. If you genuinely don't know a thing, say so plainly and point at who might. You
2615
2615
  front half the pay at the meet and settle the rest on delivery (the engine moves the money
2616
- -- never haggle it yourself unless they invoke a negotiation).`
2616
+ -- never haggle it yourself unless they invoke a negotiation).
2617
+ You chose this venue and you HOLD YOUR SEAT in it: never move, walk, approach, follow or
2618
+ leave -- the runner comes to you. Turn, lean, gesture or speak instead.`
2617
2619
  : '';
2618
2620
  // ---------- prompt ----------
2619
2621
  const prompt = `
@@ -3180,6 +3180,10 @@ export class Player extends AbstractPlayer {
3180
3180
  * rather than leaving you drawn at the old one. NPCs keep re-deriving --
3181
3181
  * a picket or a crowd is placed by spot on purpose (NPC overrides).
3182
3182
  */
3183
+ /** An exact cell somebody PUT this body on -- a step, a shove, a
3184
+ * flight -- as opposed to a drift seat pinned only so it is drawn in
3185
+ * one place (pinDerivedSeat). earshot.ts reads the first as a place. */
3186
+ get placedOnCell() { return this._atCell !== undefined && !this._cellPinnedFromSeat; }
3183
3187
  get pinsDerivedSeat() { return true; }
3184
3188
  pinDerivedSeat(cell) {
3185
3189
  this.atCell = cell;
@@ -1,4 +1,4 @@
1
- import { sameSpot, spotOf, spotsActive, isPerchSpot, OPEN_FLOOR, CONVERSATION_M, actorDistanceMeters } from './spots.js';
1
+ import { sameSpot, spotOf, spotsActive, isPerchSpot, OPEN_FLOOR, CONVERSATION_M, actorDistanceMeters, onTheFloor } from './spots.js';
2
2
  /**
3
3
  * WHO CAN OVERHEAR A MURMUR (playtest, Deditri: "a private place for
4
4
  * calls"). A call is audible to the room, and that is correct and
@@ -123,6 +123,28 @@ export function hearingRangeMeters(listener) {
123
123
  const bonus = listener.augBonus('hearing') + listener.qualityBonus('perception');
124
124
  return Math.max(SPEECH_METERS, SPEECH_METERS + bonus);
125
125
  }
126
+ /**
127
+ * HAS A PLACE IN THE ROOM: a named spot, OR an exact cell on the floor.
128
+ *
129
+ * THE SECOND HALF IS THE FIX (xurFSiqNtRNbXA88A: "I'm getting 'Nobody's
130
+ * close enough to catch that', but I'm right next to Mr. Vellum"). The
131
+ * runner stepped into Vellum's square; the excuse-me rule (spots.ts
132
+ * shoulderAside) moved him one cell over and, as it and yieldDoorway both
133
+ * do on purpose, left him on that EXACT cell with no spot name. This
134
+ * module read "no spot name" as "circulating", and a circulating listener
135
+ * is passing through and hears no murmur -- so a Johnson drawn one square
136
+ * from the runner was deaf to everything said to him for the rest of the
137
+ * meet. An exact cell is the most precise position the engine has
138
+ * (seatingIn seats it first), and distance is measured from it.
139
+ *
140
+ * A DRIFT SEAT IS STILL NOT A PLACE: a circulating runner's derived seat
141
+ * is pinned to atCell only so it is drawn in one spot (Player.
142
+ * pinDerivedSeat), and placedOnCell says no to it -- the Kessler rule
143
+ * below stands (speech-range.test.ts).
144
+ */
145
+ function standsSomewhere(actor) {
146
+ return spotOf(actor) !== undefined || (onTheFloor(actor) && actor.placedOnCell);
147
+ }
126
148
  /**
127
149
  * CAN THIS LISTENER HEAR ORDINARY SPEECH FROM THERE -- the whole of the
128
150
  * `say` rule, in one place so the verb, the NPC fan-out and any future
@@ -176,7 +198,7 @@ export function canHearShout(speaker, listener) {
176
198
  const room = speaker.currentLocation;
177
199
  if (!spotsActive(room))
178
200
  return true;
179
- if (spotOf(speaker) === undefined || spotOf(listener) === undefined)
201
+ if (!standsSomewhere(speaker) || !standsSomewhere(listener))
180
202
  return true;
181
203
  const reach = SHOUT_METERS + (hearingRangeMeters(listener) - SPEECH_METERS);
182
204
  return actorDistanceMeters(room, speaker, listener) <= reach;
@@ -186,10 +208,10 @@ export function canHearSpeech(speaker, listener) {
186
208
  if (!spotsActive(room))
187
209
  return true;
188
210
  // The speaker is unplaced: they are moving among everyone.
189
- if (spotOf(speaker) === undefined)
211
+ if (!standsSomewhere(speaker))
190
212
  return true;
191
213
  // The listener is unplaced: they are passing through, not standing here.
192
- if (spotOf(listener) === undefined)
214
+ if (!standsSomewhere(listener))
193
215
  return false;
194
216
  return actorDistanceMeters(room, speaker, listener) <= hearingRangeMeters(listener);
195
217
  }
@@ -126,6 +126,33 @@ export function authorizeNpcAction(scene, actor, verb, args) {
126
126
  }
127
127
  return ALLOWED;
128
128
  }
129
+ /**
130
+ * THE JOHNSON HOLDS THE MEET (CWMrB2aNyL4hhrjyG: "when at the meet, the
131
+ * johnson shouldn't just keep moving around the room").
132
+ *
133
+ * The run's client picks the venue and waits in it (Run Faster's meet,
134
+ * p.197-198: the Johnson sets the place, the runners come to them). His
135
+ * AI read every beat as a chance to stage himself -- "Mr. Lacquer moves
136
+ * to the open floor", "moves to the counter", prod 2026-10-04 -- so the
137
+ * man the runner came to see wandered the room for the whole briefing.
138
+ * Prose can still have him turn, lean, or look at the door; the engine
139
+ * keeps his body where the meet put it until the job is settled, and his
140
+ * leaving after the settlement is the engine's own beat (game.ts
141
+ * "Johnsons don't linger"), not his AI's.
142
+ *
143
+ * A FIGHT IS NOT A MEET: inside a Combat Turn the turn loop moves him,
144
+ * and a Johnson ducking for cover is not this rule's business.
145
+ *
146
+ * Returns a refusal reason, or undefined when he may move.
147
+ */
148
+ export function clientHoldsTheMeet(scene, actor) {
149
+ const client = scene.ownerGame?.currentClient;
150
+ if (!client || client.name !== actor.name || client.reported || client.dead)
151
+ return undefined;
152
+ if (actor.inExchange || scene.encounterIn?.(actor.currentLocation))
153
+ return undefined;
154
+ return `You hold your seat for the meet -- the runner came to you, and you stay where you are until the job is settled. Turn, gesture, or speak instead of walking.`;
155
+ }
129
156
  /**
130
157
  * Is this actor home? A decker jacked into the Matrix, a magician
131
158
  * projecting astrally and a rigger jumped into a drone all leave a body
@@ -7,6 +7,7 @@ import { fuzzyPickName } from './fuzzy-match.js';
7
7
  import { resetOverwatch } from './overwatch.js';
8
8
  import { rebootWipe } from './marks.js';
9
9
  import { hint } from './hints.js';
10
+ import { codesOnPage } from './clue-reading.js';
10
11
  import { actorCell } from './spots.js';
11
12
  import { announceGameOver } from './game-over.js';
12
13
  import { holdAtBrink } from './not-dead-yet.js';
@@ -624,6 +625,43 @@ export function isObjectiveItem(scene, itemName) {
624
625
  export function objectiveMark(scene, itemName) {
625
626
  return isObjectiveItem(scene, itemName) ? `{yellow-fg}${OBJECTIVE_GLYPH}{/yellow-fg} ` : '';
626
627
  }
628
+ /**
629
+ * PART OF THE JOB, NOT THE PRIZE (nfrBW27eRRxstJ8uC: "the 'Lot Catalogue
630
+ * Proof Sheet' isn't marked in my inventory as quest specific").
631
+ *
632
+ * The star is the objective alone, so a page the job's route runs
633
+ * through -- the proof sheet with the vault shutter's code pencilled in
634
+ * its margin -- sat unmarked among the loot, and the runner could not
635
+ * tell it from a souvenir. A LEAD is an item this scene's barriers
636
+ * answer to, read off the same facts the engine itself opens them with:
637
+ * - a page that spells one of the scene's device codes (clue-reading.ts
638
+ * codesOnPage -- the fact ensureClueDetails guarantees is written), or
639
+ * - the key a device declares as its own (Device.keyItem).
640
+ * Nothing else: a lead the engine cannot point to would be a guess.
641
+ *
642
+ * It marks the item, never the code -- whether the runner can READ the
643
+ * number off the page is still the Perception Test (clue-reading.ts).
644
+ * Hub scenes have no win condition and mark nothing, like the star.
645
+ */
646
+ export const LEAD_GLYPH = '☆';
647
+ export function isJobLead(scene, item) {
648
+ if (!scene?.getWinCondition?.())
649
+ return false;
650
+ const name = item.name.trim().toLowerCase();
651
+ for (const room of Object.values(scene.getRooms())) {
652
+ for (const device of room.devices ?? []) {
653
+ if (device.keyItem?.name.trim().toLowerCase() === name)
654
+ return true;
655
+ }
656
+ }
657
+ return codesOnPage(scene, item).length > 0;
658
+ }
659
+ /** `★ ` for the objective, `☆ ` for a lead (isJobLead), '' otherwise. */
660
+ export function jobMark(scene, item) {
661
+ if (isObjectiveItem(scene, item.name))
662
+ return objectiveMark(scene, item.name);
663
+ return isJobLead(scene, item) ? `{yellow-fg}${LEAD_GLYPH}{/yellow-fg} ` : '';
664
+ }
627
665
  /**
628
666
  * The "you can see them, but you can't touch them" refusal. When a target
629
667
  * name matches someone in the room on a plane the actor perceives but
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maka/maka-cli",
3
- "version": "5.382.0",
3
+ "version": "5.383.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.",