stego-cli 0.4.3 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +68 -22
  2. package/dist/shared/src/domain/frontmatter/validators.js +20 -2
  3. package/dist/shared/src/domain/images/index.js +1 -0
  4. package/dist/shared/src/domain/images/style.js +185 -0
  5. package/dist/shared/src/index.js +1 -0
  6. package/dist/stego-cli/src/app/command-registry.js +93 -1
  7. package/dist/stego-cli/src/app/create-cli-app.js +3 -0
  8. package/dist/stego-cli/src/app/error-boundary.js +11 -1
  9. package/dist/stego-cli/src/modules/comments/commands/comments-add.js +0 -1
  10. package/dist/stego-cli/src/modules/comments/commands/comments-clear-resolved.js +0 -1
  11. package/dist/stego-cli/src/modules/comments/commands/comments-delete.js +0 -1
  12. package/dist/stego-cli/src/modules/comments/commands/comments-read.js +0 -1
  13. package/dist/stego-cli/src/modules/comments/commands/comments-reply.js +0 -1
  14. package/dist/stego-cli/src/modules/comments/commands/comments-set-status.js +0 -1
  15. package/dist/stego-cli/src/modules/comments/commands/comments-sync-anchors.js +0 -1
  16. package/dist/stego-cli/src/modules/compile/application/compile-manuscript.js +12 -1
  17. package/dist/stego-cli/src/modules/compile/commands/build.js +1 -2
  18. package/dist/stego-cli/src/modules/compile/domain/compile-structure.js +0 -3
  19. package/dist/stego-cli/src/modules/compile/domain/image-settings.js +394 -0
  20. package/dist/stego-cli/src/modules/export/application/run-export.js +22 -1
  21. package/dist/stego-cli/src/modules/export/commands/export.js +1 -2
  22. package/dist/stego-cli/src/modules/export/infra/pandoc-exporter.js +29 -2
  23. package/dist/stego-cli/src/modules/manuscript/commands/new-manuscript.js +1 -1
  24. package/dist/stego-cli/src/modules/project/application/create-project.js +41 -1
  25. package/dist/stego-cli/src/modules/project/application/infer-project.js +1 -1
  26. package/dist/stego-cli/src/modules/project/commands/new-project.js +1 -1
  27. package/dist/stego-cli/src/modules/quality/application/inspect-project.js +147 -112
  28. package/dist/stego-cli/src/modules/quality/commands/check-stage.js +1 -2
  29. package/dist/stego-cli/src/modules/quality/commands/lint.js +1 -2
  30. package/dist/stego-cli/src/modules/quality/commands/validate.js +1 -2
  31. package/dist/stego-cli/src/modules/scaffold/commands/init.js +2 -2
  32. package/dist/stego-cli/src/modules/scaffold/domain/templates.js +15 -14
  33. package/dist/stego-cli/src/modules/spine/commands/spine-deprecated-aliases.js +2 -2
  34. package/dist/stego-cli/src/modules/spine/commands/spine-new-category.js +1 -2
  35. package/dist/stego-cli/src/modules/spine/commands/spine-new-entry.js +1 -2
  36. package/dist/stego-cli/src/modules/spine/commands/spine-read.js +1 -2
  37. package/filters/image-layout.css +23 -0
  38. package/filters/image-layout.lua +170 -0
  39. package/package.json +4 -2
  40. package/projects/fiction-example/assets/README.md +31 -0
  41. package/projects/stego-docs/assets/README.md +31 -0
  42. package/projects/stego-docs/manuscript/500-project-configuration.md +37 -0
  43. package/projects/stego-docs/manuscript/800-build-export-and-release-outputs.md +4 -0
package/README.md CHANGED
@@ -17,9 +17,9 @@ stego init
17
17
  npm install
18
18
 
19
19
  stego list-projects
