@craftrpgs/cli 0.1.4 → 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 +187 -9
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -26939,6 +26939,22 @@ var imageObjectSchema = exports_external.preprocess((input) => normalizeImageInp
26939
26939
  variations: exports_external.array(imageVariationSchema).max(MAX_FILE_TYPE_STATES).optional()
26940
26940
  }).strict());
26941
26941
 
26942
+ // ../shared/src/projects/manual.ts
26943
+ var MANUAL_VERSION = 1;
26944
+ var MAX_MANUAL_CHAPTERS = 50;
26945
+ var MAX_MANUAL_TITLE_LENGTH = 120;
26946
+ var MAX_MANUAL_CONTENT_LENGTH = 1e5;
26947
+ var manualChapterSchema = exports_external.object({
26948
+ id: exports_external.string().min(1).max(64),
26949
+ title: exports_external.string().trim().min(1).max(MAX_MANUAL_TITLE_LENGTH),
26950
+ content: exports_external.string().max(MAX_MANUAL_CONTENT_LENGTH)
26951
+ });
26952
+ var manualSchema = exports_external.object({
26953
+ version: exports_external.literal(MANUAL_VERSION),
26954
+ updatedAt: exports_external.string().max(40).optional(),
26955
+ chapters: exports_external.array(manualChapterSchema).max(MAX_MANUAL_CHAPTERS)
26956
+ });
26957
+
26942
26958
  // ../shared/src/projects/project-import.ts
