@maka/maka-cli 5.161.0 → 5.163.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.161.0",
3
+ "version": "5.163.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,6 +1,7 @@
1
1
  import { Command } from './command.js';
2
2
  import { hint } from '../utilities/hints.js';
3
3
  import { authToken } from '../utilities/cloud-saves.js';
4
+ import { currentSession } from '../utilities/session-context.js';
4
5
  import { submitBacklog, fetchBacklog, postBacklogComment, postBacklogSignoff, postBacklogReject, postBacklogClose, BACKLOG_STATUSES, } from '../utilities/backlog.js';
5
6
  import { noteBacklogOutcome } from '../utilities/playtester.js';
6
7
  import { takeIndex } from '../utilities/fuzzy-match.js';
@@ -163,6 +164,10 @@ export class BacklogCommand extends Command {
163
164
  cliVersion: cliVersion(),
164
165
  engineVersion: engineVersion(),
165
166
  platform: process.platform,
167
+ // The host names the client (session-context.ts): a browser run's
168
+ // engine is the same code on a linux box, and 'linux' told nobody
169
+ // where the report actually came from (wQEyefjzbwtdmdrJo).
170
+ client: currentSession()?.client ?? 'cli',
166
171
  });
167
172
  // A real call classifies the account for free -- no second request.
168
173
  noteBacklogOutcome(outcome);
@@ -392,5 +392,9 @@
392
392
  // aggressor and their target; the world event carries it; "initiative"
393
393
  // marks them. Hostiles are red on the room map by stance or by live
394
394
  // enmity, not by the retired exchange flag.
395
- export const ENGINE_VERSION = '1.44.0';
395
+ // 1.45.0 (2026-09-11): THE MAPS AS DATA. The condition beat carries the
396
+ // room's live grid, every body's engine-resolved square, and the room
397
+ // panel's own hostile test; the map event carries the fog-applied
398
+ // district layout (data.district). Envelope and beat shape both grew.
399
+ export const ENGINE_VERSION = '1.45.0';
396
400
  //# sourceMappingURL=engine-version.js.map
@@ -2401,7 +2401,11 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
2401
2401
  if (!this._conditionSink && process.env.MAKA_NO_DDP === '1')
2402
2402
  return;
2403
2403
  void import('./utilities/condition-report.js')
2404
- .then(m => m.reportConditionSoon(this.player, this._conditionSink))
2404
+ .then(m => m.reportConditionSoon(this.player, this._conditionSink, {
2405
+ // The room panel's own red (updateExits below): stance OR live
2406
+ // enmity in the viewer's encounter. Same predicate, same map.
2407
+ isHostile: a => hostileByStance(a) || enemyInFight(this.scene, this.player, a),
2408
+ }))
2405
2409
  .catch(() => undefined);
2406
2410
  };
2407
2411
  // A ROOM CHANGE IS A SAVE BEAT (player request 2026-09-02): where you
@@ -5303,6 +5307,15 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
5303
5307
  const heat = this.heatBand();
5304
5308
  if (heat && player === this.player)
5305
5309
  lines.push(heat);
5310
+ // THE ALARM (kqrSCWjj96SP4MRXj, web run 2026-09-11: "I got a
5311
+ // notification that the alarms sound, but I don't see any
5312
+ // indication in my status bar"). The site going loud was a world
5313
+ // event that scrolled away; Scene.alarmRaised has been the standing
5314
+ // fact since 2026-08-27, and it belongs on the HUD for as long as it
5315
+ // holds. A fact about the PLACE, so every player at the table sees
5316
+ // it -- unlike heat, which is the primary's own ledger.
5317
+ if (this.scene?.alarmRaised)
5318
+ lines.push(`{red-fg}🚨 ALARM -- the site is lit{/red-fg}`);
5306
5319
  // Line 3: the rest of the readiness read -- armor, and whether the
5307
5320
  // commlink is actually live (equipped), the recurring "why can't I