20
- stego validate --project fiction-example
21
- stego build --project fiction-example
22
- stego new --project fiction-example
20
+ stego validate -p fiction-example
21
+ stego build -p fiction-example
22
+ stego new -p fiction-example
23
23
  ```
24
24
 
25
25
  `stego init` scaffolds two example projects:
@@ -54,18 +54,20 @@ Run commands from the workspace root and target a project with `--project`.
54
54
 
55
55
  ```bash
56
56
  stego list-projects
57
- stego new-project --project my-book --title "My Book"
58
- stego new --project fiction-example
59
- stego validate --project fiction-example
60
- stego build --project fiction-example
61
- stego check-stage --project fiction-example --stage revise
62
- stego export --project fiction-example --format md
63
- stego spine read --project fiction-example
64
- stego spine new-category --project fiction-example --key characters
65
- stego spine new --project fiction-example --category characters --filename supporting/abigail
57
+ stego new-project -p my-book --title "My Book"
58
+ stego new -p fiction-example
59
+ stego validate -p fiction-example
60
+ stego build -p fiction-example
61
+ stego check-stage -p fiction-example --stage revise
62
+ stego export -p fiction-example --format md
63
+ stego spine read -p fiction-example
64
+ stego spine new-category -p fiction-example --key characters
65
+ stego spine new -p fiction-example --category characters --filename supporting/abigail
66
66
  stego metadata read projects/fiction-example/manuscript/100-the-commission.md --format json
67
67
  ```
68
68
 
69
+ All project-scoped commands accept `-p` as shorthand for `--project`.
70
+
69
71
  `stego new` also supports `--i <prefix>` for numeric prefix override and `--filename <name>` for an explicit manuscript filename.
70
72
 
71
73
  Spine V2 is directory-inferred:
@@ -74,6 +76,50 @@ Spine V2 is directory-inferred:
74
76
  - category metadata lives at `spine/<category>/_category.md`
75
77
  - entries are markdown files in each category directory tree
76
78
 
79
+ ## Image assets and manuscript image settings
80
+
81
+ Stego projects scaffold an `assets/` directory for manuscript images.
82
+
83
+ Use standard Markdown image syntax in manuscript files:
84
+
85
+ ```md
86
+ ![Map](../assets/maps/city-plan.png)
87
+ ```
88
+
89
+ Set global image defaults in `stego-project.json`:
90
+
91
+ ```json
92
+ {
93
+ "images": {
94
+ "layout": "block",
95
+ "align": "center",
96
+ "width": "50%",
97
+ "classes": ["illustration"]
98
+ }
99
+ }
100
+ ```
101
+
102
+ Use manuscript frontmatter `images` only for per-path overrides:
103
+
104
+ ```yaml
105
+ images:
106
+ assets/maps/city-plan.png:
107
+ layout: inline
108
+ align: left
109
+ width: 100%
110
+ classes: [diagram]
111
+ ```
112
+
113
+ Rules:
114
+
115
+ - project-level global keys in `stego-project.json images`: `width`, `height`, `classes`, `id`, `attrs`, `layout`, `align`
116
+ - manuscript frontmatter `images` keys are per-image overrides by project-relative asset path
117
+ - manuscript frontmatter should not define global keys; put defaults in `stego-project.json`
118
+ - `layout` (`block|inline`) and `align` (`left|center|right`) are emitted as image attrs (`data-layout`, `data-align`) in compiled markdown
119
+ - EPUB export includes a default image layout stylesheet (`filters/image-layout.css`) for `data-layout`/`data-align` behavior
120
+ - inline image attrs in markdown win over both project defaults and frontmatter overrides
121
+ - local manuscript image targets outside `assets/` are reported as validate warnings
122
+
77
123
  Projects also include local npm scripts so you can work from inside a project directory.
78
124
 
79
125
  ## Complete CLI command reference
@@ -90,16 +136,16 @@ Current `stego --help` command index:
90
136
  ```text
91
137
  init [--force]
92
138
  list-projects [--root <path>]
