stego-cli 0.1.2 → 0.1.4

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 CHANGED
@@ -1,9 +1,12 @@
1
1
  # Stego
2
2
 
3
- This workspace is a Markdown-first creative writing pipeline for short stories through novels.
3
+ Stego is a Markdown-first creative writing workflow packaged as an installable CLI: `stego-cli`.
4
+
5
+ This repository is the source for the CLI and the example/template content that `stego init` scaffolds into a new workspace.
4
6
 
5
7
  ## What this includes
6
8
 
9
+ - Installable CLI (`stego`) for creating and operating Stego workspaces
7
10
  - Project-per-folder structure inside one monorepo.
8
11
  - Flexible manuscript files with per-project metadata requirements.
9
12
  - Configurable manuscript grouping via `compileStructure.levels` (for example `part` + `chapter`).
@@ -11,23 +14,77 @@ This workspace is a Markdown-first creative writing pipeline for short stories t
11
14
  - Deterministic build into one manuscript Markdown file.
12
15
  - Stage-based quality gates (`draft` -> `final`).
13
16
  - Export abstraction (`md` always, `docx`/`pdf` via optional `pandoc`).
14
- - TypeScript-based tooling executed directly by Node (`--experimental-strip-types`).
17
+ - Example/demo projects (`docs-demo`, `plague-demo`) included in `stego init`.
15
18
 
16
- ## Quick start
19
+ ## Getting started (from npm)
17
20
 
18
21
  ```bash
19
- cd ~/Code/stego
22
+ npm install -g stego-cli
23
+
24
+ mkdir my-stego-workspace
25
+ cd my-stego-workspace
26
+ stego init
27
+
28
+ npm install
20
29
  npm run list-projects
21
- npm run new-project -- --project my-new-project --title "My New Project"
22
30
  npm run validate -- --project plague-demo
23
31
  npm run build -- --project plague-demo
24
32
  npm run check-stage -- --project plague-demo --stage revise
25
33
  npm run export -- --project plague-demo --format md
26
34
  ```
27
35
 
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`.
36
+ `stego init` scaffolds a new workspace in the current directory (it must be empty unless you pass `--force`).
37
+
38
+ Create another project in the workspace:
39
+
40
+ ```bash
41
+ stego new-project --project my-new-project --title "My New Project"
42
+ ```
43
+
44
+ `stego new-project` scaffolds `manuscript/`, `spine/`, `notes/`, and `dist/`, seeds `stego-project.json`, creates a project-local `package.json`, and writes `.vscode/extensions.json` recommendations (Stego + Saurus) for that project.
45
+
46
+ ## Running commands for a specific project
47
+
48
+ From the workspace root, target a project with `--project`:
49
+
50
+ ```bash
51
+ npm run validate -- --project plague-demo
52
+ npm run build -- --project plague-demo
53
+ npm run check-stage -- --project plague-demo --stage proof
54
+ npm run export -- --project plague-demo --format md
55
+ ```
56
+
57
+ Each project also has local scripts, so you can work from inside the project directory:
58
+
59
+ ```bash
60
+ cd projects/plague-demo
61
+ npm run validate
62
+ npm run build
63
+ npm run check-stage -- --stage proof
64
+ npm run export -- --format md
65
+ ```
66
+
67
+ ## VS Code workflow
68
+
69
+ When you are actively working on one project, open that project directory directly in VS Code (for example `projects/plague-demo`) rather than the whole workspace.
70
+
71
+ This keeps editor context focused and applies the project's recommended extensions via `projects/<project-id>/.vscode/extensions.json`.
72
+
73
+ ## Developing `stego-cli` (this repo)
74
+
75
+ If you are working on the CLI itself (this repository), use the local development scripts:
76
+
77
+ ```bash
78
+ npm install
79
+ npm run list-projects
80
+ npm run validate -- --project docs-demo
81
+ npm run build -- --project plague-demo
82
+ npm run test:compile-structure
83
+ npm run build:cli
84
+ npm run pack:dry-run
85
+ ```
86
+
87
+ These source-repo scripts run the TypeScript CLI directly with Node (`--experimental-strip-types`) for local development.
31
88
 
32
89
  ## Export requirements (DOCX/PDF)
33
90
 
@@ -63,14 +120,22 @@ npm run export -- --project plague-demo --format docx
63
120
  npm run export -- --project plague-demo --format pdf
64
121
  ```
65
122
 
66
- ## Project layout
123
+ ## Scaffolded workspace layout
67
124
 
68
125
  - `projects/<project-id>/manuscript/` source manuscript files
69
126
  - `projects/<project-id>/spine/` canonical spine category files (`spineCategories[*].notesFile`)
70
127
  - `projects/<project-id>/notes/` regular notes and planning docs
71
128
  - `projects/<project-id>/dist/` generated outputs only
