stego-cli 0.1.1
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/.cspell.json +14 -0
- package/.gitignore +8 -0
- package/.markdownlint.json +5 -0
- package/.vscode/tasks.json +72 -0
- package/README.md +179 -0
- package/dist/exporters/exporter-types.js +1 -0
- package/dist/exporters/markdown-exporter.js +14 -0
- package/dist/exporters/pandoc-exporter.js +65 -0
- package/dist/stego-cli.js +1803 -0
- package/docs/conventions.md +182 -0
- package/docs/workflow.md +78 -0
- package/package.json +50 -0
- package/projects/docs-demo/README.md +20 -0
- package/projects/docs-demo/dist/.gitkeep +0 -0
- package/projects/docs-demo/manuscript/100-what-stego-is.md +37 -0
- package/projects/docs-demo/manuscript/200-writing-workflow.md +69 -0
- package/projects/docs-demo/manuscript/300-quality-gates.md +36 -0
- package/projects/docs-demo/manuscript/400-build-and-export.md +42 -0
- package/projects/docs-demo/notes/implementation-notes.md +17 -0
- package/projects/docs-demo/notes/style-guide.md +7 -0
- package/projects/docs-demo/package.json +10 -0
- package/projects/docs-demo/stego-project.json +9 -0
- package/projects/plague-demo/.markdownlint.json +4 -0
- package/projects/plague-demo/README.md +19 -0
- package/projects/plague-demo/dist/.gitkeep +0 -0
- package/projects/plague-demo/manuscript/100-the-commission.md +24 -0
- package/projects/plague-demo/manuscript/200-at-the-wards.md +38 -0
- package/projects/plague-demo/manuscript/300-the-hearing.md +38 -0
- package/projects/plague-demo/manuscript/400-the-final-account.md +30 -0
- package/projects/plague-demo/notes/style-guide.md +7 -0
- package/projects/plague-demo/package.json +10 -0
- package/projects/plague-demo/spine/characters.md +31 -0
- package/projects/plague-demo/spine/locations.md +33 -0
- package/projects/plague-demo/spine/sources.md +28 -0
- package/projects/plague-demo/stego-project.json +41 -0
- package/stego.config.json +56 -0
package/.cspell.json
ADDED
package/.gitignore
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "2.0.0",
|
|
3
|
+
"tasks": [
|
|
4
|
+
{
|
|
5
|
+
"label": "Stego: List Projects",
|
|
6
|
+
"type": "shell",
|
|
7
|
+
"command": "npm run list-projects",
|
|
8
|
+
"problemMatcher": []
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"label": "Stego: Validate Project",
|
|
12
|
+
"type": "shell",
|
|
13
|
+
"command": "npm run validate -- --project ${input:projectId}",
|
|
14
|
+
"problemMatcher": []
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"label": "Stego: Build Project",
|
|
18
|
+
"type": "shell",
|
|
19
|
+
"command": "npm run build -- --project ${input:projectId}",
|
|
20
|
+
"problemMatcher": []
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"label": "Stego: Check Stage",
|
|
24
|
+
"type": "shell",
|
|
25
|
+
"command": "npm run check-stage -- --project ${input:projectId} --stage ${input:stageName}",
|
|
26
|
+
"problemMatcher": []
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"label": "Stego: Export Markdown",
|
|
30
|
+
"type": "shell",
|
|
31
|
+
"command": "npm run export -- --project ${input:projectId} --format md",
|
|
32
|
+
"problemMatcher": []
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"label": "Stego: Export DOCX",
|
|
36
|
+
"type": "shell",
|
|
37
|
+
"command": "npm run export -- --project ${input:projectId} --format docx",
|
|
38
|
+
"problemMatcher": []
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"label": "Stego: Test Compile Structure",
|
|
42
|
+
"type": "shell",
|
|
43
|
+
"command": "npm",
|
|
44
|
+
"args": [
|
|
45
|
+
"run",
|
|
46
|
+
"test:compile-structure"
|
|
47
|
+
],
|
|
48
|
+
"isBackground": false
|
|
49
|
+
}
|
|
50
|
+
],
|
|
51
|
+
"inputs": [
|
|
52
|
+
{
|
|
53
|
+
"id": "projectId",
|
|
54
|
+
"type": "promptString",
|
|
55
|
+
"description": "Project id",
|
|
56
|
+
"default": "plague-demo"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"id": "stageName",
|
|
60
|
+
"type": "pickString",
|
|
61
|
+
"description": "Stage",
|
|
62
|
+
"options": [
|
|
63
|
+
"draft",
|
|
64
|
+
"revise",
|
|
65
|
+
"line-edit",
|
|
66
|
+
"proof",
|
|
67
|
+
"final"
|
|
68
|
+
],
|
|
69
|
+
"default": "draft"
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
}
|
package/README.md
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# Stego
|
|
2
|
+
|
|
3
|
+
This workspace is a Markdown-first creative writing pipeline for short stories through novels.
|
|
4
|
+
|
|
5
|
+
## What this includes
|
|
6
|
+
|
|
7
|
+
- Project-per-folder structure inside one monorepo.
|
|
8
|
+
- Flexible manuscript files with per-project metadata requirements.
|
|
9
|
+
- Configurable manuscript grouping via `compileStructure.levels` (for example `part` + `chapter`).
|
|
10
|
+
- Project-defined spine categories (configured in each `stego-project.json`).
|
|
11
|
+
- Deterministic build into one manuscript Markdown file.
|
|
12
|
+
- Stage-based quality gates (`draft` -> `final`).
|
|
13
|
+
- Export abstraction (`md` always, `docx`/`pdf` via optional `pandoc`).
|
|
14
|
+
- TypeScript-based tooling executed directly by Node (`--experimental-strip-types`).
|
|
15
|
+
|
|
16
|
+
## Quick start
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
cd ~/Code/stego
|
|
20
|
+
npm run list-projects
|
|
21
|
+
npm run new-project -- --project my-new-project --title "My New Project"
|
|
22
|
+
npm run validate -- --project plague-demo
|
|
23
|
+
npm run build -- --project plague-demo
|
|
24
|
+
npm run check-stage -- --project plague-demo --stage revise
|
|
25
|
+
npm run export -- --project plague-demo --format md
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`npm run new-project` scaffolds `manuscript/`, `spine/`, `notes/`, and `dist/`, and seeds `stego-project.json` with a default `characters` category plus `spine/characters.md`.
|
|
29
|
+
It also creates `projects/<project-id>/.vscode/settings.json` so markdown font settings apply when opening the project folder directly.
|
|
30
|
+
It also creates a project-local `package.json` so you can run `npm run validate`, `npm run build`, etc. from inside that project directory without `--project`.
|
|
31
|
+
|
|
32
|
+
## Export requirements (DOCX/PDF)
|
|
33
|
+
|
|
34
|
+
`docx` and `pdf` export require `pandoc` on your system `PATH`.
|
|
35
|
+
|
|
36
|
+
Install:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# macOS (Homebrew)
|
|
40
|
+
brew install pandoc
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# Ubuntu/Debian
|
|
45
|
+
sudo apt-get update && sudo apt-get install -y pandoc
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# Windows (winget)
|
|
50
|
+
winget install --id JohnMacFarlane.Pandoc -e
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Verify:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pandoc --version
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Then run:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
npm run export -- --project plague-demo --format docx
|
|
63
|
+
npm run export -- --project plague-demo --format pdf
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Project layout
|
|
67
|
+
|
|
68
|
+
- `projects/<project-id>/manuscript/` source manuscript files
|
|
69
|
+
- `projects/<project-id>/spine/` canonical spine category files (`spineCategories[*].notesFile`)
|
|
70
|
+
- `projects/<project-id>/notes/` regular notes and planning docs
|
|
71
|
+
- `projects/<project-id>/dist/` generated outputs only
|
|
72
|
+
- `docs/` workflow and conventions
|
|
73
|
+
- `tools/` build, checks, export CLI
|
|
74
|
+
|
|
75
|
+
## Project spine categories
|
|
76
|
+
|
|
77
|
+
Spine categories are not fixed. Each project can declare them in `stego-project.json` under `spineCategories`.
|
|
78
|
+
|
|
79
|
+
Example:
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
{
|
|
83
|
+
"spineCategories": [
|
|
84
|
+
{ "key": "cast", "prefix": "CHAR", "notesFile": "characters.md" },
|
|
85
|
+
{ "key": "places", "prefix": "LOC", "notesFile": "locations.md" },
|
|
86
|
+
{ "key": "incidents", "prefix": "EVENT", "notesFile": "timeline.md" },
|
|
87
|
+
{ "key": "ordinances", "prefix": "STATUTE", "notesFile": "ordinances.md" }
|
|
88
|
+
]
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Use those keys as metadata arrays in manuscript files (for example `cast`, `places`, `incidents`, `ordinances`).
|
|
93
|
+
Each `notesFile` is a filename resolved in `spine/` (for example `spine/characters.md`).
|
|
94
|
+
|
|
95
|
+
If `spineCategories` is omitted or empty, category-based continuity validation is disabled.
|
|
96
|
+
|
|
97
|
+
## Project metadata requirements
|
|
98
|
+
|
|
99
|
+
Base config defaults to `status`.
|
|
100
|
+
|
|
101
|
+
Each project can override required keys in `stego-project.json`:
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"requiredMetadata": ["status", "chapter", "pov", "timeline"]
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
These keys are advisory and reported as warnings when missing; they do not block validate/build/export.
|
|
110
|
+
Files may omit metadata entirely.
|
|
111
|
+
|
|
112
|
+
## Compile structure (grouped manuscript output)
|
|
113
|
+
|
|
114
|
+
Build grouping is configured per project with `compileStructure.levels`.
|
|
115
|
+
|
|
116
|
+
Example:
|
|
117
|
+
|
|
118
|
+
```json
|
|
119
|
+
{
|
|
120
|
+
"compileStructure": {
|
|
121
|
+
"levels": [
|
|
122
|
+
{
|
|
123
|
+
"key": "part",
|
|
124
|
+
"label": "Part",
|
|
125
|
+
"titleKey": "part_title",
|
|
126
|
+
"injectHeading": true,
|
|
127
|
+
"headingTemplate": "{label} {value}: {title}",
|
|
128
|
+
"pageBreak": "between-groups"
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"key": "chapter",
|
|
132
|
+
"label": "Chapter",
|
|
133
|
+
"titleKey": "chapter_title",
|
|
134
|
+
"injectHeading": true,
|
|
135
|
+
"headingTemplate": "{label} {value}: {title}",
|
|
136
|
+
"pageBreak": "between-groups"
|
|
137
|
+
}
|
|
138
|
+
]
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Notes:
|
|
144
|
+
|
|
145
|
+
- `pageBreak` currently supports `none` or `between-groups`.
|
|
146
|
+
- TOC entries are nested by level depth.
|
|
147
|
+
- Missing group key/title values inherit from the previous manuscript file, so you only need to set metadata at structural boundaries.
|
|
148
|
+
- `validate` reports configuration errors for invalid `compileStructure` entries.
|
|
149
|
+
|
|
150
|
+
## Included examples
|
|
151
|
+
|
|
152
|
+
- `plague-demo`: full configuration — rich metadata (`pov`, `timeline`), three spine categories (`characters`, `locations`, `sources`), cross-linked spine with Wikipedia reference links
|
|
153
|
+
- `docs-demo`: nonfiction documentation configuration — no spine categories, freeform notes only, primarily `status` metadata
|
|
154
|
+
|
|
155
|
+
## Placeholder edit workflow (`{{...}}` + Cmd+I)
|
|
156
|
+
|
|
157
|
+
This repo includes Copilot instruction files to keep placeholder edits scoped:
|
|
158
|
+
|
|
159
|
+
- `.github/copilot-instructions.md`
|
|
160
|
+
- `.github/instructions/placeholder-fill.instructions.md`
|
|
161
|
+
|
|
162
|
+
Placeholder convention:
|
|
163
|
+
|
|
164
|
+
- Write draft placeholders as `{{...}}` in manuscript prose.
|
|
165
|
+
- Select the placeholder text and run Cmd+I.
|
|
166
|
+
- Use short prompts like `fill placeholder` or `replace only inside {{}}`.
|
|
167
|
+
|
|
168
|
+
Expected behavior:
|
|
169
|
+
|
|
170
|
+
- Replace only the content inside braces.
|
|
171
|
+
- Preserve surrounding sentence/paragraph text.
|
|
172
|
+
|
|
173
|
+
## Next Steps
|
|
174
|
+
|
|
175
|
+
- Add Mermaid graphs of metadata (entity relationships, co-occurrence, chapter sequence).
|
|
176
|
+
|
|
177
|
+
## VS Code tasks
|
|
178
|
+
|
|
179
|
+
Tasks are defined in `.vscode/tasks.json` for validate/build/stage-check/export.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export const markdownExporter = {
|
|
4
|
+
id: "md",
|
|
5
|
+
description: "Copy compiled manuscript markdown",
|
|
6
|
+
canRun() {
|
|
7
|
+
return { ok: true };
|
|
8
|
+
},
|
|
9
|
+
run({ inputPath, outputPath }) {
|
|
10
|
+
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
11
|
+
fs.copyFileSync(inputPath, outputPath);
|
|
12
|
+
return { outputPath };
|
|
13
|
+
}
|
|
14
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
function hasPandoc() {
|
|
5
|
+
const result = spawnSync("pandoc", ["--version"], { stdio: "ignore" });
|
|
6
|
+
return result.status === 0;
|
|
7
|
+
}
|
|
8
|
+
function hasCommand(command) {
|
|
9
|
+
const result = spawnSync("which", [command], { stdio: "ignore" });
|
|
10
|
+
return result.status === 0;
|
|
11
|
+
}
|
|
12
|
+
function resolvePdfEngine() {
|
|
13
|
+
const preferredEngines = ["tectonic", "xelatex", "lualatex", "pdflatex", "wkhtmltopdf", "weasyprint", "prince", "typst"];
|
|
14
|
+
for (const engine of preferredEngines) {
|
|
15
|
+
if (hasCommand(engine)) {
|
|
16
|
+
return engine;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
function getMissingPdfEngineReason() {
|
|
22
|
+
return "No PDF engine found. Install one of: tectonic, xelatex, lualatex, pdflatex, wkhtmltopdf, weasyprint, prince, or typst.";
|
|
23
|
+
}
|
|
24
|
+
export function createPandocExporter(format) {
|
|
25
|
+
return {
|
|
26
|
+
id: format,
|
|
27
|
+
description: `Export ${format.toUpperCase()} with pandoc`,
|
|
28
|
+
canRun() {
|
|
29
|
+
if (!hasPandoc()) {
|
|
30
|
+
return {
|
|
31
|
+
ok: false,
|
|
32
|
+
reason: "pandoc is not installed. Install pandoc to enable docx/pdf/epub exports."
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
if (format === "pdf" && !resolvePdfEngine()) {
|
|
36
|
+
return {
|
|
37
|
+
ok: false,
|
|
38
|
+
reason: getMissingPdfEngineReason()
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
return { ok: true };
|
|
42
|
+
},
|
|
43
|
+
run({ inputPath, outputPath }) {
|
|
44
|
+
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
45
|
+
const args = [inputPath, "-o", outputPath];
|
|
46
|
+
if (format === "pdf") {
|
|
47
|
+
const engine = resolvePdfEngine();
|
|
48
|
+
if (!engine) {
|
|
49
|
+
throw new Error(getMissingPdfEngineReason());
|
|
50
|
+
}
|
|
51
|
+
args.push(`--pdf-engine=${engine}`);
|
|
52
|
+
}
|
|
53
|
+
const result = spawnSync("pandoc", args, {
|
|
54
|
+
encoding: "utf8"
|
|
55
|
+
});
|
|
56
|
+
if (result.status !== 0) {
|
|
57
|
+
const stderr = (result.stderr || "").trim();
|
|
58
|
+
const stdout = (result.stdout || "").trim();
|
|
59
|
+
const details = stderr || stdout || "Unknown pandoc error";
|
|
60
|
+
throw new Error(`pandoc export failed: ${details}`);
|
|
61
|
+
}
|
|
62
|
+
return { outputPath };
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|