26943
26959
  var projectImportJsonValueSchema = exports_external.lazy(() => exports_external.union([
26944
26960
  exports_external.string(),
@@ -27027,7 +27043,9 @@ var projectImportProjectSettingsSchema = exports_external.object({
27027
27043
  gmToolSettings: gmToolSettingsInputSchema.optional(),
27028
27044
  rootMapFileReferenceId: exports_external.string().uuid().nullable().optional(),
27029
27045
  gm: projectImportGameMasterSettingsSchema.optional(),
27030
- preludeFlow: exports_external.unknown().nullable().optional()
27046
+ preludeFlow: exports_external.unknown().nullable().optional(),
27047
+ playerHandbook: manualSchema.nullable().optional(),
27048
+ builderManual: manualSchema.nullable().optional()
27031
27049
  }).strict();
27032
27050
  var projectImportProjectSchema = exports_external.object({
27033
27051
  name: exports_external.string().trim().min(1),
@@ -41882,7 +41900,7 @@ function describeRemoteSource(resolved) {
41882
41900
  // package.json
41883
41901
  var package_default = {
41884
41902
  name: "@craftrpgs/cli",
41885
- version: "0.1.4",
41903
+ version: "0.1.6",
41886
41904
  description: "Sync Craft projects with a local folder — clone, edit with any coding agent, push back.",
41887
41905
  license: "UNLICENSED",
41888
41906
  homepage: "https://craftrpgs.com",
@@ -43819,7 +43837,8 @@ var LEADING_SLASH_RE = /^\//;
43819
43837
  var CONTENT_EXTENSION_RE = /\.(json|md)$/i;
43820
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;
43821
43839
  function entryName(entry) {
43822
- 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, "");
43823
43842
  const suffix = `.${entry.fileType}`;
43824
43843
  return base.endsWith(suffix) ? base.slice(0, -suffix.length) : base;
43825
43844
  }
@@ -43871,6 +43890,13 @@ function resolveLink({
43871
43890
  if (bySlug) {
43872
43891
  return fromEntry(bySlug);
43873
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
+ }
43874
43900
  return { status: "unknown" };
43875
43901
  }
43876
43902
  function referenceIdToPath({
@@ -44917,6 +44943,8 @@ var FILE_DESIGNATION_METADATA_SCHEMA_VERSION_V6 = "file-designation-metadata:v6"
44917
44943
  var FILE_DESIGNATION_METADATA_SCHEMA_VERSION_V7 = "file-designation-metadata:v7";
44918
44944
  var FILE_DESIGNATION_METADATA_SCHEMA_VERSION_V8 = "file-designation-metadata:v8";
44919
44945
  var GAME_START_OPENING_MAX_LENGTH = 1e4;
44946
+ var GAME_START_MAX_OPENING_BEATS = 50;
44947
+ var GAME_START_MAX_CHARACTER_OPENINGS = 100;
44920
44948
  var GM_INSTRUCTION_TRIGGER_PHASES = [
44921
44949
  "post_player_message",
44922
44950
  "post_gm_message"
@@ -45119,10 +45147,25 @@ var GmInstructionMetadataSchema = exports_external.union([
45119
45147
  GmInstructionToolMetadataSchema,
45120
45148
  GmInstructionNarrativeMetadataSchema
45121
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
+ });
45122
45164
  var GameStartOpeningSchema = exports_external.discriminatedUnion("kind", [
45123
45165
  exports_external.object({
45124
45166
  kind: exports_external.literal("static"),
45125
- 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()
45126
45169
  }),
45127
45170
  exports_external.object({
45128
45171
  kind: exports_external.literal("dynamic"),
@@ -45132,16 +45175,55 @@ var GameStartOpeningSchema = exports_external.discriminatedUnion("kind", [
45132
45175
  var GameStartCharacterSchema = exports_external.object({
45133
45176
  fileReferenceId: exports_external.string().uuid()
45134
45177
  });
45135
- 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({
45136
45189
  startingLocationFileReferenceId: exports_external.string().uuid().nullable().optional(),
45137
45190
  startingCharacters: exports_external.array(GameStartCharacterSchema).default([]),
45138
45191
  opening: GameStartOpeningSchema.default({
45139
45192
  kind: "static",
45140
45193
  message: "Welcome to the game. What would you like to do first?"
45141
45194
  }),
45195
+ characterOpenings: exports_external.array(GameStartCharacterOpeningSchema).max(GAME_START_MAX_CHARACTER_OPENINGS).default([]),
45142
45196
  displayOrder: exports_external.number().int().default(0),
45143
45197
  openingInstructions: exports_external.string().max(GAME_START_OPENING_MAX_LENGTH).default("")
45144
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
+ });
45145
45227
  var FileDesignationMetadataV1Schema = exports_external.object({
45146
45228
  schemaVersionId: exports_external.literal(FILE_DESIGNATION_METADATA_SCHEMA_VERSION_V1).default(FILE_DESIGNATION_METADATA_SCHEMA_VERSION_V1),
45147
45229
  map: MapDataV1Schema.optional(),
@@ -45651,7 +45733,12 @@ async function runMetaGet(argv) {
45651
45733
  const gameStart = parsed?.gameStart ? {
45652
45734
  ...parsed.gameStart,
45653
45735
  startingLocation: pathForReference(workspace, parsed.gameStart.startingLocationFileReferenceId),
45654
- 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
+ }))
45655
45742
  } : null;
45656
45743
  if (gameStart) {
45657
45744
  gameStart.startingLocationFileReferenceId = undefined;
@@ -45672,6 +45759,24 @@ async function runMetaGet(argv) {
45672
45759
  });
45673
45760
  return 0;
45674
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
+ }
45675
45780
  function translateGameStartInput(workspace, raw) {
45676
45781
  if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
45677
45782
  throw new CliError("The game start JSON must be a single object.", 2);
@@ -45693,6 +45798,55 @@ function translateGameStartInput(workspace, raw) {
45693
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`);
45694
45799
  return null;
45695
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
+ };
45696
45850
  if ("startingLocation" in input) {
45697
45851
  input.startingLocationFileReferenceId = resolve6(input.startingLocation, "startingLocation");
45698
45852
  input.startingLocation = undefined;
@@ -45707,11 +45861,28 @@ function translateGameStartInput(workspace, raw) {
45707
45861
  return character;
45708
45862
  });
45709
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
+ }
45710
45881
  if (failures.length > 0) {
45711
45882
  throw new CliError(`Nothing was saved — some references do not resolve to pushed files:
45712
45883
  ` + `${failures.map((f) => ` ${f}`).join(`
45713
45884
  `)}
45714
- ` + "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);
45715
45886
  }
45716
45887
  return input;
45717
45888
  }
@@ -50485,12 +50656,19 @@ content. File type changes from the zip apply only with --include-types
50485
50656
  (missing types are created, drifted types updated \u2014 the server revalidates
50486
50657
  existing files against schema changes). Project settings changes from the
50487
50658
  zip's .craft/project.json apply only with --include-settings; without it,
50488
- differing settings are listed but left untouched.
50659
+ differing settings are listed but left untouched. Every field of
50660
+ project.json is diffed and applied individually: name, description, tags,
50661
+ and settings.* \u2014 theme, image, projectIcon, attribution, gameSystemName,
50662
+ headlineLabel, headline, body, authorsNotes, gallery, galleryLayout,
50663
+ isClonable, hideHeroTitle, imageStyleInstructions, autoImageGeneration,
50664
+ defaultImageModel, gmToolSettings, rootMapFileReferenceId, preludeFlow, gm,
50665
+ playerHandbook, builderManual.
50489
50666
 
50490
50667
  --dry-run shows the full plan without writing. --exclude <referenceId>
50491
50668
  skips individual rows from the plan (repeatable, or comma-separated; get the
50492
50669
  ids from a --dry-run --json plan). --exclude-setting <key> vetoes a single
50493
- settings change (e.g. settings.preludeFlow). Runs inside the target
50670
+ settings change, named exactly as the plan's rows are keyed (e.g.
50671
+ settings.preludeFlow, settings.playerHandbook). Runs inside the target
50494
50672
  project's workspace, or anywhere with --project <projectId>. The apply is
50495
50673
  one atomic batch: on any per-file failure (409) nothing is written and every
50496
50674
  failure is listed. Exits 1 while conflicts or failures remain.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@craftrpgs/cli",
3
- "version": "0.1.4",
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",