129
+ - `stego.config.json` workspace configuration
72
130
  - `docs/` workflow and conventions
73
- - `tools/` build, checks, export CLI
131
+ - `.vscode/tasks.json` root VS Code tasks for common Stego commands
132
+
133
+ ## This repo layout (`stego-cli` source)
134
+
135
+ - `tools/` CLI source code and exporters
136
+ - `projects/` template/demo projects bundled by `stego init`
137
+ - `docs/` user-facing docs copied into scaffolded workspaces
138
+ - `.github/workflows/` CI + Changesets release automation
74
139
 
75
140
  ## Project spine categories
76
141
 
package/dist/stego-cli.js CHANGED
@@ -4,6 +4,7 @@ import os from "node:os";
4
4
  import path from "node:path";
5
5
  import process from "node:process";
6
6
  import { spawnSync } from "node:child_process";
7
+ import { createInterface } from "node:readline/promises";
7
8
  import { fileURLToPath } from "node:url";
8
9
  import { markdownExporter } from "./exporters/markdown-exporter.js";
9
10
  import { createPandocExporter } from "./exporters/pandoc-exporter.js";
@@ -16,6 +17,7 @@ const STATUS_RANK = {
16
17
  };
17
18
  const RESERVED_COMMENT_PREFIX = "CMT";
18
19
  const ROOT_CONFIG_FILENAME = "stego.config.json";
20
+ const PROSE_FONT_PROMPT = "Switch workspace to proportional (prose-style) font? (recommended)";
19
21
  const SCAFFOLD_GITIGNORE_CONTENT = `node_modules/
20
22
  /dist/
21
23
  .DS_Store
@@ -25,6 +27,67 @@ projects/*/dist/*
25
27
  projects/*/.vscode/settings.json
26
28
  .vscode/settings.json
27
29
  `;
