@scrthq/runlog 0.0.38 → 0.0.40
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/README.md +4 -0
- package/dist/runlog.js +1098 -65
- package/package.json +1 -1
package/dist/runlog.js
CHANGED
|
@@ -7369,8 +7369,8 @@ var require_dist = __commonJS({
|
|
|
7369
7369
|
|
|
7370
7370
|
// packages/cli/src/bin.ts
|
|
7371
7371
|
var import_yaml4 = __toESM(require_dist(), 1);
|
|
7372
|
-
import { readFileSync as
|
|
7373
|
-
import { basename, dirname, resolve as
|
|
7372
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3, existsSync as existsSync3 } from "node:fs";
|
|
7373
|
+
import { basename as basename2, dirname, resolve as resolve4 } from "node:path";
|
|
7374
7374
|
import { createHash } from "node:crypto";
|
|
7375
7375
|
|
|
7376
7376
|
// node_modules/zod/v4/classic/external.js
|
|
@@ -17794,8 +17794,8 @@ var error43 = () => {
|
|
|
17794
17794
|
case "not_multiple_of":
|
|
17795
17795
|
return `N\xFAmero inv\xE1lido: deve ser m\xFAltiplo de ${issue2.divisor}`;
|
|
17796
17796
|
case "unrecognized_keys": {
|
|
17797
|
-
const
|
|
17798
|
-
return `Chave${
|
|
17797
|
+
const plural2 = issue2.keys.length > 1 ? "s" : "";
|
|
17798
|
+
return `Chave${plural2} inv\xE1lida${plural2}: ${joinValues(issue2.keys, ", ")}`;
|
|
17799
17799
|
}
|
|
17800
17800
|
case "invalid_key":
|
|
17801
17801
|
return `Entrada inv\xE1lida n${translateOriginWithArticle(issue2.origin, "definite")}`;
|
|
@@ -17937,8 +17937,8 @@ var error44 = () => {
|
|
|
17937
17937
|
case "not_multiple_of":
|
|
17938
17938
|
return `N\xFAmero inv\xE1lido: deve ser m\xFAltiplo de ${issue2.divisor}`;
|
|
17939
17939
|
case "unrecognized_keys": {
|
|
17940
|
-
const
|
|
17941
|
-
return `Chave${
|
|
17940
|
+
const plural2 = issue2.keys.length > 1 ? "s" : "";
|
|
17941
|
+
return `Chave${plural2} inv\xE1lida${plural2}: ${joinValues(issue2.keys, ", ")}`;
|
|
17942
17942
|
}
|
|
17943
17943
|
case "invalid_key":
|
|
17944
17944
|
return `Entrada inv\xE1lida n${translateOriginWithArticle(issue2.origin, "definite")}`;
|
|
@@ -20135,9 +20135,9 @@ function codePointLengthVar(doc, ctx, accessor, inDoubt) {
|
|
|
20135
20135
|
doc.write(`const ${v} = typeof ${accessor} === "string" && ${inDoubt} ? ${cpLen}(${accessor}) : ${accessor}.length;`);
|
|
20136
20136
|
return v;
|
|
20137
20137
|
}
|
|
20138
|
-
function numericOperand(value,
|
|
20138
|
+
function numericOperand(value, label2) {
|
|
20139
20139
|
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
20140
|
-
throw new ZodCompileUnsupportedError(`${
|
|
20140
|
+
throw new ZodCompileUnsupportedError(`${label2} bound of type ${typeof value}`);
|
|
20141
20141
|
}
|
|
20142
20142
|
return `${value}`;
|
|
20143
20143
|
}
|
|
@@ -27524,13 +27524,13 @@ function lintPack(pack) {
|
|
|
27524
27524
|
}
|
|
27525
27525
|
}
|
|
27526
27526
|
const claimed = new Set(pack.capabilities);
|
|
27527
|
-
const requireCap = (
|
|
27528
|
-
if (used && !claimed.has(
|
|
27527
|
+
const requireCap = (cap2, used, why) => {
|
|
27528
|
+
if (used && !claimed.has(cap2)) {
|
|
27529
27529
|
d.push(
|
|
27530
27530
|
warn(
|
|
27531
27531
|
"capability/undeclared",
|
|
27532
27532
|
"capabilities",
|
|
27533
|
-
`pack ${why} but does not declare the "${
|
|
27533
|
+
`pack ${why} but does not declare the "${cap2}" capability`
|
|
27534
27534
|
)
|
|
27535
27535
|
);
|
|
27536
27536
|
}
|
|
@@ -27638,12 +27638,12 @@ function parsePack(input2) {
|
|
|
27638
27638
|
const unsupported = pack.capabilities.filter(
|
|
27639
27639
|
(c) => !IMPLEMENTED_CAPABILITIES.includes(c)
|
|
27640
27640
|
);
|
|
27641
|
-
for (const
|
|
27641
|
+
for (const cap2 of unsupported) {
|
|
27642
27642
|
diagnostics.push({
|
|
27643
27643
|
level: "error",
|
|
27644
27644
|
code: "pack/unsupported-capability",
|
|
27645
27645
|
path: "capabilities",
|
|
27646
|
-
message: `pack requires the "${
|
|
27646
|
+
message: `pack requires the "${cap2}" capability, which this build does not implement`
|
|
27647
27647
|
});
|
|
27648
27648
|
}
|
|
27649
27649
|
if (hasErrors(diagnostics)) return { ok: false, pack: null, diagnostics };
|
|
@@ -27885,6 +27885,830 @@ async function verifyPack(document) {
|
|
|
27885
27885
|
}
|
|
27886
27886
|
}
|
|
27887
27887
|
|
|
27888
|
+
// packages/rules-schema/src/describe.ts
|
|
27889
|
+
function entryKeys(table) {
|
|
27890
|
+
switch (table.resolution) {
|
|
27891
|
+
case "lookup":
|
|
27892
|
+
return table.entries.map((e) => e.range[0] === e.range[1] ? `${e.range[0]}` : `${e.range[0]}\u2013${e.range[1]}`);
|
|
27893
|
+
case "bands":
|
|
27894
|
+
return table.entries.map(
|
|
27895
|
+
(e) => [e.gte !== void 0 ? `\u2265${e.gte}` : "", e.lte !== void 0 ? `\u2264${e.lte}` : ""].filter(Boolean).join(" ")
|
|
27896
|
+
);
|
|
27897
|
+
case "opposed":
|
|
27898
|
+
return table.entries.map((e) => `beats ${e.beats}`);
|
|
27899
|
+
case "keyed":
|
|
27900
|
+
return table.entries.map((e) => e.key);
|
|
27901
|
+
}
|
|
27902
|
+
}
|
|
27903
|
+
function howConsulted(table) {
|
|
27904
|
+
switch (table.resolution) {
|
|
27905
|
+
case "lookup":
|
|
27906
|
+
return `Roll ${table.roll}.`;
|
|
27907
|
+
case "bands":
|
|
27908
|
+
return `Roll ${table.roll} and find the band it lands in.`;
|
|
27909
|
+
case "opposed":
|
|
27910
|
+
return `Roll ${table.action}${table.addResource ? ` and add ${table.addResource}` : ""} against ${table.challenge.count} \xD7 ${table.challenge.dice}; count how many of the challenge dice your total beats.`;
|
|
27911
|
+
case "keyed":
|
|
27912
|
+
return "Find the row whose key matches what was drawn.";
|
|
27913
|
+
}
|
|
27914
|
+
}
|
|
27915
|
+
function nouns(pack) {
|
|
27916
|
+
const v = pack.vocabulary;
|
|
27917
|
+
return {
|
|
27918
|
+
run: v.run.one,
|
|
27919
|
+
runs: v.run.many,
|
|
27920
|
+
unit: v.unit.one,
|
|
27921
|
+
units: v.unit.many,
|
|
27922
|
+
subject: v.subject.one,
|
|
27923
|
+
subjects: v.subject.many,
|
|
27924
|
+
finalize: v.finalize
|
|
27925
|
+
};
|
|
27926
|
+
}
|
|
27927
|
+
var label = (pack, kind, id) => {
|
|
27928
|
+
const entry = pack[kind]?.[id];
|
|
27929
|
+
return entry?.label ?? entry?.title ?? id;
|
|
27930
|
+
};
|
|
27931
|
+
function targetInWords(pack, to) {
|
|
27932
|
+
const n = nouns(pack);
|
|
27933
|
+
if (to === void 0) return `this ${n.subject}`;
|
|
27934
|
+
if (typeof to === "object") return `the ${n.subject} chosen as \u201C${to.var}\u201D`;
|
|
27935
|
+
switch (to) {
|
|
27936
|
+
case "thisSubject":
|
|
27937
|
+
return `this ${n.subject}`;
|
|
27938
|
+
case "targetSubject":
|
|
27939
|
+
return `the target`;
|
|
27940
|
+
case "allSubjects":
|
|
27941
|
+
return `every ${n.subject}`;
|
|
27942
|
+
case "allPriorSubjects":
|
|
27943
|
+
return `every earlier ${n.subject}`;
|
|
27944
|
+
case "run":
|
|
27945
|
+
return `the ${n.run}`;
|
|
27946
|
+
}
|
|
27947
|
+
}
|
|
27948
|
+
function boundInWords(pack, b) {
|
|
27949
|
+
const parts = [];
|
|
27950
|
+
if (b.eq !== void 0) parts.push(`exactly ${b.eq}`);
|
|
27951
|
+
if (b.gte !== void 0 && b.lte !== void 0) parts.push(`between ${b.gte} and ${b.lte}`);
|
|
27952
|
+
else {
|
|
27953
|
+
if (b.gte !== void 0) parts.push(`${b.gte} or more`);
|
|
27954
|
+
if (b.lte !== void 0) parts.push(`${b.lte} or fewer`);
|
|
27955
|
+
}
|
|
27956
|
+
if (b.gteCounter !== void 0) parts.push(`at least the ${label(pack, "counters", b.gteCounter)} tally`);
|
|
27957
|
+
if (b.lteCounter !== void 0) parts.push(`no more than the ${label(pack, "counters", b.lteCounter)} tally`);
|
|
27958
|
+
return parts.join(" and ");
|
|
27959
|
+
}
|
|
27960
|
+
function predicateInWords(pack, p) {
|
|
27961
|
+
const n = nouns(pack);
|
|
27962
|
+
if ("ask" in p) return `you answer yes to \u201C${p.ask}\u201D`;
|
|
27963
|
+
if ("unitIndex" in p) return p.unitIndex.eq !== void 0 ? `this is ${n.unit} ${p.unitIndex.eq}` : `the ${n.unit} number is ${boundInWords(pack, p.unitIndex)}`;
|
|
27964
|
+
if ("subjectCount" in p) return `there are ${boundInWords(pack, p.subjectCount)} ${n.subjects}`;
|
|
27965
|
+
if ("eligibleTargets" in p) return `there are ${boundInWords(pack, p.eligibleTargets)} ${n.subjects} that can be targeted`;
|
|
27966
|
+
if ("counter" in p) return `${label(pack, "counters", p.counter)} is ${boundInWords(pack, p.is)}`;
|
|
27967
|
+
if ("resource" in p) return `${label(pack, "resources", p.resource)} is ${boundInWords(pack, p.is)}`;
|
|
27968
|
+
if ("flag" in p) return `${p.flag} is ${p.is === false ? "not " : ""}set`;
|
|
27969
|
+
if ("subjectHasState" in p) return `${targetInWords(pack, p.of)} is ${label(pack, "states", p.subjectHasState)}`;
|
|
27970
|
+
if ("priorSubjectTagged" in p) return `an earlier ${n.subject} was tagged \u201C${p.priorSubjectTagged}\u201D`;
|
|
27971
|
+
if ("modeIs" in p) return `playing ${p.modeIs.map((m) => label(pack, "modes", m)).join(" or ")}`;
|
|
27972
|
+
if ("not" in p) return `not (${predicateInWords(pack, p.not)})`;
|
|
27973
|
+
if ("allOf" in p) return p.allOf.map((q) => predicateInWords(pack, q)).join(" and ");
|
|
27974
|
+
if ("anyOf" in p) return p.anyOf.map((q) => predicateInWords(pack, q)).join(" or ");
|
|
27975
|
+
return "";
|
|
27976
|
+
}
|
|
27977
|
+
function conditionsInWords(pack, preds, joiner = "all") {
|
|
27978
|
+
if (!preds || preds.length === 0) return "";
|
|
27979
|
+
return preds.map((p) => predicateInWords(pack, p)).join(joiner === "all" ? " and " : ", or ");
|
|
27980
|
+
}
|
|
27981
|
+
var signed = (by, set2) => set2 !== void 0 ? `set to ${set2}` : by === void 0 ? "+1" : by >= 0 ? `+${by}` : `\u2212${Math.abs(by)}`;
|
|
27982
|
+
function actionInWords(pack, a) {
|
|
27983
|
+
const n = nouns(pack);
|
|
27984
|
+
switch (a.do) {
|
|
27985
|
+
case "roll":
|
|
27986
|
+
return a.label ? `${a.label} (${a.dice})` : `roll ${a.dice}`;
|
|
27987
|
+
case "rollOn": {
|
|
27988
|
+
const t = label(pack, "tables", a.table);
|
|
27989
|
+
if (a.times && a.times > 1) return `roll ${a.times} times on ${t}${a.choose === "one" ? " and keep one" : ""}`;
|
|
27990
|
+
return `roll on ${t}`;
|
|
27991
|
+
}
|
|
27992
|
+
case "branch": {
|
|
27993
|
+
const cases = a.cases.map((c) => {
|
|
27994
|
+
const cond = c.in ? `on ${c.in.join(", ")}` : c.is ? boundInWords(pack, c.is) : "otherwise";
|
|
27995
|
+
return `${cond} \u2192 ${actionsInWords(pack, c.then)}`;
|
|
27996
|
+
});
|
|
27997
|
+
if (a.else) cases.push(`otherwise \u2192 ${actionsInWords(pack, a.else)}`);
|
|
27998
|
+
return `then, by the result: ${cases.join("; ")}`;
|
|
27999
|
+
}
|
|
28000
|
+
case "prompt":
|
|
28001
|
+
return a.label;
|
|
28002
|
+
case "resolveTarget":
|
|
28003
|
+
return `pick the target${a.from === "choice" ? " yourself" : a.from === "event" ? " by the pack's targeting rule" : " from the roll"}`;
|
|
28004
|
+
case "applyState":
|
|
28005
|
+
return `mark ${targetInWords(pack, a.to)} ${label(pack, "states", a.state)}`;
|
|
28006
|
+
case "removeState":
|
|
28007
|
+
return `clear ${label(pack, "states", a.state)} from ${targetInWords(pack, a.to)}`;
|
|
28008
|
+
case "removeSubject":
|
|
28009
|
+
return `remove ${targetInWords(pack, a.to)} from play`;
|
|
28010
|
+
case "ban":
|
|
28011
|
+
return a.label ?? (a.subjectType ? `no more ${a.subjectType} ${n.subjects} this ${n.run}` : `no more ${n.subjects} of ${targetInWords(pack, a.from)}'s kind this ${n.run}`);
|
|
28012
|
+
case "forceUnit": {
|
|
28013
|
+
const c = a.count ?? 1;
|
|
28014
|
+
return `add ${c} more ${c === 1 ? n.unit : n.units} before the ${n.run} may end`;
|
|
28015
|
+
}
|
|
28016
|
+
case "modCounter":
|
|
28017
|
+
return `${label(pack, "counters", a.counter)} ${signed(a.by, a.set)}`;
|
|
28018
|
+
case "modResource":
|
|
28019
|
+
return `${label(pack, "resources", a.resource)} ${signed(a.by, a.set)}`;
|
|
28020
|
+
case "grantCard": {
|
|
28021
|
+
const c = a.count ?? 1;
|
|
28022
|
+
return `draw ${c} from ${label(pack, "decks", a.deck)}`;
|
|
28023
|
+
}
|
|
28024
|
+
case "discardCard": {
|
|
28025
|
+
const c = a.count ?? 1;
|
|
28026
|
+
return `discard ${c} from ${label(pack, "decks", a.deck)}`;
|
|
28027
|
+
}
|
|
28028
|
+
case "startTimer":
|
|
28029
|
+
return a.label ? `${a.label} (${a.minutes} min)` : `start a ${a.minutes}-minute timer`;
|
|
28030
|
+
case "note":
|
|
28031
|
+
return a.text;
|
|
28032
|
+
case "setFlag":
|
|
28033
|
+
return a.value === false ? `unset ${a.flag}` : `set ${a.flag}`;
|
|
28034
|
+
case "endRunAttempt":
|
|
28035
|
+
return `try to end the ${n.run}`;
|
|
28036
|
+
case "when": {
|
|
28037
|
+
const then = `if ${conditionsInWords(pack, a.all)}: ${actionsInWords(pack, a.then)}`;
|
|
28038
|
+
return a.else ? `${then}; otherwise ${actionsInWords(pack, a.else)}` : then;
|
|
28039
|
+
}
|
|
28040
|
+
}
|
|
28041
|
+
}
|
|
28042
|
+
function actionsInWords(pack, actions) {
|
|
28043
|
+
return actions.map((a) => unstop(actionInWords(pack, a))).join("; ");
|
|
28044
|
+
}
|
|
28045
|
+
function unstop(s) {
|
|
28046
|
+
return s.replace(/\.\s*$/, "");
|
|
28047
|
+
}
|
|
28048
|
+
function sentence(s) {
|
|
28049
|
+
const t = s.trim();
|
|
28050
|
+
return /[.!?…]$/.test(t) ? t : `${t}.`;
|
|
28051
|
+
}
|
|
28052
|
+
function triggerPointInWords(pack, on) {
|
|
28053
|
+
const n = nouns(pack);
|
|
28054
|
+
switch (on) {
|
|
28055
|
+
case "immediately":
|
|
28056
|
+
return "Then";
|
|
28057
|
+
case "onEnterUnit":
|
|
28058
|
+
return `When a ${n.unit} begins`;
|
|
28059
|
+
case "onDeclareSubject":
|
|
28060
|
+
return `When the ${n.subject} is declared`;
|
|
28061
|
+
case "afterWork":
|
|
28062
|
+
return "After the work";
|
|
28063
|
+
case "onFinalize":
|
|
28064
|
+
return `When you ${n.finalize}`;
|
|
28065
|
+
case "onDeclareRunOver":
|
|
28066
|
+
return `When you declare the ${n.run} over`;
|
|
28067
|
+
case "onRunEnd":
|
|
28068
|
+
return `When the ${n.run} ends`;
|
|
28069
|
+
}
|
|
28070
|
+
}
|
|
28071
|
+
function triggerInWords(pack, t) {
|
|
28072
|
+
const when = conditionsInWords(pack, t.when);
|
|
28073
|
+
const head = t.label ? `${t.label} \u2014 ${triggerPointInWords(pack, t.on).toLowerCase()}` : triggerPointInWords(pack, t.on);
|
|
28074
|
+
return `${head}${when ? ` (if ${when})` : ""}: ${actionsInWords(pack, t.do)}`;
|
|
28075
|
+
}
|
|
28076
|
+
function modeLength(pack, mode) {
|
|
28077
|
+
const n = nouns(pack);
|
|
28078
|
+
const u = mode.units;
|
|
28079
|
+
if (u?.fixed !== void 0) return `${u.fixed} ${u.fixed === 1 ? n.unit : n.units}`;
|
|
28080
|
+
if (u?.roll) return `roll ${u.roll} for the number of ${n.units}`;
|
|
28081
|
+
if (u?.min !== void 0 && u?.max !== void 0) return `${u.min}\u2013${u.max} ${n.units}`;
|
|
28082
|
+
if (u?.max !== void 0) return `up to ${u.max} ${n.units}`;
|
|
28083
|
+
if (u?.min !== void 0) return `at least ${u.min} ${n.units}`;
|
|
28084
|
+
const max = pack.unit.max;
|
|
28085
|
+
return `as many ${n.units} as you like${max ? `, up to ${max}` : ""}`;
|
|
28086
|
+
}
|
|
28087
|
+
function modePlayers(mode) {
|
|
28088
|
+
const p = mode.players;
|
|
28089
|
+
if (!p || p.min <= 1 && p.max <= 1) return "solo";
|
|
28090
|
+
return p.min === p.max ? `${p.min} players` : `${p.min}\u2013${p.max} players`;
|
|
28091
|
+
}
|
|
28092
|
+
var diceOf = (expr) => {
|
|
28093
|
+
const m = /^(\d*)d(\d+)/.exec(expr);
|
|
28094
|
+
return m ? `d${m[2]}` : expr;
|
|
28095
|
+
};
|
|
28096
|
+
function diceNeeded(pack) {
|
|
28097
|
+
const found = /* @__PURE__ */ new Set();
|
|
28098
|
+
const seeActions = (actions) => {
|
|
28099
|
+
for (const a of actions ?? []) {
|
|
28100
|
+
if (a.do === "roll") found.add(diceOf(a.dice));
|
|
28101
|
+
if (a.do === "branch") {
|
|
28102
|
+
for (const c of a.cases) seeActions(c.then);
|
|
28103
|
+
seeActions(a.else);
|
|
28104
|
+
}
|
|
28105
|
+
if (a.do === "when") {
|
|
28106
|
+
seeActions(a.then);
|
|
28107
|
+
seeActions(a.else);
|
|
28108
|
+
}
|
|
28109
|
+
}
|
|
28110
|
+
};
|
|
28111
|
+
const seeTriggers = (triggers) => {
|
|
28112
|
+
for (const t of triggers ?? []) seeActions(t.do);
|
|
28113
|
+
};
|
|
28114
|
+
for (const table of Object.values(pack.tables)) {
|
|
28115
|
+
if (table.resolution === "lookup" || table.resolution === "bands") found.add(diceOf(table.roll));
|
|
28116
|
+
if (table.resolution === "opposed") {
|
|
28117
|
+
found.add(diceOf(table.action));
|
|
28118
|
+
found.add(diceOf(table.challenge.dice));
|
|
28119
|
+
}
|
|
28120
|
+
for (const e of table.entries) seeTriggers(e.triggers);
|
|
28121
|
+
}
|
|
28122
|
+
for (const deck of Object.values(pack.decks ?? {})) if (deck.kind === "cards") for (const c of deck.cards) seeTriggers(c.triggers);
|
|
28123
|
+
for (const c of Object.values(pack.counters ?? {})) for (const t of c.triggers ?? []) seeActions(t.do);
|
|
28124
|
+
for (const m of Object.values(pack.moves ?? {})) seeActions(m.do);
|
|
28125
|
+
for (const m of Object.values(pack.modes)) {
|
|
28126
|
+
if (m.units?.roll) found.add(diceOf(m.units.roll));
|
|
28127
|
+
for (const p of m.perUnit ?? []) seeActions(p.extra);
|
|
28128
|
+
}
|
|
28129
|
+
for (const p of pack.phases) for (const s of p.steps) if (s.kind === "actions") seeActions(s.do);
|
|
28130
|
+
seeTriggers(pack.triggers);
|
|
28131
|
+
if (pack.targeting?.strategy === "anchoredOffset" && pack.targeting.eventFallback) found.add(diceOf(pack.targeting.eventFallback.roll));
|
|
28132
|
+
return [...found].sort((a, b) => Number(a.slice(1)) - Number(b.slice(1)));
|
|
28133
|
+
}
|
|
28134
|
+
function diceInWords(dice) {
|
|
28135
|
+
const names = dice.map((d) => d === "d100" ? "percentile dice (d100)" : `a ${d}`);
|
|
28136
|
+
if (names.length === 0) return "";
|
|
28137
|
+
if (names.length === 1) return names[0];
|
|
28138
|
+
return `${names.slice(0, -1).join(", ")} and ${names[names.length - 1]}`;
|
|
28139
|
+
}
|
|
28140
|
+
|
|
28141
|
+
// packages/rules-schema/src/docs.ts
|
|
28142
|
+
var DOC_KINDS = [
|
|
28143
|
+
{ kind: "summary", label: "Summary", what: "What a catalog shows: the shape of the game without its rules.", full: false },
|
|
28144
|
+
{ kind: "rulebook", label: "Rulebook", what: "Everything, in reading order: setup, flow, every table, every rule.", full: true },
|
|
28145
|
+
{ kind: "quickstart", label: "Quick start", what: "Enough to play the first time; the rest is on the reference card.", full: true },
|
|
28146
|
+
{ kind: "reference", label: "Reference card", what: "Every table and the things people forget, compact enough to keep beside you.", full: true },
|
|
28147
|
+
{ kind: "runlog", label: "Run log sheet", what: "A printable sheet to write a run on by hand.", full: true }
|
|
28148
|
+
];
|
|
28149
|
+
var Builder = class {
|
|
28150
|
+
blocks = [];
|
|
28151
|
+
h(level, text) {
|
|
28152
|
+
this.blocks.push({ kind: "heading", level, text });
|
|
28153
|
+
return this;
|
|
28154
|
+
}
|
|
28155
|
+
p(text, tone) {
|
|
28156
|
+
if (text && text.trim()) this.blocks.push(tone ? { kind: "paragraph", text: text.trim(), tone } : { kind: "paragraph", text: text.trim() });
|
|
28157
|
+
return this;
|
|
28158
|
+
}
|
|
28159
|
+
list(items, ordered = false) {
|
|
28160
|
+
if (items.length > 0) this.blocks.push(ordered ? { kind: "list", items, ordered } : { kind: "list", items });
|
|
28161
|
+
return this;
|
|
28162
|
+
}
|
|
28163
|
+
table(columns, rows, compact = false) {
|
|
28164
|
+
if (rows.length > 0) this.blocks.push(compact ? { kind: "table", columns, rows, compact } : { kind: "table", columns, rows });
|
|
28165
|
+
return this;
|
|
28166
|
+
}
|
|
28167
|
+
terms(items) {
|
|
28168
|
+
if (items.length > 0) this.blocks.push({ kind: "terms", items });
|
|
28169
|
+
return this;
|
|
28170
|
+
}
|
|
28171
|
+
form(fields) {
|
|
28172
|
+
this.blocks.push({ kind: "form", fields });
|
|
28173
|
+
return this;
|
|
28174
|
+
}
|
|
28175
|
+
rule() {
|
|
28176
|
+
this.blocks.push({ kind: "rule" });
|
|
28177
|
+
return this;
|
|
28178
|
+
}
|
|
28179
|
+
page() {
|
|
28180
|
+
this.blocks.push({ kind: "pagebreak" });
|
|
28181
|
+
return this;
|
|
28182
|
+
}
|
|
28183
|
+
};
|
|
28184
|
+
var plural = (n, one, many) => `${n} ${n === 1 ? one : many}`;
|
|
28185
|
+
var cap = (s) => s ? s[0].toUpperCase() + s.slice(1) : s;
|
|
28186
|
+
function stepInWords(pack, step) {
|
|
28187
|
+
const n = nouns(pack);
|
|
28188
|
+
switch (step.kind) {
|
|
28189
|
+
case "rollTable":
|
|
28190
|
+
return `${step.label ?? `Roll on ${pack.tables[step.table]?.title ?? step.table}`}${step.optional ? " (optional)" : ""}`;
|
|
28191
|
+
case "declareSubject":
|
|
28192
|
+
return step.label ?? `Declare the ${n.subject}${step.constrainedBy ? `, within what ${pack.tables[step.constrainedBy]?.title ?? step.constrainedBy} allowed` : ""}`;
|
|
28193
|
+
case "manual":
|
|
28194
|
+
return step.label;
|
|
28195
|
+
case "actions":
|
|
28196
|
+
return sentence(cap(actionsInWords(pack, step.do)));
|
|
28197
|
+
case "finalizeUnit":
|
|
28198
|
+
return step.label ?? `${n.finalize} the ${n.unit}`;
|
|
28199
|
+
}
|
|
28200
|
+
}
|
|
28201
|
+
var checklistText = (item) => typeof item === "string" ? item : item.text;
|
|
28202
|
+
function stepDetail(pack, step) {
|
|
28203
|
+
const out = [];
|
|
28204
|
+
if (step.kind === "manual") {
|
|
28205
|
+
if (step.description) out.push(step.description);
|
|
28206
|
+
for (const c of step.checklist ?? []) out.push(`\u2610 ${checklistText(c)}`);
|
|
28207
|
+
}
|
|
28208
|
+
if (step.kind === "finalizeUnit") for (const c of step.confirm ?? []) out.push(`\u2610 ${checklistText(c)}`);
|
|
28209
|
+
if ("skipWhen" in step && step.skipWhen?.length) out.push(`Skipped when ${conditionsInWords(pack, step.skipWhen, "any")}.`);
|
|
28210
|
+
return out;
|
|
28211
|
+
}
|
|
28212
|
+
function whatYouNeed(pack) {
|
|
28213
|
+
const n = nouns(pack);
|
|
28214
|
+
const items = [];
|
|
28215
|
+
const dice = diceInWords(diceNeeded(pack));
|
|
28216
|
+
if (dice) items.push(cap(dice) + ".");
|
|
28217
|
+
for (const deck of Object.values(pack.decks ?? {})) {
|
|
28218
|
+
if (deck.kind === "standard52") items.push(`A standard deck of playing cards${deck.includeJokers ? " with the jokers in" : ""}.`);
|
|
28219
|
+
else items.push(`${plural(deck.cards.length, "card", "cards")} for ${deck.title} \u2014 write them out, or use the app.`);
|
|
28220
|
+
}
|
|
28221
|
+
if (pack.capabilities.includes("timers")) items.push("A timer.");
|
|
28222
|
+
items.push(pack.journal?.enabled === false ? `Somewhere to keep the ${n.run} log.` : `Somewhere to keep the ${n.run} log \u2014 the run log sheet, or the app.`);
|
|
28223
|
+
return items;
|
|
28224
|
+
}
|
|
28225
|
+
function flowSteps(pack, opts) {
|
|
28226
|
+
const items = [];
|
|
28227
|
+
for (const phase of pack.phases) {
|
|
28228
|
+
const head = phase.steps.length === 1 ? `${phase.label}: ${stepInWords(pack, phase.steps[0])}` : phase.label;
|
|
28229
|
+
const notes = [];
|
|
28230
|
+
if (opts.detail && phase.description) notes.push(phase.description);
|
|
28231
|
+
if (opts.detail && phase.skipWhen?.length) notes.push(`Skipped when ${conditionsInWords(pack, phase.skipWhen, "any")}.`);
|
|
28232
|
+
if (phase.steps.length > 1) for (const s of phase.steps) notes.push(`${stepInWords(pack, s)}${opts.detail ? stepDetail(pack, s).map((d) => ` \u2014 ${d}`).join("") : ""}`);
|
|
28233
|
+
else if (opts.detail) notes.push(...stepDetail(pack, phase.steps[0]));
|
|
28234
|
+
items.push([head, ...notes.map((t) => ` ${t}`)].join("\n"));
|
|
28235
|
+
}
|
|
28236
|
+
return items;
|
|
28237
|
+
}
|
|
28238
|
+
function modeRows(pack, opts) {
|
|
28239
|
+
return Object.entries(pack.modes).map(([id, m]) => [
|
|
28240
|
+
`${m.label}${id === pack.defaultMode ? " (default)" : ""}`,
|
|
28241
|
+
modeLength(pack, m),
|
|
28242
|
+
modePlayers(m) + (m.seeded ? ", seeded" : ""),
|
|
28243
|
+
[m.description ?? "", ...opts.notes ? modeExtras(pack, m) : []].filter(Boolean).join(" ")
|
|
28244
|
+
]);
|
|
28245
|
+
}
|
|
28246
|
+
function modeExtras(pack, m) {
|
|
28247
|
+
const n = nouns(pack);
|
|
28248
|
+
const out = [];
|
|
28249
|
+
for (const note of m.notes ?? []) out.push(note);
|
|
28250
|
+
const off = [
|
|
28251
|
+
...(m.disable?.tables ?? []).map((t) => pack.tables[t]?.title ?? t),
|
|
28252
|
+
...(m.disable?.decks ?? []).map((d) => pack.decks?.[d]?.title ?? d),
|
|
28253
|
+
...(m.disable?.counters ?? []).map((c) => pack.counters?.[c]?.label ?? c),
|
|
28254
|
+
...(m.disable?.phases ?? []).map((p) => pack.phases.find((x) => x.id === p)?.label ?? p)
|
|
28255
|
+
];
|
|
28256
|
+
if (off.length) out.push(`Without ${off.join(", ")}.`);
|
|
28257
|
+
for (const p of m.perUnit ?? []) {
|
|
28258
|
+
const which = p.unit === "all" ? `Every ${n.unit}` : `${cap(n.unit)} ${p.unit}`;
|
|
28259
|
+
const skip = (p.skipPhases ?? []).map((id) => pack.phases.find((x) => x.id === id)?.label ?? id);
|
|
28260
|
+
const parts = [skip.length ? `skips ${skip.join(", ")}` : "", p.extra?.length ? actionsInWords(pack, p.extra) : ""].filter(Boolean);
|
|
28261
|
+
if (parts.length) out.push(sentence(`${which}: ${parts.join("; ")}`));
|
|
28262
|
+
}
|
|
28263
|
+
for (const r of m.players?.roles ?? []) out.push(`${r.label}: ${r.description ?? ""}`.trim());
|
|
28264
|
+
return out;
|
|
28265
|
+
}
|
|
28266
|
+
function tableRows(pack, table, opts) {
|
|
28267
|
+
const keys = entryKeys(table);
|
|
28268
|
+
return table.entries.map((e, i) => {
|
|
28269
|
+
const extras = [];
|
|
28270
|
+
if (opts.full) {
|
|
28271
|
+
for (const g of e.grants ?? []) extras.push(`\u2192 ${pack.states?.[g]?.label ?? g}`);
|
|
28272
|
+
for (const t of e.triggers ?? []) extras.push(sentence(triggerInWords(pack, t)));
|
|
28273
|
+
if (e.requires?.length) extras.push(`Only if ${conditionsInWords(pack, e.requires)}; otherwise roll again.`);
|
|
28274
|
+
}
|
|
28275
|
+
const text = [e.title ? `${e.title}. ` : "", sentence(e.text), ...extras.map((x) => ` ${x}`)].join("");
|
|
28276
|
+
return [keys[i] ?? "", text];
|
|
28277
|
+
});
|
|
28278
|
+
}
|
|
28279
|
+
function license(pack) {
|
|
28280
|
+
const l = pack.license;
|
|
28281
|
+
const who = l.holder ? ` \xA9 ${l.holder}` : "";
|
|
28282
|
+
return `${l.id}${who}${l.redistributable ? "" : " \u2014 private; not for redistribution"}${l.notice ? `. ${l.notice}` : ""}${l.url ? ` (${l.url})` : ""}`;
|
|
28283
|
+
}
|
|
28284
|
+
function byline(pack) {
|
|
28285
|
+
return [pack.author ? `by ${pack.author}` : "", `version ${pack.version}`].filter(Boolean).join(" \xB7 ");
|
|
28286
|
+
}
|
|
28287
|
+
function summaryDoc(pack) {
|
|
28288
|
+
const n = nouns(pack);
|
|
28289
|
+
const b = new Builder();
|
|
28290
|
+
b.p(pack.description);
|
|
28291
|
+
b.p(license(pack), "muted");
|
|
28292
|
+
b.h(2, "How it plays");
|
|
28293
|
+
const unitRange = `${pack.unit.min === pack.unit.max ? pack.unit.min : `${pack.unit.min}\u2013${pack.unit.max}`}`;
|
|
28294
|
+
b.p(
|
|
28295
|
+
`A ${n.run} is a series of ${n.units}${pack.unit.createsSubject ? `, each producing a ${n.subject}` : ""}; ${unitRange} of them, depending on the mode. Each ${n.unit} moves through ${plural(pack.phases.length, "phase", "phases")}: ${pack.phases.map((p) => p.label).join(", ")}.`
|
|
28296
|
+
);
|
|
28297
|
+
b.table(["Mode", "Length", "Players", "About"], modeRows(pack, { notes: false }));
|
|
28298
|
+
b.h(2, "What is inside");
|
|
28299
|
+
const tables = Object.values(pack.tables);
|
|
28300
|
+
const decks = Object.values(pack.decks ?? {});
|
|
28301
|
+
const parts = [];
|
|
28302
|
+
if (tables.length) parts.push(`${plural(tables.length, "table", "tables")}: ${tables.map((t) => `${t.title} (${howConsulted(t).replace(/\.$/, "").toLowerCase()}, ${plural(t.entries.length, "entry", "entries")})`).join("; ")}.`);
|
|
28303
|
+
if (decks.length) parts.push(`${plural(decks.length, "deck", "decks")}: ${decks.map((d) => d.kind === "cards" ? `${d.title} (${plural(d.cards.length, "card", "cards")})` : `${d.title} (a standard deck)`).join("; ")}.`);
|
|
28304
|
+
const states = Object.values(pack.states ?? {});
|
|
28305
|
+
if (states.length) parts.push(`${plural(states.length, "state", "states")} a ${n.subject} or ${n.run} can carry: ${states.map((s) => s.label).join(", ")}.`);
|
|
28306
|
+
const counters = Object.values(pack.counters ?? {}).filter((c) => !c.hidden);
|
|
28307
|
+
if (counters.length) parts.push(`${plural(counters.length, "tally", "tallies")}: ${counters.map((c) => c.label).join(", ")}.`);
|
|
28308
|
+
const resources = Object.values(pack.resources ?? {});
|
|
28309
|
+
if (resources.length) parts.push(`${plural(resources.length, "track", "tracks")}: ${resources.map((r) => r.label).join(", ")}.`);
|
|
28310
|
+
const moves = Object.values(pack.moves ?? {});
|
|
28311
|
+
if (moves.length) parts.push(`${plural(moves.length, "move", "moves")} you may make: ${moves.map((m) => m.label).join(", ")}.`);
|
|
28312
|
+
if (pack.endings?.length) parts.push(`${plural(pack.endings.length, "ending", "endings")}: ${pack.endings.map((e) => e.label).join(", ")}.`);
|
|
28313
|
+
if (pack.targeting && pack.targeting.strategy !== "none") parts.push(`Consequences can reach back to earlier ${n.subjects}.`);
|
|
28314
|
+
if (pack.journal?.enabled !== false) parts.push(`A journal line per ${n.unit}${pack.journal?.required ? ", required" : ""}.`);
|
|
28315
|
+
b.list(parts);
|
|
28316
|
+
b.h(2, "What you need");
|
|
28317
|
+
b.list(whatYouNeed(pack));
|
|
28318
|
+
if (pack.homepage) b.p(`More at ${pack.homepage}`, "muted");
|
|
28319
|
+
return { kind: "summary", layout: "book", title: pack.title, subtitle: byline(pack), blocks: b.blocks };
|
|
28320
|
+
}
|
|
28321
|
+
function rulebookDoc(pack) {
|
|
28322
|
+
const n = nouns(pack);
|
|
28323
|
+
const b = new Builder();
|
|
28324
|
+
b.p(pack.description);
|
|
28325
|
+
b.p(license(pack), "muted");
|
|
28326
|
+
b.h(2, "What you need");
|
|
28327
|
+
b.list(whatYouNeed(pack));
|
|
28328
|
+
b.h(2, "Terms");
|
|
28329
|
+
b.terms([
|
|
28330
|
+
{ term: cap(n.run), text: `One session of play. A ${n.run} is a series of ${n.units}, and ends when you say so or when the game does.` },
|
|
28331
|
+
{ term: cap(n.unit), text: `One round. Every ${n.unit} passes through the same phases${pack.unit.createsSubject ? ` and produces one ${n.subject}` : ""}.` },
|
|
28332
|
+
...pack.unit.createsSubject ? [{ term: cap(n.subject), text: `The thing a ${n.unit} makes. States attach to it, and consequences can land on it later.` }] : [],
|
|
28333
|
+
{ term: cap(n.finalize), text: `Closing a ${n.unit}. Once ${n.finalize.toLowerCase()}d, what it made is fixed unless a rule says otherwise.` },
|
|
28334
|
+
...Object.entries(pack.vocabulary.terms ?? {}).map(([term, text]) => ({ term, text }))
|
|
28335
|
+
]);
|
|
28336
|
+
if (pack.hierarchy?.length) b.p(`When rules contradict each other, the more specific wins, in this order: ${pack.hierarchy.join(" > ")}.`, "note");
|
|
28337
|
+
b.h(2, `A ${n.unit}, step by step`);
|
|
28338
|
+
b.list(flowSteps(pack, { detail: true }), true);
|
|
28339
|
+
if (pack.journal?.enabled !== false) b.p(`${pack.journal?.prompt ? `After each ${n.unit}: \u201C${pack.journal.prompt}\u201D` : `Keep a line of notes per ${n.unit}.`}${pack.journal?.required ? " A note is required before moving on." : ""}`);
|
|
28340
|
+
b.h(2, "Modes");
|
|
28341
|
+
b.table(["Mode", "Length", "Players", "About"], modeRows(pack, { notes: true }));
|
|
28342
|
+
b.h(2, "Tables");
|
|
28343
|
+
for (const [id, table] of Object.entries(pack.tables)) {
|
|
28344
|
+
b.h(3, table.title);
|
|
28345
|
+
b.p([howConsulted(table), table.description ?? ""].filter(Boolean).join(" "));
|
|
28346
|
+
b.table([table.resolution === "keyed" ? "Key" : "Roll", "Result"], tableRows(pack, table, { full: true }));
|
|
28347
|
+
void id;
|
|
28348
|
+
}
|
|
28349
|
+
const decks = Object.entries(pack.decks ?? {});
|
|
28350
|
+
if (decks.length) {
|
|
28351
|
+
b.h(2, "Cards");
|
|
28352
|
+
for (const [, deck] of decks) {
|
|
28353
|
+
b.h(3, deck.title);
|
|
28354
|
+
if (deck.kind === "standard52") {
|
|
28355
|
+
b.p([deck.description, `A standard deck${deck.includeJokers ? " with jokers" : ""}; ${deck.drawAtStart ? `draw ${deck.drawAtStart} at the start. ` : ""}Each draw is read by ${deck.resolveBy}${deck.resolveOn ? ` on ${pack.tables[deck.resolveOn]?.title ?? deck.resolveOn}` : ""}.`].filter(Boolean).join(" "));
|
|
28356
|
+
} else {
|
|
28357
|
+
b.p([deck.description, deck.drawAtStart ? `Draw ${deck.drawAtStart} at the start.` : "", deck.unique ? "" : "Cards may repeat.", deck.carriesOver ? `Unused cards carry over to the next ${n.run}.` : ""].filter(Boolean).join(" "));
|
|
28358
|
+
b.table(
|
|
28359
|
+
["Card", "Effect"],
|
|
28360
|
+
deck.cards.map((c) => [c.title, [c.text, ...(c.triggers ?? []).map((t) => triggerInWords(pack, t)), c.requires?.length ? `Only if ${conditionsInWords(pack, c.requires)}.` : ""].filter(Boolean).join(" ")])
|
|
28361
|
+
);
|
|
28362
|
+
}
|
|
28363
|
+
}
|
|
28364
|
+
}
|
|
28365
|
+
const states = Object.entries(pack.states ?? {});
|
|
28366
|
+
if (states.length) {
|
|
28367
|
+
b.h(2, "States");
|
|
28368
|
+
b.table(
|
|
28369
|
+
["State", "On", "Means"],
|
|
28370
|
+
states.map(([, s]) => [
|
|
28371
|
+
s.short ? `${s.label} (${s.short})` : s.label,
|
|
28372
|
+
s.scope === "run" ? `the ${n.run}` : `a ${n.subject}`,
|
|
28373
|
+
[s.description ?? "", ...(s.semantics ?? []).map(semanticInWords)].filter(Boolean).join(" ")
|
|
28374
|
+
])
|
|
28375
|
+
);
|
|
28376
|
+
}
|
|
28377
|
+
const counters = Object.entries(pack.counters ?? {}).filter(([, c]) => !c.hidden);
|
|
28378
|
+
const resources = Object.entries(pack.resources ?? {});
|
|
28379
|
+
if (counters.length || resources.length) {
|
|
28380
|
+
b.h(2, "Tallies and tracks");
|
|
28381
|
+
b.table(
|
|
28382
|
+
["Name", "Starts at", "Rule"],
|
|
28383
|
+
[
|
|
28384
|
+
...counters.map(([, c]) => [
|
|
28385
|
+
c.label,
|
|
28386
|
+
String(c.initial ?? 0),
|
|
28387
|
+
[
|
|
28388
|
+
c.min !== void 0 && c.max !== void 0 ? `Stays within ${c.min}\u2013${c.max}.` : c.max !== void 0 ? `Never above ${c.max}.` : c.min !== void 0 && c.min !== 0 ? `Never below ${c.min}.` : "",
|
|
28389
|
+
...(c.triggers ?? []).map((t) => sentence(`${t.label ? `${t.label} \u2014 ` : ""}at ${boundText(t.when)}${t.oncePerRun ? `, once per ${n.run}` : ""}: ${actionsInWords(pack, t.do)}`))
|
|
28390
|
+
].filter(Boolean).join(" ")
|
|
28391
|
+
]),
|
|
28392
|
+
...resources.map(([, r]) => [r.label, String(r.initial ?? 0), [r.description ?? "", `From ${r.min ?? 0}${r.max !== void 0 ? ` to ${r.max}` : ""}, in steps of ${r.step ?? 1}.`].filter(Boolean).join(" ")])
|
|
28393
|
+
]
|
|
28394
|
+
);
|
|
28395
|
+
}
|
|
28396
|
+
const moves = Object.entries(pack.moves ?? {});
|
|
28397
|
+
if (moves.length) {
|
|
28398
|
+
b.h(2, "Moves");
|
|
28399
|
+
b.table(
|
|
28400
|
+
["Move", "When", "What happens"],
|
|
28401
|
+
moves.map(([, m]) => [
|
|
28402
|
+
m.label,
|
|
28403
|
+
[whenInWords(m.when, n), m.oncePerRun ? `once per ${n.run}` : "", m.available?.length ? `if ${conditionsInWords(pack, m.available)}` : ""].filter(Boolean).join("; "),
|
|
28404
|
+
[m.description ?? "", sentence(cap(actionsInWords(pack, m.do)))].filter(Boolean).join(" ")
|
|
28405
|
+
])
|
|
28406
|
+
);
|
|
28407
|
+
}
|
|
28408
|
+
if (pack.targeting && pack.targeting.strategy !== "none") {
|
|
28409
|
+
b.h(2, "Reaching back");
|
|
28410
|
+
b.p(targetingInWords(pack));
|
|
28411
|
+
}
|
|
28412
|
+
if (pack.triggers?.length) {
|
|
28413
|
+
b.h(2, "Always in effect");
|
|
28414
|
+
b.list(pack.triggers.map((t) => sentence(cap(triggerInWords(pack, t)))));
|
|
28415
|
+
}
|
|
28416
|
+
if (pack.endings?.length) {
|
|
28417
|
+
b.h(2, `Ending a ${n.run}`);
|
|
28418
|
+
b.terms(pack.endings.map((e) => ({ term: e.label, text: [e.text ?? "", e.requires?.length ? `Available when ${conditionsInWords(pack, e.requires)}.` : ""].filter(Boolean).join(" ") })));
|
|
28419
|
+
}
|
|
28420
|
+
return { kind: "rulebook", layout: "book", title: pack.title, subtitle: byline(pack), blocks: b.blocks };
|
|
28421
|
+
}
|
|
28422
|
+
function quickstartDoc(pack) {
|
|
28423
|
+
const n = nouns(pack);
|
|
28424
|
+
const b = new Builder();
|
|
28425
|
+
b.p(pack.description);
|
|
28426
|
+
b.h(2, "What you need");
|
|
28427
|
+
b.list(whatYouNeed(pack));
|
|
28428
|
+
b.h(2, "The words");
|
|
28429
|
+
b.p(`A ${n.run} is one session. It is made of ${n.units}${pack.unit.createsSubject ? `, and each ${n.unit} makes one ${n.subject}` : ""}. Closing a ${n.unit} is called \u201C${n.finalize}\u201D.`);
|
|
28430
|
+
b.h(2, `Each ${n.unit}`);
|
|
28431
|
+
b.list(flowSteps(pack, { detail: false }), true);
|
|
28432
|
+
const d = pack.modes[pack.defaultMode];
|
|
28433
|
+
if (d) {
|
|
28434
|
+
b.h(2, `Start with ${d.label}`);
|
|
28435
|
+
b.p([d.description ?? "", `${cap(modeLength(pack, d))}, ${modePlayers(d)}.`, ...modeExtras(pack, d)].filter(Boolean).join(" "));
|
|
28436
|
+
const others = Object.entries(pack.modes).filter(([id]) => id !== pack.defaultMode);
|
|
28437
|
+
if (others.length) b.p(`Other ways to play: ${others.map(([, m]) => m.label).join(", ")}. They are in the rulebook.`, "muted");
|
|
28438
|
+
}
|
|
28439
|
+
if (pack.endings?.length) {
|
|
28440
|
+
b.h(2, "When you stop");
|
|
28441
|
+
b.terms(pack.endings.map((e) => ({ term: e.label, text: e.text ?? "" })));
|
|
28442
|
+
}
|
|
28443
|
+
b.p("Every table is on the reference card. Everything else is in the rulebook.", "muted");
|
|
28444
|
+
return { kind: "quickstart", layout: "book", title: pack.title, subtitle: "Quick start", blocks: b.blocks };
|
|
28445
|
+
}
|
|
28446
|
+
function referenceDoc(pack) {
|
|
28447
|
+
const n = nouns(pack);
|
|
28448
|
+
const b = new Builder();
|
|
28449
|
+
b.h(2, `Each ${n.unit}`);
|
|
28450
|
+
b.list(flowSteps(pack, { detail: false }), true);
|
|
28451
|
+
for (const [, table] of Object.entries(pack.tables)) {
|
|
28452
|
+
b.h(2, `${table.title} \u2014 ${howConsulted(table).replace(/\.$/, "")}`);
|
|
28453
|
+
b.table([table.resolution === "keyed" ? "Key" : "Roll", ""], tableRows(pack, table, { full: true }), true);
|
|
28454
|
+
}
|
|
28455
|
+
const states = Object.values(pack.states ?? {});
|
|
28456
|
+
if (states.length) {
|
|
28457
|
+
b.h(2, "States");
|
|
28458
|
+
b.table(["", "State"], states.map((s) => [s.short ?? "", `${s.label}${s.description ? ` \u2014 ${s.description}` : ""}`]), true);
|
|
28459
|
+
}
|
|
28460
|
+
const moves = Object.values(pack.moves ?? {});
|
|
28461
|
+
if (moves.length) {
|
|
28462
|
+
b.h(2, "Moves");
|
|
28463
|
+
b.list(moves.map((m) => sentence(`${m.label} (${whenInWords(m.when, n)}${m.oncePerRun ? `, once per ${n.run}` : ""}): ${actionsInWords(pack, m.do)}`)));
|
|
28464
|
+
}
|
|
28465
|
+
const remember = [];
|
|
28466
|
+
for (const t of pack.triggers ?? []) remember.push(sentence(cap(triggerInWords(pack, t))));
|
|
28467
|
+
for (const c of Object.values(pack.counters ?? {})) for (const t of c.triggers ?? []) remember.push(sentence(`${c.label} at ${boundText(t.when)}: ${actionsInWords(pack, t.do)}`));
|
|
28468
|
+
for (const m of Object.values(pack.modes)) for (const note of m.notes ?? []) remember.push(`${m.label}: ${note}`);
|
|
28469
|
+
if (pack.hierarchy?.length) remember.push(`Precedence: ${pack.hierarchy.join(" > ")}.`);
|
|
28470
|
+
if (remember.length) {
|
|
28471
|
+
b.h(2, "Frequently forgotten");
|
|
28472
|
+
b.list(remember);
|
|
28473
|
+
}
|
|
28474
|
+
if (pack.endings?.length) {
|
|
28475
|
+
b.h(2, "Endings");
|
|
28476
|
+
b.list(pack.endings.map((e) => `${e.label}${e.text ? `: ${e.text}` : ""}`));
|
|
28477
|
+
}
|
|
28478
|
+
return { kind: "reference", layout: "card", title: pack.title, subtitle: `Reference \xB7 v${pack.version}`, blocks: b.blocks };
|
|
28479
|
+
}
|
|
28480
|
+
function runlogDoc(pack) {
|
|
28481
|
+
const n = nouns(pack);
|
|
28482
|
+
const b = new Builder();
|
|
28483
|
+
const head = [{ label: "Date", width: "short" }, { label: "Mode", width: "short" }];
|
|
28484
|
+
if (Object.values(pack.modes).some((m) => m.seeded)) head.push({ label: "Seed", width: "short" });
|
|
28485
|
+
if (Object.values(pack.modes).some((m) => (m.players?.max ?? 1) > 1)) head.push({ label: "Players", width: "long" });
|
|
28486
|
+
for (const deck of Object.values(pack.decks ?? {})) if (deck.kind === "cards" && deck.drawAtStart) head.push({ label: deck.title, width: "short" });
|
|
28487
|
+
for (const c of Object.values(pack.counters ?? {}).filter((c2) => !c2.hidden)) head.push({ label: c.label, width: "short", box: true });
|
|
28488
|
+
for (const r of Object.values(pack.resources ?? {})) head.push({ label: r.label, width: "short", box: true });
|
|
28489
|
+
head.push({ label: "Page", width: "short" });
|
|
28490
|
+
b.form(head);
|
|
28491
|
+
b.rule();
|
|
28492
|
+
const unitFields = [];
|
|
28493
|
+
unitFields.push({ label: `${cap(n.unit)} \u2116`, width: "short" });
|
|
28494
|
+
if (pack.unit.createsSubject) unitFields.push({ label: `${cap(n.subject)} / states`, width: "long" });
|
|
28495
|
+
for (const phase of pack.phases) {
|
|
28496
|
+
for (const step of phase.steps) {
|
|
28497
|
+
if (step.kind === "rollTable") unitFields.push({ label: pack.tables[step.table]?.title ?? step.table, width: "full", lines: 2 });
|
|
28498
|
+
}
|
|
28499
|
+
}
|
|
28500
|
+
if (pack.journal?.enabled !== false) unitFields.push({ label: "Notes", width: "full", lines: 2 });
|
|
28501
|
+
for (let i = 0; i < 6; i++) b.form(unitFields);
|
|
28502
|
+
b.rule();
|
|
28503
|
+
b.form([{ label: `${cap(n.run)} states`, width: "full", lines: 2 }, { label: "Ending", width: "long" }]);
|
|
28504
|
+
return { kind: "runlog", layout: "sheet", title: pack.title, subtitle: `${cap(n.run)} log`, blocks: b.blocks };
|
|
28505
|
+
}
|
|
28506
|
+
function semanticInWords(s) {
|
|
28507
|
+
switch (s) {
|
|
28508
|
+
case "blocksEdit":
|
|
28509
|
+
return "Nothing about it may be changed.";
|
|
28510
|
+
case "makesUntargetable":
|
|
28511
|
+
return "Consequences cannot land on it.";
|
|
28512
|
+
case "excludesFromResult":
|
|
28513
|
+
return "It does not count toward the result.";
|
|
28514
|
+
case "locksValue":
|
|
28515
|
+
return "Its value is fixed.";
|
|
28516
|
+
case "removesFromPlay":
|
|
28517
|
+
return "It is out of play.";
|
|
28518
|
+
default:
|
|
28519
|
+
return s;
|
|
28520
|
+
}
|
|
28521
|
+
}
|
|
28522
|
+
function whenInWords(when, n) {
|
|
28523
|
+
switch (when) {
|
|
28524
|
+
case "betweenUnits":
|
|
28525
|
+
return `between ${n.units}`;
|
|
28526
|
+
case "beforeEnding":
|
|
28527
|
+
return `before ending the ${n.run}`;
|
|
28528
|
+
default:
|
|
28529
|
+
return "any time";
|
|
28530
|
+
}
|
|
28531
|
+
}
|
|
28532
|
+
function boundText(b) {
|
|
28533
|
+
if (b.eq !== void 0) return String(b.eq);
|
|
28534
|
+
if (b.gte !== void 0 && b.lte !== void 0) return `${b.gte}\u2013${b.lte}`;
|
|
28535
|
+
if (b.gte !== void 0) return `${b.gte}+`;
|
|
28536
|
+
if (b.lte !== void 0) return `\u2264${b.lte}`;
|
|
28537
|
+
return "";
|
|
28538
|
+
}
|
|
28539
|
+
function targetingInWords(pack) {
|
|
28540
|
+
const n = nouns(pack);
|
|
28541
|
+
const t = pack.targeting;
|
|
28542
|
+
if (!t) return "";
|
|
28543
|
+
switch (t.strategy) {
|
|
28544
|
+
case "none":
|
|
28545
|
+
return "";
|
|
28546
|
+
case "playerChoice":
|
|
28547
|
+
return `When a consequence reaches back, you choose which earlier ${n.subject} it lands on.`;
|
|
28548
|
+
case "random":
|
|
28549
|
+
return `When a consequence reaches back, pick an earlier ${n.subject} at random.`;
|
|
28550
|
+
case "oldest":
|
|
28551
|
+
return `When a consequence reaches back, it lands on the oldest ${n.subject} still in play.`;
|
|
28552
|
+
case "newest":
|
|
28553
|
+
return `When a consequence reaches back, it lands on the newest ${n.subject} still in play.`;
|
|
28554
|
+
case "anchoredOffset": {
|
|
28555
|
+
const bands = t.bands.map((b) => {
|
|
28556
|
+
const anchor2 = b.anchor === "playerChoice" ? "you choose" : `count ${b.direction ?? "before"} from the ${b.anchor}`;
|
|
28557
|
+
return `${b.range[0]}\u2013${b.range[1]}: ${anchor2}`;
|
|
28558
|
+
});
|
|
28559
|
+
return `When a roll reaches back, its ones digit says how far: ${bands.join("; ")}. Counting ${t.wraparound === false ? "stops at the end" : "wraps around"}${t.skipIneligible === false ? "" : ` and skips any ${n.subject} that cannot be targeted`}. A 0 lands on the anchor itself.${t.eventFallback ? ` When something other than a roll reaches back, roll ${t.eventFallback.roll} and read it the same way${t.eventFallback.missOn ? `; a ${t.eventFallback.missOn} misses` : ""}.` : ""}`;
|
|
28560
|
+
}
|
|
28561
|
+
}
|
|
28562
|
+
}
|
|
28563
|
+
function generateDoc(pack, kind) {
|
|
28564
|
+
switch (kind) {
|
|
28565
|
+
case "summary":
|
|
28566
|
+
return summaryDoc(pack);
|
|
28567
|
+
case "rulebook":
|
|
28568
|
+
return rulebookDoc(pack);
|
|
28569
|
+
case "quickstart":
|
|
28570
|
+
return quickstartDoc(pack);
|
|
28571
|
+
case "reference":
|
|
28572
|
+
return referenceDoc(pack);
|
|
28573
|
+
case "runlog":
|
|
28574
|
+
return runlogDoc(pack);
|
|
28575
|
+
}
|
|
28576
|
+
}
|
|
28577
|
+
var mdEscape = (s) => s.replace(/\|/g, "\\|").replace(/\n/g, " ");
|
|
28578
|
+
function toMarkdown(doc) {
|
|
28579
|
+
const out = [`# ${doc.title}`];
|
|
28580
|
+
if (doc.subtitle) out.push(`*${doc.subtitle}*`);
|
|
28581
|
+
for (const block of doc.blocks) {
|
|
28582
|
+
switch (block.kind) {
|
|
28583
|
+
case "heading":
|
|
28584
|
+
out.push(`${"#".repeat(block.level)} ${block.text}`);
|
|
28585
|
+
break;
|
|
28586
|
+
case "paragraph":
|
|
28587
|
+
out.push(block.tone === "note" ? `> ${block.text}` : block.text);
|
|
28588
|
+
break;
|
|
28589
|
+
case "list":
|
|
28590
|
+
out.push(block.items.map((item, i) => `${block.ordered ? `${i + 1}.` : "-"} ${item.replace(/\n\s*/g, " \n ")}`).join("\n"));
|
|
28591
|
+
break;
|
|
28592
|
+
case "table":
|
|
28593
|
+
out.push([`| ${block.columns.map(mdEscape).join(" | ")} |`, `| ${block.columns.map(() => "---").join(" | ")} |`, ...block.rows.map((r) => `| ${r.map(mdEscape).join(" | ")} |`)].join("\n"));
|
|
28594
|
+
break;
|
|
28595
|
+
case "terms":
|
|
28596
|
+
out.push(block.items.map((t) => `**${t.term}** \u2014 ${t.text}`).join("\n\n"));
|
|
28597
|
+
break;
|
|
28598
|
+
case "form":
|
|
28599
|
+
out.push(block.fields.map((f) => `${f.label}: ${f.box ? "[ ]" : "_".repeat(f.width === "full" ? 40 : f.width === "long" ? 24 : 12)}`).join(" \n"));
|
|
28600
|
+
break;
|
|
28601
|
+
case "rule":
|
|
28602
|
+
out.push("---");
|
|
28603
|
+
break;
|
|
28604
|
+
case "pagebreak":
|
|
28605
|
+
out.push("");
|
|
28606
|
+
break;
|
|
28607
|
+
}
|
|
28608
|
+
}
|
|
28609
|
+
return `${out.join("\n\n")}
|
|
28610
|
+
`;
|
|
28611
|
+
}
|
|
28612
|
+
var esc2 = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
28613
|
+
var multiline = (s) => esc2(s).replace(/\n/g, "<br>");
|
|
28614
|
+
var CSS = `
|
|
28615
|
+
:root { color-scheme: light; }
|
|
28616
|
+
body { margin: 0; background: #fff; color: #161513; font: 11pt/1.45 Georgia, "Times New Roman", serif; }
|
|
28617
|
+
main { max-width: 42em; margin: 0 auto; padding: 2.5rem 1.5rem; }
|
|
28618
|
+
h1 { font-size: 2rem; margin: 0 0 .25rem; line-height: 1.1; }
|
|
28619
|
+
.subtitle { margin: 0 0 2rem; color: #5b5751; font-style: italic; }
|
|
28620
|
+
h2 { font-size: 1.25rem; margin: 2rem 0 .5rem; border-bottom: 1px solid #d9d4cb; padding-bottom: .2rem; }
|
|
28621
|
+
h3 { font-size: 1.05rem; margin: 1.4rem 0 .35rem; }
|
|
28622
|
+
p { margin: 0 0 .75rem; }
|
|
28623
|
+
p.muted { color: #5b5751; font-size: .92em; }
|
|
28624
|
+
p.note { border-left: 3px solid #b9b2a5; padding-left: .75rem; color: #3f3b36; }
|
|
28625
|
+
ol, ul { margin: 0 0 .9rem; padding-left: 1.4rem; }
|
|
28626
|
+
li { margin: .2rem 0; white-space: pre-line; }
|
|
28627
|
+
table { border-collapse: collapse; width: 100%; margin: 0 0 1rem; font-size: .95em; }
|
|
28628
|
+
th, td { text-align: left; vertical-align: top; padding: .3rem .5rem; border-bottom: 1px solid #e4dfd6; }
|
|
28629
|
+
th { font-family: system-ui, sans-serif; font-size: .75em; letter-spacing: .04em; text-transform: uppercase; color: #5b5751; }
|
|
28630
|
+
td:first-child { white-space: nowrap; font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; font-size: .9em; }
|
|
28631
|
+
table.compact td { padding: .12rem .35rem; font-size: .85em; }
|
|
28632
|
+
dl { margin: 0 0 1rem; }
|
|
28633
|
+
dt { font-weight: 700; margin-top: .5rem; }
|
|
28634
|
+
dd { margin: 0; }
|
|
28635
|
+
hr { border: 0; border-top: 1px solid #d9d4cb; margin: 1.5rem 0; }
|
|
28636
|
+
.page { break-after: page; }
|
|
28637
|
+
.form { display: flex; flex-wrap: wrap; gap: .5rem 1.25rem; margin: .6rem 0 .9rem; font-family: system-ui, sans-serif; font-size: .85em; }
|
|
28638
|
+
.field { display: flex; align-items: flex-end; gap: .4rem; }
|
|
28639
|
+
.field .line { display: inline-block; border-bottom: 1px solid #161513; height: 1.3em; }
|
|
28640
|
+
.field.short .line { width: 7em; } .field.long .line { width: 16em; } .field.full { flex: 1 1 100%; }
|
|
28641
|
+
.field.full .line { flex: 1; }
|
|
28642
|
+
.field .lines { flex: 1; display: grid; gap: .55em; }
|
|
28643
|
+
.field .lines .line { width: 100%; }
|
|
28644
|
+
.field .box { display: inline-block; width: 2.6em; height: 1.6em; border: 1px solid #161513; }
|
|
28645
|
+
.layout-card main { max-width: none; column-width: 17em; column-gap: 2rem; padding: 1.25rem; font-size: 9pt; }
|
|
28646
|
+
.layout-card h1 { column-span: all; font-size: 1.4rem; }
|
|
28647
|
+
.layout-card .subtitle { column-span: all; margin-bottom: 1rem; }
|
|
28648
|
+
.layout-card h2 { margin-top: 1rem; font-size: .95rem; break-after: avoid; }
|
|
28649
|
+
.layout-card table { break-inside: auto; }
|
|
28650
|
+
.layout-card tr { break-inside: avoid; }
|
|
28651
|
+
.layout-sheet main { max-width: none; padding: 1.25rem; }
|
|
28652
|
+
.layout-sheet .form + .form { border-top: 1px dashed #d9d4cb; padding-top: .6rem; }
|
|
28653
|
+
@media print { main { padding: 0; } @page { margin: 1.6cm; } .layout-card @page { margin: 1cm; } }
|
|
28654
|
+
`;
|
|
28655
|
+
function toHtml(doc) {
|
|
28656
|
+
const body = [];
|
|
28657
|
+
for (const block of doc.blocks) {
|
|
28658
|
+
switch (block.kind) {
|
|
28659
|
+
case "heading":
|
|
28660
|
+
body.push(`<h${block.level}>${esc2(block.text)}</h${block.level}>`);
|
|
28661
|
+
break;
|
|
28662
|
+
case "paragraph":
|
|
28663
|
+
body.push(`<p${block.tone ? ` class="${block.tone}"` : ""}>${multiline(block.text)}</p>`);
|
|
28664
|
+
break;
|
|
28665
|
+
case "list":
|
|
28666
|
+
body.push(`<${block.ordered ? "ol" : "ul"}>${block.items.map((i) => `<li>${esc2(i)}</li>`).join("")}</${block.ordered ? "ol" : "ul"}>`);
|
|
28667
|
+
break;
|
|
28668
|
+
case "table":
|
|
28669
|
+
body.push(
|
|
28670
|
+
`<table${block.compact ? ' class="compact"' : ""}><thead><tr>${block.columns.map((c) => `<th>${esc2(c)}</th>`).join("")}</tr></thead><tbody>${block.rows.map((r) => `<tr>${r.map((c) => `<td>${multiline(c)}</td>`).join("")}</tr>`).join("")}</tbody></table>`
|
|
28671
|
+
);
|
|
28672
|
+
break;
|
|
28673
|
+
case "terms":
|
|
28674
|
+
body.push(`<dl>${block.items.map((t) => `<dt>${esc2(t.term)}</dt><dd>${multiline(t.text)}</dd>`).join("")}</dl>`);
|
|
28675
|
+
break;
|
|
28676
|
+
case "form":
|
|
28677
|
+
body.push(
|
|
28678
|
+
`<div class="form">${block.fields.map((f) => {
|
|
28679
|
+
const width = f.width ?? "short";
|
|
28680
|
+
const line = f.box ? `<span class="box"></span>` : f.lines && f.lines > 1 ? `<span class="lines">${'<span class="line"></span>'.repeat(f.lines)}</span>` : `<span class="line"></span>`;
|
|
28681
|
+
return `<span class="field ${width}"><span class="label">${esc2(f.label)}</span>${line}</span>`;
|
|
28682
|
+
}).join("")}</div>`
|
|
28683
|
+
);
|
|
28684
|
+
break;
|
|
28685
|
+
case "rule":
|
|
28686
|
+
body.push("<hr>");
|
|
28687
|
+
break;
|
|
28688
|
+
case "pagebreak":
|
|
28689
|
+
body.push('<div class="page"></div>');
|
|
28690
|
+
break;
|
|
28691
|
+
}
|
|
28692
|
+
}
|
|
28693
|
+
return `<!doctype html>
|
|
28694
|
+
<html lang="en" class="layout-${doc.layout}">
|
|
28695
|
+
<head>
|
|
28696
|
+
<meta charset="utf-8">
|
|
28697
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
28698
|
+
<title>${esc2(doc.title)}${doc.subtitle ? ` \u2014 ${esc2(doc.subtitle)}` : ""}</title>
|
|
28699
|
+
<style>${CSS}</style>
|
|
28700
|
+
</head>
|
|
28701
|
+
<body>
|
|
28702
|
+
<main>
|
|
28703
|
+
<h1>${esc2(doc.title)}</h1>
|
|
28704
|
+
${doc.subtitle ? `<p class="subtitle">${esc2(doc.subtitle)}</p>` : ""}
|
|
28705
|
+
${body.join("\n")}
|
|
28706
|
+
</main>
|
|
28707
|
+
</body>
|
|
28708
|
+
</html>
|
|
28709
|
+
`;
|
|
28710
|
+
}
|
|
28711
|
+
|
|
27888
28712
|
// packages/engine/src/log.ts
|
|
27889
28713
|
function voidedIds(events) {
|
|
27890
28714
|
const out = /* @__PURE__ */ new Set();
|
|
@@ -28214,11 +29038,14 @@ import { resolve as resolve2 } from "node:path";
|
|
|
28214
29038
|
|
|
28215
29039
|
// packages/cli/src/account.ts
|
|
28216
29040
|
var import_yaml2 = __toESM(require_dist(), 1);
|
|
29041
|
+
import { spawn } from "node:child_process";
|
|
28217
29042
|
import { chmodSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
28218
29043
|
import { homedir } from "node:os";
|
|
28219
29044
|
import { join, resolve } from "node:path";
|
|
28220
29045
|
import { createInterface } from "node:readline";
|
|
28221
29046
|
var DEFAULT_API = "https://runlog.scrthq.com/api";
|
|
29047
|
+
var RENEW_MARGIN_MS = 6e4;
|
|
29048
|
+
var DEVICE_GRANT = "urn:ietf:params:oauth:grant-type:device_code";
|
|
28222
29049
|
function configDir() {
|
|
28223
29050
|
const base = process.env["RUNLOG_CONFIG_DIR"] ?? (process.platform === "win32" ? join(process.env["APPDATA"] ?? join(homedir(), "AppData", "Roaming"), "runlog") : join(process.env["XDG_CONFIG_HOME"] ?? join(homedir(), ".config"), "runlog"));
|
|
28224
29051
|
return base;
|
|
@@ -28229,12 +29056,31 @@ function credentials() {
|
|
|
28229
29056
|
if (fromEnv) return { api: process.env["RUNLOG_API"] ?? DEFAULT_API, key: fromEnv, savedAt: "" };
|
|
28230
29057
|
try {
|
|
28231
29058
|
const raw = JSON.parse(readFileSync(credentialsPath(), "utf8"));
|
|
28232
|
-
if (typeof raw.
|
|
28233
|
-
return { api: raw.api, key: raw.key, savedAt: raw.savedAt ?? "" };
|
|
29059
|
+
if (typeof raw.api !== "string") return null;
|
|
29060
|
+
if (typeof raw.key === "string") return { api: raw.api, key: raw.key, savedAt: raw.savedAt ?? "" };
|
|
29061
|
+
const s = raw.session;
|
|
29062
|
+
if (s && typeof s.accessToken === "string" && typeof s.refreshToken === "string" && typeof s.clientId === "string") {
|
|
29063
|
+
return {
|
|
29064
|
+
api: raw.api,
|
|
29065
|
+
session: { clientId: s.clientId, issuer: s.issuer ?? WORKOS, accessToken: s.accessToken, refreshToken: s.refreshToken, expiresAt: s.expiresAt ?? "" },
|
|
29066
|
+
savedAt: raw.savedAt ?? ""
|
|
29067
|
+
};
|
|
29068
|
+
}
|
|
29069
|
+
return null;
|
|
28234
29070
|
} catch {
|
|
28235
29071
|
return null;
|
|
28236
29072
|
}
|
|
28237
29073
|
}
|
|
29074
|
+
function save(creds) {
|
|
29075
|
+
const dir = configDir();
|
|
29076
|
+
mkdirSync(dir, { recursive: true });
|
|
29077
|
+
writeFileSync(credentialsPath(), `${JSON.stringify(creds, null, 2)}
|
|
29078
|
+
`, "utf8");
|
|
29079
|
+
try {
|
|
29080
|
+
chmodSync(credentialsPath(), 384);
|
|
29081
|
+
} catch {
|
|
29082
|
+
}
|
|
29083
|
+
}
|
|
28238
29084
|
async function ask(prompt, hidden = false) {
|
|
28239
29085
|
const rl = createInterface({ input: process.stdin, output: process.stdout, terminal: true });
|
|
28240
29086
|
return new Promise((done) => {
|
|
@@ -28260,60 +29106,182 @@ async function ask(prompt, hidden = false) {
|
|
|
28260
29106
|
}
|
|
28261
29107
|
});
|
|
28262
29108
|
}
|
|
28263
|
-
|
|
28264
|
-
|
|
28265
|
-
|
|
28266
|
-
|
|
28267
|
-
|
|
28268
|
-
|
|
28269
|
-
|
|
29109
|
+
var WORKOS = "https://api.workos.com";
|
|
29110
|
+
var realDeps = {
|
|
29111
|
+
fetch: (input2, init) => fetch(input2, init),
|
|
29112
|
+
sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
|
|
29113
|
+
say: (line) => console.log(line),
|
|
29114
|
+
open: (url2) => {
|
|
29115
|
+
try {
|
|
29116
|
+
const [cmd, args] = process.platform === "win32" ? ["cmd", ["/c", "start", "", url2]] : process.platform === "darwin" ? ["open", [url2]] : ["xdg-open", [url2]];
|
|
29117
|
+
spawn(cmd, args, { detached: true, stdio: "ignore" }).on("error", () => {
|
|
29118
|
+
}).unref();
|
|
29119
|
+
} catch {
|
|
29120
|
+
}
|
|
29121
|
+
},
|
|
29122
|
+
now: () => Date.now()
|
|
29123
|
+
};
|
|
29124
|
+
async function postForm(deps, url2, form) {
|
|
29125
|
+
const response = await deps.fetch(url2, {
|
|
29126
|
+
method: "POST",
|
|
29127
|
+
headers: { "content-type": "application/x-www-form-urlencoded", accept: "application/json" },
|
|
29128
|
+
body: new URLSearchParams(form).toString()
|
|
28270
29129
|
});
|
|
28271
29130
|
const text = await response.text();
|
|
28272
|
-
let parsed = null;
|
|
28273
29131
|
try {
|
|
28274
|
-
|
|
29132
|
+
return { status: response.status, body: JSON.parse(text) };
|
|
28275
29133
|
} catch {
|
|
28276
|
-
throw new Error(`
|
|
29134
|
+
throw new Error(`WorkOS did not answer as expected (${response.status})`);
|
|
28277
29135
|
}
|
|
28278
|
-
|
|
28279
|
-
|
|
28280
|
-
|
|
28281
|
-
|
|
29136
|
+
}
|
|
29137
|
+
function expiryOf(accessToken, now) {
|
|
29138
|
+
try {
|
|
29139
|
+
const payload = JSON.parse(Buffer.from(accessToken.split(".")[1] ?? "", "base64url").toString("utf8"));
|
|
29140
|
+
if (typeof payload.exp === "number") return new Date(payload.exp * 1e3).toISOString();
|
|
29141
|
+
} catch {
|
|
28282
29142
|
}
|
|
28283
|
-
return
|
|
29143
|
+
return new Date(now + 5 * 6e4).toISOString();
|
|
29144
|
+
}
|
|
29145
|
+
async function deviceFlow(clientId, issuer, deps = realDeps) {
|
|
29146
|
+
const start = await postForm(deps, `${issuer}/user_management/authorize/device`, { client_id: clientId });
|
|
29147
|
+
const s = start.body;
|
|
29148
|
+
if (start.status >= 400 || !s.device_code || !s.user_code || !s.verification_uri) {
|
|
29149
|
+
throw new Error(s.error_description ?? `WorkOS would not start a sign-in (${start.status})`);
|
|
29150
|
+
}
|
|
29151
|
+
deps.say("");
|
|
29152
|
+
deps.say(` Open ${s.verification_uri}`);
|
|
29153
|
+
deps.say(` Code ${s.user_code}`);
|
|
29154
|
+
deps.say("");
|
|
29155
|
+
deps.say("Waiting for you to confirm it there. Ctrl-C gives up.");
|
|
29156
|
+
if (s.verification_uri_complete) deps.open(s.verification_uri_complete);
|
|
29157
|
+
let interval = Math.max(1, s.interval ?? 5);
|
|
29158
|
+
const deadline = deps.now() + (s.expires_in ?? 300) * 1e3;
|
|
29159
|
+
while (deps.now() < deadline) {
|
|
29160
|
+
await deps.sleep(interval * 1e3);
|
|
29161
|
+
const poll = await postForm(deps, `${issuer}/user_management/authenticate`, { grant_type: DEVICE_GRANT, device_code: s.device_code, client_id: clientId });
|
|
29162
|
+
const a = poll.body;
|
|
29163
|
+
if (a.access_token && a.refresh_token) {
|
|
29164
|
+
return { clientId, issuer, accessToken: a.access_token, refreshToken: a.refresh_token, expiresAt: expiryOf(a.access_token, deps.now()) };
|
|
29165
|
+
}
|
|
29166
|
+
switch (a.error) {
|
|
29167
|
+
case "authorization_pending":
|
|
29168
|
+
continue;
|
|
29169
|
+
case "slow_down":
|
|
29170
|
+
interval += 1;
|
|
29171
|
+
continue;
|
|
29172
|
+
case "access_denied":
|
|
29173
|
+
throw new Error("the sign-in was refused in the browser");
|
|
29174
|
+
case "expired_token":
|
|
29175
|
+
throw new Error("the code expired before it was confirmed; run `runlog login` again");
|
|
29176
|
+
default:
|
|
29177
|
+
throw new Error(a.error_description ?? a.error ?? `WorkOS answered ${poll.status}`);
|
|
29178
|
+
}
|
|
29179
|
+
}
|
|
29180
|
+
throw new Error("the code expired before it was confirmed; run `runlog login` again");
|
|
29181
|
+
}
|
|
29182
|
+
async function renew(session, deps = realDeps) {
|
|
29183
|
+
const answer = await postForm(deps, `${session.issuer}/user_management/authenticate`, {
|
|
29184
|
+
grant_type: "refresh_token",
|
|
29185
|
+
refresh_token: session.refreshToken,
|
|
29186
|
+
client_id: session.clientId
|
|
29187
|
+
});
|
|
29188
|
+
const a = answer.body;
|
|
29189
|
+
if (!a.access_token || !a.refresh_token) {
|
|
29190
|
+
throw new Error("your sign-in has lapsed; run `runlog login` again");
|
|
29191
|
+
}
|
|
29192
|
+
return { ...session, accessToken: a.access_token, refreshToken: a.refresh_token, expiresAt: expiryOf(a.access_token, deps.now()) };
|
|
29193
|
+
}
|
|
29194
|
+
async function bearer(creds, force = false, deps = realDeps) {
|
|
29195
|
+
if (creds.key) return creds.key;
|
|
29196
|
+
if (!creds.session) throw new Error("not signed in: run `runlog login`");
|
|
29197
|
+
const lapsing = !creds.session.expiresAt || Date.parse(creds.session.expiresAt) - deps.now() < RENEW_MARGIN_MS;
|
|
29198
|
+
if (!force && !lapsing) return creds.session.accessToken;
|
|
29199
|
+
const session = await renew(creds.session, deps);
|
|
29200
|
+
creds.session = session;
|
|
29201
|
+
if (!process.env["RUNLOG_API_KEY"]) save(creds);
|
|
29202
|
+
return session.accessToken;
|
|
29203
|
+
}
|
|
29204
|
+
async function api(method, path, body) {
|
|
29205
|
+
const creds = credentials();
|
|
29206
|
+
if (!creds) throw new Error("not signed in: run `runlog login`");
|
|
29207
|
+
const once = async (token) => {
|
|
29208
|
+
const response = await fetch(`${creds.api.replace(/\/$/, "")}${path}`, {
|
|
29209
|
+
method,
|
|
29210
|
+
headers: { authorization: `Bearer ${token}`, ...body !== void 0 ? { "content-type": "application/json" } : {} },
|
|
29211
|
+
...body !== void 0 ? { body: JSON.stringify(body) } : {}
|
|
29212
|
+
});
|
|
29213
|
+
const text = await response.text();
|
|
29214
|
+
let parsed = null;
|
|
29215
|
+
try {
|
|
29216
|
+
parsed = JSON.parse(text);
|
|
29217
|
+
} catch {
|
|
29218
|
+
throw new Error(`the API at ${creds.api} did not answer as expected (${response.status})`);
|
|
29219
|
+
}
|
|
29220
|
+
return { status: response.status, parsed };
|
|
29221
|
+
};
|
|
29222
|
+
let answer = await once(await bearer(creds));
|
|
29223
|
+
if (answer.status === 401 && creds.session) answer = await once(await bearer(creds, true));
|
|
29224
|
+
if (answer.status === 401) {
|
|
29225
|
+
throw new Error(creds.key ? "that key is not accepted; make a new one on your profile page and `runlog login --key` again" : "your sign-in has lapsed; run `runlog login` again");
|
|
29226
|
+
}
|
|
29227
|
+
if (answer.status >= 400) {
|
|
29228
|
+
const said = answer.parsed?.error;
|
|
29229
|
+
throw new Error(said ?? `the API said ${answer.status}`);
|
|
29230
|
+
}
|
|
29231
|
+
return answer.parsed;
|
|
29232
|
+
}
|
|
29233
|
+
async function greet(apiUrl) {
|
|
29234
|
+
const me = await api("GET", "/me");
|
|
29235
|
+
const who = me.profile?.name ?? me.profile?.email ?? "you";
|
|
29236
|
+
console.log(`signed in as ${who} at ${apiUrl}`);
|
|
28284
29237
|
}
|
|
28285
|
-
async function cmdLogin(args) {
|
|
28286
|
-
const apiUrl = flag(args, "--api") ?? process.env["RUNLOG_API"] ?? DEFAULT_API;
|
|
28287
|
-
|
|
28288
|
-
|
|
28289
|
-
if (!key.startsWith("rl_")) {
|
|
28290
|
-
console.error("that does not look like a Runlog key; they begin with rl_");
|
|
29238
|
+
async function cmdLogin(args, deps = realDeps) {
|
|
29239
|
+
const apiUrl = (flag(args, "--api") ?? process.env["RUNLOG_API"] ?? DEFAULT_API).replace(/\/$/, "");
|
|
29240
|
+
if (process.env["RUNLOG_API_KEY"]) {
|
|
29241
|
+
console.error("RUNLOG_API_KEY is set, so that is what every command will use; unset it to sign in as yourself");
|
|
28291
29242
|
return 1;
|
|
28292
29243
|
}
|
|
28293
|
-
|
|
28294
|
-
|
|
28295
|
-
|
|
28296
|
-
|
|
28297
|
-
|
|
28298
|
-
|
|
28299
|
-
|
|
28300
|
-
|
|
29244
|
+
if (args.includes("--key")) {
|
|
29245
|
+
console.log("Paste a command-line key from your profile page in Runlog. It is not shown as you type.");
|
|
29246
|
+
const key = await ask("key: ", true);
|
|
29247
|
+
if (!key.startsWith("rl_")) {
|
|
29248
|
+
console.error("that does not look like a Runlog key; they begin with rl_");
|
|
29249
|
+
return 1;
|
|
29250
|
+
}
|
|
29251
|
+
save({ api: apiUrl, key, savedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
29252
|
+
try {
|
|
29253
|
+
await greet(apiUrl);
|
|
29254
|
+
return 0;
|
|
29255
|
+
} catch (error61) {
|
|
29256
|
+
unlinkSync(credentialsPath());
|
|
29257
|
+
console.error(error61 instanceof Error ? error61.message : String(error61));
|
|
29258
|
+
return 1;
|
|
29259
|
+
}
|
|
28301
29260
|
}
|
|
28302
29261
|
try {
|
|
28303
|
-
const
|
|
28304
|
-
const
|
|
28305
|
-
|
|
29262
|
+
const response = await deps.fetch(`${apiUrl}/auth/cli`, { headers: { accept: "application/json" } });
|
|
29263
|
+
const text = await response.text();
|
|
29264
|
+
let about = {};
|
|
29265
|
+
try {
|
|
29266
|
+
about = JSON.parse(text);
|
|
29267
|
+
} catch {
|
|
29268
|
+
throw new Error(`the API at ${apiUrl} did not answer as expected (${response.status})`);
|
|
29269
|
+
}
|
|
29270
|
+
if (!about.clientId) throw new Error(`the API at ${apiUrl} cannot sign in a terminal yet; use \`runlog login --key\` with a key from your profile page`);
|
|
29271
|
+
const session = await deviceFlow(about.clientId, about.issuer ?? WORKOS, deps);
|
|
29272
|
+
save({ api: apiUrl, session, savedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
29273
|
+
await greet(apiUrl);
|
|
28306
29274
|
return 0;
|
|
28307
29275
|
} catch (error61) {
|
|
28308
|
-
unlinkSync(credentialsPath());
|
|
28309
29276
|
console.error(error61 instanceof Error ? error61.message : String(error61));
|
|
28310
29277
|
return 1;
|
|
28311
29278
|
}
|
|
28312
29279
|
}
|
|
28313
29280
|
function cmdLogout() {
|
|
29281
|
+
const creds = credentials();
|
|
28314
29282
|
if (existsSync(credentialsPath())) {
|
|
28315
29283
|
unlinkSync(credentialsPath());
|
|
28316
|
-
console.log("signed out; the key is still valid until you revoke it on your profile page");
|
|
29284
|
+
console.log(creds?.key ? "signed out; the key is still valid until you revoke it on your profile page" : "signed out");
|
|
28317
29285
|
} else {
|
|
28318
29286
|
console.log("not signed in");
|
|
28319
29287
|
}
|
|
@@ -28321,9 +29289,12 @@ function cmdLogout() {
|
|
|
28321
29289
|
}
|
|
28322
29290
|
async function cmdWhoami() {
|
|
28323
29291
|
try {
|
|
29292
|
+
const creds = credentials();
|
|
28324
29293
|
const me = await api("GET", "/me");
|
|
28325
29294
|
const claims = await api("GET", "/claims");
|
|
29295
|
+
const how = process.env["RUNLOG_API_KEY"] ? "with RUNLOG_API_KEY" : creds?.key ? "with a key from the profile page" : "from the browser";
|
|
28326
29296
|
console.log(`${me.profile?.name ?? "(no name)"} <${me.profile?.email ?? "?"}> ${me.sub}`);
|
|
29297
|
+
console.log(`signed in ${how} at ${creds?.api ?? DEFAULT_API}`);
|
|
28327
29298
|
console.log(claims.claims.length > 0 ? `claimed signing keys: ${claims.claims.map((c) => c.fingerprint).join(", ")}` : "no claimed signing keys yet: `runlog claim key.json`");
|
|
28328
29299
|
return 0;
|
|
28329
29300
|
} catch (error61) {
|
|
@@ -28475,11 +29446,11 @@ async function cmdSign(args) {
|
|
|
28475
29446
|
const format = detectFormat(path);
|
|
28476
29447
|
const document = import_yaml3.default.parse(text);
|
|
28477
29448
|
const signature = await signPack(document, key.privateKey, flag2(args, "--as"));
|
|
28478
|
-
const
|
|
29449
|
+
const signed2 = { ...document, signature };
|
|
28479
29450
|
writeFileSync2(
|
|
28480
29451
|
path,
|
|
28481
|
-
format === "json" ? `${JSON.stringify(
|
|
28482
|
-
` : import_yaml3.default.stringify(
|
|
29452
|
+
format === "json" ? `${JSON.stringify(signed2, null, 2)}
|
|
29453
|
+
` : import_yaml3.default.stringify(signed2, { lineWidth: 90 }),
|
|
28483
29454
|
"utf8"
|
|
28484
29455
|
);
|
|
28485
29456
|
console.log(`${paint(GREEN, "signed")} ${input2}`);
|
|
@@ -28620,6 +29591,62 @@ async function reportSignature(document) {
|
|
|
28620
29591
|
);
|
|
28621
29592
|
}
|
|
28622
29593
|
|
|
29594
|
+
// packages/cli/src/docs.ts
|
|
29595
|
+
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
29596
|
+
import { basename, join as join2, resolve as resolve3 } from "node:path";
|
|
29597
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
29598
|
+
function cmdDocs(args) {
|
|
29599
|
+
const files = args.filter((a) => !a.startsWith("-"));
|
|
29600
|
+
const input2 = files[0];
|
|
29601
|
+
if (!input2) {
|
|
29602
|
+
console.error("usage: runlog docs <pack.yaml> [-o dir] [--only summary,rulebook,quickstart,reference,runlog] [--md | --html]");
|
|
29603
|
+
return 2;
|
|
29604
|
+
}
|
|
29605
|
+
const outIndex = args.findIndex((a) => a === "-o" || a === "--out");
|
|
29606
|
+
const onlyIndex = args.findIndex((a) => a === "--only");
|
|
29607
|
+
const wanted = new Set(
|
|
29608
|
+
onlyIndex >= 0 && args[onlyIndex + 1] ? args[onlyIndex + 1].split(",").map((k) => k.trim()) : DOC_KINDS.map((k) => k.kind)
|
|
29609
|
+
);
|
|
29610
|
+
for (const k of wanted) {
|
|
29611
|
+
if (!DOC_KINDS.some((d) => d.kind === k)) {
|
|
29612
|
+
console.error(`no such document: ${k}. Choose from ${DOC_KINDS.map((d) => d.kind).join(", ")}.`);
|
|
29613
|
+
return 2;
|
|
29614
|
+
}
|
|
29615
|
+
}
|
|
29616
|
+
const formats = args.includes("--md") && !args.includes("--html") ? ["md"] : args.includes("--html") && !args.includes("--md") ? ["html"] : ["html", "md"];
|
|
29617
|
+
const path = resolve3(input2);
|
|
29618
|
+
let text;
|
|
29619
|
+
try {
|
|
29620
|
+
text = readFileSync3(path, "utf8");
|
|
29621
|
+
} catch {
|
|
29622
|
+
console.error(`cannot read ${input2}`);
|
|
29623
|
+
return 1;
|
|
29624
|
+
}
|
|
29625
|
+
const loaded = loadPackText(text, detectFormat(path));
|
|
29626
|
+
if (!loaded.ok) {
|
|
29627
|
+
console.error(`${basename(input2)} does not load; run \`runlog validate\` to see why`);
|
|
29628
|
+
return 1;
|
|
29629
|
+
}
|
|
29630
|
+
const { pack } = loaded;
|
|
29631
|
+
const dir = outIndex >= 0 && args[outIndex + 1] ? resolve3(args[outIndex + 1]) : resolve3(`${pack.id}-docs`);
|
|
29632
|
+
mkdirSync2(dir, { recursive: true });
|
|
29633
|
+
for (const kind of DOC_KINDS) {
|
|
29634
|
+
if (!wanted.has(kind.kind)) continue;
|
|
29635
|
+
const doc = generateDoc(pack, kind.kind);
|
|
29636
|
+
for (const format of formats) {
|
|
29637
|
+
const file2 = join2(dir, `${kind.kind}.${format}`);
|
|
29638
|
+
writeFileSync3(file2, format === "md" ? toMarkdown(doc) : toHtml(doc), "utf8");
|
|
29639
|
+
console.log(`${kind.label.padEnd(14)} ${file2}`);
|
|
29640
|
+
}
|
|
29641
|
+
}
|
|
29642
|
+
if (!pack.license.redistributable) {
|
|
29643
|
+
console.log("note: this pack is marked non-redistributable. The summary is safe to show anyone; keep the rest as private as the pack.");
|
|
29644
|
+
} else {
|
|
29645
|
+
console.log("open an .html in a browser and print it for a PDF; the reference card lays out in columns and the run log as a sheet.");
|
|
29646
|
+
}
|
|
29647
|
+
return 0;
|
|
29648
|
+
}
|
|
29649
|
+
|
|
28623
29650
|
// packages/cli/src/bin.ts
|
|
28624
29651
|
var RESET2 = "\x1B[0m";
|
|
28625
29652
|
var RED2 = "\x1B[31m";
|
|
@@ -28647,17 +29674,17 @@ function printDiagnostics(diagnostics, file2) {
|
|
|
28647
29674
|
console.error(` ${parts.join(", ")} in ${file2}`);
|
|
28648
29675
|
}
|
|
28649
29676
|
function read(path) {
|
|
28650
|
-
const abs =
|
|
29677
|
+
const abs = resolve4(path);
|
|
28651
29678
|
if (!existsSync3(abs)) {
|
|
28652
29679
|
console.error(paint2(RED2, `no such file: ${path}`));
|
|
28653
29680
|
process.exit(2);
|
|
28654
29681
|
}
|
|
28655
|
-
const text =
|
|
29682
|
+
const text = readFileSync4(abs, "utf8");
|
|
28656
29683
|
const result = loadPackText(text, detectFormat(abs));
|
|
28657
29684
|
return { text, pack: result.pack, diagnostics: result.diagnostics };
|
|
28658
29685
|
}
|
|
28659
29686
|
function report(path, diagnostics, strict) {
|
|
28660
|
-
printDiagnostics(diagnostics,
|
|
29687
|
+
printDiagnostics(diagnostics, basename2(path));
|
|
28661
29688
|
if (hasErrors(diagnostics)) return 1;
|
|
28662
29689
|
if (strict && diagnostics.length > 0) {
|
|
28663
29690
|
console.error(paint2(YELLOW2, " --strict: treating warnings as failures"));
|
|
@@ -28683,7 +29710,7 @@ async function cmdValidate(args) {
|
|
|
28683
29710
|
console.log(
|
|
28684
29711
|
`${paint2(GREEN2, "ok")} ${paint2(BOLD, pack.title)} ${paint2(DIM2, `v${pack.version}`)} \u2014 ${tables} table${tables === 1 ? "" : "s"}, ${entries} entries, ${Object.keys(pack.modes).length} mode(s)` + (diagnostics.length ? paint2(YELLOW2, `, ${diagnostics.length} warning(s)`) : "")
|
|
28685
29712
|
);
|
|
28686
|
-
await reportSignature(import_yaml4.default.parse(
|
|
29713
|
+
await reportSignature(import_yaml4.default.parse(readFileSync4(resolve4(file2), "utf8")));
|
|
28687
29714
|
}
|
|
28688
29715
|
}
|
|
28689
29716
|
return worst;
|
|
@@ -28703,14 +29730,14 @@ function cmdBundle(args) {
|
|
|
28703
29730
|
}
|
|
28704
29731
|
printDiagnostics(
|
|
28705
29732
|
diagnostics.filter((d) => d.level === "warning"),
|
|
28706
|
-
|
|
29733
|
+
basename2(input2)
|
|
28707
29734
|
);
|
|
28708
29735
|
const body = JSON.stringify(pack, null, 2);
|
|
28709
29736
|
const hash2 = createHash("sha256").update(body).digest("hex").slice(0, 16);
|
|
28710
29737
|
const bundled = { ...pack, bundledAt: (/* @__PURE__ */ new Date()).toISOString(), contentHash: hash2 };
|
|
28711
|
-
const out = outIndex >= 0 && args[outIndex + 1] ?
|
|
28712
|
-
|
|
28713
|
-
|
|
29738
|
+
const out = outIndex >= 0 && args[outIndex + 1] ? resolve4(args[outIndex + 1]) : resolve4(`${pack.id}-${pack.version}.pack.json`);
|
|
29739
|
+
mkdirSync3(dirname(out), { recursive: true });
|
|
29740
|
+
writeFileSync4(out, `${JSON.stringify(bundled, null, 2)}
|
|
28714
29741
|
`, "utf8");
|
|
28715
29742
|
console.log(`${paint2(GREEN2, "bundled")} ${out} ${paint2(DIM2, `sha256:${hash2}`)}`);
|
|
28716
29743
|
if (!pack.license.redistributable) {
|
|
@@ -28763,12 +29790,12 @@ function cmdTest(args) {
|
|
|
28763
29790
|
}
|
|
28764
29791
|
function cmdInit(args) {
|
|
28765
29792
|
const name = args.filter((a) => !a.startsWith("-"))[0] ?? "my-pack";
|
|
28766
|
-
const out =
|
|
29793
|
+
const out = resolve4(`${name}.yaml`);
|
|
28767
29794
|
if (existsSync3(out)) {
|
|
28768
29795
|
console.error(paint2(RED2, `refusing to overwrite ${out}`));
|
|
28769
29796
|
return 1;
|
|
28770
29797
|
}
|
|
28771
|
-
|
|
29798
|
+
writeFileSync4(out, SKELETON.replaceAll("__NAME__", name), "utf8");
|
|
28772
29799
|
console.log(`${paint2(GREEN2, "created")} ${out}`);
|
|
28773
29800
|
console.log(paint2(DIM2, ` next: runlog validate ${name}.yaml`));
|
|
28774
29801
|
return 0;
|
|
@@ -28830,19 +29857,23 @@ usage:
|
|
|
28830
29857
|
runlog bundle <pack> [-o out.json] normalize to a distributable JSON
|
|
28831
29858
|
runlog test <pack> replay the pack's own fixtures
|
|
28832
29859
|
runlog init [name] scaffold a new pack
|
|
29860
|
+
runlog docs <pack> [-o dir] write its rulebook, quick start, reference card,
|
|
29861
|
+
[--only kinds] run log sheet and catalog summary (HTML and Markdown)
|
|
28833
29862
|
runlog keygen [-o key.json] make a signing key for your packs
|
|
28834
29863
|
runlog sign <pack> --key key.json sign a pack, proving you wrote it
|
|
28835
29864
|
runlog issue <pack> --to "Name" stamp a copy with a buyer's name and sign it
|
|
28836
29865
|
[--seal] \u2026and seal it, so it needs a license key to open
|
|
28837
29866
|
|
|
28838
|
-
runlog login [--api URL]
|
|
29867
|
+
runlog login [--api URL] sign in: a code to confirm in your browser
|
|
29868
|
+
[--key] \u2026or paste a key from your profile page, for a machine with no browser
|
|
28839
29869
|
runlog whoami who the command line is acting as
|
|
28840
29870
|
runlog claim key.json prove a signing key is yours; the app then names you
|
|
28841
29871
|
runlog publish <pack> put a pack in your library, on every device
|
|
28842
|
-
runlog logout forget the
|
|
29872
|
+
runlog logout forget the sign-in
|
|
28843
29873
|
|
|
28844
29874
|
--strict makes warnings fail, which is what you want in CI. In CI, set
|
|
28845
|
-
RUNLOG_API_KEY instead of running login
|
|
29875
|
+
RUNLOG_API_KEY to a key from your profile page instead of running login;
|
|
29876
|
+
nobody is there to confirm a code.
|
|
28846
29877
|
|
|
28847
29878
|
Signing proves authorship. It does not restrict copying and cannot: the app
|
|
28848
29879
|
has to read every word of a pack to play it. What it gives you is that an
|
|
@@ -28857,6 +29888,8 @@ async function main(argv) {
|
|
|
28857
29888
|
return cmdValidate(args);
|
|
28858
29889
|
case "bundle":
|
|
28859
29890
|
return cmdBundle(args);
|
|
29891
|
+
case "docs":
|
|
29892
|
+
return cmdDocs(args);
|
|
28860
29893
|
case "test":
|
|
28861
29894
|
return cmdTest(args);
|
|
28862
29895
|
case "keygen":
|