@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.
- package/bundle/typescript/package.json +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/archetypes.js +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/archetypes.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/command.d.ts +16 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/command.js +10 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/command.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/compile.d.ts +27 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/compile.js +8 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/compile.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/crew.d.ts +12 -2
- package/bundle/typescript/src/commands/game/sideQuest/commands/crew.js +69 -79
- package/bundle/typescript/src/commands/game/sideQuest/commands/crew.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/players.d.ts +4 -6
- package/bundle/typescript/src/commands/game/sideQuest/commands/players.js +6 -9
- package/bundle/typescript/src/commands/game/sideQuest/commands/players.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/register.d.ts +16 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/register.js +74 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/register.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/thread.d.ts +17 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/thread.js +165 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/thread.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/engine-version.d.ts +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/engine-version.js +3 -1
- package/bundle/typescript/src/commands/game/sideQuest/engine-version.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/game.d.ts +17 -4
- package/bundle/typescript/src/commands/game/sideQuest/game.js +122 -46
- package/bundle/typescript/src/commands/game/sideQuest/game.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/player.d.ts +9 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/player.js +17 -4
- package/bundle/typescript/src/commands/game/sideQuest/models/player.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/types/save-file.d.ts +7 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/shared-run.d.ts +5 -5
- package/bundle/typescript/src/commands/game/sideQuest/utilities/shared-run.js +9 -9
- package/bundle/typescript/src/commands/game/sideQuest/utilities/shared-run.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/social.d.ts +1 -30
- package/bundle/typescript/src/commands/game/sideQuest/utilities/social.js +4 -89
- package/bundle/typescript/src/commands/game/sideQuest/utilities/social.js.map +1 -1
- package/package.json +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/crew-ledger.d.ts +0 -21
- package/bundle/typescript/src/commands/game/sideQuest/utilities/crew-ledger.js +0 -25
- 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 {
|
|
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
|
|
231
|
-
// the
|
|
232
|
-
//
|
|
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
|
-
|
|
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
|
-
//
|
|
1772
|
-
//
|
|
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
|
-
|
|
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:
|
|
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:
|
|
3194
|
-
//
|
|
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>"
|
|
3239
|
-
|
|
3240
|
-
|
|
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
|
-
|
|
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
|
}
|