5308
5321
  // call anyone" friction surfaced at a glance. The armed state used
@@ -17,7 +17,7 @@ import { Logger } from './utilities/logger.js';
17
17
  import { currentSession, runInSession } from './utilities/session-context.js';
18
18
  import { restorePlayer, serializePlayer } from './utilities/persistence.js';
19
19
  import { runWithAuthToken } from './utilities/cloud-saves.js';
20
- import { renderMapViewport, mapCaption } from './utilities/map-view.js';
20
+ import { renderMapViewport, mapCaption, serializeDistrictMap } from './utilities/map-view.js';
21
21
  import { hostileByStance, enemyInFight, renderRoomLayoutCompact, crewOfTable, roomCaption } from './utilities/room-view.js';
22
22
  import { serializeRoomGrid } from './utilities/room-grid.js';
23
23
  import { entrySpotFor, spotOf } from './utilities/spots.js';
@@ -190,7 +190,7 @@ export function buildPlayerFromSave(save) {
190
190
  }
191
191
  export async function createHeadlessSession(opts) {
192
192
  const logger = new EmitterLogger(opts.sink, opts.debug);
193
- const ctx = { sessionId: opts.sessionId, logger };
193
+ const ctx = { sessionId: opts.sessionId, logger, client: opts.client };
194
194
  return runInSession(ctx, async () => {
195
195
  const primary = buildPlayerFromSave(opts.players[0]);
196
196
  const game = new Game(primary, opts.seed, opts.conditionSink);
@@ -297,7 +297,18 @@ export async function createHeadlessSession(opts) {
297
297
  const mapLines = renderMapViewport(rooms, here, sealing, eyes);
298
298
  if (mapLines.length > 0)
299
299
  mapLines.push(mapCaption(here.name));
300
- logger.system(mapLines.join('\n'), { actor: p.name }, 'map');
300
+ // STRUCTURED DISTRICT MAP, ADDITIVE (2026-09-11): the same layout
301
+ // as data, on this same event, so the browser client draws it in
302
+ // its own style instead of reparsing the text (the `data.grid`
303
+ // pattern below). A decoration must never kill the push.
304
+ let district;
305
+ try {
306
+ district = serializeDistrictMap(rooms, here, sealing, eyes);
307
+ }
308
+ catch (err) {
309
+ logger.write(`District map serialize failed for ${p.name} in ${here.name}: ${err instanceof Error ? err.stack ?? err.message : String(err)}`);
310
+ }
311
+ logger.system(mapLines.join('\n'), { actor: p.name }, 'map', district ? { district } : undefined);
301
312
  // THE ROOM BOX'S TWIN (playtest 2026-08-27, reported twice:
302
313
  // "the room minimap isn't updating in the shared run, I only
303
314
  // see @open floor"). Game.updateExits early-returns on
@@ -1,6 +1,8 @@
1
1
  import { Logger } from './logger.js';
2
- import { visibleActorsIn } from './spots.js';
2
+ import { visibleActorsIn, seatingIn } from './spots.js';
3
3
  import { hostLabel } from './grid-names.js';
4
+ import { serializeRoomGrid } from './room-grid.js';
5
+ import { hostileByStance } from './room-view.js';
4
6
  /**
5
7
  * THE LIVE CONDITION EMITTER -- the CLI half of the realtime sheet lane
6
8
  * (contract agreed with maka-cli.com, 2026-09-01). On every condition
@@ -71,13 +73,56 @@ export function setMarksPlacedResolver(fn) {
71
73
  export function setActorRoleResolver(fn) {
72
74
  actorRole = fn;
73
75
  }
76
+ /**
77
+ * THE ROOM'S GEOMETRY, ON THE BEAT (2026-09-11, 8bZXzNXLgKwE5wxgh: "no
78
+ * icons being drawn on the character sheet in the active run"). The web
79
+ * sheet rebuilt its room from the SAVE's hub seed, and a generated run
80
+ * room is not in it -- so in a run the sheet had no furniture, no doors,
81
+ * and no cell to seat a spot-sitting NPC on. The beat already fires on
82
+ * every position change and is the one channel both the sheet and the
83
+ * play page read, so the live grid rides it: the SAME wire shape the
84
+ * room event carries for the browser client (headless.ts pushMap ->
85
+ * serializeRoomGrid). Physical planes only -- jacked in, the meat map is
86
+ * a lie the sheet already refuses to draw. A decoration never kills the
87
+ * beat: any throw here is an absent field, not a lost report.
88
+ */
89
+ function roomGridFor(player) {
90
+ const room = player.currentLocation;
91
+ if (!room || (player.plane !== 'meat' && player.plane !== 'drone'))
92
+ return undefined;
93
+ try {
94
+ const grid = room.ensureGrid();
95
+ return grid ? serializeRoomGrid(grid, room.name) : undefined;
96
+ }
97
+ catch {
98
+ return undefined;
99
+ }
100
+ }
74
101
  export function buildConditionReport(player, deps) {
75
- const { saveSlug, arSightUp } = deps;
102
+ const { saveSlug, arSightUp, isHostile } = deps;
76
103
  // BRICKED INCLUDED (web report 2026-09-02: "my deck got bricked, but its
77
104
  // status still says 4/12"): a forced dump clears activeDeck and
78
105
  // getCyberdeck() hides a bricked deck, so the beat read nothing and the
79
106
  // sheet froze on the last number. Status reads getAnyCyberdeck.
80
107
  const deck = player.activeDeck ?? player.getAnyCyberdeck();
108
+ // WHERE EVERYONE ACTUALLY STANDS (2026-09-11, user: "movement isn't
109
+ // snappy" on the web). The beat sent `atCell` alone, and `atCell` is
110
+ // the EXCEPTION -- set by a tactical step, cleared by "move to <spot>"
111
+ // and by most arrivals -- so after a plain move the beat carried no
112
+ // cell, the server kept the last one it had, and the dot on the web
113
+ // sat still at a square the runner had left. seatingIn is the engine's
114
+ // one answer to "which square" (the room panel, cover and ranges all
115
+ // read it), so the web now gets the same square the CLI draws. Read at
116
+ // fire time like everything else here; a throw means "no seat", never
117
+ // a lost beat.
118
+ const room = player.currentLocation;
119
+ let seats = new Map();
120
+ if (room && (player.plane === 'meat' || player.plane === 'drone')) {
121
+ try {
122
+ seats = seatingIn(room);
123
+ }
124
+ catch { /* keep the empty map */ }
125
+ }
81
126
  return {
82
127
  slug: saveSlug(player.name),
83
128
  damageTaken: player.damageTaken,
@@ -85,7 +130,8 @@ export function buildConditionReport(player, deps) {
85
130
  overflow: 0,
86
131
  room: player.currentLocation?.name,
87
132
  spot: player.atSpot,
88
- cell: player.atCell,
133
+ cell: player.atCell ?? seats.get(player.name),
134
+ grid: roomGridFor(player),
89
135
  plane: player.plane,
90
136
  // Where the meat sits while the persona rides the grid (web accepts
91
137
  // since 2026-09-02): "jacked in from Your Hideout". Absent in the meat.
@@ -196,11 +242,14 @@ export function buildConditionReport(player, deps) {
196
242
  .slice(0, 24)
197
243
  .map(a => ({
198
244
  name: a.name,
245
+ // THE SAME RED THE ROOM PANEL USES (see IConditionReportDeps
246
+ // .isHostile): stance flag OR live enmity, and never a
247
+ // surrendered one -- hostileByStance owns that clause.
199
248
  kind: a.allyOf ? 'ally'
200
- : a.hostile ? 'hostile' : 'neutral',
249
+ : (isHostile?.(a) ?? hostileByStance(a)) ? 'hostile' : 'neutral',
201
250
  role: actorRole(a),
202
251
  spot: a.atSpot,
203
- cell: a.atCell,
252
+ cell: a.atCell ?? seats.get(a.name),
204
253
  })),
205
254
  };
206
255
  }
@@ -227,7 +276,7 @@ function noteRoster(report) {
227
276
  const ADDITIVE_FIELDS = [
228
277
  'ar', 'actors', 'deckLine', 'deckDamage', 'astralPerceiving', 'bodyRoom', 'sustainingPenalty',
229
278
  'matrix', 'panMarks', 'hostMarksOnYou', 'marksPlaced', 'marksOnYou', 'deckStored', 'droneDamage', 'companions', 'pools', 'initiative',
230
- 'karma', 'nuyen', 'edge',
279
+ 'karma', 'nuyen', 'edge', 'grid',
231
280
  ];
232
281
  /** Refusal reasons already written to the session log -- one line each. */
233
282
  const announcedRefusals = new Map();
@@ -242,7 +291,10 @@ function noteRefusal(why) {
242
291
  catch { /* no session */ }
243
292
  }
244
293
  }
245
- export function reportConditionSoon(player, sink) {
294
+ export function reportConditionSoon(player, sink,
295
+ /** The Scene-side half of the beat (IConditionReportDeps.isHostile);
296
+ * the caller that owns a Scene supplies it, nobody else needs to. */
297
+ extra) {
246
298
  // Before the TIMER, not just before the call: a scheduled beat firing
247
299
  // after a Jest file finished lazy-imported into a torn-down
248
300
  // environment and failed CI with every test green (2026-09-01). Under
@@ -269,7 +321,7 @@ export function reportConditionSoon(player, sink) {
269
321
  // stance and brandish flow through their accessors' own reports;
270
322
  // sustained and deck programs are composed here and ride whatever
271
323
  // beat fires next. All read at FIRE time, like position.
272
- const report = buildConditionReport(player, { saveSlug, arSightUp });
324
+ const report = buildConditionReport(player, { saveSlug, arSightUp, isHostile: extra?.isHostile });
273
325
  noteRoster(report);
274
326
  if (sink) {
275
327
  // IN-PROCESS: no HubLink, no MAKA_NO_DDP gate, no version-skew
@@ -220,6 +220,39 @@ export function renderMapViewport(rooms, here, sealing, eyes, viewW = 21, viewH
220
220
  out[halfH] = out[halfH].slice(0, halfW) + `{inverse}${out[halfH][halfW] === ' ' ? '▣' : out[halfH][halfW]}{/inverse}` + out[halfH].slice(halfW + 1);
221
221
  return out;
222
222
  }
223
+ export function serializeDistrictMap(rooms, here, sealing, eyes) {
224
+ const floors = layoutFloors(rooms, here, sealing);
225
+ if (floors.length === 0)
226
+ return undefined;
227
+ const eye = fogEye(rooms, eyes?.visited);
228
+ const ar = eyes?.arEyes !== false;
229
+ const out = floors.map(floor => {
230
+ const linkSeen = (k) => {
231
+ const a = floor.roomAt.get(`${k.ax},${k.ay}`);
232
+ const b = floor.roomAt.get(`${k.bx},${k.by}`);
233
+ return !!a && !!b && eye.sees(a) && eye.sees(b) && (eye.knows(a) || eye.knows(b));
234
+ };
235
+ const seen = [...floor.posByRoom.entries()].filter(([r]) => eye.sees(r));
236
+ return {
237
+ rooms: seen.map(([r, p]) => {
238
+ const known = eye.knows(r);
239
+ return {
240
+ x: p.x, y: p.y,
241
+ ...(known ? { name: r.name } : {}),
242
+ known,
243
+ here: r === here,
244
+ node: known && ar && !!r.hasNode,
245
+ up: known && hasWay(r, 'up'),
246
+ down: known && hasWay(r, 'down'),
247
+ };
248
+ }),
249
+ links: floor.links.filter(linkSeen).map(k => ({ ...k })),
250
+ };
251
+ });
252
+ const current = Math.max(0, floors.findIndex(f => f.posByRoom.has(here)));
253
+ const notes = fogNotes(floors[current]?.notes ?? [], rooms, eye);
254
+ return { current, floors: out, notes };
255
+ }
223
256
  /**
224
257
  * The DETAILED view (the "map" text command): named cells, box-drawing
225
258
  * connections, and annotations -- the renderer shipped in 5.48.0, now
@@ -822,6 +822,14 @@ export function serializeRoomGrid(grid, roomName) {
822
822
  const [x, y, z] = cellKey.split(',').map(Number);
823
823
  return { kind: link.kind, x, y, z };
824
824
  });
825
+ const terrain = [...grid.terrain.values()].map(r => ({
826
+ id: r.id, label: r.label, cover: r.cover, blocking: r.blocking, cleared: r.cleared,
827
+ cells: r.cells.map(c => ({ x: c.x, y: c.y, z: c.z })),
828
+ }));
829
+ const levels = [];
830
+ for (let z = 1; z < grid.dims.z; z++) {
831
+ levels.push({ z, cells: levelFootprint(grid, z).map(c => ({ x: c.x, y: c.y })) });
832
+ }
825
833
  return {
826
834
  name: roomName,
827
835
  dims: { x: grid.dims.x, y: grid.dims.y, z: grid.dims.z },
@@ -829,6 +837,8 @@ export function serializeRoomGrid(grid, roomName) {
829
837
  doors,
830
838
  footprints,
831
839
  vertical,
840
+ terrain,
841
+ levels,
832
842
  };
833
843
  }
834
844
  function clamp(n, lo, hi) { return Math.max(lo, Math.min(hi, n)); }
@@ -1188,6 +1188,29 @@ export class SharedRunSession {
1188
1188
  }
1189
1189
  if (!verb)
1190
1190
  return '';
1191
+ // PULLING SOMEBODY INTO A LIVE RUN (user 2026-09-11: "ensure that I
1192
+ // can pull in another runner during a live shared run, and the run
1193
+ // won't collapse"). The crew ledger is hub-only (crew.ts), and the
1194
+ // session's party was frozen at createSession -- so out here "crew
1195
+ // invite" had nowhere to go. The server adds the seat to the OPEN
1196
+ // session (sideQuest.inviteToOpen); their commlink buzzes through
1197
+ // the same invite feed, and their taxi seats them through the same
1198
+ // addPlayer a late joiner always used. Nothing about the scene is
1199
+ // touched: the job was sized at draft time and stays that size.
1200
+ const inviteMatch = /^(?:crew|party)\s+invite\s+(.+)$/i.exec(input.trim());
1201
+ if (inviteMatch) {
1202
+ try {
1203
+ const res = await HubLink.call('sideQuest.inviteToOpen', {
1204
+ sessionID: this.sessionID,
1205
+ query: inviteMatch[1].trim(),
1206
+ });
1207
+ return `Invite sent to ${res.memberRunner} -- a seat at this table, mid-job.${res.online ? '' : ` (They're off the street -- it'll wait on their link.)`}`;
1208
+ }
1209
+ catch (err) {
1210
+ const e = err;
1211
+ return `{yellow-fg}${e.reason ?? e.message ?? 'The offer does not go through.'}{/yellow-fg}`;
1212
+ }
1213
+ }
1191
1214
  // THE RIDE HOME: on a shared run the taxi is the settlement
1192
1215
  // handshake, not an engine verb -- the leader closes the table for
1193
1216
  // everyone (snapshots persist, shares already on each stick); a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maka/maka-cli",
3
- "version": "5.161.0",
3
+ "version": "5.163.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.",