@jokerized/decksmith 0.9.4 → 0.9.5

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/cli.js CHANGED
@@ -13788,9 +13788,9 @@ function number(flag, value) {
13788
13788
 
13789
13789
  // src/plan/arc.ts
13790
13790
  var ARC_ROLES = ["intro", "background", "limitations", "conclusion"];
13791
- function requiredRoles(beatCount) {
13792
- if (beatCount >= 8) return ARC_ROLES;
13793
- if (beatCount >= 5) return ["limitations", "conclusion"];
13791
+ function requiredRoles(beatCount2) {
13792
+ if (beatCount2 >= 8) return ARC_ROLES;
13793
+ if (beatCount2 >= 5) return ["limitations", "conclusion"];
13794
13794
  return [];
13795
13795
  }
13796
13796
  function paperArcRequested(prefs) {
@@ -14043,7 +14043,8 @@ DRAWING ARCHETYPES \u2014 reach here first
14043
14043
  repeat. Leave it off only when the figure's whole layout is the
14044
14044
  argument.
14045
14045
 
14046
- grid A cols\xD7rows lattice with up to four regions lit in turn. The
14046
+ grid A cols\xD7rows lattice (cols 2\u201324, rows 1\u201316) with one to four
14047
+ regions lit in turn \u2014 never a fifth. The
14047
14048
  tell: A FIELD WITH NO ORDER \u2014 patches, windows, tokens, tiles,
14048
14049
  receptive fields, masks, strides, a neighbourhood, a block-sparse
14049
14050
  pattern. Regions are cell coordinates, so the lattice IS the
@@ -14935,6 +14936,10 @@ function stripNulls(node) {
14935
14936
  var PLANNER_INVISIBLE = /* @__PURE__ */ new Set(["tilt"]);
14936
14937
  function plannerInvisible(prefs) {
14937
14938
  const hidden = new Set(PLANNER_INVISIBLE);
14939
+ if (prefs.images && !prefs.images.enabled) {
14940
+ hidden.add("illustration");
14941
+ hidden.add("backdrop");
14942
+ }
14938
14943
  if (!paperArcRequested(prefs)) hidden.add("role");
14939
14944
  if (!bespokeFor(prefs, designFor(prefs))) hidden.add("bespoke");
14940
14945
  return hidden;
@@ -14966,7 +14971,7 @@ function schemaFor(prefs) {
14966
14971
  plannerInvisible(prefs)
14967
14972
  );
14968
14973
  }
14969
- var SCHEMA = schemaFor({ genre: "general" });
14974
+ var SCHEMA = schemaFor(prefsSchema.parse({}));
14970
14975
  async function codexPlanner(source, opts = {}) {
14971
14976
  const prefs = opts.prefs ?? prefsSchema.parse({});
14972
14977
  const dir = await mkdtemp(join6(tmpdir(), "decksmith-plan-"));
@@ -14986,14 +14991,29 @@ async function codexPlanner(source, opts = {}) {
14986
14991
  return readPlan(outPath, source, prefs);
14987
14992
  };
14988
14993
  const prompt = buildPrompt(source, prefs);
14989
- const first = await ask(prompt);
14990
- const broken = varietyFindings(first, prefs.images, prefs.design);
14991
- if (broken.length === 0) return first;
14994
+ let first;
14995
+ let firstJson;
14996
+ let broken;
14997
+ try {
14998
+ first = await ask(prompt);
14999
+ firstJson = first;
15000
+ broken = varietyFindings(first, prefs.images, prefs.design);
15001
+ } catch (err) {
15002
+ if (!(err instanceof PlanRejected)) throw err;
15003
+ firstJson = err.plan;
15004
+ broken = err.problems;
15005
+ }
15006
+ if (first && broken.length === 0) return first;
14992
15007
  opts.onRepair?.(broken);
14993
- const second = await ask(repairPrompt(prompt, first, broken));
15008
+ const second = await ask(repairPrompt(prompt, firstJson, broken, prefs.images.enabled)).catch(
15009
+ (err) => {
15010
+ throw err instanceof PlanRejected ? new Error(`Codex's plan is still refused after one repair. ${err.message}`) : err;
15011
+ }
15012
+ );
15013
+ const firstBeats = beatCount(firstJson);
14994
15014
  const still = [
14995
- ...second.beats.length < first.beats.length ? [
14996
- `the repair returned ${second.beats.length} beats for a plan of ${first.beats.length}; a repair changes shapes and may not drop beats.`
15015
+ ...second.beats.length < firstBeats ? [
15016
+ `the repair returned ${second.beats.length} beats for a plan of ${firstBeats}; a repair changes shapes and may not drop beats.`
14997
15017
  ] : [],
14998
15018
  ...varietyFindings(second, prefs.images, prefs.design)
14999
15019
  ];
@@ -15008,21 +15028,32 @@ ${still.map((m) => ` ${m}`).join("\n")}`
15008
15028
  await rm2(dir, { recursive: true, force: true });
15009
15029
  }
15010
15030
  }
15011
- function repairPrompt(prompt, plan, broken) {
15031
+ function repairPrompt(prompt, plan, broken, images) {
15032
+ const moves = images ? "change archetypes, add stage beats with illustration briefs, give diagram beats a backdrop brief, redraw a panel as a hero-number or a kinetic claim where its point is a number or a claim," : "change archetypes, redraw a panel as a hero-number or a kinetic claim where its point is a number or a claim, cite only ids the source lists,";
15012
15033
  return `${prompt}
15013
15034
 
15014
- YOUR PREVIOUS STORYBOARD, BELOW, BREAKS THE VARIETY RULES:
15035
+ YOUR PREVIOUS STORYBOARD, BELOW, IS REFUSED FOR:
15015
15036
  ${broken.map((m) => ` - ${m}`).join("\n")}
15016
15037
 
15017
- Return the whole storyboard again with these fixed. Keep every beat whose shape
15018
- is not named above as it is; change archetypes, add stage beats with
15019
- illustration briefs, give diagram beats a backdrop brief, redraw a panel as a
15020
- hero-number or a kinetic claim where its point is a number or a claim, or
15021
- reorder neighbours only as far as the fix needs. Never
15022
- drop or merge beats: a repair with fewer beats than the plan below is refused.
15038
+ Return the whole storyboard again with these fixed. Keep every beat that is not
15039
+ named above as it is; ${moves} or reorder neighbours only as far as the fix
15040
+ needs. Never drop or merge beats: a repair with fewer beats than the plan below
15041
+ is refused.
15023
15042
 
15024
15043
  ${JSON.stringify(plan)}`;
15025
15044
  }
15045
+ function beatCount(plan) {
15046
+ const beats = plan?.beats;
15047
+ return Array.isArray(beats) ? beats.length : 0;
15048
+ }
15049
+ var PlanRejected = class extends Error {
15050
+ constructor(message2, problems, plan) {
15051
+ super(message2);
15052
+ this.problems = problems;
15053
+ this.plan = plan;
15054
+ this.name = "PlanRejected";
15055
+ }
15056
+ };
15026
15057
  async function readPlan(outPath, source, prefs) {
15027
15058
  const raw2 = await readFile7(outPath, "utf8").catch(() => "");
15028
15059
  if (raw2.trim() === "") {
@@ -15037,22 +15068,41 @@ async function readPlan(outPath, source, prefs) {
15037
15068
  ${raw2.slice(0, 200)}`
15038
15069
  );
15039
15070
  }
15040
- const result = storyboardSchema.safeParse(stripNulls(parsed));
15071
+ const plan = stripNulls(parsed);
15072
+ const result = storyboardSchema.safeParse(plan);
15041
15073
  if (!result.success) {
15042
- const issues = result.error.issues.map((i) => ` ${i.path.join(".") || "(root)"}: ${i.message}`).join("\n");
15043
- throw new Error(`Codex returned a storyboard that does not validate:
15044
- ${issues}`);
15074
+ const issues = result.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`);
15075
+ throw new PlanRejected(
15076
+ `Codex returned a storyboard that does not validate:
15077
+ ${issues.map((i) => ` ${i}`).join("\n")}`,
15078
+ issues,
15079
+ plan
15080
+ );
15045
15081
  }
15046
15082
  if (!prefs.images.enabled) {
15047
15083
  const pending = pendingIllustrations(result.data);
15048
15084
  if (pending.length) {
15049
15085
  const beats = [...new Set(pending.map((p) => p.beatId))].join(", ");
15050
- throw new Error(
15051
- `Codex asked for ${pending.length} illustration${pending.length === 1 ? "" : "s"} (${beats}) with images off. Pass --images to let the plan request pictures, or re-run without it.`
15086
+ const msg = `Codex asked for ${pending.length} illustration${pending.length === 1 ? "" : "s"} (${beats}) with images off. Pass --images to let the plan request pictures, or re-run without it.`;
15087
+ throw new PlanRejected(
15088
+ msg,
15089
+ [
15090
+ `${beats} asked for a picture with images off: remove every illustration and backdrop brief`
15091
+ ],
15092
+ plan
15052
15093
  );
15053
15094
  }
15054
15095
  }
15055
- assertRefsResolve(result.data, source, { pending: prefs.images.enabled ? "allow" : "refuse" });
15096
+ try {
15097
+ assertRefsResolve(result.data, source, { pending: prefs.images.enabled ? "allow" : "refuse" });
15098
+ } catch (err) {
15099
+ const msg = err instanceof Error ? err.message : String(err);
15100
+ throw new PlanRejected(
15101
+ msg,
15102
+ msg.split("\n").map((l) => l.trim()).filter(Boolean),
15103
+ plan
15104
+ );
15105
+ }
15056
15106
  return result.data;
15057
15107
  }
15058
15108
  function buildPrompt(source, prefs) {
@@ -27501,7 +27551,10 @@ program.command("ingest").description("Parse a document or a web page into sourc
27501
27551
  imageFlags(
27502
27552
  lookFlags(
27503
27553
  planFlags(
27504
- lengthFlags(program.command("plan")).description("Ask Codex for a storyboard. Read and edit the result before building.").argument("<source>", "source.json from ingest").requiredOption("-o, --out <file>", "where to write storyboard.json").option("--bespoke", "let the planner mark the beats a generated scene should draw")
27554
+ lengthFlags(program.command("plan")).description("Ask Codex for a storyboard. Read and edit the result before building.").argument("<source>", "source.json from ingest").requiredOption("-o, --out <file>", "where to write storyboard.json").option("--bespoke", "let the planner mark the beats a generated scene should draw").option(
27555
+ "--no-bespoke",
27556
+ "v2: plan for a build run with --no-bespoke (no bespoke hints in the prompt or schema)"
27557
+ )
27505
27558
  )
27506
27559
  )
27507
27560
  ).action(async (src, o) => {
@@ -27513,7 +27566,7 @@ imageFlags(
27513
27566
  const planned = await codexPlanner(source, {
27514
27567
  prefs,
27515
27568
  onRepair: (broken) => {
27516
- step("plan: the first plan broke the variety rule, asking Codex to repair it \u2014");
27569
+ step("plan: the first plan was refused, asking Codex to repair it \u2014");
27517
27570
  for (const m of broken) step(`plan: ${m}`);
27518
27571
  }
27519
27572
  });
package/dist/index.js CHANGED
@@ -16720,9 +16720,9 @@ ${issues}`);
16720
16720
 
16721
16721
  // src/plan/arc.ts
16722
16722
  var ARC_ROLES = ["intro", "background", "limitations", "conclusion"];
16723
- function requiredRoles(beatCount) {
16724
- if (beatCount >= 8) return ARC_ROLES;
16725
- if (beatCount >= 5) return ["limitations", "conclusion"];
16723
+ function requiredRoles(beatCount2) {
16724
+ if (beatCount2 >= 8) return ARC_ROLES;
16725
+ if (beatCount2 >= 5) return ["limitations", "conclusion"];
16726
16726
  return [];
16727
16727
  }
16728
16728
  function paperArcRequested(prefs) {
@@ -16975,7 +16975,8 @@ DRAWING ARCHETYPES \u2014 reach here first
16975
16975
  repeat. Leave it off only when the figure's whole layout is the
16976
16976
  argument.
16977
16977
 
16978
- grid A cols\xD7rows lattice with up to four regions lit in turn. The
16978
+ grid A cols\xD7rows lattice (cols 2\u201324, rows 1\u201316) with one to four
16979
+ regions lit in turn \u2014 never a fifth. The
16979
16980
  tell: A FIELD WITH NO ORDER \u2014 patches, windows, tokens, tiles,
16980
16981
  receptive fields, masks, strides, a neighbourhood, a block-sparse
16981
16982
  pattern. Regions are cell coordinates, so the lattice IS the
@@ -17867,6 +17868,10 @@ function stripNulls(node) {
17867
17868
  var PLANNER_INVISIBLE = /* @__PURE__ */ new Set(["tilt"]);
17868
17869
  function plannerInvisible(prefs) {
17869
17870
  const hidden = new Set(PLANNER_INVISIBLE);
17871
+ if (prefs.images && !prefs.images.enabled) {
17872
+ hidden.add("illustration");
17873
+ hidden.add("backdrop");
17874
+ }
17870
17875
  if (!paperArcRequested(prefs)) hidden.add("role");
17871
17876
  if (!bespokeFor(prefs, designFor(prefs))) hidden.add("bespoke");
17872
17877
  return hidden;
@@ -17898,7 +17903,7 @@ function schemaFor(prefs) {
17898
17903
  plannerInvisible(prefs)
17899
17904
  );
17900
17905
  }
17901
- var SCHEMA = schemaFor({ genre: "general" });
17906
+ var SCHEMA = schemaFor(prefsSchema.parse({}));
17902
17907
  async function codexPlanner(source, opts = {}) {
17903
17908
  const prefs = opts.prefs ?? prefsSchema.parse({});
17904
17909
  const dir = await mkdtemp(join10(tmpdir(), "decksmith-plan-"));
@@ -17918,14 +17923,29 @@ async function codexPlanner(source, opts = {}) {
17918
17923
  return readPlan(outPath, source, prefs);
17919
17924
  };
17920
17925
  const prompt = buildPrompt(source, prefs);
17921
- const first = await ask(prompt);
17922
- const broken = varietyFindings(first, prefs.images, prefs.design);
17923
- if (broken.length === 0) return first;
17926
+ let first;
17927
+ let firstJson;
17928
+ let broken;
17929
+ try {
17930
+ first = await ask(prompt);
17931
+ firstJson = first;
17932
+ broken = varietyFindings(first, prefs.images, prefs.design);
17933
+ } catch (err) {
17934
+ if (!(err instanceof PlanRejected)) throw err;
17935
+ firstJson = err.plan;
17936
+ broken = err.problems;
17937
+ }
17938
+ if (first && broken.length === 0) return first;
17924
17939
  opts.onRepair?.(broken);
17925
- const second = await ask(repairPrompt(prompt, first, broken));
17940
+ const second = await ask(repairPrompt(prompt, firstJson, broken, prefs.images.enabled)).catch(
17941
+ (err) => {
17942
+ throw err instanceof PlanRejected ? new Error(`Codex's plan is still refused after one repair. ${err.message}`) : err;
17943
+ }
17944
+ );
17945
+ const firstBeats = beatCount(firstJson);
17926
17946
  const still = [
17927
- ...second.beats.length < first.beats.length ? [
17928
- `the repair returned ${second.beats.length} beats for a plan of ${first.beats.length}; a repair changes shapes and may not drop beats.`
17947
+ ...second.beats.length < firstBeats ? [
17948
+ `the repair returned ${second.beats.length} beats for a plan of ${firstBeats}; a repair changes shapes and may not drop beats.`
17929
17949
  ] : [],
17930
17950
  ...varietyFindings(second, prefs.images, prefs.design)
17931
17951
  ];
@@ -17940,21 +17960,32 @@ ${still.map((m) => ` ${m}`).join("\n")}`
17940
17960
  await rm4(dir, { recursive: true, force: true });
17941
17961
  }
17942
17962
  }
17943
- function repairPrompt(prompt, plan, broken) {
17963
+ function repairPrompt(prompt, plan, broken, images) {
17964
+ const moves = images ? "change archetypes, add stage beats with illustration briefs, give diagram beats a backdrop brief, redraw a panel as a hero-number or a kinetic claim where its point is a number or a claim," : "change archetypes, redraw a panel as a hero-number or a kinetic claim where its point is a number or a claim, cite only ids the source lists,";
17944
17965
  return `${prompt}
17945
17966
 
17946
- YOUR PREVIOUS STORYBOARD, BELOW, BREAKS THE VARIETY RULES:
17967
+ YOUR PREVIOUS STORYBOARD, BELOW, IS REFUSED FOR:
17947
17968
  ${broken.map((m) => ` - ${m}`).join("\n")}
17948
17969
 
17949
- Return the whole storyboard again with these fixed. Keep every beat whose shape
17950
- is not named above as it is; change archetypes, add stage beats with
17951
- illustration briefs, give diagram beats a backdrop brief, redraw a panel as a
17952
- hero-number or a kinetic claim where its point is a number or a claim, or
17953
- reorder neighbours only as far as the fix needs. Never
17954
- drop or merge beats: a repair with fewer beats than the plan below is refused.
17970
+ Return the whole storyboard again with these fixed. Keep every beat that is not
17971
+ named above as it is; ${moves} or reorder neighbours only as far as the fix
17972
+ needs. Never drop or merge beats: a repair with fewer beats than the plan below
17973
+ is refused.
17955
17974
 
17956
17975
  ${JSON.stringify(plan)}`;
17957
17976
  }
17977
+ function beatCount(plan) {
17978
+ const beats = plan?.beats;
17979
+ return Array.isArray(beats) ? beats.length : 0;
17980
+ }
17981
+ var PlanRejected = class extends Error {
17982
+ constructor(message2, problems, plan) {
17983
+ super(message2);
17984
+ this.problems = problems;
17985
+ this.plan = plan;
17986
+ this.name = "PlanRejected";
17987
+ }
17988
+ };
17958
17989
  async function readPlan(outPath, source, prefs) {
17959
17990
  const raw2 = await readFile12(outPath, "utf8").catch(() => "");
17960
17991
  if (raw2.trim() === "") {
@@ -17969,22 +18000,41 @@ async function readPlan(outPath, source, prefs) {
17969
18000
  ${raw2.slice(0, 200)}`
17970
18001
  );
17971
18002
  }
17972
- const result = storyboardSchema.safeParse(stripNulls(parsed));
18003
+ const plan = stripNulls(parsed);
18004
+ const result = storyboardSchema.safeParse(plan);
17973
18005
  if (!result.success) {
17974
- const issues = result.error.issues.map((i) => ` ${i.path.join(".") || "(root)"}: ${i.message}`).join("\n");
17975
- throw new Error(`Codex returned a storyboard that does not validate:
17976
- ${issues}`);
18006
+ const issues = result.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`);
18007
+ throw new PlanRejected(
18008
+ `Codex returned a storyboard that does not validate:
18009
+ ${issues.map((i) => ` ${i}`).join("\n")}`,
18010
+ issues,
18011
+ plan
18012
+ );
17977
18013
  }
17978
18014
  if (!prefs.images.enabled) {
17979
18015
  const pending = pendingIllustrations(result.data);
17980
18016
  if (pending.length) {
17981
18017
  const beats = [...new Set(pending.map((p) => p.beatId))].join(", ");
17982
- throw new Error(
17983
- `Codex asked for ${pending.length} illustration${pending.length === 1 ? "" : "s"} (${beats}) with images off. Pass --images to let the plan request pictures, or re-run without it.`
18018
+ const msg = `Codex asked for ${pending.length} illustration${pending.length === 1 ? "" : "s"} (${beats}) with images off. Pass --images to let the plan request pictures, or re-run without it.`;
18019
+ throw new PlanRejected(
18020
+ msg,
18021
+ [
18022
+ `${beats} asked for a picture with images off: remove every illustration and backdrop brief`
18023
+ ],
18024
+ plan
17984
18025
  );
17985
18026
  }
17986
18027
  }
17987
- assertRefsResolve(result.data, source, { pending: prefs.images.enabled ? "allow" : "refuse" });
18028
+ try {
18029
+ assertRefsResolve(result.data, source, { pending: prefs.images.enabled ? "allow" : "refuse" });
18030
+ } catch (err) {
18031
+ const msg = err instanceof Error ? err.message : String(err);
18032
+ throw new PlanRejected(
18033
+ msg,
18034
+ msg.split("\n").map((l) => l.trim()).filter(Boolean),
18035
+ plan
18036
+ );
18037
+ }
17988
18038
  return result.data;
17989
18039
  }
17990
18040
  function buildPrompt(source, prefs) {
package/dist/mcp.js CHANGED
@@ -15277,9 +15277,9 @@ function bespokeFor(prefs, design2) {
15277
15277
 
15278
15278
  // src/plan/arc.ts
15279
15279
  var ARC_ROLES = ["intro", "background", "limitations", "conclusion"];
15280
- function requiredRoles(beatCount) {
15281
- if (beatCount >= 8) return ARC_ROLES;
15282
- if (beatCount >= 5) return ["limitations", "conclusion"];
15280
+ function requiredRoles(beatCount2) {
15281
+ if (beatCount2 >= 8) return ARC_ROLES;
15282
+ if (beatCount2 >= 5) return ["limitations", "conclusion"];
15283
15283
  return [];
15284
15284
  }
15285
15285
  function paperArcRequested(prefs) {
@@ -15473,7 +15473,8 @@ DRAWING ARCHETYPES \u2014 reach here first
15473
15473
  repeat. Leave it off only when the figure's whole layout is the
15474
15474
  argument.
15475
15475
 
15476
- grid A cols\xD7rows lattice with up to four regions lit in turn. The
15476
+ grid A cols\xD7rows lattice (cols 2\u201324, rows 1\u201316) with one to four
15477
+ regions lit in turn \u2014 never a fifth. The
15477
15478
  tell: A FIELD WITH NO ORDER \u2014 patches, windows, tokens, tiles,
15478
15479
  receptive fields, masks, strides, a neighbourhood, a block-sparse
15479
15480
  pattern. Regions are cell coordinates, so the lattice IS the
@@ -16323,6 +16324,10 @@ function stripNulls(node) {
16323
16324
  var PLANNER_INVISIBLE = /* @__PURE__ */ new Set(["tilt"]);
16324
16325
  function plannerInvisible(prefs) {
16325
16326
  const hidden = new Set(PLANNER_INVISIBLE);
16327
+ if (prefs.images && !prefs.images.enabled) {
16328
+ hidden.add("illustration");
16329
+ hidden.add("backdrop");
16330
+ }
16326
16331
  if (!paperArcRequested(prefs)) hidden.add("role");
16327
16332
  if (!bespokeFor(prefs, designFor(prefs))) hidden.add("bespoke");
16328
16333
  return hidden;
@@ -16354,7 +16359,7 @@ function schemaFor(prefs) {
16354
16359
  plannerInvisible(prefs)
16355
16360
  );
16356
16361
  }
16357
- var SCHEMA = schemaFor({ genre: "general" });
16362
+ var SCHEMA = schemaFor(prefsSchema.parse({}));
16358
16363
  async function codexPlanner(source, opts = {}) {
16359
16364
  const prefs = opts.prefs ?? prefsSchema.parse({});
16360
16365
  const dir = await mkdtemp(join9(tmpdir2(), "decksmith-plan-"));
@@ -16374,14 +16379,29 @@ async function codexPlanner(source, opts = {}) {
16374
16379
  return readPlan(outPath, source, prefs);
16375
16380
  };
16376
16381
  const prompt = buildPrompt(source, prefs);
16377
- const first = await ask(prompt);
16378
- const broken = varietyFindings(first, prefs.images, prefs.design);
16379
- if (broken.length === 0) return first;
16382
+ let first;
16383
+ let firstJson;
16384
+ let broken;
16385
+ try {
16386
+ first = await ask(prompt);
16387
+ firstJson = first;
16388
+ broken = varietyFindings(first, prefs.images, prefs.design);
16389
+ } catch (err) {
16390
+ if (!(err instanceof PlanRejected)) throw err;
16391
+ firstJson = err.plan;
16392
+ broken = err.problems;
16393
+ }
16394
+ if (first && broken.length === 0) return first;
16380
16395
  opts.onRepair?.(broken);
16381
- const second = await ask(repairPrompt(prompt, first, broken));
16396
+ const second = await ask(repairPrompt(prompt, firstJson, broken, prefs.images.enabled)).catch(
16397
+ (err) => {
16398
+ throw err instanceof PlanRejected ? new Error(`Codex's plan is still refused after one repair. ${err.message}`) : err;
16399
+ }
16400
+ );
16401
+ const firstBeats = beatCount(firstJson);
16382
16402
  const still = [
16383
- ...second.beats.length < first.beats.length ? [
16384
- `the repair returned ${second.beats.length} beats for a plan of ${first.beats.length}; a repair changes shapes and may not drop beats.`
16403
+ ...second.beats.length < firstBeats ? [
16404
+ `the repair returned ${second.beats.length} beats for a plan of ${firstBeats}; a repair changes shapes and may not drop beats.`
16385
16405
  ] : [],
16386
16406
  ...varietyFindings(second, prefs.images, prefs.design)
16387
16407
  ];
@@ -16396,21 +16416,32 @@ ${still.map((m) => ` ${m}`).join("\n")}`
16396
16416
  await rm4(dir, { recursive: true, force: true });
16397
16417
  }
16398
16418
  }
16399
- function repairPrompt(prompt, plan, broken) {
16419
+ function repairPrompt(prompt, plan, broken, images) {
16420
+ const moves = images ? "change archetypes, add stage beats with illustration briefs, give diagram beats a backdrop brief, redraw a panel as a hero-number or a kinetic claim where its point is a number or a claim," : "change archetypes, redraw a panel as a hero-number or a kinetic claim where its point is a number or a claim, cite only ids the source lists,";
16400
16421
  return `${prompt}
16401
16422
 
16402
- YOUR PREVIOUS STORYBOARD, BELOW, BREAKS THE VARIETY RULES:
16423
+ YOUR PREVIOUS STORYBOARD, BELOW, IS REFUSED FOR:
16403
16424
  ${broken.map((m) => ` - ${m}`).join("\n")}
16404
16425
 
16405
- Return the whole storyboard again with these fixed. Keep every beat whose shape
16406
- is not named above as it is; change archetypes, add stage beats with
16407
- illustration briefs, give diagram beats a backdrop brief, redraw a panel as a
16408
- hero-number or a kinetic claim where its point is a number or a claim, or
16409
- reorder neighbours only as far as the fix needs. Never
16410
- drop or merge beats: a repair with fewer beats than the plan below is refused.
16426
+ Return the whole storyboard again with these fixed. Keep every beat that is not
16427
+ named above as it is; ${moves} or reorder neighbours only as far as the fix
16428
+ needs. Never drop or merge beats: a repair with fewer beats than the plan below
16429
+ is refused.
16411
16430
 
16412
16431
  ${JSON.stringify(plan)}`;
16413
16432
  }
16433
+ function beatCount(plan) {
16434
+ const beats = plan?.beats;
16435
+ return Array.isArray(beats) ? beats.length : 0;
16436
+ }
16437
+ var PlanRejected = class extends Error {
16438
+ constructor(message2, problems, plan) {
16439
+ super(message2);
16440
+ this.problems = problems;
16441
+ this.plan = plan;
16442
+ this.name = "PlanRejected";
16443
+ }
16444
+ };
16414
16445
  async function readPlan(outPath, source, prefs) {
16415
16446
  const raw2 = await readFile9(outPath, "utf8").catch(() => "");
16416
16447
  if (raw2.trim() === "") {
@@ -16425,22 +16456,41 @@ async function readPlan(outPath, source, prefs) {
16425
16456
  ${raw2.slice(0, 200)}`
16426
16457
  );
16427
16458
  }
16428
- const result = storyboardSchema.safeParse(stripNulls(parsed));
16459
+ const plan = stripNulls(parsed);
16460
+ const result = storyboardSchema.safeParse(plan);
16429
16461
  if (!result.success) {
16430
- const issues = result.error.issues.map((i) => ` ${i.path.join(".") || "(root)"}: ${i.message}`).join("\n");
16431
- throw new Error(`Codex returned a storyboard that does not validate:
16432
- ${issues}`);
16462
+ const issues = result.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`);
16463
+ throw new PlanRejected(
16464
+ `Codex returned a storyboard that does not validate:
16465
+ ${issues.map((i) => ` ${i}`).join("\n")}`,
16466
+ issues,
16467
+ plan
16468
+ );
16433
16469
  }
16434
16470
  if (!prefs.images.enabled) {
16435
16471
  const pending = pendingIllustrations(result.data);
16436
16472
  if (pending.length) {
16437
16473
  const beats = [...new Set(pending.map((p) => p.beatId))].join(", ");
16438
- throw new Error(
16439
- `Codex asked for ${pending.length} illustration${pending.length === 1 ? "" : "s"} (${beats}) with images off. Pass --images to let the plan request pictures, or re-run without it.`
16474
+ const msg = `Codex asked for ${pending.length} illustration${pending.length === 1 ? "" : "s"} (${beats}) with images off. Pass --images to let the plan request pictures, or re-run without it.`;
16475
+ throw new PlanRejected(
16476
+ msg,
16477
+ [
16478
+ `${beats} asked for a picture with images off: remove every illustration and backdrop brief`
16479
+ ],
16480
+ plan
16440
16481
  );
16441
16482
  }
16442
16483
  }
16443
- assertRefsResolve(result.data, source, { pending: prefs.images.enabled ? "allow" : "refuse" });
16484
+ try {
16485
+ assertRefsResolve(result.data, source, { pending: prefs.images.enabled ? "allow" : "refuse" });
16486
+ } catch (err) {
16487
+ const msg = err instanceof Error ? err.message : String(err);
16488
+ throw new PlanRejected(
16489
+ msg,
16490
+ msg.split("\n").map((l) => l.trim()).filter(Boolean),
16491
+ plan
16492
+ );
16493
+ }
16444
16494
  return result.data;
16445
16495
  }
16446
16496
  function buildPrompt(source, prefs) {
@@ -1,10 +1,12 @@
1
1
  import { type Prefs } from "../prefs.js";
2
2
  import { type Source, type Storyboard } from "../types.js";
3
3
  /** The schema for one run. `role` is present only when the paper arc was asked for. */
4
- export declare function schemaFor(prefs: Pick<Prefs, "genre" | "bespoke" | "design">): unknown;
4
+ export declare function schemaFor(prefs: Pick<Prefs, "genre" | "bespoke" | "design"> & {
5
+ images?: Prefs["images"];
6
+ }): unknown;
5
7
  /**
6
- * The default-preferences schema, unchanged and still exported: `general` hides
7
- * `role`, so these bytes are what they have always been.
8
+ * The default-preferences schema, still exported: `general` hides `role`, and
9
+ * images default off, so it hides the picture briefs too (0.9.5).
8
10
  */
9
11
  export declare const SCHEMA: unknown;
10
12
  export interface CodexOptions {
@@ -47,6 +49,16 @@ export interface RunnerArgs {
47
49
  /** What the planner needs from the outside world: a prompt in, a final message out. */
48
50
  export type Runner = (args: RunnerArgs) => Promise<void>;
49
51
  export declare function codexPlanner(source: Source, opts?: CodexOptions): Promise<Storyboard>;
52
+ /**
53
+ * A plan Codex answered that this source cannot build — out of the schema's
54
+ * bounds, a picture brief with images off, an id the source lacks — with what
55
+ * is wrong and the JSON it came as, for the one repair round.
56
+ */
57
+ export declare class PlanRejected extends Error {
58
+ readonly problems: string[];
59
+ readonly plan: unknown;
60
+ constructor(message: string, problems: string[], plan: unknown);
61
+ }
50
62
  /**
51
63
  * The `codex` command line for a run, and the environment it needs.
52
64
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jokerized/decksmith",
3
- "version": "0.9.4",
3
+ "version": "0.9.5",
4
4
  "description": "Turn a source document into an animated explanation deck.",
5
5
  "type": "module",
6
6
  "license": "MIT",