30
+ const SCAFFOLD_README_CONTENT = `# Stego Workspace
31
+
32
+ This directory is a Stego writing workspace (a monorepo for one or more writing projects).
33
+
34
+ ## What was scaffolded
35
+
36
+ - \`stego.config.json\` workspace configuration
37
+ - \`projects/\` demo projects (\`docs-demo\` and \`plague-demo\`)
38
+ - \`docs/\` workflow and conventions docs
39
+ - root \`package.json\` scripts for Stego commands
40
+ - root \`.vscode/tasks.json\` tasks for common workflows
41
+
42
+ ## First run
43
+
44
+ \`\`\`bash
45
+ npm install
46
+ npm run list-projects
47
+ \`\`\`
48
+
49
+ ## Run commands for a specific project (from workspace root)
50
+
51
+ \`\`\`bash
52
+ npm run validate -- --project plague-demo
53
+ npm run build -- --project plague-demo
54
+ npm run check-stage -- --project plague-demo --stage revise
55
+ npm run export -- --project plague-demo --format md
56
+ \`\`\`
57
+
58
+ ## Work inside one project
59
+
60
+ Each project also has local scripts, so you can run commands from inside a project directory:
61
+
62
+ \`\`\`bash
63
+ cd projects/plague-demo
64
+ npm run validate
65
+ npm run build
66
+ \`\`\`
67
+
68
+ ## VS Code recommendation
69
+
70
+ When you are actively working on one project, open that project directory directly in VS Code (for example \`projects/plague-demo\`).
71
+
72
+ This keeps your editor context focused and applies the project's recommended extensions (including Stego + Saurus) for that project.
73
+
74
+ ## Create a new project
75
+
76
+ \`\`\`bash
77
+ stego new-project --project my-book --title "My Book"
78
+ \`\`\`
79
+ `;
80
+ const PROSE_MARKDOWN_EDITOR_SETTINGS = {
81
+ "[markdown]": {
82
+ "editor.fontFamily": "Inter, Helvetica Neue, Helvetica, Arial, sans-serif",
83
+ "editor.fontSize": 17,
84
+ "editor.lineHeight": 28,
85
+ "editor.wordWrap": "wordWrapColumn",
86
+ "editor.wordWrapColumn": 72,
87
+ "editor.lineNumbers": "off"
88
+ },
89
+ "markdown.preview.fontFamily": "Inter, Helvetica Neue, Helvetica, Arial, sans-serif"
90
+ };
28
91
  const PROJECT_EXTENSION_RECOMMENDATIONS = [
29
92
  "matt-gold.stego-extension",
30
93
  "matt-gold.saurus-extension"
@@ -33,8 +96,8 @@ const scriptDir = path.dirname(fileURLToPath(import.meta.url));
33
96
  const packageRoot = path.resolve(scriptDir, "..");
34
97
  let repoRoot = "";
35
98
  let config;
36
- main();
37
- function main() {
99
+ void main();
100
+ async function main() {
38
101
  const { command, options } = parseArgs(process.argv.slice(2));
39
102
  if (!command || command === "help" || command === "--help" || command === "-h") {
40
103
  printUsage();
@@ -43,7 +106,7 @@ function main() {
43
106
  try {
44
107
  switch (command) {
45
108
  case "init":
46
- initWorkspace({ force: readBooleanOption(options, "force") });
109
+ await initWorkspace({ force: readBooleanOption(options, "force") });
47
110
  return;
48
111
  case "list-projects":
49
112
  activateWorkspace(options);
@@ -51,7 +114,7 @@ function main() {
51
114
  return;
52
115
  case "new-project":
53
116
  activateWorkspace(options);
54
- createProject(readStringOption(options, "project"), readStringOption(options, "title"));
117
+ await createProject(readStringOption(options, "project"), readStringOption(options, "title"));
55
118
  return;
56
119
  case "validate": {
57
120
  activateWorkspace(options);
@@ -388,7 +451,7 @@ function findNearestFileUpward(startPath, filename) {
388
451
  current = parent;
389
452
  }
390
453
  }
391
- function initWorkspace(options) {
454
+ async function initWorkspace(options) {
392
455
  const targetRoot = process.cwd();
393
456
  const entries = fs
394
457
  .readdirSync(targetRoot, { withFileTypes: true })
@@ -398,6 +461,7 @@ function initWorkspace(options) {
398
461
  }
399
462
  const copiedPaths = [];
400
463
  writeScaffoldGitignore(targetRoot, copiedPaths);
464
+ writeScaffoldReadme(targetRoot, copiedPaths);
401
465
  copyTemplateAsset(".markdownlint.json", targetRoot, copiedPaths);
402
466
  copyTemplateAsset(".cspell.json", targetRoot, copiedPaths);
403
467
  copyTemplateAsset(ROOT_CONFIG_FILENAME, targetRoot, copiedPaths);
@@ -406,6 +470,10 @@ function initWorkspace(options) {
406
470
  copyTemplateAsset(path.join(".vscode", "tasks.json"), targetRoot, copiedPaths);
407
471
  copyTemplateAsset(path.join(".vscode", "extensions.json"), targetRoot, copiedPaths, { optional: true });
408
472
  rewriteTemplateProjectPackageScripts(targetRoot);
473
+ const enableProseFont = await promptYesNo(PROSE_FONT_PROMPT, true);
474
+ if (enableProseFont) {
475
+ writeProjectProseEditorSettings(targetRoot, copiedPaths);
476
+ }
409
477
  writeInitRootPackageJson(targetRoot);
410
478
  logLine(`Initialized Stego workspace in ${targetRoot}`);
411
479
  for (const relativePath of copiedPaths) {
@@ -418,6 +486,34 @@ function initWorkspace(options) {
418
486
  logLine(" npm run list-projects");
419
487
  logLine(" npm run validate -- --project plague-demo");
420
488
  }
489
+ async function promptYesNo(question, defaultYes) {
490
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
491
+ return defaultYes;
492
+ }
493
+ const rl = createInterface({
494
+ input: process.stdin,
495
+ output: process.stdout
496
+ });
497
+ const suffix = defaultYes ? " [Y/n] " : " [y/N] ";
498
+ try {
499
+ while (true) {
500
+ const answer = (await rl.question(`${question}${suffix}`)).trim().toLowerCase();
501
+ if (!answer) {
502
+ return defaultYes;
503
+ }
504
+ if (answer === "y" || answer === "yes") {
505
+ return true;
506
+ }
507
+ if (answer === "n" || answer === "no") {
508
+ return false;
509
+ }
510
+ console.log("Please answer y or n.");
511
+ }
512
+ }
513
+ finally {
514
+ rl.close();
515
+ }
516
+ }
421
517
  function copyTemplateAsset(sourceRelativePath, targetRoot, copiedPaths, options) {
422
518
  const sourcePath = path.join(packageRoot, sourceRelativePath);
423
519
  if (!fs.existsSync(sourcePath)) {
@@ -447,6 +543,11 @@ function writeScaffoldGitignore(targetRoot, copiedPaths) {
447
543
  fs.writeFileSync(destinationPath, SCAFFOLD_GITIGNORE_CONTENT, "utf8");
448
544
  copiedPaths.push(".gitignore");
449
545
  }
546
+ function writeScaffoldReadme(targetRoot, copiedPaths) {
547
+ const destinationPath = path.join(targetRoot, "README.md");
548
+ fs.writeFileSync(destinationPath, SCAFFOLD_README_CONTENT, "utf8");
549
+ copiedPaths.push("README.md");
550
+ }
450
551
  function shouldCopyTemplatePath(currentSourcePath) {
451
552
  const relativePath = path.relative(packageRoot, currentSourcePath);
452
553
  if (!relativePath || relativePath.startsWith("..")) {
@@ -524,6 +625,53 @@ function ensureProjectExtensionsRecommendations(projectRoot) {
524
625
  };
525
626
  fs.writeFileSync(extensionsPath, `${JSON.stringify(extensionsConfig, null, 2)}\n`, "utf8");
526
627
  }
628
+ function writeProjectProseEditorSettings(targetRoot, copiedPaths) {
629
+ const projectsRoot = path.join(targetRoot, "projects");
630
+ if (!fs.existsSync(projectsRoot)) {
631
+ return;
632
+ }
633
+ for (const entry of fs.readdirSync(projectsRoot, { withFileTypes: true })) {
634
+ if (!entry.isDirectory()) {
635
+ continue;
636
+ }
637
+ const projectRoot = path.join(projectsRoot, entry.name);
638
+ const settingsPath = writeProseEditorSettingsForProject(projectRoot);
639
+ copiedPaths.push(path.relative(targetRoot, settingsPath));
640
+ }
641
+ }
642
+ function writeProseEditorSettingsForProject(projectRoot) {
643
+ const vscodeDir = path.join(projectRoot, ".vscode");
644
+ const settingsPath = path.join(vscodeDir, "settings.json");
645
+ fs.mkdirSync(vscodeDir, { recursive: true });
646
+ let existingSettings = {};
647
+ if (fs.existsSync(settingsPath)) {
648
+ try {
649
+ const parsed = readJson(settingsPath);
650
+ if (isPlainObject(parsed)) {
651
+ existingSettings = parsed;
652
+ }
653
+ }
654
+ catch {
655
+ existingSettings = {};
656
+ }
657
+ }
658
+ const proseMarkdownSettings = isPlainObject(PROSE_MARKDOWN_EDITOR_SETTINGS["[markdown]"])
659
+ ? PROSE_MARKDOWN_EDITOR_SETTINGS["[markdown]"]
660
+ : {};
661
+ const existingMarkdownSettings = isPlainObject(existingSettings["[markdown]"])
662
+ ? existingSettings["[markdown]"]
663
+ : {};
664
+ const nextSettings = {
665
+ ...existingSettings,
666
+ "[markdown]": {
667
+ ...existingMarkdownSettings,
668
+ ...proseMarkdownSettings
669
+ },
670
+ "markdown.preview.fontFamily": PROSE_MARKDOWN_EDITOR_SETTINGS["markdown.preview.fontFamily"]
671
+ };
672
+ fs.writeFileSync(settingsPath, `${JSON.stringify(nextSettings, null, 2)}\n`, "utf8");
673
+ return settingsPath;
674
+ }
527
675
  function writeInitRootPackageJson(targetRoot) {
528
676
  const cliPackage = readJson(path.join(packageRoot, "package.json"));
529
677
  const cliVersion = typeof cliPackage.version === "string" ? cliPackage.version : "0.1.0";
@@ -565,7 +713,7 @@ function listProjects() {
565
713
  console.log(`- ${id}`);
566
714
  }
567
715
  }
568
- function createProject(projectIdOption, titleOption) {
716
+ async function createProject(projectIdOption, titleOption) {
569
717
  const projectId = (projectIdOption || "").trim();
570
718
  if (!projectId) {
571
719
  throw new Error("Project id is required. Use --project <project-id>.");
@@ -625,11 +773,19 @@ function createProject(projectIdOption, titleOption) {
625
773
  fs.writeFileSync(charactersNotesPath, "# Characters\n\n", "utf8");
626
774
  const projectExtensionsPath = path.join(projectRoot, ".vscode", "extensions.json");
627
775
  ensureProjectExtensionsRecommendations(projectRoot);
776
+ let projectSettingsPath = null;
777
+ const enableProseFont = await promptYesNo(PROSE_FONT_PROMPT, true);
778
+ if (enableProseFont) {
779
+ projectSettingsPath = writeProseEditorSettingsForProject(projectRoot);
780
+ }
628
781
  logLine(`Created project: ${path.relative(repoRoot, projectRoot)}`);
629
782
  logLine(`- ${path.relative(repoRoot, projectJsonPath)}`);
630
783
  logLine(`- ${path.relative(repoRoot, projectPackagePath)}`);
631
784
  logLine(`- ${path.relative(repoRoot, charactersNotesPath)}`);
632
785
  logLine(`- ${path.relative(repoRoot, projectExtensionsPath)}`);
786
+ if (projectSettingsPath) {
787
+ logLine(`- ${path.relative(repoRoot, projectSettingsPath)}`);
788
+ }
633
789
  }
634
790
  function getProjectIds() {
635
791
  const projectsDir = path.join(repoRoot, config.projectsDir);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stego-cli",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "description": "Installable CLI for the Stego writing monorepo workflow.",
6
6
  "bin": {