93
- new-project --project <project-id> [--title <title>] [--prose-font <yes|no|prompt>] [--format <text|json>] [--root <path>]
94
- new --project <project-id> [--i <prefix>|-i <prefix>] [--filename <name>] [--format <text|json>] [--root <path>]
95
- validate --project <project-id> [--file <project-relative-manuscript-path>] [--root <path>]
96
- build --project <project-id> [--root <path>]
97
- check-stage --project <project-id> --stage <draft|revise|line-edit|proof|final> [--file <project-relative-manuscript-path>] [--root <path>]
98
- lint --project <project-id> [--manuscript|--spine] [--root <path>]
99
- export --project <project-id> --format <md|docx|pdf|epub> [--output <path>] [--root <path>]
100
- spine read --project <project-id> [--format <text|json>] [--root <path>]
101
- spine new-category --project <project-id> --key <category> [--label <label>] [--require-metadata] [--format <text|json>] [--root <path>]
102
- spine new --project <project-id> --category <category> [--filename <relative-path>] [--format <text|json>] [--root <path>]
139
+ new-project --project|-p <project-id> [--title <title>] [--prose-font <yes|no|prompt>] [--format <text|json>] [--root <path>]
140
+ new --project|-p <project-id> [--i <prefix>|-i <prefix>] [--filename <name>] [--format <text|json>] [--root <path>]
141
+ validate --project|-p <project-id> [--file <project-relative-manuscript-path>] [--root <path>]
142
+ build --project|-p <project-id> [--root <path>]
143
+ check-stage --project|-p <project-id> --stage <draft|revise|line-edit|proof|final> [--file <project-relative-manuscript-path>] [--root <path>]
144
+ lint --project|-p <project-id> [--manuscript|--spine] [--root <path>]
145
+ export --project|-p <project-id> --format <md|docx|pdf|epub> [--output <path>] [--root <path>]
146
+ spine read --project|-p <project-id> [--format <text|json>] [--root <path>]
147
+ spine new-category --project|-p <project-id> --key <category> [--label <label>] [--require-metadata] [--format <text|json>] [--root <path>]
148
+ spine new --project|-p <project-id> --category <category> [--filename <relative-path>] [--format <text|json>] [--root <path>]
103
149
  metadata read <markdown-path> [--format <text|json>]
104
150
  metadata apply <markdown-path> --input <path|-> [--format <text|json>]
105
151
  comments read <manuscript> [--format <text|json>]
@@ -32,7 +32,18 @@ export function normalizeFrontmatterRecord(raw) {
32
32
  }
33
33
  function normalizeFrontmatterValue(value, key) {
34
34
  if (Array.isArray(value)) {
35
- return value.map((item) => normalizeFrontmatterScalar(item, key));
35
+ return value.map((item) => normalizeFrontmatterValue(item, key));
36
+ }
37
+ if (isPlainObject(value)) {
38
+ const result = {};
39
+ for (const [entryKey, entryValue] of Object.entries(value)) {
40
+ const normalizedKey = entryKey.trim();
41
+ if (!normalizedKey) {
42
+ throw new Error(`Metadata key '${key}' contains an empty nested key.`);
43
+ }
44
+ result[normalizedKey] = normalizeFrontmatterValue(entryValue, key);
45
+ }
46
+ return result;
36
47
  }
37
48
  return normalizeFrontmatterScalar(value, key);
38
49
  }
@@ -43,5 +54,12 @@ function normalizeFrontmatterScalar(value, key) {
43
54
  if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
44
55
  return value;
45
56
  }
46
- throw new Error(`Metadata key '${key}' must be a scalar or array of scalars.`);
57
+ throw new Error(`Metadata key '${key}' must be scalar, array, or object.`);
58
+ }
59
+ function isPlainObject(value) {
60
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
61
+ return false;
62
+ }
63
+ const prototype = Object.getPrototypeOf(value);
64
+ return prototype === Object.prototype || prototype === null;
47
65
  }
