@craftrpgs/cli 0.1.5 → 0.1.6

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 (2) hide show
  1. package/dist/bin.js +159 -6
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -41900,7 +41900,7 @@ function describeRemoteSource(resolved) {
41900
41900
  // package.json
41901
41901
  var package_default = {
41902
41902
  name: "@craftrpgs/cli",
41903
- version: "0.1.5",
41903
+ version: "0.1.6",
41904
41904
  description: "Sync Craft projects with a local folder — clone, edit with any coding agent, push back.",
41905
41905
  license: "UNLICENSED",
41906
41906
  homepage: "https://craftrpgs.com",
@@ -43837,7 +43837,8 @@ var LEADING_SLASH_RE = /^\//;
43837
43837
  var CONTENT_EXTENSION_RE = /\.(json|md)$/i;
43838
43838
  var UUID_RE3 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
43839
43839
  function entryName(entry) {
43840
- const base = (entry.path.split("/").at(-1) ?? entry.path).replace(CONTENT_EXTENSION_RE, "");
43840
+ const livePath = entry.movedTo ?? entry.path;
43841
+ const base = (livePath.split("/").at(-1) ?? livePath).replace(CONTENT_EXTENSION_RE, "");
43841
43842
  const suffix = `.${entry.fileType}`;
43842
43843
  return base.endsWith(suffix) ? base.slice(0, -suffix.length) : base;
43843
43844
  }
@@ -43889,6 +43890,13 @@ function resolveLink({
43889
43890
  if (bySlug) {
43890
43891
  return fromEntry(bySlug);
43891
43892
  }
43893
+ const nameKey = generateSlug(trimmed.replace(/^<@|>$/g, ""));
43894
+ if (nameKey) {
43895
+ const byName = ledger.files.find((entry) => generateSlug(entryName(entry)) === nameKey);
43896
+ if (byName) {
43897
+ return fromEntry(byName);
43898
+ }
43899
+ }
43892
43900
  return { status: "unknown" };
43893
43901
  }
43894
43902
  function referenceIdToPath({
@@ -44935,6 +44943,8 @@ var FILE_DESIGNATION_METADATA_SCHEMA_VERSION_V6 = "file-designation-metadata:v6"
44935
44943
  var FILE_DESIGNATION_METADATA_SCHEMA_VERSION_V7 = "file-designation-metadata:v7";
44936
44944
  var FILE_DESIGNATION_METADATA_SCHEMA_VERSION_V8 = "file-designation-metadata:v8";
44937
44945
  var GAME_START_OPENING_MAX_LENGTH = 1e4;
44946
+ var GAME_START_MAX_OPENING_BEATS = 50;
44947
+ var GAME_START_MAX_CHARACTER_OPENINGS = 100;
44938
44948
  var GM_INSTRUCTION_TRIGGER_PHASES = [
44939
44949
  "post_player_message",
44940
44950
  "post_gm_message"
@@ -45137,10 +45147,25 @@ var GmInstructionMetadataSchema = exports_external.union([
45137
45147
  GmInstructionToolMetadataSchema,
45138
45148
  GmInstructionNarrativeMetadataSchema
45139
45149
  ]);
45150
+ var GameStartOpeningBeatSpeakerSchema = exports_external.discriminatedUnion("kind", [
45151
+ exports_external.object({ kind: exports_external.literal("gm") }),
45152
+ exports_external.object({
45153
+ kind: exports_external.literal("character"),
45154
+ fileReferenceId: exports_external.string().uuid(),
45155
+ name: exports_external.string().trim().max(200).optional()
45156
+ })
45157
+ ]);
45158
+ var GameStartOpeningBeatSchema = exports_external.object({
45159
+ id: exports_external.string().uuid(),
45160
+ speaker: GameStartOpeningBeatSpeakerSchema,
45161
+ text: exports_external.string().trim().min(1).max(GAME_START_OPENING_MAX_LENGTH),
45162
+ expression: exports_external.string().trim().max(80).nullish()
45163
+ });
45140
45164
  var GameStartOpeningSchema = exports_external.discriminatedUnion("kind", [
45141
45165
  exports_external.object({
45142
45166
  kind: exports_external.literal("static"),
45143
- message: exports_external.string().trim().min(1).max(GAME_START_OPENING_MAX_LENGTH)
45167
+ message: exports_external.string().trim().min(1).max(GAME_START_OPENING_MAX_LENGTH),
45168
+ beats: exports_external.array(GameStartOpeningBeatSchema).max(GAME_START_MAX_OPENING_BEATS).optional()
45144
45169
  }),
45145
45170
  exports_external.object({
45146
45171
  kind: exports_external.literal("dynamic"),
@@ -45150,16 +45175,55 @@ var GameStartOpeningSchema = exports_external.discriminatedUnion("kind", [
45150
45175
  var GameStartCharacterSchema = exports_external.object({
45151
45176
  fileReferenceId: exports_external.string().uuid()
45152
45177
  });
45153
- var GameStartMetadataSchema = exports_external.object({
45178
+ var GameStartCharacterOpeningSchema = exports_external.object({
45179
+ characterFileReferenceId: exports_external.string().uuid(),
45180
+ opening: GameStartOpeningSchema
45181
+ });
45182
+ function combinedOpeningLength(opening) {
45183
+ if (opening.kind !== "static" || !opening.beats) {
45184
+ return 0;
45185
+ }
45186
+ return opening.beats.reduce((total, beat) => total + beat.text.length, 0);
45187
+ }
45188
+ var GameStartMetadataObjectSchema = exports_external.object({
45154
45189
  startingLocationFileReferenceId: exports_external.string().uuid().nullable().optional(),
45155
45190
  startingCharacters: exports_external.array(GameStartCharacterSchema).default([]),
45156
45191
  opening: GameStartOpeningSchema.default({
45157
45192
  kind: "static",
45158
45193
  message: "Welcome to the game. What would you like to do first?"
45159
45194
  }),
45195
+ characterOpenings: exports_external.array(GameStartCharacterOpeningSchema).max(GAME_START_MAX_CHARACTER_OPENINGS).default([]),
45160
45196
  displayOrder: exports_external.number().int().default(0),
45161
45197
  openingInstructions: exports_external.string().max(GAME_START_OPENING_MAX_LENGTH).default("")
45162
45198
  });
45199
+ var GameStartMetadataSchema = GameStartMetadataObjectSchema.superRefine((metadata, ctx) => {
45200
+ if (combinedOpeningLength(metadata.opening) > GAME_START_OPENING_MAX_LENGTH) {
45201
+ ctx.addIssue({
45202
+ code: "custom",
45203
+ path: ["opening", "beats"],
45204
+ message: `Combined opening beats must be at most ${GAME_START_OPENING_MAX_LENGTH} characters.`
45205
+ });
45206
+ }
45207
+ const seenCharacterReferenceIds = new Set;
45208
+ metadata.characterOpenings.forEach((characterOpening, index) => {
45209
+ const referenceId = characterOpening.characterFileReferenceId.toLowerCase();
45210
+ if (seenCharacterReferenceIds.has(referenceId)) {
45211
+ ctx.addIssue({
45212
+ code: "custom",
45213
+ path: ["characterOpenings", index, "characterFileReferenceId"],
45214
+ message: "Each character can only have one opening override."
45215
+ });
45216
+ }
45217
+ seenCharacterReferenceIds.add(referenceId);
45218
+ if (combinedOpeningLength(characterOpening.opening) > GAME_START_OPENING_MAX_LENGTH) {
45219
+ ctx.addIssue({
45220
+ code: "custom",
45221
+ path: ["characterOpenings", index, "opening", "beats"],
45222
+ message: `Combined opening beats must be at most ${GAME_START_OPENING_MAX_LENGTH} characters.`
45223
+ });
45224
+ }
45225
+ });
45226
+ });
45163
45227
  var FileDesignationMetadataV1Schema = exports_external.object({
45164
45228
  schemaVersionId: exports_external.literal(FILE_DESIGNATION_METADATA_SCHEMA_VERSION_V1).default(FILE_DESIGNATION_METADATA_SCHEMA_VERSION_V1),
45165
45229
  map: MapDataV1Schema.optional(),
@@ -45669,7 +45733,12 @@ async function runMetaGet(argv) {
45669
45733
  const gameStart = parsed?.gameStart ? {
45670
45734
  ...parsed.gameStart,
45671
45735
  startingLocation: pathForReference(workspace, parsed.gameStart.startingLocationFileReferenceId),
45672
- startingCharacters: parsed.gameStart.startingCharacters.map((character) => pathForReference(workspace, character.fileReferenceId) ?? character.fileReferenceId)
45736
+ startingCharacters: parsed.gameStart.startingCharacters.map((character) => pathForReference(workspace, character.fileReferenceId) ?? character.fileReferenceId),
45737
+ opening: projectOpeningForOutput(workspace, parsed.gameStart.opening),
45738
+ characterOpenings: parsed.gameStart.characterOpenings.map((characterOpening) => ({
45739
+ character: pathForReference(workspace, characterOpening.characterFileReferenceId) ?? characterOpening.characterFileReferenceId,
45740
+ opening: projectOpeningForOutput(workspace, characterOpening.opening)
45741
+ }))
45673
45742
  } : null;
45674
45743
  if (gameStart) {
45675
45744
  gameStart.startingLocationFileReferenceId = undefined;
@@ -45690,6 +45759,24 @@ async function runMetaGet(argv) {
45690
45759
  });
45691
45760
  return 0;
45692
45761
  }
45762
+ function projectOpeningForOutput(workspace, opening) {
45763
+ if (opening.kind !== "static" || !opening.beats?.length) {
45764
+ return opening;
45765
+ }
45766
+ return {
45767
+ kind: "static",
45768
+ beats: opening.beats.map((beat) => ({
45769
+ speaker: beat.speaker.kind === "gm" ? "gm" : pathForReference(workspace, beat.speaker.fileReferenceId) ?? beat.speaker.fileReferenceId,
45770
+ text: beat.text,
45771
+ ...beat.expression ? { expression: beat.expression } : {}
45772
+ }))
45773
+ };
45774
+ }
45775
+ function speakerNameForLink(link) {
45776
+ const base = link.split("/").filter(Boolean).at(-1);
45777
+ const name = base?.split(".")[0]?.trim();
45778
+ return name || undefined;
45779
+ }
45693
45780
  function translateGameStartInput(workspace, raw) {
45694
45781
  if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
45695
45782
  throw new CliError("The game start JSON must be a single object.", 2);
@@ -45711,6 +45798,55 @@ function translateGameStartInput(workspace, raw) {
45711
45798
  failures.push(resolution.status === "unpushed" ? `${where}: "${link}" has not been pushed yet — push it first` : `${where}: "${link}" does not resolve to a tracked file`);
45712
45799
  return null;
45713
45800
  };
45801
+ const translateOpening = (opening, where) => {
45802
+ if (typeof opening !== "object" || opening === null) {
45803
+ return opening;
45804
+ }
45805
+ const openingInput = { ...opening };
45806
+ if (!Array.isArray(openingInput.beats)) {
45807
+ return openingInput;
45808
+ }
45809
+ const beats = openingInput.beats.map((beat, index) => {
45810
+ if (typeof beat !== "object" || beat === null) {
45811
+ return beat;
45812
+ }
45813
+ const beatInput = { ...beat };
45814
+ if (typeof beatInput.id !== "string") {
45815
+ beatInput.id = crypto.randomUUID();
45816
+ }
45817
+ const speaker = beatInput.speaker;
45818
+ if (typeof speaker === "string" && speaker !== "gm") {
45819
+ beatInput.speaker = {
45820
+ kind: "character",
45821
+ fileReferenceId: resolve6(speaker, `${where}.beats[${index}].speaker`),
45822
+ name: speakerNameForLink(speaker)
45823
+ };
45824
+ } else if (speaker === "gm") {
45825
+ beatInput.speaker = { kind: "gm" };
45826
+ } else if (typeof speaker === "object" && speaker !== null && "character" in speaker) {
45827
+ const { character, ...speakerRest } = speaker;
45828
+ beatInput.speaker = {
45829
+ kind: "character",
45830
+ name: typeof character === "string" ? speakerNameForLink(character) : undefined,
45831
+ ...speakerRest,
45832
+ fileReferenceId: resolve6(character, `${where}.beats[${index}].speaker.character`)
45833
+ };
45834
+ }
45835
+ return beatInput;
45836
+ });
45837
+ openingInput.beats = beats;
45838
+ if (typeof openingInput.message !== "string" && openingInput.kind === "static") {
45839
+ const projectable = beats.filter((beat) => typeof beat === "object" && beat !== null && typeof beat.text === "string");
45840
+ openingInput.message = projectable.map((beat) => {
45841
+ const speaker = beat.speaker;
45842
+ const name = speaker && speaker.kind === "character" && typeof speaker.name === "string" ? speaker.name : null;
45843
+ return name ? `${name}: ${beat.text}` : beat.text;
45844
+ }).join(`
45845
+
45846
+ `).slice(0, GAME_START_OPENING_MAX_LENGTH);
45847
+ }
45848
+ return openingInput;
45849
+ };
45714
45850
  if ("startingLocation" in input) {
45715
45851
  input.startingLocationFileReferenceId = resolve6(input.startingLocation, "startingLocation");
45716
45852
  input.startingLocation = undefined;
@@ -45725,11 +45861,28 @@ function translateGameStartInput(workspace, raw) {
45725
45861
  return character;
45726
45862
  });
45727
45863
  }
45864
+ if ("opening" in input) {
45865
+ input.opening = translateOpening(input.opening, "opening");
45866
+ }
45867
+ if (Array.isArray(input.characterOpenings)) {
45868
+ input.characterOpenings = input.characterOpenings.map((characterOpening, index) => {
45869
+ if (typeof characterOpening !== "object" || characterOpening === null) {
45870
+ return characterOpening;
45871
+ }
45872
+ const entry = { ...characterOpening };
45873
+ if ("character" in entry) {
45874
+ entry.characterFileReferenceId = resolve6(entry.character, `characterOpenings[${index}].character`);
45875
+ entry.character = undefined;
45876
+ }
45877
+ entry.opening = translateOpening(entry.opening, `characterOpenings[${index}].opening`);
45878
+ return entry;
45879
+ });
45880
+ }
45728
45881
  if (failures.length > 0) {
45729
45882
  throw new CliError(`Nothing was saved — some references do not resolve to pushed files:
45730
45883
  ` + `${failures.map((f) => ` ${f}`).join(`
45731
45884
  `)}
45732
- ` + "Use workspace paths, slug.type tokens, or referenceIds of PUSHED files.", 2);
45885
+ ` + "Use workspace paths (preferred), slug.type tokens, slugs, or file names of PUSHED files.", 2);
45733
45886
  }
45734
45887
  return input;
45735
45888
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@craftrpgs/cli",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Sync Craft projects with a local folder — clone, edit with any coding agent, push back.",
5
5
  "license": "UNLICENSED",
6
6
  "homepage": "https://craftrpgs.com",