stego-cli 0.1.7 → 0.2.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/README.md CHANGED
@@ -19,6 +19,7 @@ npm install
19
19
  stego list-projects
20
20
  stego validate --project fiction-example
21
21
  stego build --project fiction-example
22
+ stego new --project fiction-example
22
23
  ```
23
24
 
24
25
  `stego init` scaffolds two example projects:
@@ -26,7 +27,7 @@ stego build --project fiction-example
26
27
  - `stego-docs` (the full documentation project)
27
28
  - `fiction-example` (a fiction-oriented demo with rich Spine usage)
28
29
 
29
- For day-to-day editing, open a project folder in VS Code (for example `projects/stego-docs`) and use the Stego VS Code extension, which is the official UI for Stego projects.
30
+ For day-to-day editing, open a project folder in VS Code (for example `projects/stego-docs`) and use the [Stego VS Code extension](https://github.com/matt-gold/stego/tree/main/packages/stego-extension), which is the official UI for Stego projects.
30
31
 
31
32
  ## Full documentation
32
33
 
@@ -48,6 +49,7 @@ Run commands from the workspace root and target a project with `--project`.
48
49
  ```bash
49
50
  stego list-projects
50
51
  stego new-project --project my-book --title "My Book"
52
+ stego new --project fiction-example
51
53
  stego validate --project fiction-example
52
54
  stego build --project fiction-example
53
55
  stego check-stage --project fiction-example --stage revise
package/dist/stego-cli.js CHANGED
@@ -16,6 +16,7 @@ const STATUS_RANK = {
16
16
  final: 4
17
17
  };
18
18
  const RESERVED_COMMENT_PREFIX = "CMT";
19
+ const DEFAULT_NEW_MANUSCRIPT_SLUG = "new-document";
19
20
  const ROOT_CONFIG_FILENAME = "stego.config.json";
20
21
  const PROSE_FONT_PROMPT = "Switch workspace to proportional (prose-style) font? (recommended)";
21
22
  const SCAFFOLD_GITIGNORE_CONTENT = `node_modules/
@@ -54,6 +55,7 @@ stego validate --project fiction-example
54
55
  stego build --project fiction-example
55
56
  stego check-stage --project fiction-example --stage revise
56
57
  stego export --project fiction-example --format md
58
+ stego new --project fiction-example
57
59
  \`\`\`
58
60
 
59
61
  ## Work inside one project
@@ -77,6 +79,12 @@ This keeps your editor context focused and applies the project's recommended ext
77
79
  \`\`\`bash
78
80
  stego new-project --project my-book --title "My Book"
79
81
  \`\`\`
82
+
83
+ ## Add a new manuscript file
84
+
85
+ \`\`\`bash
86
+ stego new --project fiction-example
87
+ \`\`\`
80
88
  `;
