@ignacioroan/scriptorium
v2.6.1
Published
npm CLI for initializing, validating, compiling, backing up, and updating literary projects written with AI assistance.
Downloads
2,069
Readme
Scriptorium
scriptorium is an npm CLI for initializing, validating, compiling, backing up, and updating literary projects created with AI help.
This tool is designed for literary projects written in Spanish.
scriptorium is a standalone package. Each work lives in its own directory and is managed locally through its project package.json and .scriptorium/manifest.json.
Requirements
- Node.js 24 or later
- No manual Pandoc install is required on supported platforms.
scriptoriumdownloads a local bundled Pandoc duringpostinstalland falls back topandocinPATHwhen the bundled binary is unavailable.
Bootstrap
mkdir my-novel
cd my-novel
npx @ignacioroan/scriptorium@latest initThat command:
- creates the base editorial structure in the current directory
- creates the living project documentation inside
docs/, centered ondocs/dosier-narrativo.mdand the stable canon indexes - generates
AGENTS.md,CLAUDE.md, andMEMORY.mdas normal working files for the project - generates a local
package.jsonpinning the exact@ignacioroan/scriptoriumversion it was generated with, and installs it as a development dependency - writes
.scriptorium/manifest.jsonto record managed surfaces - initializes a git repository with a managed, vault-aware
.gitignoreblock and an initial commit; scriptorium versions the<vault>/.obsidianconfig and ignores only Obsidian's volatile UI-state files (<vault>/.obsidian/workspace.json,<vault>/.obsidian/workspace-mobile.json), alongside the standardnode_modules/,output/,backup/,.DS_Storeentries; if git is unavailable this step is skipped with a warning andinitstill succeeds - copies managed shared skills into
.agents/skills/and.claude/skills/ - makes
initandupdatemanage the local Obsidian vault surface in.obsidian/
After bootstrap, docs/, MEMORY.md, and the generated living documents stop being package scaffolding and become project-owned content.
Bootstrap From A Local Checkout
If you want the generated project to stay linked to a local scriptorium checkout, init accepts an absolute file: source:
node /path/to/scriptorium/bin/scriptorium.mjs init --from file:/path/to/scriptoriumWithout --from, the dependency is pinned to the exact published version used to generate the project (not latest). With --from, that file source takes precedence and is persisted in the generated project package.json, for example:
{
"devDependencies": {
"@ignacioroan/scriptorium": "file:/path/to/scriptorium"
}
}Manual setup after init
Each initialized project now ships a Spanish README.md at its root that
contains the canonical, friendly configuration checklist. That README is
the source of truth for users. The summary below documents the same
steps in English for package maintainers.
init sets up everything: the CSS snippet (justified text with a first-line
indent for manuscript scenes, in both reading and editing views), the bundled scriptorium-obsidian-plugin
(which renders the README dashboard —word summary, act/chapter word tree, project
map—, registers the punctuation insertion commands driving the native mobile
toolbar, and opens the README on startup), and sync: false as a default for new projects (you can turn Obsidian Sync on; update won't override your choice). No third-party
community plugins are required: the dashboard is native to the bundled plugin and
the character/world/scene tables use Bases (a core Obsidian feature). The user
only needs to enable "Community plugins" once per device so the bundled plugin
loads.
Also:
- Bookmark the dashboard (core plugin) for one-tap access.
Project Workflow
Inside an initialized project:
npm run validate
npm run compile -- --format md
npm run compile -- --format epub
npm run backup
npm run sync
npm run create:act
npm run create:chapter
npm run create:scene
npm run create:personaje
npm run create:mundo
npx @ignacioroan/scriptorium@latest updatenpm run sync is also a normalizer: it renumbers scene escena: fields contiguously, compacts gaps in acto_NN / capitulo_NN folder numbering (two-phase rename, safe against duplicates), rewrites path-style wikilinks that point to renamed folders, mirrors the structure into metadata.yaml, reports non-conformance warnings for files that do not match the strict naming contract, and detects and writes scene/ficha mentions (see Codex Mentions below). Run it after editing the manuscript structure by hand (for example deleting a chapter in Finder).
npm run mentions runs only the mention-detection pass without the structural normalization — useful when you have added or renamed fichas and want to refresh the mention index without renumbering anything.
npm run context assembles the ficha content relevant to a scene or an explicit list of fichas and prints it to stdout — ready to paste into an agent context window. Two forms:
# From a scene: reads personajes/lugares/objetos managed fields and loads matching fichas
npm run context -- --escena manuscrito/acto_01/capitulo_01/escena_01
# Explicit list: comma-separated ficha slugs per type
npm run context -- --personajes "Elena,Marco" --lugares "La taberna" --objetos "El mapa"Both scriptorium-prosa and scriptorium-edicion call this helper automatically before writing or reviewing, so the agent always has the full cast in context.
Codex Mentions
sync and mentions scan the manuscript and cross-reference it against the
ficha catalog in docs/personajes/ and docs/mundo/ to build a live index of
which characters, places, and objects appear in each scene.
Managed fields — written and regenerated on every run; do not hand-edit:
| Surface | Field | Content |
|---------|-------|---------|
| Each scene | personajes: | Wikilinks to matching character fichas |
| Each scene | lugares: | Wikilinks to matching place fichas |
| Each scene | objetos: | Wikilinks to matching object fichas |
| Each ficha | aparece_en: | Path-style wikilinks to every scene it appears in |
Author controls — written by you in Obsidian's Properties panel; never overwritten by scriptorium:
| Field | Effect |
|-------|--------|
| personajes_add: / lugares_add: / objetos_add: | Force an entry into the managed list even if the name was not detected in the prose |
| personajes_veto: / lugares_veto: / objetos_veto: | Drop a false positive from the managed list |
| alias: on a ficha | Alternate names the matcher also recognises |
Fichas with tipo: faccion or tipo: regla are browsable in the vault but are not tracked by the mentions system.
Project Structure
Each standalone project uses this baseline:
my-novel/
README.md
AGENTS.md
CLAUDE.md
MEMORY.md
package.json
metadata.yaml
epub.css
cover.jpg
<vault>/
manuscrito/
docs/
dosier-narrativo.md
estructura/
README.md
cronologia.md
estructura-narrativa.md
mundo/
README.md
notas/
README.md
personajes/
README.md
bases/
plantillas/
escena.md
acto.md
capitulo.md
.obsidian/
app.json
appearance.json
core-plugins.json
templates.json
community-plugins.json
plugins/
scriptorium-obsidian-plugin/
snippets/
scriptorium.css
output/
backup/
.scriptorium/
manifest.json
.agents/
skills/
.claude/
skills/Managed Surfaces
scriptorium may rewrite only these managed surfaces:
.scriptorium/manifest.jsonREADME.mdat the project root (the Spanish dashboard + initial setup checklist; rewritten on everyinitandupdate)- the local
@ignacioroan/scriptoriumdependency inpackage.json - the shared skills copied into
.agents/skills/and.claude/skills/ .obsidian/snippets/scriptorium.css(its header comment carries the scriptorium version, stamped on everyinitandupdate).obsidian/app.json(merge-addsuserIgnoreFiltersfornode_modules/,backup/,output/so they are excluded from search/graph, andmobileToolbarCommandsso the native mobile toolbar shows the plugin's typographic insertion commands; preserves your other app settings. Note: excluded folders are removed from indexing but Obsidian still shows them, greyed, in the file explorer — that cannot be changed fromapp.json)- the managed portion of
.obsidian/appearance.jsonthat ensuresenabledCssSnippetscontainsscriptorium(and removes the legacystyleentry) .obsidian/core-plugins.json(enables Bases/Templates/Bookmarks; setssync: falseas a default only when the project is first created, and never overrides your Sync toggle onupdate; preserves your other settings).obsidian/templates.json(templates folder; defaults todocs/plantillas, andupdatemigrates the old managed defaultplantillas).obsidian/community-plugins.json(merge-adds the bundledscriptorium-obsidian-plugin; strips obsolete managed ids it used to ship —scriptorium-corchera,etb-glyph-icons,homepage,typewriter-mode,editing-toolbar—; preserves your other community plugins).obsidian/plugins/scriptorium-obsidian-plugin/(the bundled Scriptorium plugin, built fromobsidian-plugin/and shipped as a pre-built artifact. It provides: the Corchera de fichas single-dashboard corkboard view —the whole manuscript at once as a collapsible tree of acts → chapters → scenes (act/chapter headers show their number and title, or a placeholder when untitled); tap a scene card to open the note; create acts, chapters, and scenes; rename acts and chapters from the view; and drag-and-drop reordering at every level (scenes within/between chapters, chapters within/between acts, and acts), which renames the folders/files and updates the managed number fields just likesync—; a "Nueva ficha" command (available in the command palette and as a ribbon icon) that opens a type selector (personaje / lugar / objeto) and creates the ficha note in the correctdocs/subfolder, seeding scene mention keys in the frontmatter — equivalent tonpm run create:personaje/npm run create:mundobut without leaving Obsidian; an opt-in "Actualizar menciones al guardar" toggle in the plugin settings that runs mention detection each time you save a scene note, sopersonajes:/lugares:/objetos:stay current without a manualsyncrun; registration of the punctuation insertion commands (the 12 typographic signs, . ; : — … ¿ ? ¡ ! « ») that drive the native mobile toolbar, plus the ability to add your own custom insertion commands from the plugin settings. Itsmanifest.jsonversion is stamped with the scriptorium version on everyinitandupdate, so Obsidian detects each release as a plugin update)- the managed
.gitignoreblock delimited by# scriptorium:start/# scriptorium:end(vault-aware: scriptorium versions the<vault>/.obsidianconfig and ignores only<vault>/.obsidian/workspace.jsonand<vault>/.obsidian/workspace-mobile.json).initwrites the block;updatemigrates legacy marker-less files (absorbing the bare legacy entries) and rewrites the block content, preserving all your lines outside the markers docs/plantillas/escena.md,docs/plantillas/acto.md, anddocs/plantillas/capitulo.md(rewritten byinitandupdatefrom the package's canonical templates; user edits to these files will be overwritten)
It does not automatically modify docs/, MEMORY.md, manuscrito/, output/, metadata.yaml, or project-specific custom skills.
For now, update also does not migrate or resync the dossier-centered documentation base, MEMORY.md, or the docs/ placeholders created by init: once created, they are project-owned content.
The intended project contract is dossier-centered: docs/dosier-narrativo.md is the single working hub where all narrative material is born — active hypotheses, open questions, relevant discards, coherence risks, and recent consolidations. When the dossier grows too large, consolidated material moves into the stable canon surfaces: character sheets in docs/personajes/, world sheets in docs/mundo/, and docs/estructura/ (which holds estructura-narrativa.md and cronologia.md). update does not migrate docs/ content in existing projects; only the managed docs/plantillas/ surface is migrated automatically.
metadata.yaml
metadata.yaml remains the single source of truth for project editorial and compilation settings:
- editorial identity
- compilation assets
- output directory and base filename
- export formats
- structured manuscript layout or monolithic input file
The literary compile format (per-level title templates and separators) lives
instead in the author-owned formato: block of <vault>/manuscrito/_manuscrito.md
(see "Compile format" below). The legacy compilation.separators keys in
metadata.yaml remain as a backward-compatibility fallback for separators only.
Compile format
compile renders each act/chapter/scene heading and the separators between
blocks from the formato: block in <vault>/manuscrito/_manuscrito.md
frontmatter. It is an author-owned, add-only surface: init ships it in the
template note, update seeds the defaults when absent, and neither overwrites an
existing block.
formato:
titulo_acto: "{titulo}"
titulo_capitulo: "{titulo}"
titulo_escena: "{titulo}"
separador_acto: "pagebreak"
separador_capitulo: "pagebreak"
separador_escena: "***"- One template per level, work-wide. Title templates take three tokens:
{numero}(the node's frontmatter number —acto:/capitulo:/escena:),{romano}(that number as uppercase Roman numerals), and{titulo}(thetitulo:field). Substitution is literal: an absent token resolves to the empty string and an unknown token is left untouched. If the resolved heading is blank, no heading is emitted. There is no hardcodedActo N/Capítulo Nfallback anymore — the author writes the numbering into the template (e.g."Capítulo {romano} — {titulo}"). - Separators:
pagebreakinserts a page break; any other non-blank string is used literally; a null/blank value joins blocks with a plain blank line.
Separator → CSS map. The template chooses what the separator is; epub.css
controls how much it breathes. *** / * * * / --- are Markdown thematic
breaks → pandoc emits <hr>; style its vertical margin with hr { margin: 2.5em 0 }
in epub.css (new projects ship this rule). A glyph that is not a thematic break
(⁂, ❋, · · ·) becomes a <p> and is styled as a paragraph. md output has
no styling; docx separator spacing is limited. epub.css is a user-owned asset
(seeded by init, never overwritten by update).
Backups
npm run backup creates a timestamped .zip of the whole project inside backup/, excluding node_modules/ and previous backups.
Restaurar un backup
Para restaurar un backup, descomprime el .zip en una carpeta y ejecuta
npx @ignacioroan/scriptorium@latest update desde la raíz del proyecto restaurado.
No uses init: solo opera sobre carpetas vacías. update reinstala las
dependencias y reconstruye cualquier superficie gestionada que falte
(incluido .scriptorium/manifest.json), respetando todo tu contenido.
Package Development
In this repository:
npm testInternal templates live in templates/project/, and the shared skill catalog lives in skills/.
This repository may also contain repository-local maintainer skills in
.agents/skills/ and .claude/skills/. Those root-level skills are for
maintaining the package itself and are not part of the shared project-facing
catalog unless they are explicitly added under skills/.
build:plugin
npm run build:pluginInstalls the obsidian-plugin/ sub-project dependencies, compiles the TypeScript source with esbuild, and stages the three built artifacts (main.js, manifest.json, styles.css) into src/assets/scriptorium-obsidian-plugin/. Run this after modifying any file in obsidian-plugin/src/. The built artifacts are committed so the published package ships a ready-to-use plugin.
Maintainer Release
Public package: @ignacioroan/scriptorium
Source repository: private GitHub repository
First Publish
npm login
npm whoami
npm test
npm pack --dry-run
npm publish --access publicAfter the first publish, configure trusted publishing for .github/workflows/publish.yml in npm.
Version Bumps
npm run version:patch
npm run version:minor
npm run version:majorThese commands remain available when you want to bump the version manually without releasing.
One-Command Release
npm run release:patch
npm run release:minor
npm run release:majorEach release:* command:
- Verifies a clean working tree on
main. - Verifies local
mainis in sync withorigin/main. - Runs the matching version bump.
- Commits
package.jsonandpackage-lock.jsonaschore: bump version to X.Y.Z. - Pushes that commit to
origin main. - Runs
npm test. - Runs
npm pack --dry-run. - Creates and pushes the release tag.
Use this path when you want the standard maintainer release in one command.
Manual Release Flow
If you want to keep the steps separate, the original commands still work:
Example:
npm run version:patch
git add package.json package-lock.json
git commit -m "chore: bump version to 0.1.2"
git push origin main
npm test
npm pack --dry-run
node ./scripts/release.mjsThe final publish step remains available as an internal script:
node ./scripts/release.mjsThat script requires:
- a clean working tree
- the current branch to be
main - local
origin/mainto be in sync - the
v<version>tag to not exist locally or remotely
Typical release flow after the first publish:
- Run
npm run release:patch,npm run release:minor, ornpm run release:major. - Monitor
.github/workflows/publish.yml. - Verify the new version on npm.
Manual alternative:
- Run
npm run version:patch,npm run version:minor, ornpm run version:major. - Commit the version change.
- Push or merge that commit to
main. - Run
npm testonmain. - Run
npm pack --dry-runonmain. - Run
node ./scripts/release.mjsonmain. - Monitor
.github/workflows/publish.yml. - Verify the new version on npm.
Security Notes
- Publishing uses npm trusted publishing with GitHub Actions OIDC.
- No long-lived
NPM_TOKENis required for the publish step. - Provenance is not claimed in this phase because the source repository is private.
scripts/install-pandoc.mjsdownloads a pinned Pandoc release, but it does not yet verify upstream checksums.
Operational runbook: docs/release-runbook.md