@@ -0,0 +1 @@
1
+ export * from "./style.js";
@@ -0,0 +1,185 @@
1
+ export const IMAGE_STYLE_KEYS = ["width", "height", "classes", "id", "attrs", "layout", "align"];
2
+ export const IMAGE_GLOBAL_KEYS = new Set(IMAGE_STYLE_KEYS);
3
+ export function asPlainRecord(value) {
4
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
5
+ return undefined;
6
+ }
7
+ const prototype = Object.getPrototypeOf(value);
8
+ if (prototype !== Object.prototype && prototype !== null) {
9
+ return undefined;
10
+ }
11
+ return value;
12
+ }
13
+ export function normalizeImageScalar(value) {
14
+ if (typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") {
15
+ return undefined;
16
+ }
17
+ const normalized = String(value).trim();
18
+ return normalized.length > 0 ? normalized : undefined;
19
+ }
20
+ export function normalizeImageClasses(value) {
21
+ if (typeof value === "string") {
22
+ const classes = value
23
+ .split(/\s+/)
24
+ .map((entry) => entry.trim())
25
+ .filter((entry) => entry.length > 0);
26
+ return classes;
27
+ }
28
+ if (!Array.isArray(value)) {
29
+ return undefined;
30
+ }
31
+ const classes = [];
32
+ for (const entry of value) {
33
+ if (typeof entry !== "string") {
34
+ return undefined;
35
+ }
36
+ const normalized = entry.trim();
37
+ if (!normalized) {
38
+ continue;
39
+ }
40
+ classes.push(normalized);
41
+ }
42
+ return classes;
43
+ }
44
+ export function normalizeImageAttrs(value) {
45
+ const record = asPlainRecord(value);
46
+ if (!record) {
47
+ return undefined;
48
+ }
49
+ const attrs = {};
50
+ for (const [key, raw] of Object.entries(record)) {
51
+ const normalized = normalizeImageScalar(raw);
52
+ if (!normalized) {
53
+ continue;
54
+ }
55
+ attrs[key] = normalized;
56
+ }
57
+ return Object.keys(attrs).length > 0 ? attrs : undefined;
58
+ }
59
+ export function cloneImageStyle(style) {
60
+ return {
61
+ width: style?.width,
62
+ height: style?.height,
63
+ id: style?.id,
64
+ classes: style?.classes ? [...style.classes] : undefined,
65
+ attrs: style?.attrs ? { ...style.attrs } : undefined,
66
+ layout: style?.layout,
67
+ align: style?.align
68
+ };
69
+ }
70
+ export function mergeImageStyles(base, override) {
71
+ const merged = cloneImageStyle(base);
72
+ if (override.width) {
73
+ merged.width = override.width;
74
+ }
75
+ if (override.height) {
76
+ merged.height = override.height;
77
+ }
78
+ if (override.id) {
79
+ merged.id = override.id;
80
+ }
81
+ if (override.layout) {
82
+ merged.layout = override.layout;
83
+ }
84
+ if (override.align) {
85
+ merged.align = override.align;
86
+ }
87
+ if (override.classes && override.classes.length > 0) {
88
+ merged.classes = [...override.classes];
89
+ }
90
+ const attrs = { ...(merged.attrs ?? {}) };
91
+ for (const [key, value] of Object.entries(override.attrs ?? {})) {
92
+ attrs[key] = value;
93
+ }
94
+ merged.attrs = Object.keys(attrs).length > 0 ? attrs : undefined;
95
+ return merged;
96
+ }
97
+ export function isImageStyleEmpty(style) {
98
+ if (!style) {
99
+ return true;
100
+ }
101
+ return !style.width
102
+ && !style.height
103
+ && !style.id
104
+ && !style.layout
105
+ && !style.align
106
+ && (!style.classes || style.classes.length === 0)
107
+ && (!style.attrs || Object.keys(style.attrs).length === 0);
108
+ }
109
+ export function parseImageStyle(value) {
110
+ const record = asPlainRecord(value);
111
+ if (!record) {
112
+ return undefined;
113
+ }
114
+ const style = {};
115
+ for (const [key, raw] of Object.entries(record)) {
116
+ if (key === "width" || key === "height" || key === "id") {
117
+ const normalized = normalizeImageScalar(raw);
118
+ if (normalized) {
119
+ style[key] = normalized;
120
+ }
121
+ continue;
122
+ }
123
+ if (key === "layout") {
124
+ if (raw === "block" || raw === "inline") {
125
+ style.layout = raw;
126
+ }
127
+ continue;
128
+ }
129
+ if (key === "align") {
130
+ if (raw === "left" || raw === "center" || raw === "right") {
131
+ style.align = raw;
132
+ }
133
+ continue;
134
+ }
135
+ if (key === "classes") {
136
+ const normalized = normalizeImageClasses(raw);
137
+ if (normalized) {
138
+ style.classes = normalized;
139
+ }
140
+ continue;
141
+ }
142
+ if (key === "attrs") {
143
+ const normalized = normalizeImageAttrs(raw);
144
+ if (normalized) {
145
+ style.attrs = normalized;
146
+ }
147
+ continue;
148
+ }
149
+ }
150
+ const layoutFromAttrs = style.attrs?.["data-layout"];
151
+ if (!style.layout && (layoutFromAttrs === "block" || layoutFromAttrs === "inline")) {
152
+ style.layout = layoutFromAttrs;
153
+ }
154
+ const alignFromAttrs = style.attrs?.["data-align"];
155
+ if (!style.align && (alignFromAttrs === "left" || alignFromAttrs === "center" || alignFromAttrs === "right")) {
156
+ style.align = alignFromAttrs;
157
+ }
158
+ return isImageStyleEmpty(style) ? undefined : style;
159
+ }
160
+ export function normalizeImagePathKey(value) {
161
+ return value.trim().replaceAll("\\", "/").replace(/^\.\//, "").replace(/^\/+/, "");
162
+ }
163
+ export function extractImageDestinationTarget(value) {
164
+ let target = value.trim();
165
+ if (target.startsWith("<") && target.endsWith(">")) {
166
+ target = target.slice(1, -1).trim();
167
+ }
168
+ return target
169
+ .split(/\s+"/)[0]
170
+ .split(/\s+'/)[0]
171
+ .trim();
172
+ }
173
+ export function stripImageQueryAndAnchor(target) {
174
+ return target.split("#")[0].split("?")[0].trim();
175
+ }
176
+ export function isExternalImageTarget(target) {
177
+ return target.startsWith("http://")
178
+ || target.startsWith("https://")
179
+ || target.startsWith("mailto:")
180
+ || target.startsWith("tel:")
181
+ || target.startsWith("data:");
182
+ }
183
+ export function inferEffectiveImageLayout(style) {
184
+ return style.layout ?? (style.align ? "block" : undefined);
185
+ }
@@ -3,4 +3,5 @@ export * as FrontmatterDomain from "./domain/frontmatter/index.js";
3
3
  export * as CommentsDomain from "./domain/comments/index.js";
4
4
  export * as StagesDomain from "./domain/stages/index.js";
5
5
  export * as ProjectDomain from "./domain/project/index.js";
6
+ export * as ImagesDomain from "./domain/images/index.js";
6
7
  export * as SharedUtils from "./utils/index.js";
@@ -3,15 +3,62 @@ export class CommandRegistry {
3
3
  cli;
4
4
  appContext;
5
5
  multiTokenCommandMappings = [];
6
+ helpEntries = [];
6
7
  constructor(appContext) {
7
8
  this.appContext = appContext;
8
9
  this.cli = cac("stego");
9
10
  }
10
11
  showHelp() {
11
- this.cli.outputHelp();
12
+ const commandWidth = this.helpEntries.reduce((max, entry) => Math.max(max, entry.usageName.length), 0);
13
+ const lines = [
14
+ "stego",
15
+ "",
16
+ "Usage:",
17
+ " $ stego <command> [options]",
18
+ "",
19
+ "Commands:"
20
+ ];
21
+ for (const entry of this.helpEntries) {
22
+ lines.push(` ${entry.usageName.padEnd(commandWidth + 2)}${entry.description}`);
23
+ }
24
+ lines.push("");
25
+ lines.push("For more info, run any command with the `--help` flag:");
26
+ for (const entry of this.helpEntries) {
27
+ lines.push(` $ stego ${entry.commandTokens.join(" ")} --help`);
28
+ }
29
+ this.appContext.stdout.write(`${lines.join("\n")}\n`);
30
+ }
31
+ tryShowCommandHelp(argv) {
32
+ if (!containsHelpFlag(argv)) {
33
+ return false;
34
+ }
35
+ const candidate = argv.filter((token) => token !== "--help" && token !== "-h");
36
+ const entry = this.findHelpEntry(candidate);
37
+ if (!entry) {
38
+ return false;
39
+ }
40
+ const optionRows = [...entry.options.map((option) => ({
41
+ flags: option.flags,
42
+ description: option.description ?? ""
43
+ })), { flags: "-h, --help", description: "Display this message" }];
44
+ const optionWidth = optionRows.reduce((max, row) => Math.max(max, row.flags.length), 0);
45
+ const lines = [
46
+ "stego",
47
+ "",
48
+ "Usage:",
49
+ ` $ stego ${entry.usageName}`,
50
+ "",
51
+ "Options:"
52
+ ];
53
+ for (const row of optionRows) {
54
+ lines.push(` ${row.flags.padEnd(optionWidth + 2)}${row.description}`);
55
+ }
56
+ this.appContext.stdout.write(`${lines.join("\n")}\n`);
57
+ return true;
12
58
  }
13
59
  register(spec) {
14
60
  const commandName = normalizeRegisteredCommandName(spec.name, this.multiTokenCommandMappings);
61
+ this.helpEntries.push(createCommandHelpEntry(spec.name, spec.description, spec.options ?? []));
15
62
  let command = this.cli.command(commandName, spec.description);
16
63
  for (const option of spec.options ?? []) {
17
64
  const optionConfig = option.defaultValue === undefined
@@ -46,6 +93,27 @@ export class CommandRegistry {
46
93
  }
47
94
  return this.cli.runMatchedCommand();
48
95
  }
96
+ findHelpEntry(argv) {
97
+ if (argv.length === 0) {
98
+ return null;
99
+ }
100
+ const entries = [...this.helpEntries]
101
+ .sort((a, b) => b.commandTokens.length - a.commandTokens.length);
102
+ for (const entry of entries) {
103
+ if (startsWithTokens(argv, entry.commandTokens)) {
104
+ return entry;
105
+ }
106
+ }
107
+ const colonCandidate = argv[0];
108
+ if (typeof colonCandidate === "string") {
109
+ for (const entry of entries) {
110
+ if (entry.normalizedToken === colonCandidate) {
111
+ return entry;
112
+ }
113
+ }
114
+ }
115
+ return null;
116
+ }
49
117
  }
50
118
  function normalizeRegisteredCommandName(rawName, mappings) {
51
119
  const tokens = rawName.trim().split(/\s+/).filter(Boolean);
@@ -72,6 +140,27 @@ function normalizeRegisteredCommandName(rawName, mappings) {
72
140
  ? `${normalizedToken} ${argumentTokens.join(" ")}`
73
141
  : normalizedToken;
74
142
  }
143
+ function createCommandHelpEntry(rawName, description, options) {
144
+ const tokens = rawName.trim().split(/\s+/).filter(Boolean);
145
+ const commandTokens = extractCommandTokens(tokens);
146
+ return {
147
+ usageName: rawName,
148
+ description,
149
+ commandTokens,
150
+ normalizedToken: commandTokens.join(":"),
151
+ options
152
+ };
153
+ }
154
+ function extractCommandTokens(tokens) {
155
+ const commandTokens = [];
156
+ for (const token of tokens) {
157
+ if (token.startsWith("<") || token.startsWith("[")) {
158
+ break;
159
+ }
160
+ commandTokens.push(token);
161
+ }
162
+ return commandTokens;
163
+ }
75
164
  function normalizeIncomingArgv(argv, mappings) {
76
165
  if (argv.length === 0 || mappings.length === 0) {
77
166
  return argv;
@@ -100,6 +189,9 @@ function normalizeDashInputValueArgv(argv) {
100
189
  }
101
190
  return normalized;
102
191
  }
192
+ function containsHelpFlag(argv) {
193
+ return argv.includes("--help") || argv.includes("-h");
194
+ }
103
195
  function startsWithTokens(input, expected) {
104
196
  if (input.length < expected.length) {
105
197
  return false;
@@ -19,6 +19,9 @@ export function createCliApp(appContext) {
19
19
  writeText(resolveCliVersion());
20
20
  return;
21
21
  }
22
+ if (registry.tryShowCommandHelp(appContext.argv)) {
23
+ return;
24
+ }
22
25
  try {
23
26
  await registry.run(appContext.argv);
24
27
  }
@@ -1,5 +1,15 @@
1
1
  function shouldRenderJsonError(argv) {
2
- return argv.includes("--format") && argv.includes("json");
2
+ for (let index = 0; index < argv.length; index += 1) {
3
+ const current = argv[index];
4
+ const next = argv[index + 1];
5
+ if (current === "--format" && typeof next === "string" && next.toLowerCase() === "json") {
6
+ return true;
7
+ }
8
+ if (current.startsWith("--format=") && current.slice("--format=".length).toLowerCase() === "json") {
9
+ return true;
10
+ }
11
+ }
12
+ return false;
3
13
  }
4
14
  function errorEnvelope(code, message, details) {
5
15
  return {
@@ -6,7 +6,6 @@ export function registerCommentsAddCommand(registry) {
6
6
  registry.register({
7
7
  name: "comments add <manuscript>",
8
8
  description: "Add a new comment",
9
- allowUnknownOptions: true,
10
9
  options: [
11
10
  { flags: "--message <text>", description: "Comment text" },
12
11
  { flags: "--author <name>", description: "Comment author" },
@@ -6,7 +6,6 @@ export function registerCommentsClearResolvedCommand(registry) {
6
6
  registry.register({
7
7
  name: "comments clear-resolved <manuscript>",
8
8
  description: "Clear resolved comments",
9
- allowUnknownOptions: true,
10
9
  options: [
11
10
  { flags: "--format <format>", description: "text|json" }
12
11
  ],
@@ -6,7 +6,6 @@ export function registerCommentsDeleteCommand(registry) {
6
6
  registry.register({
7
7
  name: "comments delete <manuscript>",
8
8
  description: "Delete a comment",
9
- allowUnknownOptions: true,
10
9
  options: [
11
10
  { flags: "--comment-id <id>", description: "Comment id (CMT-####)" },
12
11
  { flags: "--format <format>", description: "text|json" }
@@ -6,7 +6,6 @@ export function registerCommentsReadCommand(registry) {
6
6
  registry.register({
7
7
  name: "comments read <manuscript>",
8
8
  description: "Read comments state",
9
- allowUnknownOptions: true,
10
9
  options: [
11
10
  { flags: "--format <format>", description: "text|json" }
12
11
  ],
@@ -6,7 +6,6 @@ export function registerCommentsReplyCommand(registry) {
6
6
  registry.register({
7
7
  name: "comments reply <manuscript>",
8
8
  description: "Reply to an existing comment",
9
- allowUnknownOptions: true,
10
9
  options: [
11
10
  { flags: "--comment-id <id>", description: "Comment id (CMT-####)" },
12
11
  { flags: "--message <text>", description: "Reply text" },
@@ -6,7 +6,6 @@ export function registerCommentsSetStatusCommand(registry) {
6
6
  registry.register({
7
7
  name: "comments set-status <manuscript>",
8
8
  description: "Set comment status",
9
- allowUnknownOptions: true,
10
9
  options: [
11
10
  { flags: "--comment-id <id>", description: "Comment id (CMT-####)" },
12
11
  { flags: "--status <status>", description: "open|resolved" },
@@ -6,7 +6,6 @@ export function registerCommentsSyncAnchorsCommand(registry) {
6
6
  registry.register({
7
7
  name: "comments sync-anchors <manuscript>",
8
8
  description: "Sync comment anchors",
9
- allowUnknownOptions: true,
10
9
  options: [
11
10
  { flags: "--input <path>", description: "JSON payload path or '-'" },
12
11
  { flags: "--format <format>", description: "text|json" }
@@ -1,14 +1,25 @@
1
1
  import { nowIsoString } from "../../../platform/clock.js";
2
2
  import { renderCompiledManuscript } from "../domain/compile-structure.js";
3
+ import { rewriteMarkdownImagesForChapter } from "../domain/image-settings.js";
3
4
  import { writeCompiledOutput } from "../infra/dist-writer.js";
4
5
  export function compileManuscript(input) {
6
+ const chapters = input.chapters.map((chapter) => ({
7
+ ...chapter,
8
+ body: rewriteMarkdownImagesForChapter({
9
+ body: chapter.body,
10
+ chapterPath: chapter.path,
11
+ projectRoot: input.project.root,
12
+ projectMeta: input.project.meta,
13
+ frontmatter: chapter.metadata
14
+ })
15
+ }));
5
16
  const markdown = renderCompiledManuscript({
6
17
  generatedAt: nowIsoString(),
7
18
  projectId: input.project.id,
8
19
  title: input.project.meta.title || input.project.id,
9
20
  subtitle: input.project.meta.subtitle,
10
21
  author: input.project.meta.author,
11
- chapters: input.chapters,
22
+ chapters,
12
23
  compileStructureLevels: input.compileStructureLevels
13
24
  });
14
25
  const outputPath = writeCompiledOutput(input.project.distDir, input.project.id, markdown);
@@ -7,9 +7,8 @@ export function registerBuildCommand(registry) {
7
7
  registry.register({
8
8
  name: "build",
9
9
  description: "Compile manuscript output",
10
- allowUnknownOptions: true,
11
10
  options: [
12
- { flags: "--project <project-id>", description: "Project id" },
11
+ { flags: "-p, --project <project-id>", description: "Project id" },
13
12
  { flags: "--root <path>", description: "Workspace root path" }
14
13
  ],
15
14
  action: (context) => {
@@ -2,7 +2,6 @@ export function renderCompiledManuscript(input) {
2
2
  const tocEntries = [];
3
3
  const previousGroupValues = new Map();
4
4
  const previousGroupTitles = new Map();
5
- const entryHeadingLevel = Math.min(6, 2 + input.compileStructureLevels.length);
6
5
  const lines = [];
7
6
  lines.push(`<!-- generated: ${input.generatedAt} -->`);
8
7
  lines.push("");
@@ -59,8 +58,6 @@ export function renderCompiledManuscript(input) {
59
58
  previousGroupValues.set(level.key, currentValue);
60
59
  previousGroupTitles.set(level.key, currentTitle);
61
60
  }
62
- lines.push(`${"#".repeat(entryHeadingLevel)} ${chapter.title}`);
63
- lines.push("");
64
61
  lines.push(`<!-- source: ${chapter.relativePath} | order: ${chapter.order} | status: ${chapter.status} -->`);
65
62
  lines.push("");
66
63
  lines.push(chapter.body.trim());