81
89
  const PROSE_MARKDOWN_EDITOR_SETTINGS = {
82
90
  "[markdown]": {
@@ -117,6 +125,13 @@ async function main() {
117
125
  activateWorkspace(options);
118
126
  await createProject(readStringOption(options, "project"), readStringOption(options, "title"));
119
127
  return;
128
+ case "new": {
129
+ activateWorkspace(options);
130
+ const project = resolveProject(readStringOption(options, "project"));
131
+ const createdPath = createNewManuscript(project, readStringOption(options, "i"));
132
+ logLine(`Created manuscript: ${createdPath}`);
133
+ return;
134
+ }
120
135
  case "validate": {
121
136
  activateWorkspace(options);
122
137
  const project = resolveProject(readStringOption(options, "project"));
@@ -394,18 +409,36 @@ function parseArgs(argv) {
394
409
  const options = { _: [] };
395
410
  for (let i = 0; i < rest.length; i += 1) {
396
411
  const token = rest[i];
397
- if (!token.startsWith("--")) {
398
- options._.push(token);
412
+ if (token === "--") {
413
+ options._.push(...rest.slice(i + 1));
414
+ break;
415
+ }
416
+ if (token.startsWith("--")) {
417
+ const key = token.slice(2);
418
+ const next = rest[i + 1];
419
+ if (!next || next.startsWith("-")) {
420
+ options[key] = true;
421
+ continue;
422
+ }
423
+ options[key] = next;
424
+ i += 1;
399
425
  continue;
400
426
  }
401
- const key = token.slice(2);
402
- const next = rest[i + 1];
403
- if (!next || next.startsWith("--")) {
404
- options[key] = true;
427
+ if (token.startsWith("-") && token.length > 1) {
428
+ const key = token.slice(1);
429
+ const next = rest[i + 1];
430
+ if (!next || next.startsWith("-")) {
431
+ options[key] = true;
432
+ continue;
433
+ }
434
+ options[key] = next;
435
+ i += 1;
436
+ continue;
437
+ }
438
+ if (!token.startsWith("--")) {
439
+ options._.push(token);
405
440
  continue;
406
441
  }
407
- options[key] = next;
408
- i += 1;
409
442
  }
410
443
  return { command, options };
411
444
  }
@@ -607,6 +640,7 @@ function rewriteTemplateProjectPackageScripts(targetRoot) {
607
640
  scripts.build = "npx --no-install stego build";
608
641
  scripts["check-stage"] = "npx --no-install stego check-stage";
609
642
  scripts.export = "npx --no-install stego export";
643
+ scripts.new = "npx --no-install stego new";
610
644
  projectPackage.scripts = scripts;
611
645
  fs.writeFileSync(packageJsonPath, `${JSON.stringify(projectPackage, null, 2)}\n`, "utf8");
612
646
  ensureProjectExtensionsRecommendations(projectRoot);
@@ -697,6 +731,7 @@ function writeInitRootPackageJson(targetRoot) {
697
731
  scripts: {
698
732
  "list-projects": "stego list-projects",
699
733
  "new-project": "stego new-project",
734
+ new: "stego new",
700
735
  lint: "stego lint",
701
736
  validate: "stego validate",
702
737
  build: "stego build",
@@ -712,7 +747,7 @@ function writeInitRootPackageJson(targetRoot) {
712
747
  fs.writeFileSync(path.join(targetRoot, "package.json"), `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
713
748
  }
714
749
  function printUsage() {
715
- console.log(`Stego CLI\n\nCommands:\n init [--force]\n list-projects [--root <path>]\n new-project --project <project-id> [--title <title>] [--root <path>]\n validate --project <project-id> [--file <project-relative-manuscript-path>] [--root <path>]\n build --project <project-id> [--root <path>]\n check-stage --project <project-id> --stage <draft|revise|line-edit|proof|final> [--file <project-relative-manuscript-path>] [--root <path>]\n lint --project <project-id> [--manuscript|--spine] [--root <path>]\n export --project <project-id> --format <md|docx|pdf|epub> [--output <path>] [--root <path>]\n`);
750
+ console.log(`Stego CLI\n\nCommands:\n init [--force]\n list-projects [--root <path>]\n new-project --project <project-id> [--title <title>] [--root <path>]\n new --project <project-id> [--i <prefix>|-i <prefix>] [--root <path>]\n validate --project <project-id> [--file <project-relative-manuscript-path>] [--root <path>]\n build --project <project-id> [--root <path>]\n check-stage --project <project-id> --stage <draft|revise|line-edit|proof|final> [--file <project-relative-manuscript-path>] [--root <path>]\n lint --project <project-id> [--manuscript|--spine] [--root <path>]\n export --project <project-id> --format <md|docx|pdf|epub> [--output <path>] [--root <path>]\n`);
716
751
  }
717
752
  function listProjects() {
718
753
  const ids = getProjectIds();
@@ -774,6 +809,7 @@ async function createProject(projectIdOption, titleOption) {
774
809
  name: `stego-project-${projectId}`,
775
810
  private: true,
776
811
  scripts: {
812
+ new: "npx --no-install stego new",
777
813
  lint: "npx --no-install stego lint",
778
814
  validate: "npx --no-install stego validate",
779
815
  build: "npx --no-install stego build",
@@ -813,6 +849,96 @@ Start writing here.
813
849
  logLine(`- ${path.relative(repoRoot, projectSettingsPath)}`);
814
850
  }
815
851
  }
852
+ function createNewManuscript(project, requestedPrefixRaw) {
853
+ fs.mkdirSync(project.manuscriptDir, { recursive: true });
854
+ const requiredMetadataState = resolveRequiredMetadata(project, config);
855
+ const requiredMetadataErrors = requiredMetadataState.issues
856
+ .filter((issue) => issue.level === "error")
857
+ .map((issue) => issue.message);
858
+ if (requiredMetadataErrors.length > 0) {
859
+ throw new Error(`Unable to resolve required metadata for project '${project.id}': ${requiredMetadataErrors.join(" ")}`);
860
+ }
861
+ const existingEntries = listManuscriptOrderEntries(project.manuscriptDir);
862
+ const explicitPrefix = parseManuscriptPrefix(requestedPrefixRaw);
863
+ const nextPrefix = explicitPrefix ?? inferNextManuscriptPrefix(existingEntries);
864
+ const collision = existingEntries.find((entry) => entry.order === nextPrefix);
865
+ if (collision) {
866
+ throw new Error(`Manuscript prefix '${nextPrefix}' is already used by '${collision.filename}'. Re-run with --i <number> to choose an unused prefix.`);
867
+ }
868
+ const filename = `${nextPrefix}-${DEFAULT_NEW_MANUSCRIPT_SLUG}.md`;
869
+ const manuscriptPath = path.join(project.manuscriptDir, filename);
870
+ const content = renderNewManuscriptTemplate(requiredMetadataState.requiredMetadata);
871
+ fs.writeFileSync(manuscriptPath, content, "utf8");
872
+ return path.relative(repoRoot, manuscriptPath);
873
+ }
874
+ function listManuscriptOrderEntries(manuscriptDir) {
875
+ if (!fs.existsSync(manuscriptDir)) {
876
+ return [];
877
+ }
878
+ return fs
879
+ .readdirSync(manuscriptDir, { withFileTypes: true })
880
+ .filter((entry) => entry.isFile() && entry.name.endsWith(".md"))
881
+ .map((entry) => {
882
+ const match = entry.name.match(/^(\d+)[-_]/);
883
+ if (!match) {
884
+ return null;
885
+ }
886
+ return {
887
+ order: Number(match[1]),
888
+ filename: entry.name
889
+ };
890
+ })
891
+ .filter((entry) => entry !== null)
892
+ .sort((a, b) => {
893
+ if (a.order === b.order) {
894
+ return a.filename.localeCompare(b.filename);
895
+ }
896
+ return a.order - b.order;
897
+ });
898
+ }
899
+ function parseManuscriptPrefix(raw) {
900
+ if (raw == null) {
901
+ return undefined;
902
+ }
903
+ const normalized = raw.trim();
904
+ if (!normalized) {
905
+ throw new Error("Option --i/-i requires a numeric value.");
906
+ }
907
+ if (!/^\d+$/.test(normalized)) {
908
+ throw new Error(`Invalid manuscript prefix '${raw}'. Use a non-negative integer.`);
909
+ }
910
+ const parsed = Number(normalized);
911
+ if (!Number.isSafeInteger(parsed)) {
912
+ throw new Error(`Invalid manuscript prefix '${raw}'. Use a smaller integer value.`);
913
+ }
914
+ return parsed;
915
+ }
916
+ function inferNextManuscriptPrefix(entries) {
917
+ if (entries.length === 0) {
918
+ return 100;
919
+ }
920
+ if (entries.length === 1) {
921
+ return entries[0].order + 100;
922
+ }
923
+ const previous = entries[entries.length - 2].order;
924
+ const latest = entries[entries.length - 1].order;
925
+ const step = latest - previous;
926
+ return latest + (step > 0 ? step : 1);
927
+ }
928
+ function renderNewManuscriptTemplate(requiredMetadata) {
929
+ const lines = ["---", "status: draft"];
930
+ const seenKeys = new Set(["status"]);
931
+ for (const key of requiredMetadata) {
932
+ const normalized = key.trim();
933
+ if (!normalized || seenKeys.has(normalized)) {
934
+ continue;
935
+ }
936
+ seenKeys.add(normalized);
937
+ lines.push(`${normalized}:`);
938
+ }
939
+ lines.push("---", "", "# New Document", "");
940
+ return `${lines.join("\n")}\n`;
941
+ }
816
942
  function getProjectIds() {
817
943
  const projectsDir = path.join(repoRoot, config.projectsDir);
818
944
  if (!fs.existsSync(projectsDir)) {
package/package.json CHANGED
@@ -1,17 +1,18 @@
1
1
  {
2
2
  "name": "stego-cli",
3
- "version": "0.1.7",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "description": "Installable CLI for the Stego writing monorepo workflow.",
6
6
  "license": "Apache-2.0",
7
7
  "repository": {
8
8
  "type": "git",
9
- "url": "https://github.com/matt-gold/stego-cli.git"
9
+ "url": "https://github.com/matt-gold/stego.git",
10
+ "directory": "packages/stego-cli"
10
11
  },
11
12
  "bugs": {
12
- "url": "https://github.com/matt-gold/stego-cli/issues"
13
+ "url": "https://github.com/matt-gold/stego/issues"
13
14
  },
14
- "homepage": "https://github.com/matt-gold/stego-cli#readme",
15
+ "homepage": "https://github.com/matt-gold/stego/tree/main/packages/stego-cli#readme",
15
16
  "bin": {
16
17
  "stego": "dist/stego-cli.js"
17
18
  },
@@ -42,6 +43,7 @@
42
43
  "release:version": "changeset version",
43
44
  "list-projects": "node --experimental-strip-types tools/stego-cli.ts list-projects",
44
45
  "new-project": "node --experimental-strip-types tools/stego-cli.ts new-project",
46
+ "new": "node --experimental-strip-types tools/stego-cli.ts new",
45
47
  "lint": "node --experimental-strip-types tools/stego-cli.ts lint",
46
48
  "validate": "node --experimental-strip-types tools/stego-cli.ts validate",
47
49
  "build": "node --experimental-strip-types tools/stego-cli.ts build",
@@ -2,6 +2,7 @@
2
2
  "name": "stego-project-fiction-example",
3
3
  "private": true,
4
4
  "scripts": {
5
+ "new": "node --experimental-strip-types ../../tools/stego-cli.ts new",
5
6
  "validate": "node --experimental-strip-types ../../tools/stego-cli.ts validate",
6
7
  "build": "node --experimental-strip-types ../../tools/stego-cli.ts build",
7
8
  "check-stage": "node --experimental-strip-types ../../tools/stego-cli.ts check-stage",
@@ -10,6 +10,7 @@ concepts:
10
10
  commands:
11
11
  - CMD-LIST-PROJECTS
12
12
  - CMD-NEW-PROJECT
13
+ - CMD-NEW
13
14
  - CMD-VALIDATE
14
15
  - CMD-BUILD
15
16
  - CMD-CHECK-STAGE
@@ -41,6 +42,7 @@ From the workspace root, target a project with `--project`.
41
42
 
42
43
  ```bash
43
44
  stego list-projects
45
+ stego new --project fiction-example
44
46
  stego validate --project fiction-example
45
47
  stego build --project fiction-example
46
48
  stego check-stage --project fiction-example --stage revise
@@ -53,6 +55,7 @@ Projects also include local npm scripts. These are useful when you want to stay
53
55
 
54
56
  ```bash
55
57
  cd projects/fiction-example
58
+ npm run new
56
59
  npm run validate
57
60
  npm run build
58
61
  npm run check-stage -- --stage revise
@@ -2,6 +2,7 @@
2
2
  "name": "stego-project-stego-docs",
3
3
  "private": true,
4
4
  "scripts": {
5
+ "new": "node --experimental-strip-types ../../tools/stego-cli.ts new",
5
6
  "validate": "node --experimental-strip-types ../../tools/stego-cli.ts validate",
6
7
  "build": "node --experimental-strip-types ../../tools/stego-cli.ts build",
7
8
  "check-stage": "node --experimental-strip-types ../../tools/stego-cli.ts check-stage",
@@ -25,6 +25,15 @@ label: stego new-project --project <project-id> [--title <title>] [--root <path>
25
25
  - Related workflows: FLOW-NEW-PROJECT.
26
26
  - Related concepts: CON-PROJECT, CON-MANUSCRIPT, CON-NOTES, CON-DIST.
27
27
 
28
+ ## CMD-NEW
29
+ label: stego new --project <project-id> [--i <prefix>|-i <prefix>] [--root <path>]
30
+
31
+ - `stego new --project <project-id> [--i <prefix>|-i <prefix>] [--root <path>]`
32
+ - Create a new manuscript file with an inferred numeric prefix and draft frontmatter.
33
+ - Related workflows: FLOW-DAILY-WRITING.
34
+ - Related concepts: CON-MANUSCRIPT, CON-METADATA.
35
+ - Related configuration: CFG-REQUIRED-METADATA.
36
+
28
37
  ## CMD-VALIDATE
29
38
  label: stego validate --project <project-id> [--file <project-relative-manuscript-path>] [--root <path>]
30
39