@craftrpgs/cli 0.2.0-alpha.1 → 0.2.0-alpha.2
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/dist/bin.js +801 -192
- package/package.json +3 -3
package/dist/bin.js
CHANGED
|
@@ -26811,6 +26811,345 @@ function contentTypeForContentExtension(extension) {
|
|
|
26811
26811
|
// ../shared/src/constants/gm-engine.ts
|
|
26812
26812
|
var GM_ENGINES = ["v1", "v2"];
|
|
26813
26813
|
|
|
26814
|
+
// ../shared/src/board-time.ts
|
|
26815
|
+
var TIME_TRACKER_KEY = "time";
|
|
26816
|
+
|
|
26817
|
+
// ../shared/src/same-json.ts
|
|
26818
|
+
function sortedKeys(value) {
|
|
26819
|
+
if (Array.isArray(value)) {
|
|
26820
|
+
return value.map(sortedKeys);
|
|
26821
|
+
}
|
|
26822
|
+
if (value !== null && typeof value === "object") {
|
|
26823
|
+
const record2 = value;
|
|
26824
|
+
return Object.fromEntries(Object.keys(record2).filter((key) => record2[key] !== undefined).sort().map((key) => [key, sortedKeys(record2[key])]));
|
|
26825
|
+
}
|
|
26826
|
+
return value;
|
|
26827
|
+
}
|
|
26828
|
+
function sameJson(a, b) {
|
|
26829
|
+
return JSON.stringify(sortedKeys(a)) === JSON.stringify(sortedKeys(b));
|
|
26830
|
+
}
|
|
26831
|
+
|
|
26832
|
+
// ../shared/src/board-trackers.ts
|
|
26833
|
+
var TRACKER_TONES = ["good", "bad"];
|
|
26834
|
+
var TRACKER_ITEM_STATES = ["open", "done", "failed"];
|
|
26835
|
+
var TRACKER_LASTS = ["here", "game", "always"];
|
|
26836
|
+
var COUNT_DISPLAYS = ["number", "bar", "pips"];
|
|
26837
|
+
var RECORDS_DISPLAYS = ["table", "roster"];
|
|
26838
|
+
var TRACKER_LIMITS = {
|
|
26839
|
+
board: 10,
|
|
26840
|
+
name: 60,
|
|
26841
|
+
label: 40,
|
|
26842
|
+
text: 160,
|
|
26843
|
+
fields: 8,
|
|
26844
|
+
items: 16,
|
|
26845
|
+
entries: 20,
|
|
26846
|
+
records: 12,
|
|
26847
|
+
links: 12,
|
|
26848
|
+
recordFields: 4,
|
|
26849
|
+
show: 6,
|
|
26850
|
+
types: 6,
|
|
26851
|
+
sourceName: 80,
|
|
26852
|
+
marks: 10,
|
|
26853
|
+
pipsMax: 20,
|
|
26854
|
+
clockMin: 2,
|
|
26855
|
+
clockMax: 12,
|
|
26856
|
+
scaleSteps: 21
|
|
26857
|
+
};
|
|
26858
|
+
var labelSchema = exports_external.string().trim().max(TRACKER_LIMITS.label).nullish();
|
|
26859
|
+
var hiddenSchema = exports_external.boolean().nullish();
|
|
26860
|
+
var refSchema = exports_external.object({
|
|
26861
|
+
name: exports_external.string().trim().min(1).max(TRACKER_LIMITS.name),
|
|
26862
|
+
file: exports_external.string().nullish()
|
|
26863
|
+
});
|
|
26864
|
+
var showSchema = exports_external.array(exports_external.string().trim().min(1).max(TRACKER_LIMITS.label)).max(TRACKER_LIMITS.show).nullish();
|
|
26865
|
+
var showSourceSchema = exports_external.object({
|
|
26866
|
+
value: exports_external.string().trim().min(1).max(TRACKER_LIMITS.sourceName),
|
|
26867
|
+
max: exports_external.string().trim().min(1).max(TRACKER_LIMITS.sourceName).nullish()
|
|
26868
|
+
});
|
|
26869
|
+
var showSourcesSchema = exports_external.record(exports_external.string().trim().min(1).max(TRACKER_LIMITS.label), exports_external.record(exports_external.string().trim().min(1).max(TRACKER_LIMITS.sourceName), showSourceSchema)).nullish();
|
|
26870
|
+
var entryTypesSchema = exports_external.array(exports_external.string().trim().min(1).max(TRACKER_LIMITS.sourceName)).max(TRACKER_LIMITS.types).nullish();
|
|
26871
|
+
var numberShape = {
|
|
26872
|
+
label: labelSchema,
|
|
26873
|
+
hidden: hiddenSchema,
|
|
26874
|
+
value: exports_external.number(),
|
|
26875
|
+
min: exports_external.number().nullish(),
|
|
26876
|
+
max: exports_external.number().nullish(),
|
|
26877
|
+
tone: exports_external.enum(TRACKER_TONES).nullish(),
|
|
26878
|
+
at: exports_external.record(exports_external.string(), exports_external.string()).nullish(),
|
|
26879
|
+
start: exports_external.number().nullish()
|
|
26880
|
+
};
|
|
26881
|
+
var countFieldSchema = exports_external.object({
|
|
26882
|
+
kind: exports_external.literal("count"),
|
|
26883
|
+
...numberShape,
|
|
26884
|
+
display: exports_external.enum(COUNT_DISPLAYS).nullish()
|
|
26885
|
+
});
|
|
26886
|
+
var clockFieldSchema = exports_external.object({ kind: exports_external.literal("clock"), ...numberShape });
|
|
26887
|
+
var scaleFieldSchema = exports_external.object({
|
|
26888
|
+
kind: exports_external.literal("scale"),
|
|
26889
|
+
...numberShape,
|
|
26890
|
+
ends: exports_external.tuple([exports_external.string(), exports_external.string()]).nullish()
|
|
26891
|
+
});
|
|
26892
|
+
var listItemSchema = exports_external.object({
|
|
26893
|
+
text: exports_external.string().trim().min(1).max(TRACKER_LIMITS.text),
|
|
26894
|
+
state: exports_external.enum(TRACKER_ITEM_STATES),
|
|
26895
|
+
note: exports_external.string().trim().max(TRACKER_LIMITS.text).nullish(),
|
|
26896
|
+
ref: refSchema.nullish(),
|
|
26897
|
+
hidden: hiddenSchema
|
|
26898
|
+
});
|
|
26899
|
+
var listFieldSchema = exports_external.object({
|
|
26900
|
+
kind: exports_external.literal("list"),
|
|
26901
|
+
label: labelSchema,
|
|
26902
|
+
hidden: hiddenSchema,
|
|
26903
|
+
items: exports_external.array(listItemSchema).max(TRACKER_LIMITS.items)
|
|
26904
|
+
});
|
|
26905
|
+
var orderEntrySchema = exports_external.object({
|
|
26906
|
+
name: exports_external.string().trim().min(1),
|
|
26907
|
+
score: exports_external.number().nullish()
|
|
26908
|
+
});
|
|
26909
|
+
var orderFieldSchema = exports_external.object({
|
|
26910
|
+
kind: exports_external.literal("order"),
|
|
26911
|
+
label: labelSchema,
|
|
26912
|
+
hidden: hiddenSchema,
|
|
26913
|
+
entries: exports_external.array(orderEntrySchema).max(TRACKER_LIMITS.entries),
|
|
26914
|
+
current: exports_external.string().nullish(),
|
|
26915
|
+
round: exports_external.number().int().nullish(),
|
|
26916
|
+
show: showSchema,
|
|
26917
|
+
types: entryTypesSchema,
|
|
26918
|
+
sources: showSourcesSchema
|
|
26919
|
+
});
|
|
26920
|
+
var textFieldSchema = exports_external.object({
|
|
26921
|
+
kind: exports_external.literal("text"),
|
|
26922
|
+
label: labelSchema,
|
|
26923
|
+
hidden: hiddenSchema,
|
|
26924
|
+
text: exports_external.string().trim().max(TRACKER_LIMITS.text)
|
|
26925
|
+
});
|
|
26926
|
+
var refFieldSchema = exports_external.object({
|
|
26927
|
+
kind: exports_external.literal("ref"),
|
|
26928
|
+
label: labelSchema,
|
|
26929
|
+
hidden: hiddenSchema,
|
|
26930
|
+
ref: refSchema,
|
|
26931
|
+
show: showSchema
|
|
26932
|
+
});
|
|
26933
|
+
var trackerRecordFieldSchema = exports_external.discriminatedUnion("kind", [
|
|
26934
|
+
countFieldSchema,
|
|
26935
|
+
clockFieldSchema,
|
|
26936
|
+
scaleFieldSchema,
|
|
26937
|
+
listFieldSchema,
|
|
26938
|
+
textFieldSchema
|
|
26939
|
+
]);
|
|
26940
|
+
var recordSchema = exports_external.object({
|
|
26941
|
+
ref: refSchema,
|
|
26942
|
+
hidden: hiddenSchema,
|
|
26943
|
+
fields: exports_external.array(trackerRecordFieldSchema).max(TRACKER_LIMITS.recordFields)
|
|
26944
|
+
});
|
|
26945
|
+
var recordsFieldSchema = exports_external.object({
|
|
26946
|
+
kind: exports_external.literal("records"),
|
|
26947
|
+
label: labelSchema,
|
|
26948
|
+
hidden: hiddenSchema,
|
|
26949
|
+
entries: exports_external.array(recordSchema).max(TRACKER_LIMITS.records),
|
|
26950
|
+
show: showSchema,
|
|
26951
|
+
types: entryTypesSchema,
|
|
26952
|
+
sources: showSourcesSchema,
|
|
26953
|
+
display: exports_external.enum(RECORDS_DISPLAYS).nullish(),
|
|
26954
|
+
each: exports_external.array(trackerRecordFieldSchema).max(TRACKER_LIMITS.recordFields).nullish()
|
|
26955
|
+
});
|
|
26956
|
+
var linkSchema = exports_external.object({
|
|
26957
|
+
between: exports_external.tuple([refSchema, refSchema]),
|
|
26958
|
+
hidden: hiddenSchema,
|
|
26959
|
+
fields: exports_external.array(trackerRecordFieldSchema).max(TRACKER_LIMITS.recordFields)
|
|
26960
|
+
});
|
|
26961
|
+
var linksFieldSchema = exports_external.object({
|
|
26962
|
+
kind: exports_external.literal("links"),
|
|
26963
|
+
label: labelSchema,
|
|
26964
|
+
hidden: hiddenSchema,
|
|
26965
|
+
entries: exports_external.array(linkSchema).max(TRACKER_LIMITS.links),
|
|
26966
|
+
types: entryTypesSchema,
|
|
26967
|
+
each: exports_external.array(trackerRecordFieldSchema).max(TRACKER_LIMITS.recordFields).nullish()
|
|
26968
|
+
});
|
|
26969
|
+
var trackerFieldSchema = exports_external.discriminatedUnion("kind", [
|
|
26970
|
+
countFieldSchema,
|
|
26971
|
+
clockFieldSchema,
|
|
26972
|
+
scaleFieldSchema,
|
|
26973
|
+
listFieldSchema,
|
|
26974
|
+
orderFieldSchema,
|
|
26975
|
+
textFieldSchema,
|
|
26976
|
+
refFieldSchema,
|
|
26977
|
+
recordsFieldSchema,
|
|
26978
|
+
linksFieldSchema
|
|
26979
|
+
]);
|
|
26980
|
+
var trackerShapeSchema = exports_external.object({
|
|
26981
|
+
name: exports_external.string().trim().min(1).max(TRACKER_LIMITS.name),
|
|
26982
|
+
fields: exports_external.array(trackerFieldSchema).min(1).max(TRACKER_LIMITS.fields),
|
|
26983
|
+
for: exports_external.string().trim().min(1).nullish(),
|
|
26984
|
+
hidden: exports_external.boolean().nullish(),
|
|
26985
|
+
lasts: exports_external.enum(TRACKER_LASTS).nullish(),
|
|
26986
|
+
from: exports_external.string().nullish(),
|
|
26987
|
+
since: exports_external.string().nullish(),
|
|
26988
|
+
changed: exports_external.string().nullish()
|
|
26989
|
+
});
|
|
26990
|
+
function isObject2(value) {
|
|
26991
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
26992
|
+
}
|
|
26993
|
+
function upgradedRow(row) {
|
|
26994
|
+
if (!isObject2(row)) {
|
|
26995
|
+
return row;
|
|
26996
|
+
}
|
|
26997
|
+
if (row.kind === "count") {
|
|
26998
|
+
const { style, ...rest } = row;
|
|
26999
|
+
if (style === "clock" || style === "scale") {
|
|
27000
|
+
return { ...rest, kind: style };
|
|
27001
|
+
}
|
|
27002
|
+
return rest;
|
|
27003
|
+
}
|
|
27004
|
+
if (row.kind === "order" && Array.isArray(row.order)) {
|
|
27005
|
+
const { order, ...rest } = row;
|
|
27006
|
+
return { ...rest, entries: order };
|
|
27007
|
+
}
|
|
27008
|
+
return row;
|
|
27009
|
+
}
|
|
27010
|
+
function upgradedTracker(value) {
|
|
27011
|
+
if (!isObject2(value) || "fields" in value || !Array.isArray(value.rows)) {
|
|
27012
|
+
return value;
|
|
27013
|
+
}
|
|
27014
|
+
const { rows, ...rest } = value;
|
|
27015
|
+
return { ...rest, fields: rows.map(upgradedRow) };
|
|
27016
|
+
}
|
|
27017
|
+
var trackerSchema = exports_external.preprocess(upgradedTracker, trackerShapeSchema);
|
|
27018
|
+
var TRACKER_STARTS = [
|
|
27019
|
+
"game-start",
|
|
27020
|
+
"first-change",
|
|
27021
|
+
"when-started"
|
|
27022
|
+
];
|
|
27023
|
+
var TRACKER_UPDATES = ["on-trigger", "every-turn"];
|
|
27024
|
+
var TRACKER_DEFINITION_LIMITS = {
|
|
27025
|
+
key: 60,
|
|
27026
|
+
when: 300,
|
|
27027
|
+
trigger: 200,
|
|
27028
|
+
question: 400,
|
|
27029
|
+
answer: 600
|
|
27030
|
+
};
|
|
27031
|
+
var trackerDefinitionSchema = exports_external.object({
|
|
27032
|
+
key: exports_external.string().trim().min(1).max(TRACKER_DEFINITION_LIMITS.key).regex(/^[a-z0-9][a-z0-9-]*$/, "Keys are lowercase letters, digits and -"),
|
|
27033
|
+
start: trackerShapeSchema.omit({ since: true, changed: true, from: true }),
|
|
27034
|
+
starts: exports_external.enum(TRACKER_STARTS),
|
|
27035
|
+
when: exports_external.string().trim().max(TRACKER_DEFINITION_LIMITS.when).nullish(),
|
|
27036
|
+
updates: exports_external.enum(TRACKER_UPDATES).nullish(),
|
|
27037
|
+
trigger: exports_external.string().trim().max(TRACKER_DEFINITION_LIMITS.trigger).nullish(),
|
|
27038
|
+
watch: exports_external.object({
|
|
27039
|
+
question: exports_external.string().trim().min(1).max(TRACKER_DEFINITION_LIMITS.question),
|
|
27040
|
+
yes: exports_external.string().trim().max(TRACKER_DEFINITION_LIMITS.answer).nullish(),
|
|
27041
|
+
no: exports_external.string().trim().max(TRACKER_DEFINITION_LIMITS.answer).nullish(),
|
|
27042
|
+
remind: exports_external.string().trim().max(TRACKER_DEFINITION_LIMITS.answer).nullish()
|
|
27043
|
+
}).nullish(),
|
|
27044
|
+
perPerson: exports_external.boolean().nullish(),
|
|
27045
|
+
personTypes: entryTypesSchema
|
|
27046
|
+
});
|
|
27047
|
+
var NUMBER_KINDS = new Set(["count", "clock", "scale"]);
|
|
27048
|
+
function isNumberField(field) {
|
|
27049
|
+
return NUMBER_KINDS.has(field.kind);
|
|
27050
|
+
}
|
|
27051
|
+
var ARTICLE_RE = /^(?:the|a|an)\s+/;
|
|
27052
|
+
var POSSESSIVE_RE = /['’]s\b/g;
|
|
27053
|
+
var NON_WORD_RE = /[^\p{L}\p{N}]+/gu;
|
|
27054
|
+
function trackerKey(name) {
|
|
27055
|
+
return name.toLowerCase().replace(POSSESSIVE_RE, "").replace(NON_WORD_RE, " ").trim().replace(ARTICLE_RE, "").trim();
|
|
27056
|
+
}
|
|
27057
|
+
function numberProblem(field, label) {
|
|
27058
|
+
if (field.kind === "clock") {
|
|
27059
|
+
const size = field.max ?? 0;
|
|
27060
|
+
if (size < TRACKER_LIMITS.clockMin || size > TRACKER_LIMITS.clockMax) {
|
|
27061
|
+
return `${label} is a clock of ${TRACKER_LIMITS.clockMin} to ${TRACKER_LIMITS.clockMax} segments`;
|
|
27062
|
+
}
|
|
27063
|
+
}
|
|
27064
|
+
if (field.kind === "scale") {
|
|
27065
|
+
if (field.min == null || field.max == null || field.min >= field.max) {
|
|
27066
|
+
return `${label} is a tug of war: give it a low end below its high end`;
|
|
27067
|
+
}
|
|
27068
|
+
if (field.max - field.min + 1 > TRACKER_LIMITS.scaleSteps) {
|
|
27069
|
+
return `${label} spans at most ${TRACKER_LIMITS.scaleSteps} steps`;
|
|
27070
|
+
}
|
|
27071
|
+
}
|
|
27072
|
+
if (field.min != null && field.max != null && field.min > field.max) {
|
|
27073
|
+
return `${label} has a lowest value above its highest`;
|
|
27074
|
+
}
|
|
27075
|
+
if (field.min != null && field.value < field.min || field.max != null && field.value > field.max) {
|
|
27076
|
+
return `${label} starts outside its range`;
|
|
27077
|
+
}
|
|
27078
|
+
return null;
|
|
27079
|
+
}
|
|
27080
|
+
function fieldsProblem(fields, where) {
|
|
27081
|
+
const labels = fields.map((field) => field.label?.trim() ?? "");
|
|
27082
|
+
if (fields.length > 1 && labels.some((label) => !label)) {
|
|
27083
|
+
return `${where} has more than one field, so each needs a label`;
|
|
27084
|
+
}
|
|
27085
|
+
const keys = labels.filter(Boolean).map(trackerKey);
|
|
27086
|
+
if (new Set(keys).size !== keys.length) {
|
|
27087
|
+
return `${where} has two fields with the same label`;
|
|
27088
|
+
}
|
|
27089
|
+
for (const [index, field] of fields.entries()) {
|
|
27090
|
+
const label = labels[index] ? `"${labels[index]}"` : where;
|
|
27091
|
+
const problem = isNumberField(field) ? numberProblem(field, label) : null;
|
|
27092
|
+
if (problem) {
|
|
27093
|
+
return problem;
|
|
27094
|
+
}
|
|
27095
|
+
if ((field.kind === "records" || field.kind === "links") && field.each?.length) {
|
|
27096
|
+
const inner = fieldsProblem(field.each, `What each ${field.kind === "links" ? "link" : "record"} of ${label} starts with`);
|
|
27097
|
+
if (inner) {
|
|
27098
|
+
return inner;
|
|
27099
|
+
}
|
|
27100
|
+
}
|
|
27101
|
+
}
|
|
27102
|
+
return null;
|
|
27103
|
+
}
|
|
27104
|
+
var QUOTE_RE = /"/;
|
|
27105
|
+
function needsStartTrigger(definition) {
|
|
27106
|
+
return definition.starts === "when-started" && !definition.trigger?.trim() && !QUOTE_RE.test(definition.when ?? "");
|
|
27107
|
+
}
|
|
27108
|
+
function blankStartingName(definition) {
|
|
27109
|
+
for (const field of definition.start.fields) {
|
|
27110
|
+
if (field.kind === "links" && field.entries.some((link) => link.between.some((end) => !end.name.trim()))) {
|
|
27111
|
+
return "a name on each side of every pair it starts with";
|
|
27112
|
+
}
|
|
27113
|
+
const names = field.kind === "records" && field.entries.map((entry) => entry.ref.name) || field.kind === "order" && field.entries.map((entry) => entry.name) || field.kind === "list" && field.items.map((item) => item.text) || [];
|
|
27114
|
+
if (names.some((each) => !each.trim())) {
|
|
27115
|
+
return "a name for everyone and everything it starts with";
|
|
27116
|
+
}
|
|
27117
|
+
}
|
|
27118
|
+
return null;
|
|
27119
|
+
}
|
|
27120
|
+
function updatesEveryTurn(definition) {
|
|
27121
|
+
return definition.updates === "every-turn";
|
|
27122
|
+
}
|
|
27123
|
+
function trackerDefinitionProblem(definition) {
|
|
27124
|
+
const name = definition.start.name.trim();
|
|
27125
|
+
const tracker = name ? `The tracker "${name}"` : "A tracker";
|
|
27126
|
+
if (!name) {
|
|
27127
|
+
return "A tracker needs a name.";
|
|
27128
|
+
}
|
|
27129
|
+
if (definition.start.lasts === "always" && definition.starts !== "game-start") {
|
|
27130
|
+
return `${tracker} never goes away, so it is on the board from the game's start: its "starts" is "game-start".`;
|
|
27131
|
+
}
|
|
27132
|
+
if (needsStartTrigger(definition)) {
|
|
27133
|
+
return `${tracker} starts on a trigger, so it needs to say what starts it.`;
|
|
27134
|
+
}
|
|
27135
|
+
if (updatesEveryTurn(definition)) {
|
|
27136
|
+
if (definition.when?.trim() || definition.watch) {
|
|
27137
|
+
return `${tracker} updates every turn, so it has no update trigger: leave out "when" and "watch".`;
|
|
27138
|
+
}
|
|
27139
|
+
} else if (!definition.when?.trim()) {
|
|
27140
|
+
return `${tracker} needs to say what updates it.`;
|
|
27141
|
+
}
|
|
27142
|
+
if (definition.watch && !definition.watch.question.trim()) {
|
|
27143
|
+
return `${tracker} updates on a trigger, so it needs the trigger to check for.`;
|
|
27144
|
+
}
|
|
27145
|
+
const problem = fieldsProblem(definition.start.fields, tracker);
|
|
27146
|
+
if (problem) {
|
|
27147
|
+
return `${problem}.`;
|
|
27148
|
+
}
|
|
27149
|
+
const blank = blankStartingName(definition);
|
|
27150
|
+
return blank ? `${tracker} needs ${blank}.` : null;
|
|
27151
|
+
}
|
|
27152
|
+
|
|
26814
27153
|
// ../shared/src/platform-instructions.ts
|
|
26815
27154
|
var PLATFORM_INSTRUCTIONS = [
|
|
26816
27155
|
{
|
|
@@ -26867,12 +27206,150 @@ End each reply on something concrete in front of the player's character that the
|
|
|
26867
27206
|
},
|
|
26868
27207
|
{
|
|
26869
27208
|
id: "combat-tracker",
|
|
26870
|
-
revision:
|
|
27209
|
+
revision: 6,
|
|
26871
27210
|
instruction: {
|
|
26872
27211
|
name: "Combat tracker",
|
|
26873
27212
|
when: "always",
|
|
26874
27213
|
section: "Rules",
|
|
26875
|
-
body: `When
|
|
27214
|
+
body: `When the fight is over (every foe down, fled or surrendered, or the fighting stops), end it: "<its name>": "End".`,
|
|
27215
|
+
tracker: {
|
|
27216
|
+
key: "fight",
|
|
27217
|
+
start: {
|
|
27218
|
+
name: "Combat",
|
|
27219
|
+
fields: [
|
|
27220
|
+
{
|
|
27221
|
+
kind: "order",
|
|
27222
|
+
entries: [],
|
|
27223
|
+
round: 1,
|
|
27224
|
+
show: ["HP", "Conditions"]
|
|
27225
|
+
}
|
|
27226
|
+
]
|
|
27227
|
+
},
|
|
27228
|
+
starts: "when-started",
|
|
27229
|
+
trigger: "a fight begins",
|
|
27230
|
+
when: "a fighter's turn ends, or someone joins or leaves the fight"
|
|
27231
|
+
}
|
|
27232
|
+
}
|
|
27233
|
+
},
|
|
27234
|
+
{
|
|
27235
|
+
id: "objectives-tracker",
|
|
27236
|
+
revision: 2,
|
|
27237
|
+
instruction: {
|
|
27238
|
+
name: "Objectives",
|
|
27239
|
+
when: "always",
|
|
27240
|
+
section: "Rules",
|
|
27241
|
+
body: `The party's objectives are one list for the whole game: what they are working toward beyond this scene. Add one when the story gives the party one (an order, a job or request taken on, a goal of their own that outlasts the scene), worded as the party knows it then, short, one goal to an objective: "Objectives": "Add: Find who opened the lid". Mark it done or failed when the story settles it: "Objectives": "Find who opened the lid: done". Never write a quest's later steps ahead of the story: they come as objectives of their own. One that runs over time (keep watch, log every contact) is done only when its end comes.`,
|
|
27242
|
+
tracker: {
|
|
27243
|
+
key: "objectives",
|
|
27244
|
+
start: {
|
|
27245
|
+
name: "Objectives",
|
|
27246
|
+
lasts: "game",
|
|
27247
|
+
fields: [{ kind: "list", items: [] }]
|
|
27248
|
+
},
|
|
27249
|
+
starts: "first-change",
|
|
27250
|
+
when: "the story gives the party an objective (an order, a job or request they take on, a goal of their own that outlasts the scene)",
|
|
27251
|
+
watch: {
|
|
27252
|
+
question: "In `earlier`, `player_turn` or `narration`, has the party been given an objective, something they are now working toward beyond this scene, that {tracker} does not hold under any wording?",
|
|
27253
|
+
yes: 'Yes: an order or an assignment handed to them (they act on it or do not refuse it), a request or a job they take on, or a goal they set themselves that will outlast this scene ("Find what woke the Sleeper", "Get the ledger to the Abbot", "Learn who signed the panel"), and nothing in {tracker} says it.',
|
|
27254
|
+
no: "No: {tracker} already holds it, however worded; or it is only floated, refused or still being weighed; or it is only what this scene is for, the next move (go to the archives, ask the clerk, cross the yard), or something done in the same moment.",
|
|
27255
|
+
remind: `the story gave the party an objective the board's Objectives list does not hold (add it as the party knows it now, short, one goal to an objective: "Objectives": "Add: <the objective>")`
|
|
27256
|
+
}
|
|
27257
|
+
}
|
|
27258
|
+
}
|
|
27259
|
+
},
|
|
27260
|
+
{
|
|
27261
|
+
id: "party-tracker",
|
|
27262
|
+
revision: 2,
|
|
27263
|
+
instruction: {
|
|
27264
|
+
name: "Party",
|
|
27265
|
+
when: "always",
|
|
27266
|
+
section: "Rules",
|
|
27267
|
+
body: `The Party is everyone who travels and acts with the player's character as one of them: the player's character, who is always in it, and the companions who have joined. Add someone when they join ("Party": "Add Mira"), and take them off when they leave, part ways for good or die ("Party": "Remove Mira"). Someone who only helps for a while (a guide for the afternoon, a guard who fights beside them once) is not in it unless the story has them join.`,
|
|
27268
|
+
tracker: {
|
|
27269
|
+
key: "party",
|
|
27270
|
+
start: {
|
|
27271
|
+
name: "Party",
|
|
27272
|
+
lasts: "game",
|
|
27273
|
+
fields: [
|
|
27274
|
+
{
|
|
27275
|
+
kind: "records",
|
|
27276
|
+
entries: [],
|
|
27277
|
+
show: ["HP", "Conditions"],
|
|
27278
|
+
display: "roster"
|
|
27279
|
+
}
|
|
27280
|
+
]
|
|
27281
|
+
},
|
|
27282
|
+
starts: "game-start",
|
|
27283
|
+
when: "someone joins the party or leaves it",
|
|
27284
|
+
watch: {
|
|
27285
|
+
question: "In `earlier`, `player_turn` or `narration`, has someone joined the party (now travelling and acting with the player's character as one of them) or left it, that {tracker} does not show?",
|
|
27286
|
+
yes: `Yes: someone agrees to come along, is taken on, or sets out with them as one of the group ("I'll come with you", hired for the journey, rescued and staying with them); or someone {tracker} lists leaves, parts ways for good, is left behind or dies.`,
|
|
27287
|
+
no: "No: {tracker} already shows who is in the party; or someone only helps for a moment, offers to come but has not joined, or is only met, fought beside or travelled near.",
|
|
27288
|
+
remind: `someone joined or left the party and the board's Party does not show it (add or take them off: "Party": "Add <name>", "Party": "Remove <name>")`
|
|
27289
|
+
}
|
|
27290
|
+
}
|
|
27291
|
+
}
|
|
27292
|
+
},
|
|
27293
|
+
{
|
|
27294
|
+
id: "death-saves-tracker",
|
|
27295
|
+
revision: 3,
|
|
27296
|
+
instruction: {
|
|
27297
|
+
name: "Death saves",
|
|
27298
|
+
when: "always",
|
|
27299
|
+
section: "Rules",
|
|
27300
|
+
body: `Only for a world whose rules have the dying roll to stay alive. Each save is a roll on their turn: the player's for their character, yours for anyone else. Record each one as it lands ("Death saves for Sara": "Successes +1", "Death saves for Sara": "Failures +1"). When they stabilize, are healed or die, end it ("Death saves for Sara": "End").`,
|
|
27301
|
+
tracker: {
|
|
27302
|
+
key: "death-saves",
|
|
27303
|
+
start: {
|
|
27304
|
+
name: "Death saves",
|
|
27305
|
+
fields: [
|
|
27306
|
+
{
|
|
27307
|
+
kind: "count",
|
|
27308
|
+
label: "Successes",
|
|
27309
|
+
value: 0,
|
|
27310
|
+
min: 0,
|
|
27311
|
+
max: 3,
|
|
27312
|
+
tone: "good",
|
|
27313
|
+
display: "pips",
|
|
27314
|
+
at: { "3": "Stable" }
|
|
27315
|
+
},
|
|
27316
|
+
{
|
|
27317
|
+
kind: "count",
|
|
27318
|
+
label: "Failures",
|
|
27319
|
+
value: 0,
|
|
27320
|
+
min: 0,
|
|
27321
|
+
max: 3,
|
|
27322
|
+
tone: "bad",
|
|
27323
|
+
display: "pips",
|
|
27324
|
+
at: { "3": "Dies" }
|
|
27325
|
+
}
|
|
27326
|
+
]
|
|
27327
|
+
},
|
|
27328
|
+
starts: "when-started",
|
|
27329
|
+
trigger: "someone falls dying, and the world's rules have them roll to stay alive",
|
|
27330
|
+
perPerson: true,
|
|
27331
|
+
when: "they make a death save"
|
|
27332
|
+
}
|
|
27333
|
+
}
|
|
27334
|
+
},
|
|
27335
|
+
{
|
|
27336
|
+
id: "time-tracker",
|
|
27337
|
+
revision: 3,
|
|
27338
|
+
instruction: {
|
|
27339
|
+
name: "Time",
|
|
27340
|
+
when: "always",
|
|
27341
|
+
section: "Rules",
|
|
27342
|
+
body: `The Time tracker is the in-game time, and only you move it: nothing moves it after the turn. It is always a time someone could tell by a clock or the sky, in the world's own terms: a clock hour ("10:40 pm"), a part of the day ("dawn", "late afternoon") or how long until something ("about 2 hours until sunset"); never only how long passed ("30 minutes later": later than what?). Whenever the story takes time worth counting (a journey, a rest, a wait, a long job or talk), set it to the time it is now in the update() for that stretch of story, beside its other changes: "Time": "It is now dusk". When it shows a clock hour, a span is enough ("Time": "40 minutes pass") and the clock adds it. With nothing else to record, the time is still an update() of its own. A line or two of talk needs no record. Tell it once, as the time it is now: "It is now 12:10 am", "It is now dawn", "It is now about an hour until sunset"; never "a while later" or "30 minutes later". A rest recorded with its length ("scene": "The party rests for 1 hour") already moves the clock, so no "Time": "1 hour passes" after it. Narrate the hour, the light and what is open from the board's time, never from a guess.`,
|
|
27343
|
+
tracker: {
|
|
27344
|
+
key: "time",
|
|
27345
|
+
start: {
|
|
27346
|
+
name: "Time",
|
|
27347
|
+
lasts: "game",
|
|
27348
|
+
fields: [{ kind: "text", text: "" }]
|
|
27349
|
+
},
|
|
27350
|
+
starts: "game-start",
|
|
27351
|
+
when: "the story takes time worth counting (a journey, a rest, a wait, a long job or talk)"
|
|
27352
|
+
}
|
|
26876
27353
|
}
|
|
26877
27354
|
},
|
|
26878
27355
|
{
|
|
@@ -26898,7 +27375,6 @@ End each reply on something concrete in front of the player's character that the
|
|
|
26898
27375
|
}
|
|
26899
27376
|
];
|
|
26900
27377
|
var RESPONSE_LENGTH = "Response length";
|
|
26901
|
-
var ROLL_NUMBERS = "Roll numbers in narration";
|
|
26902
27378
|
var LONG_TRAVEL = "Long-distance travel";
|
|
26903
27379
|
var PLATFORM_OPTIONS = [
|
|
26904
27380
|
{
|
|
@@ -26952,40 +27428,6 @@ var PLATFORM_OPTIONS = [
|
|
|
26952
27428
|
]
|
|
26953
27429
|
}
|
|
26954
27430
|
},
|
|
26955
|
-
{
|
|
26956
|
-
revision: 2,
|
|
26957
|
-
option: {
|
|
26958
|
-
key: "platform-roll-numbers",
|
|
26959
|
-
label: ROLL_NUMBERS,
|
|
26960
|
-
description: "Whether the story repeats the dice. Rolls always show as dice cards in the chat.",
|
|
26961
|
-
kind: "one",
|
|
26962
|
-
default: ["left-out"],
|
|
26963
|
-
choices: [
|
|
26964
|
-
{
|
|
26965
|
-
key: "left-out",
|
|
26966
|
-
label: "Exclude",
|
|
26967
|
-
description: "The story tells what happens, not the numbers.",
|
|
26968
|
-
instructions: [
|
|
26969
|
-
{
|
|
26970
|
-
when: "always",
|
|
26971
|
-
body: "Never write dice, totals or difficulties in the story: the dice cards show them. Tell what the roll's outcome looks and feels like."
|
|
26972
|
-
}
|
|
26973
|
-
]
|
|
26974
|
-
},
|
|
26975
|
-
{
|
|
26976
|
-
key: "shown",
|
|
26977
|
-
label: "Include",
|
|
26978
|
-
description: "Each total in a short bracket where it lands.",
|
|
26979
|
-
instructions: [
|
|
26980
|
-
{
|
|
26981
|
-
when: "always",
|
|
26982
|
-
body: "Show each roll's total once, in a short bracket where it lands in the story: (Climb: 11 + 3 = 14 vs DC 12), or for an attack and its damage (15 vs AC 14, 4 piercing damage)."
|
|
26983
|
-
}
|
|
26984
|
-
]
|
|
26985
|
-
}
|
|
26986
|
-
]
|
|
26987
|
-
}
|
|
26988
|
-
},
|
|
26989
27431
|
{
|
|
26990
27432
|
revision: 1,
|
|
26991
27433
|
option: {
|
|
@@ -27036,7 +27478,7 @@ var GAME_OPTION_LABEL_MAX_LENGTH = 60;
|
|
|
27036
27478
|
var GAME_OPTION_DESCRIPTION_MAX_LENGTH = 200;
|
|
27037
27479
|
var GAME_OPTION_KEY_MAX_LENGTH = 60;
|
|
27038
27480
|
var GAME_OPTIONS_MAX_OPTIONS = 8;
|
|
27039
|
-
var GAME_OPTIONS_READ_MAX_OPTIONS =
|
|
27481
|
+
var GAME_OPTIONS_READ_MAX_OPTIONS = 15;
|
|
27040
27482
|
var GAME_OPTION_MAX_CHOICES = 8;
|
|
27041
27483
|
var GAME_OPTIONS_MAX_INSTRUCTIONS = 200;
|
|
27042
27484
|
var GAME_OPTIONS_MAX_ALWAYS_LENGTH = 1e4;
|
|
@@ -27052,6 +27494,7 @@ var gameOptionInstructionSchema = exports_external.object({
|
|
|
27052
27494
|
when: exports_external.enum(GAME_OPTION_INSTRUCTION_WHEN),
|
|
27053
27495
|
trigger: exports_external.string().trim().max(GAME_OPTION_INSTRUCTION_NAME_MAX_LENGTH).optional(),
|
|
27054
27496
|
section: exports_external.string().trim().max(GAME_OPTION_SECTION_MAX_LENGTH).optional(),
|
|
27497
|
+
tracker: trackerDefinitionSchema.optional(),
|
|
27055
27498
|
platform: exports_external.string().max(GAME_OPTION_KEY_MAX_LENGTH).optional()
|
|
27056
27499
|
});
|
|
27057
27500
|
var gameOptionChoiceSchema = exports_external.object({
|
|
@@ -27123,6 +27566,12 @@ function sectionsOf(options) {
|
|
|
27123
27566
|
return [...new Set(names)];
|
|
27124
27567
|
}
|
|
27125
27568
|
function instructionProblem(instruction, base) {
|
|
27569
|
+
if (instruction.tracker) {
|
|
27570
|
+
const problem = trackerDefinitionProblem(instruction.tracker);
|
|
27571
|
+
if (problem) {
|
|
27572
|
+
return problem.charAt(0).toLowerCase() + problem.slice(1);
|
|
27573
|
+
}
|
|
27574
|
+
}
|
|
27126
27575
|
if (instruction.when === "situational" && !instruction.trigger?.trim()) {
|
|
27127
27576
|
return "a situational instruction needs the moment it's for.";
|
|
27128
27577
|
}
|
|
@@ -27147,7 +27596,28 @@ function gameOptionsProblem(options) {
|
|
|
27147
27596
|
}
|
|
27148
27597
|
}
|
|
27149
27598
|
}
|
|
27150
|
-
return gameOptionsStructureProblem(options) ?? gameOptionsLimitProblem(options);
|
|
27599
|
+
return trackerKeysProblem(options) ?? gameOptionsStructureProblem(options) ?? gameOptionsLimitProblem(options);
|
|
27600
|
+
}
|
|
27601
|
+
function trackerKeysProblem(options) {
|
|
27602
|
+
const lists = [
|
|
27603
|
+
options.instructions.filter((instruction) => !instruction.platform),
|
|
27604
|
+
...options.options.flatMap((option) => option.choices.map((choice) => choice.instructions))
|
|
27605
|
+
];
|
|
27606
|
+
for (const list of lists) {
|
|
27607
|
+
const seen = new Map;
|
|
27608
|
+
for (const instruction of list) {
|
|
27609
|
+
const definition = instruction.tracker;
|
|
27610
|
+
if (!definition) {
|
|
27611
|
+
continue;
|
|
27612
|
+
}
|
|
27613
|
+
const other = seen.get(definition.key);
|
|
27614
|
+
if (other !== undefined) {
|
|
27615
|
+
return `The trackers "${other}" and "${definition.start.name}" share the key "${definition.key}": rename one.`;
|
|
27616
|
+
}
|
|
27617
|
+
seen.set(definition.key, definition.start.name);
|
|
27618
|
+
}
|
|
27619
|
+
}
|
|
27620
|
+
return null;
|
|
27151
27621
|
}
|
|
27152
27622
|
function gameOptionsStructureProblem(options) {
|
|
27153
27623
|
const optionKeys = new Set;
|
|
@@ -27208,6 +27678,54 @@ var storedGameOptionsSchema = storedGameOptionsShape.superRefine((options, ctx)
|
|
|
27208
27678
|
ctx.addIssue({ code: "custom", message: problem });
|
|
27209
27679
|
}
|
|
27210
27680
|
});
|
|
27681
|
+
function droppedKeys({
|
|
27682
|
+
raw,
|
|
27683
|
+
parsed,
|
|
27684
|
+
path = ""
|
|
27685
|
+
}) {
|
|
27686
|
+
if (Array.isArray(raw) && Array.isArray(parsed)) {
|
|
27687
|
+
return raw.flatMap((item, index) => droppedKeys({
|
|
27688
|
+
raw: item,
|
|
27689
|
+
parsed: parsed[index],
|
|
27690
|
+
path: `${path}[${index}]`
|
|
27691
|
+
}));
|
|
27692
|
+
}
|
|
27693
|
+
if (typeof raw !== "object" || raw === null || typeof parsed !== "object" || parsed === null) {
|
|
27694
|
+
return [];
|
|
27695
|
+
}
|
|
27696
|
+
return Object.entries(raw).flatMap(([key, value]) => (key in parsed) ? droppedKeys({
|
|
27697
|
+
raw: value,
|
|
27698
|
+
parsed: parsed[key],
|
|
27699
|
+
path: `${path}.${key}`
|
|
27700
|
+
}) : [`${path}.${key}`]);
|
|
27701
|
+
}
|
|
27702
|
+
var INDEX_RE = /\[\d+\]/g;
|
|
27703
|
+
var LEADING_DOT_RE = /^\./;
|
|
27704
|
+
function unknownGameOptionsKeys({
|
|
27705
|
+
raw,
|
|
27706
|
+
before
|
|
27707
|
+
}) {
|
|
27708
|
+
const parsed = storedGameOptionsShape.safeParse(raw);
|
|
27709
|
+
if (!parsed.success) {
|
|
27710
|
+
return [];
|
|
27711
|
+
}
|
|
27712
|
+
const stored = before === undefined || before === null ? null : storedGameOptionsShape.safeParse(before);
|
|
27713
|
+
const old = new Set(stored?.success ? droppedKeys({ raw: before, parsed: stored.data }).map((path) => path.replace(INDEX_RE, "[]")) : []);
|
|
27714
|
+
return droppedKeys({ raw, parsed: parsed.data }).filter((path) => !old.has(path.replace(INDEX_RE, "[]"))).map((path) => path.replace(LEADING_DOT_RE, ""));
|
|
27715
|
+
}
|
|
27716
|
+
function unknownGameOptionsKeysProblem({
|
|
27717
|
+
raw,
|
|
27718
|
+
before,
|
|
27719
|
+
where
|
|
27720
|
+
}) {
|
|
27721
|
+
const unknown2 = unknownGameOptionsKeys({ raw, before });
|
|
27722
|
+
if (unknown2.length === 0) {
|
|
27723
|
+
return null;
|
|
27724
|
+
}
|
|
27725
|
+
const listed = unknown2.slice(0, 5).map((path) => `${where}.${path}`).join(", ");
|
|
27726
|
+
const more = unknown2.length > 5 ? ` and ${unknown2.length - 5} more` : "";
|
|
27727
|
+
return `${listed}${more}: the format has no ${unknown2.length === 1 ? "such key" : "such keys"} there, and it would be dropped without effect. Check the field names and where each sits (a tracker's "lasts", "name", "fields" and "hidden" go inside its "start").`;
|
|
27728
|
+
}
|
|
27211
27729
|
var resolvedGameOptionsSchema = gameOptionsShape.superRefine((options, ctx) => {
|
|
27212
27730
|
const problem = gameOptionsProblem(options);
|
|
27213
27731
|
if (problem) {
|
|
@@ -27280,7 +27798,13 @@ function withPlatform(stored) {
|
|
|
27280
27798
|
const content = override ? overrideInstruction(override) : entry.instruction;
|
|
27281
27799
|
const { section: ownSection, ...rest } = content;
|
|
27282
27800
|
const section = placedIn?.trim() || ownSection?.trim() || entry.instruction.section;
|
|
27283
|
-
|
|
27801
|
+
const tracker = rest.tracker ?? entry.instruction.tracker;
|
|
27802
|
+
return {
|
|
27803
|
+
...rest,
|
|
27804
|
+
...tracker ? { tracker } : {},
|
|
27805
|
+
...section ? { section } : {},
|
|
27806
|
+
platform: entry.id
|
|
27807
|
+
};
|
|
27284
27808
|
}
|
|
27285
27809
|
const instructions = [];
|
|
27286
27810
|
const placed = new Set;
|
|
@@ -27356,6 +27880,12 @@ function instructionSignature(instruction) {
|
|
|
27356
27880
|
instruction.trigger?.trim() ?? ""
|
|
27357
27881
|
]);
|
|
27358
27882
|
}
|
|
27883
|
+
function sameTracker(a, b) {
|
|
27884
|
+
return sameJson(a ?? null, b ?? null);
|
|
27885
|
+
}
|
|
27886
|
+
function craftTracker(instruction, entry) {
|
|
27887
|
+
return sameTracker(instruction.tracker ?? entry.instruction.tracker, entry.instruction.tracker);
|
|
27888
|
+
}
|
|
27359
27889
|
function optionSignature(option) {
|
|
27360
27890
|
return JSON.stringify([
|
|
27361
27891
|
option.label.trim(),
|
|
@@ -27381,7 +27911,7 @@ function platformInstructionStatus(instruction, stored) {
|
|
|
27381
27911
|
if (!entry) {
|
|
27382
27912
|
return null;
|
|
27383
27913
|
}
|
|
27384
|
-
if (instructionSignature(withoutPlatformTag(instruction)) === instructionSignature(entry.instruction)) {
|
|
27914
|
+
if (instructionSignature(withoutPlatformTag(instruction)) === instructionSignature(entry.instruction) && craftTracker(instruction, entry)) {
|
|
27385
27915
|
return "platform";
|
|
27386
27916
|
}
|
|
27387
27917
|
const before = parseGameOptions(stored);
|
|
@@ -27416,10 +27946,11 @@ function alwaysTextLength(instructions) {
|
|
|
27416
27946
|
return always.reduce((total, instruction) => total + instruction.body.length, 0) + [...titles].reduce((total, title) => total + title.length, 0);
|
|
27417
27947
|
}
|
|
27418
27948
|
function alwaysLength(instructions, place) {
|
|
27419
|
-
|
|
27949
|
+
const trackerLines = instructions.reduce((total, instruction) => instruction.tracker ? total + instruction.tracker.start.name.length + (instruction.tracker.when?.length ?? 0) + 4 : total, 0);
|
|
27950
|
+
return alwaysTextLength(instructions.filter((instruction) => !instruction.tracker).map((instruction) => ({
|
|
27420
27951
|
...instruction,
|
|
27421
27952
|
title: instructionTitle(instruction, place)
|
|
27422
|
-
})));
|
|
27953
|
+
}))) + trackerLines;
|
|
27423
27954
|
}
|
|
27424
27955
|
function situationalCount(instructions, _place) {
|
|
27425
27956
|
return instructions.filter((instruction) => instruction.when === "situational").length;
|
|
@@ -36485,8 +37016,52 @@ function validateFileContentAgainstSchemaVariants(contents, schema) {
|
|
|
36485
37016
|
return sharedErrors.length > 0 ? { status: "invalid", errors: sharedErrors } : { status: "valid" };
|
|
36486
37017
|
}
|
|
36487
37018
|
|
|
37019
|
+
// ../shared/src/vfs/path.ts
|
|
37020
|
+
var LEADING_SLASHES = /^\/+/;
|
|
37021
|
+
var TRAILING_SLASHES = /\/+$/;
|
|
37022
|
+
function parseVfsPath(path) {
|
|
37023
|
+
const trimmed = path.replace(LEADING_SLASHES, "").replace(TRAILING_SLASHES, "");
|
|
37024
|
+
const isDir = path.endsWith("/") || path === "/";
|
|
37025
|
+
if (!trimmed) {
|
|
37026
|
+
return { kind: "folder", segments: [] };
|
|
37027
|
+
}
|
|
37028
|
+
const parts = trimmed.split("/").map((part) => part.trim());
|
|
37029
|
+
if (parts.some((part) => part.length === 0)) {
|
|
37030
|
+
throw new Error(`Invalid path "${path}": empty path segment`);
|
|
37031
|
+
}
|
|
37032
|
+
if (parts.some((part) => part === "." || part === "..")) {
|
|
37033
|
+
throw new Error(`Invalid path "${path}": "." and ".." path segments are not allowed`);
|
|
37034
|
+
}
|
|
37035
|
+
const lastPart = parts.at(-1);
|
|
37036
|
+
if (isDir || !lastPart || !lastPart.includes(".")) {
|
|
37037
|
+
return { kind: "folder", segments: parts };
|
|
37038
|
+
}
|
|
37039
|
+
const dotIdx = lastPart.lastIndexOf(".");
|
|
37040
|
+
const finalSuffix = lastPart.slice(dotIdx + 1);
|
|
37041
|
+
const stem = lastPart.slice(0, dotIdx);
|
|
37042
|
+
const stemDotIdx = stem.lastIndexOf(".");
|
|
37043
|
+
const hasContentExtension = isContentExtension(finalSuffix) && stemDotIdx > 0;
|
|
37044
|
+
return {
|
|
37045
|
+
kind: "file",
|
|
37046
|
+
segments: parts.slice(0, -1),
|
|
37047
|
+
baseName: hasContentExtension ? stem.slice(0, stemDotIdx) : stem,
|
|
37048
|
+
extension: hasContentExtension ? stem.slice(stemDotIdx + 1) : finalSuffix,
|
|
37049
|
+
...hasContentExtension && { contentExtension: finalSuffix }
|
|
37050
|
+
};
|
|
37051
|
+
}
|
|
37052
|
+
var UNSAFE_DISK_SEGMENT_CHARS = /[\\/:*?"<>|\u0000-\u001f\u007f]/g;
|
|
37053
|
+
function sanitizeVfsSegmentForDisk(segment) {
|
|
37054
|
+
if (segment === "." || segment === ".." || segment.trim().length === 0) {
|
|
37055
|
+
return "_";
|
|
37056
|
+
}
|
|
37057
|
+
const cleaned = segment.replace(UNSAFE_DISK_SEGMENT_CHARS, "_").trim();
|
|
37058
|
+
return cleaned.length > 0 ? cleaned : "_";
|
|
37059
|
+
}
|
|
37060
|
+
function sanitizeVfsPathForDisk(path) {
|
|
37061
|
+
return path.split("/").map((segment) => segment === "" ? "" : sanitizeVfsSegmentForDisk(segment)).join("/");
|
|
37062
|
+
}
|
|
37063
|
+
|
|
36488
37064
|
// ../shared/src/files/file-reference-content.ts
|
|
36489
|
-
var FILE_REFERENCE_EXTENSION_RE = /\.[^.]+$/;
|
|
36490
37065
|
var STATE_PATH_LEADING_SLASH_RE = /^\/+/;
|
|
36491
37066
|
var STATE_PATH_ARRAY_INDEX_RE = /\[(?:\d+|\*)\]/g;
|
|
36492
37067
|
function isRecord5(value) {
|
|
@@ -36637,23 +37212,36 @@ function referenceHintsFromLayout(layout) {
|
|
|
36637
37212
|
collectLayoutReferenceHints(layout, hints);
|
|
36638
37213
|
return hints;
|
|
36639
37214
|
}
|
|
36640
|
-
function
|
|
37215
|
+
function referenceNameParts(value) {
|
|
36641
37216
|
const trimmed = value.trim();
|
|
36642
37217
|
const lastSegment = trimmed.split("/").filter(Boolean).at(-1) ?? trimmed;
|
|
36643
|
-
|
|
37218
|
+
try {
|
|
37219
|
+
const parsed = parseVfsPath(lastSegment);
|
|
37220
|
+
if (parsed.kind === "file") {
|
|
37221
|
+
return { name: parsed.baseName, type: parsed.extension };
|
|
37222
|
+
}
|
|
37223
|
+
} catch {}
|
|
37224
|
+
return { name: lastSegment, type: null };
|
|
37225
|
+
}
|
|
37226
|
+
function referenceCandidate(value) {
|
|
37227
|
+
return referenceNameParts(value).name;
|
|
36644
37228
|
}
|
|
36645
37229
|
function normalizeFileReferenceLookupValue(value) {
|
|
36646
37230
|
const candidate = referenceCandidate(value);
|
|
36647
37231
|
return generateSlug(candidate) || candidate.toLowerCase();
|
|
36648
37232
|
}
|
|
36649
37233
|
function qualifiedLookupTypeSlug(value, fileTypeSlugs) {
|
|
37234
|
+
const normalizedTargets = new Set(fileTypeSlugs.map((slug) => normalizeReferenceTargetSlug(slug)).filter(Boolean));
|
|
36650
37235
|
const segments = value.trim().split("/").filter(Boolean);
|
|
36651
|
-
if (segments.length
|
|
36652
|
-
|
|
37236
|
+
if (segments.length >= 2) {
|
|
37237
|
+
const prefix = normalizeReferenceTargetSlug(segments[0]);
|
|
37238
|
+
if (normalizedTargets.has(prefix)) {
|
|
37239
|
+
return prefix;
|
|
37240
|
+
}
|
|
36653
37241
|
}
|
|
36654
|
-
const
|
|
36655
|
-
const
|
|
36656
|
-
return normalizedTargets.has(
|
|
37242
|
+
const ending = referenceNameParts(value).type;
|
|
37243
|
+
const type = ending ? normalizeReferenceTargetSlug(ending) : "";
|
|
37244
|
+
return type && normalizedTargets.has(type) ? type : null;
|
|
36657
37245
|
}
|
|
36658
37246
|
function parseFileReferenceLookupValue(value, fileTypeSlugs = []) {
|
|
36659
37247
|
const fileTypeSlug = qualifiedLookupTypeSlug(value, fileTypeSlugs);
|
|
@@ -40209,85 +40797,6 @@ function validateSectionsBlock(sections, errors3, options) {
|
|
|
40209
40797
|
}
|
|
40210
40798
|
}
|
|
40211
40799
|
}
|
|
40212
|
-
// ../shared/src/board-trackers.ts
|
|
40213
|
-
var TRACKER_COUNT_STYLES = [
|
|
40214
|
-
"number",
|
|
40215
|
-
"bar",
|
|
40216
|
-
"pips",
|
|
40217
|
-
"boxes",
|
|
40218
|
-
"clock",
|
|
40219
|
-
"scale"
|
|
40220
|
-
];
|
|
40221
|
-
var TRACKER_TONES = ["good", "bad"];
|
|
40222
|
-
var TRACKER_ITEM_STATES = ["open", "done", "failed"];
|
|
40223
|
-
var TRACKER_LASTS = ["here", "game"];
|
|
40224
|
-
var TRACKER_LIMITS = {
|
|
40225
|
-
name: 60,
|
|
40226
|
-
label: 40,
|
|
40227
|
-
text: 160,
|
|
40228
|
-
rows: 6,
|
|
40229
|
-
items: 16,
|
|
40230
|
-
order: 20,
|
|
40231
|
-
marks: 20,
|
|
40232
|
-
clockMin: 2,
|
|
40233
|
-
clockMax: 12,
|
|
40234
|
-
scaleSteps: 21
|
|
40235
|
-
};
|
|
40236
|
-
var labelSchema = exports_external.string().trim().max(TRACKER_LIMITS.label).nullish();
|
|
40237
|
-
var countRowSchema = exports_external.object({
|
|
40238
|
-
kind: exports_external.literal("count"),
|
|
40239
|
-
label: labelSchema,
|
|
40240
|
-
value: exports_external.number(),
|
|
40241
|
-
min: exports_external.number().nullish(),
|
|
40242
|
-
max: exports_external.number().nullish(),
|
|
40243
|
-
style: exports_external.enum(TRACKER_COUNT_STYLES).nullish(),
|
|
40244
|
-
tone: exports_external.enum(TRACKER_TONES).nullish(),
|
|
40245
|
-
ends: exports_external.tuple([exports_external.string(), exports_external.string()]).nullish(),
|
|
40246
|
-
at: exports_external.record(exports_external.string(), exports_external.string()).nullish(),
|
|
40247
|
-
start: exports_external.number().nullish()
|
|
40248
|
-
});
|
|
40249
|
-
var listItemSchema = exports_external.object({
|
|
40250
|
-
text: exports_external.string().trim().min(1).max(TRACKER_LIMITS.text),
|
|
40251
|
-
state: exports_external.enum(TRACKER_ITEM_STATES),
|
|
40252
|
-
note: exports_external.string().trim().max(TRACKER_LIMITS.text).nullish()
|
|
40253
|
-
});
|
|
40254
|
-
var listRowSchema = exports_external.object({
|
|
40255
|
-
kind: exports_external.literal("list"),
|
|
40256
|
-
label: labelSchema,
|
|
40257
|
-
items: exports_external.array(listItemSchema).max(TRACKER_LIMITS.items)
|
|
40258
|
-
});
|
|
40259
|
-
var orderEntrySchema = exports_external.object({
|
|
40260
|
-
name: exports_external.string().trim().min(1),
|
|
40261
|
-
score: exports_external.number().nullish()
|
|
40262
|
-
});
|
|
40263
|
-
var orderRowSchema = exports_external.object({
|
|
40264
|
-
kind: exports_external.literal("order"),
|
|
40265
|
-
label: labelSchema,
|
|
40266
|
-
order: exports_external.array(orderEntrySchema).max(TRACKER_LIMITS.order),
|
|
40267
|
-
current: exports_external.string().nullish(),
|
|
40268
|
-
round: exports_external.number().int().nullish()
|
|
40269
|
-
});
|
|
40270
|
-
var textRowSchema = exports_external.object({
|
|
40271
|
-
kind: exports_external.literal("text"),
|
|
40272
|
-
label: labelSchema,
|
|
40273
|
-
text: exports_external.string().trim().max(TRACKER_LIMITS.text)
|
|
40274
|
-
});
|
|
40275
|
-
var trackerRowSchema = exports_external.discriminatedUnion("kind", [
|
|
40276
|
-
countRowSchema,
|
|
40277
|
-
listRowSchema,
|
|
40278
|
-
orderRowSchema,
|
|
40279
|
-
textRowSchema
|
|
40280
|
-
]);
|
|
40281
|
-
var trackerSchema = exports_external.object({
|
|
40282
|
-
name: exports_external.string().trim().min(1).max(TRACKER_LIMITS.name),
|
|
40283
|
-
rows: exports_external.array(trackerRowSchema).min(1).max(TRACKER_LIMITS.rows),
|
|
40284
|
-
for: exports_external.string().trim().min(1).nullish(),
|
|
40285
|
-
hidden: exports_external.boolean().nullish(),
|
|
40286
|
-
lasts: exports_external.enum(TRACKER_LASTS).nullish(),
|
|
40287
|
-
since: exports_external.string().nullish(),
|
|
40288
|
-
changed: exports_external.string().nullish()
|
|
40289
|
-
});
|
|
40290
|
-
|
|
40291
40800
|
// ../shared/src/scripts/board.ts
|
|
40292
40801
|
var UPDATE_KEYS = new Set([
|
|
40293
40802
|
"arrive",
|
|
@@ -41061,13 +41570,14 @@ var GAME_START_MAX_CHARACTER_OPENINGS = 100;
|
|
|
41061
41570
|
var GAME_START_GM_NOTES_MAX_LENGTH = 40000;
|
|
41062
41571
|
var GAME_START_BOARD_LIMITS = {
|
|
41063
41572
|
people: 30,
|
|
41064
|
-
time: 80,
|
|
41065
41573
|
purpose: 300,
|
|
41066
41574
|
area: 60,
|
|
41067
41575
|
hiddenAs: 160,
|
|
41068
41576
|
note: 160,
|
|
41069
41577
|
objectives: 16,
|
|
41070
|
-
objective: 160
|
|
41578
|
+
objective: 160,
|
|
41579
|
+
trackers: 30,
|
|
41580
|
+
trackerKey: 60
|
|
41071
41581
|
};
|
|
41072
41582
|
var GM_INSTRUCTION_TRIGGER_PHASES = [
|
|
41073
41583
|
"post_player_message",
|
|
@@ -41449,12 +41959,43 @@ var GameStartBoardPersonSchema = exports_external.object({
|
|
|
41449
41959
|
}).nullish(),
|
|
41450
41960
|
note: exports_external.string().trim().max(GAME_START_BOARD_LIMITS.note).nullish()
|
|
41451
41961
|
});
|
|
41452
|
-
var
|
|
41962
|
+
var GameStartTrackerSchema = exports_external.object({
|
|
41963
|
+
key: exports_external.string().trim().min(1).max(GAME_START_BOARD_LIMITS.trackerKey),
|
|
41964
|
+
on: exports_external.boolean(),
|
|
41965
|
+
fields: exports_external.array(trackerFieldSchema).max(TRACKER_LIMITS.fields).nullish()
|
|
41966
|
+
});
|
|
41967
|
+
function isPlainObject2(value) {
|
|
41968
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
41969
|
+
}
|
|
41970
|
+
function boardWithTimeAsTracker(value) {
|
|
41971
|
+
if (!(isPlainObject2(value) && ("time" in value))) {
|
|
41972
|
+
return value;
|
|
41973
|
+
}
|
|
41974
|
+
const { time: time3, ...rest } = value;
|
|
41975
|
+
const words = typeof time3 === "string" ? time3.trim() : "";
|
|
41976
|
+
const trackers = Array.isArray(rest.trackers) ? rest.trackers : [];
|
|
41977
|
+
const set2 = trackers.some((entry) => isPlainObject2(entry) && entry.key === TIME_TRACKER_KEY);
|
|
41978
|
+
if (!words || set2) {
|
|
41979
|
+
return rest;
|
|
41980
|
+
}
|
|
41981
|
+
return {
|
|
41982
|
+
...rest,
|
|
41983
|
+
trackers: [
|
|
41984
|
+
...trackers,
|
|
41985
|
+
{
|
|
41986
|
+
key: TIME_TRACKER_KEY,
|
|
41987
|
+
on: true,
|
|
41988
|
+
fields: [{ kind: "text", text: words }]
|
|
41989
|
+
}
|
|
41990
|
+
]
|
|
41991
|
+
};
|
|
41992
|
+
}
|
|
41993
|
+
var GameStartBoardSchema = exports_external.preprocess(boardWithTimeAsTracker, exports_external.object({
|
|
41453
41994
|
people: exports_external.array(GameStartBoardPersonSchema).max(GAME_START_BOARD_LIMITS.people).default([]),
|
|
41454
|
-
time: exports_external.string().trim().max(GAME_START_BOARD_LIMITS.time).default(""),
|
|
41455
41995
|
purpose: exports_external.string().trim().max(GAME_START_BOARD_LIMITS.purpose).default(""),
|
|
41456
|
-
objectives: exports_external.array(exports_external.string().trim().min(1).max(GAME_START_BOARD_LIMITS.objective)).max(GAME_START_BOARD_LIMITS.objectives).default([])
|
|
41457
|
-
|
|
41996
|
+
objectives: exports_external.array(exports_external.string().trim().min(1).max(GAME_START_BOARD_LIMITS.objective)).max(GAME_START_BOARD_LIMITS.objectives).default([]),
|
|
41997
|
+
trackers: exports_external.array(GameStartTrackerSchema).max(GAME_START_BOARD_LIMITS.trackers).default([])
|
|
41998
|
+
}));
|
|
41458
41999
|
function combinedOpeningLength(opening) {
|
|
41459
42000
|
if (opening.kind !== "static" || !opening.beats) {
|
|
41460
42001
|
return 0;
|
|
@@ -41473,9 +42014,9 @@ var GameStartMetadataObjectSchema = exports_external.object({
|
|
|
41473
42014
|
openingInstructions: exports_external.string().max(GAME_START_OPENING_MAX_LENGTH).default(""),
|
|
41474
42015
|
board: GameStartBoardSchema.default({
|
|
41475
42016
|
people: [],
|
|
41476
|
-
time: "",
|
|
41477
42017
|
purpose: "",
|
|
41478
|
-
objectives: []
|
|
42018
|
+
objectives: [],
|
|
42019
|
+
trackers: []
|
|
41479
42020
|
}),
|
|
41480
42021
|
gmNotes: exports_external.string().max(GAME_START_GM_NOTES_MAX_LENGTH).default("")
|
|
41481
42022
|
});
|
|
@@ -41518,6 +42059,17 @@ var GameStartMetadataSchema = GameStartMetadataObjectSchema.superRefine((metadat
|
|
|
41518
42059
|
}
|
|
41519
42060
|
seenBoardReferenceIds.add(referenceId);
|
|
41520
42061
|
});
|
|
42062
|
+
const seenTrackerKeys = new Set;
|
|
42063
|
+
metadata.board.trackers.forEach((tracker, index) => {
|
|
42064
|
+
if (seenTrackerKeys.has(tracker.key)) {
|
|
42065
|
+
ctx.addIssue({
|
|
42066
|
+
code: "custom",
|
|
42067
|
+
path: ["board", "trackers", index, "key"],
|
|
42068
|
+
message: "Each tracker can only be set once."
|
|
42069
|
+
});
|
|
42070
|
+
}
|
|
42071
|
+
seenTrackerKeys.add(tracker.key);
|
|
42072
|
+
});
|
|
41521
42073
|
});
|
|
41522
42074
|
var FileDesignationMetadataV1Schema = exports_external.object({
|
|
41523
42075
|
schemaVersionId: exports_external.literal(FILE_DESIGNATION_METADATA_SCHEMA_VERSION_V1).default(FILE_DESIGNATION_METADATA_SCHEMA_VERSION_V1),
|
|
@@ -42022,39 +42574,95 @@ interface BoardPerson {
|
|
|
42022
42574
|
readonly details: { readonly [detail: string]: string | number | boolean };
|
|
42023
42575
|
}
|
|
42024
42576
|
|
|
42025
|
-
/**
|
|
42026
|
-
type
|
|
42577
|
+
/** A file or person a tracker points at, by name; its file is filled in by the game. */
|
|
42578
|
+
type BoardTrackerRef = { name: string; file?: string | null };
|
|
42579
|
+
|
|
42580
|
+
/** A number: a count, a clock (pressure filling toward trouble) or a scale (a tug of war between two ends). */
|
|
42581
|
+
type BoardTrackerNumber = {
|
|
42582
|
+
kind: "count" | "clock" | "scale";
|
|
42583
|
+
label?: string | null;
|
|
42584
|
+
/** The GM's alone: players never see this field. */
|
|
42585
|
+
hidden?: boolean | null;
|
|
42586
|
+
value: number;
|
|
42587
|
+
min?: number | null;
|
|
42588
|
+
max?: number | null;
|
|
42589
|
+
tone?: "good" | "bad" | null;
|
|
42590
|
+
/** A scale's two ends, low first. */
|
|
42591
|
+
ends?: [string, string] | null;
|
|
42592
|
+
/** What happens when the number reaches a value: { "6": "The guards arrive" }. */
|
|
42593
|
+
at?: { [value: string]: string } | null;
|
|
42594
|
+
};
|
|
42595
|
+
|
|
42596
|
+
type BoardTrackerList = {
|
|
42597
|
+
kind: "list";
|
|
42598
|
+
label?: string | null;
|
|
42599
|
+
hidden?: boolean | null;
|
|
42600
|
+
items: {
|
|
42601
|
+
text: string;
|
|
42602
|
+
state: "open" | "done" | "failed";
|
|
42603
|
+
note?: string | null;
|
|
42604
|
+
/** Who or what the item is about. */
|
|
42605
|
+
ref?: BoardTrackerRef | null;
|
|
42606
|
+
hidden?: boolean | null;
|
|
42607
|
+
}[];
|
|
42608
|
+
};
|
|
42609
|
+
|
|
42610
|
+
type BoardTrackerText = { kind: "text"; label?: string | null; hidden?: boolean | null; text: string };
|
|
42611
|
+
|
|
42612
|
+
/**
|
|
42613
|
+
* One field of a tracker: a number, a list, a turn order, a short text, a
|
|
42614
|
+
* file or person it points at, records (one per person or file, each with
|
|
42615
|
+
* fields of its own) or links (one per pair of them, with fields between
|
|
42616
|
+
* the two). show names live values drawn beside each person a field names:
|
|
42617
|
+
* a stat the board shows ("HP") or "where".
|
|
42618
|
+
*/
|
|
42619
|
+
type BoardTrackerField =
|
|
42620
|
+
| BoardTrackerNumber
|
|
42621
|
+
| BoardTrackerList
|
|
42027
42622
|
| {
|
|
42028
|
-
kind: "
|
|
42623
|
+
kind: "order";
|
|
42029
42624
|
label?: string | null;
|
|
42030
|
-
|
|
42031
|
-
|
|
42032
|
-
|
|
42033
|
-
|
|
42034
|
-
|
|
42035
|
-
/** A scale's two ends, low first. */
|
|
42036
|
-
ends?: [string, string] | null;
|
|
42037
|
-
/** What happens when the count reaches a value: { "6": "The guards arrive" }. */
|
|
42038
|
-
at?: { [value: string]: string } | null;
|
|
42625
|
+
hidden?: boolean | null;
|
|
42626
|
+
entries: { name: string; score?: number | null }[];
|
|
42627
|
+
current?: string | null;
|
|
42628
|
+
round?: number | null;
|
|
42629
|
+
show?: string[] | null;
|
|
42039
42630
|
}
|
|
42631
|
+
| BoardTrackerText
|
|
42040
42632
|
| {
|
|
42041
|
-
kind: "
|
|
42633
|
+
kind: "ref";
|
|
42042
42634
|
label?: string | null;
|
|
42043
|
-
|
|
42635
|
+
hidden?: boolean | null;
|
|
42636
|
+
ref: BoardTrackerRef;
|
|
42637
|
+
show?: string[] | null;
|
|
42044
42638
|
}
|
|
42045
42639
|
| {
|
|
42046
|
-
kind: "
|
|
42640
|
+
kind: "records";
|
|
42047
42641
|
label?: string | null;
|
|
42048
|
-
|
|
42049
|
-
|
|
42050
|
-
|
|
42642
|
+
hidden?: boolean | null;
|
|
42643
|
+
entries: {
|
|
42644
|
+
ref: BoardTrackerRef;
|
|
42645
|
+
hidden?: boolean | null;
|
|
42646
|
+
fields: (BoardTrackerNumber | BoardTrackerList | BoardTrackerText)[];
|
|
42647
|
+
}[];
|
|
42648
|
+
show?: string[] | null;
|
|
42051
42649
|
}
|
|
42052
|
-
| {
|
|
42650
|
+
| {
|
|
42651
|
+
kind: "links";
|
|
42652
|
+
label?: string | null;
|
|
42653
|
+
hidden?: boolean | null;
|
|
42654
|
+
entries: {
|
|
42655
|
+
/** The two it links ("Kael" and "Mira"); a pair is the same either way round. */
|
|
42656
|
+
between: [BoardTrackerRef, BoardTrackerRef];
|
|
42657
|
+
hidden?: boolean | null;
|
|
42658
|
+
fields: (BoardTrackerNumber | BoardTrackerList | BoardTrackerText)[];
|
|
42659
|
+
}[];
|
|
42660
|
+
};
|
|
42053
42661
|
|
|
42054
|
-
/** A tracker on the board: a turn order, a clock,
|
|
42662
|
+
/** A tracker on the board: a turn order, a clock, the objectives, suspects, a count. */
|
|
42055
42663
|
interface BoardTracker {
|
|
42056
42664
|
name: string;
|
|
42057
|
-
|
|
42665
|
+
fields: BoardTrackerField[];
|
|
42058
42666
|
/** Someone on the board it is about. */
|
|
42059
42667
|
for?: string | null;
|
|
42060
42668
|
/** The GM's alone: players never see it. */
|
|
@@ -43008,7 +43616,7 @@ function parseMarkdown(source) {
|
|
|
43008
43616
|
} catch {
|
|
43009
43617
|
return { content: source };
|
|
43010
43618
|
}
|
|
43011
|
-
if (!
|
|
43619
|
+
if (!isPlainObject3(parsed)) {
|
|
43012
43620
|
return { content: body };
|
|
43013
43621
|
}
|
|
43014
43622
|
const file2 = { content: body };
|
|
@@ -43056,7 +43664,7 @@ function parseFrontmatterObject(source) {
|
|
|
43056
43664
|
} catch {
|
|
43057
43665
|
return null;
|
|
43058
43666
|
}
|
|
43059
|
-
return
|
|
43667
|
+
return isPlainObject3(parsed) ? parsed : null;
|
|
43060
43668
|
}
|
|
43061
43669
|
function stripLeadingNewline(s) {
|
|
43062
43670
|
if (s.startsWith(`\r
|
|
@@ -43069,7 +43677,7 @@ function stripLeadingNewline(s) {
|
|
|
43069
43677
|
}
|
|
43070
43678
|
return s;
|
|
43071
43679
|
}
|
|
43072
|
-
function
|
|
43680
|
+
function isPlainObject3(value) {
|
|
43073
43681
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
43074
43682
|
}
|
|
43075
43683
|
function coerceString(value) {
|
|
@@ -43085,19 +43693,6 @@ function coerceString(value) {
|
|
|
43085
43693
|
// src/engine/vfs-name.ts
|
|
43086
43694
|
import { isAbsolute, relative, resolve } from "node:path";
|
|
43087
43695
|
|
|
43088
|
-
// ../shared/src/vfs/path.ts
|
|
43089
|
-
var UNSAFE_DISK_SEGMENT_CHARS = /[\\/:*?"<>|\u0000-\u001f\u007f]/g;
|
|
43090
|
-
function sanitizeVfsSegmentForDisk(segment) {
|
|
43091
|
-
if (segment === "." || segment === ".." || segment.trim().length === 0) {
|
|
43092
|
-
return "_";
|
|
43093
|
-
}
|
|
43094
|
-
const cleaned = segment.replace(UNSAFE_DISK_SEGMENT_CHARS, "_").trim();
|
|
43095
|
-
return cleaned.length > 0 ? cleaned : "_";
|
|
43096
|
-
}
|
|
43097
|
-
function sanitizeVfsPathForDisk(path) {
|
|
43098
|
-
return path.split("/").map((segment) => segment === "" ? "" : sanitizeVfsSegmentForDisk(segment)).join("/");
|
|
43099
|
-
}
|
|
43100
|
-
|
|
43101
43696
|
// ../shared/src/cli/vfs-name.ts
|
|
43102
43697
|
function parseVfsFileName(fileName) {
|
|
43103
43698
|
if (fileName.includes("/")) {
|
|
@@ -43192,10 +43787,10 @@ function deletedSidecarPath(workingPath) {
|
|
|
43192
43787
|
}
|
|
43193
43788
|
|
|
43194
43789
|
// src/engine/vfs-name.ts
|
|
43195
|
-
var
|
|
43790
|
+
var LEADING_SLASHES2 = /^\/+/;
|
|
43196
43791
|
function joinUnderWorkspaceRoot(root, vfsPath) {
|
|
43197
43792
|
const rootResolved = resolve(root);
|
|
43198
|
-
const relativePath = vfsPath.replace(
|
|
43793
|
+
const relativePath = vfsPath.replace(LEADING_SLASHES2, "");
|
|
43199
43794
|
if (relativePath.length === 0 || relativePath.split("/").some((segment) => segment === "." || segment === "..")) {
|
|
43200
43795
|
throw new Error(`path escapes workspace root: ${vfsPath}`);
|
|
43201
43796
|
}
|
|
@@ -43890,9 +44485,9 @@ async function loadBaseRecordsForMissing({
|
|
|
43890
44485
|
return records;
|
|
43891
44486
|
}
|
|
43892
44487
|
var LEADING_DOT_SLASH = /^\.\//;
|
|
43893
|
-
var
|
|
44488
|
+
var TRAILING_SLASHES2 = /\/+$/;
|
|
43894
44489
|
function normalizeVfsPath(input) {
|
|
43895
|
-
const cleaned = input.replace(LEADING_DOT_SLASH, "").replace(
|
|
44490
|
+
const cleaned = input.replace(LEADING_DOT_SLASH, "").replace(TRAILING_SLASHES2, "");
|
|
43896
44491
|
return cleaned.startsWith("/") ? cleaned : `/${cleaned}`;
|
|
43897
44492
|
}
|
|
43898
44493
|
function toIsoTimestamp(value) {
|
|
@@ -44361,6 +44956,17 @@ function checkGameOptions(workspace) {
|
|
|
44361
44956
|
detail: "shape errors are reported under project-settings"
|
|
44362
44957
|
};
|
|
44363
44958
|
}
|
|
44959
|
+
const unknown2 = unknownGameOptionsKeysProblem({
|
|
44960
|
+
raw: value,
|
|
44961
|
+
where: "settings.gameOptions"
|
|
44962
|
+
});
|
|
44963
|
+
if (unknown2) {
|
|
44964
|
+
return {
|
|
44965
|
+
...base,
|
|
44966
|
+
status: "warn",
|
|
44967
|
+
detail: `${unknown2} Push drops them, so they do nothing (format: .craft/instructions/upgrading-to-gm-harness-2.md)`
|
|
44968
|
+
};
|
|
44969
|
+
}
|
|
44364
44970
|
const parsed = gameOptionsSchema.safeParse(value);
|
|
44365
44971
|
if (!parsed.success) {
|
|
44366
44972
|
const issue3 = parsed.error.issues[0];
|
|
@@ -45139,7 +45745,7 @@ var credentialsFileSchema = exports_external.object({
|
|
|
45139
45745
|
version: exports_external.literal(1),
|
|
45140
45746
|
remotes: exports_external.record(exports_external.string(), storedCredentialsSchema)
|
|
45141
45747
|
}).strict();
|
|
45142
|
-
var
|
|
45748
|
+
var TRAILING_SLASHES3 = /\/+$/;
|
|
45143
45749
|
function credentialsPath() {
|
|
45144
45750
|
const dir = process.env.CRAFT_CONFIG_DIR ?? join4(homedir(), ".config", "craft");
|
|
45145
45751
|
return join4(dir, "credentials.json");
|
|
@@ -45148,7 +45754,7 @@ function normalizeRemoteKey(remote) {
|
|
|
45148
45754
|
try {
|
|
45149
45755
|
return new URL(remote).origin;
|
|
45150
45756
|
} catch {
|
|
45151
|
-
return remote.replace(
|
|
45757
|
+
return remote.replace(TRAILING_SLASHES3, "");
|
|
45152
45758
|
}
|
|
45153
45759
|
}
|
|
45154
45760
|
async function readCredentialsFile() {
|
|
@@ -45203,7 +45809,7 @@ async function deleteStoredCredentials(remote) {
|
|
|
45203
45809
|
|
|
45204
45810
|
// src/auth/oauth.ts
|
|
45205
45811
|
import { createHash as createHash2, randomBytes as randomBytes3 } from "node:crypto";
|
|
45206
|
-
var
|
|
45812
|
+
var TRAILING_SLASHES4 = /\/+$/;
|
|
45207
45813
|
async function fetchJson(fetchImpl, url2, init) {
|
|
45208
45814
|
let response;
|
|
45209
45815
|
try {
|
|
@@ -45221,7 +45827,7 @@ function isRecord15(value) {
|
|
|
45221
45827
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
45222
45828
|
}
|
|
45223
45829
|
function authServerMetadataUrls(issuer) {
|
|
45224
|
-
const base = issuer.replace(
|
|
45830
|
+
const base = issuer.replace(TRAILING_SLASHES4, "");
|
|
45225
45831
|
const url2 = new URL(base);
|
|
45226
45832
|
const suffixForm = `${base}/.well-known/oauth-authorization-server`;
|
|
45227
45833
|
const insertionForm = `${url2.origin}/.well-known/oauth-authorization-server${url2.pathname === "/" ? "" : url2.pathname}`;
|
|
@@ -45231,7 +45837,7 @@ async function discoverAuthorization({
|
|
|
45231
45837
|
remote,
|
|
45232
45838
|
fetchImpl = fetch
|
|
45233
45839
|
}) {
|
|
45234
|
-
const origin = remote.replace(
|
|
45840
|
+
const origin = remote.replace(TRAILING_SLASHES4, "");
|
|
45235
45841
|
const prmUrl = `${origin}/.well-known/oauth-protected-resource/mcp`;
|
|
45236
45842
|
const prm = await fetchJson(fetchImpl, prmUrl);
|
|
45237
45843
|
if (!(prm.ok && isRecord15(prm.json))) {
|
|
@@ -45589,7 +46195,7 @@ async function refreshWorkspaceScriptTypes({
|
|
|
45589
46195
|
// package.json
|
|
45590
46196
|
var package_default = {
|
|
45591
46197
|
name: "@craftrpgs/cli",
|
|
45592
|
-
version: "0.2.0-alpha.
|
|
46198
|
+
version: "0.2.0-alpha.2",
|
|
45593
46199
|
description: "Sync Craft projects with a local folder — clone, edit with any coding agent, push back.",
|
|
45594
46200
|
license: "UNLICENSED",
|
|
45595
46201
|
homepage: "https://craftrpgs.com",
|
|
@@ -45610,14 +46216,14 @@ var package_default = {
|
|
|
45610
46216
|
scripts: {
|
|
45611
46217
|
build: "bun run scripts/build.ts",
|
|
45612
46218
|
prepack: "bun run build",
|
|
45613
|
-
typecheck: "
|
|
46219
|
+
typecheck: "bun check"
|
|
45614
46220
|
},
|
|
45615
46221
|
devDependencies: {
|
|
45616
46222
|
"@craft/flow": "workspace:*",
|
|
45617
46223
|
"@craft/shared": "workspace:*",
|
|
45618
46224
|
fflate: "^0.8.3",
|
|
45619
46225
|
typescript: "7.0.2",
|
|
45620
|
-
uuid: "11.1.
|
|
46226
|
+
uuid: "11.1.1",
|
|
45621
46227
|
zod: "4.3.6"
|
|
45622
46228
|
}
|
|
45623
46229
|
};
|
|
@@ -45626,7 +46232,7 @@ var package_default = {
|
|
|
45626
46232
|
var CLI_VERSION = package_default.version;
|
|
45627
46233
|
|
|
45628
46234
|
// src/http.ts
|
|
45629
|
-
var
|
|
46235
|
+
var TRAILING_SLASHES5 = /\/+$/;
|
|
45630
46236
|
var MAX_CONTRACT_ISSUES_SHOWN = 3;
|
|
45631
46237
|
function formatContractIssues(issues) {
|
|
45632
46238
|
if (!issues || issues.length === 0) {
|
|
@@ -45762,8 +46368,8 @@ function createCliClient({
|
|
|
45762
46368
|
auth,
|
|
45763
46369
|
fetchImpl = fetch
|
|
45764
46370
|
}) {
|
|
45765
|
-
const base = remote.replace(
|
|
45766
|
-
const appBase = (appRemote ?? remote).replace(
|
|
46371
|
+
const base = remote.replace(TRAILING_SLASHES5, "");
|
|
46372
|
+
const appBase = (appRemote ?? remote).replace(TRAILING_SLASHES5, "");
|
|
45767
46373
|
async function requestWithToken(url2, token, init) {
|
|
45768
46374
|
try {
|
|
45769
46375
|
return await fetchImpl(url2, {
|
|
@@ -57037,10 +57643,13 @@ set-game-start JSON: { "startingLocation": "<path|slug.location|null>",
|
|
|
57037
57643
|
"openingInstructions": "...", "displayOrder": 0,
|
|
57038
57644
|
"board": { "people": [{ "character": "<path|slug.character>", "side": "ally|enemy|neutral",
|
|
57039
57645
|
"area": "...", "hidden": {"how":"unseen|sensed|disguised","as":"..."}, "note": "..." }],
|
|
57040
|
-
"
|
|
57646
|
+
"purpose": "...", "objectives": ["..."],
|
|
57647
|
+
"trackers": [{ "key": "time", "on": true, "fields": [{ "kind": "text", "text": "Dusk" }] },
|
|
57648
|
+
{ "key": "<tracker key>", "on": true, "fields": [{ "kind": "count", "value": 2 }] }] },
|
|
57041
57649
|
"gmNotes": "..." }
|
|
57042
57650
|
(board and gmNotes are read by GM Harness V2: the opening board and the facts it
|
|
57043
|
-
reads on its first turn instead of looking them up
|
|
57651
|
+
reads on its first turn instead of looking them up; the time it opens at is the
|
|
57652
|
+
Time tracker's, key "time")
|
|
57044
57653
|
|
|
57045
57654
|
set-gm-trigger JSON (triggered GM rules; name \u2264120 / body \u2264500 chars):
|
|
57046
57655
|
{"triggerKind":"narrative","triggerPhases":["post_player_message"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@craftrpgs/cli",
|
|
3
|
-
"version": "0.2.0-alpha.
|
|
3
|
+
"version": "0.2.0-alpha.2",
|
|
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",
|
|
@@ -21,14 +21,14 @@
|
|
|
21
21
|
"scripts": {
|
|
22
22
|
"build": "bun run scripts/build.ts",
|
|
23
23
|
"prepack": "bun run build",
|
|
24
|
-
"typecheck": "
|
|
24
|
+
"typecheck": "bun check"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@craft/flow": "workspace:*",
|
|
28
28
|
"@craft/shared": "workspace:*",
|
|
29
29
|
"fflate": "^0.8.3",
|
|
30
30
|
"typescript": "7.0.2",
|
|
31
|
-
"uuid": "11.1.
|
|
31
|
+
"uuid": "11.1.1",
|
|
32
32
|
"zod": "4.3.6"
|
|
33
33
|
}
|
|
34
34
|
}
|