@bitcobblers/wod-wiki-tools 0.36.2135 → 0.37.2137

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 (44) hide show
  1. package/dist/bin/wod.cjs +299 -16
  2. package/dist/bin/wod.cjs.map +1 -1
  3. package/dist/bin/wod.mjs +3 -2
  4. package/dist/bin/wod.mjs.map +1 -1
  5. package/dist/{chunk-OTJAPVT2.mjs → chunk-GBBJBDLS.mjs} +29 -11
  6. package/dist/chunk-GBBJBDLS.mjs.map +1 -0
  7. package/dist/{chunk-FZKXOYMJ.mjs → chunk-NATJMTAW.mjs} +47 -4
  8. package/dist/chunk-NATJMTAW.mjs.map +1 -0
  9. package/dist/chunk-SQAI6B4P.mjs +12 -0
  10. package/dist/chunk-SQAI6B4P.mjs.map +1 -0
  11. package/dist/{compiler-CTPLIZJK.mjs → compiler-B77PITYH.mjs} +3 -3
  12. package/dist/{compiler-CTPLIZJK.mjs.map → compiler-B77PITYH.mjs.map} +1 -1
  13. package/dist/index.cjs +299 -16
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.d.cts +1 -1
  16. package/dist/index.d.ts +1 -1
  17. package/dist/index.mjs +3 -2
  18. package/dist/scaffoldCatalog-LQOUCJ4X.mjs +205 -0
  19. package/dist/scaffoldCatalog-LQOUCJ4X.mjs.map +1 -0
  20. package/dist/web/playground/assets/main-CFDrblxn.css +2 -0
  21. package/dist/web/playground/assets/main-OGIV1avF.js +188 -0
  22. package/dist/web/playground/assets/{statementBuilderFlow-CCaLco1R.js → statementBuilderFlow-BL8OKlcb.js} +2 -2
  23. package/dist/web/playground/assets/{vendor-g-NJOjvC.js → vendor-CTANVdPW.js} +19 -19
  24. package/dist/web/playground/assets/{vendor-codemirror-C41SFn-r.js → vendor-codemirror-BlXDdJID.js} +8 -8
  25. package/dist/web/playground/index.html +4 -4
  26. package/dist/web/playground/seed/core/chunks/{canvas.2d3e2e0e.json → canvas.a2e32d8c.json} +1 -1
  27. package/dist/web/playground/seed/core/manifest.json +8 -5
  28. package/dist/web/playground/seed/core/report.html +9 -9
  29. package/dist/web/server/assets/main-BdUrlkaF.js +187 -0
  30. package/dist/web/server/assets/main-CFDrblxn.css +2 -0
  31. package/dist/web/server/assets/{statementBuilderFlow-MCfILbWE.js → statementBuilderFlow-xqsK_3T8.js} +2 -2
  32. package/dist/web/server/assets/{vendor-g-NJOjvC.js → vendor-CTANVdPW.js} +19 -19
  33. package/dist/web/server/assets/{vendor-codemirror-C41SFn-r.js → vendor-codemirror-BlXDdJID.js} +8 -8
  34. package/dist/web/server/index.html +4 -4
  35. package/dist/web/server/seed/core/chunks/{canvas.2d3e2e0e.json → canvas.a2e32d8c.json} +1 -1
  36. package/dist/web/server/seed/core/manifest.json +8 -5
  37. package/dist/web/server/seed/core/report.html +9 -9
  38. package/package.json +6 -6
  39. package/dist/chunk-FZKXOYMJ.mjs.map +0 -1
  40. package/dist/chunk-OTJAPVT2.mjs.map +0 -1
  41. package/dist/web/playground/assets/main-Bhn9NHgQ.css +0 -2
  42. package/dist/web/playground/assets/main-Bod1taHc.js +0 -188
  43. package/dist/web/server/assets/main-Bhn9NHgQ.css +0 -2
  44. package/dist/web/server/assets/main-DtiBHHer.js +0 -187
package/dist/bin/wod.cjs CHANGED
@@ -161,7 +161,7 @@ function sqliteEventStore(db) {
161
161
  getEventsByTimeRange: async (start, end) => (await all()).filter((r) => r.timestamp >= start && r.timestamp <= end),
162
162
  getEventsByResult: async (resultId) => (await all()).filter((r) => r.resultId === resultId),
163
163
  getEventsForNote: async (noteId) => (await all()).filter((r) => r.noteId === noteId),
164
- getEventsByContent: async (blockContentId4) => (await all()).filter((r) => r.blockContentId === blockContentId4),
164
+ getEventsByContent: async (blockContentId5) => (await all()).filter((r) => r.blockContentId === blockContentId5),
165
165
  scanAll: all,
166
166
  appendEvents: async (appended) => {
167
167
  const sql = await loadSql();
@@ -614,6 +614,31 @@ var init_report = __esm({
614
614
  "src/seedbuild/report.ts"() {
615
615
  }
616
616
  });
617
+ function object(value) {
618
+ return typeof value === "object" && value !== null && !Array.isArray(value);
619
+ }
620
+ function nonnegative(value) {
621
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
622
+ }
623
+ function assertSeedManifest2(value) {
624
+ if (!object(value) || value.schema !== SEED_SCHEMA2) throw new Error("Incompatible seed schema; rebuild seed artifacts");
625
+ if (!nonnegative(value.version) || typeof value.builtAt !== "string" || !Number.isFinite(Date.parse(value.builtAt)) || !Array.isArray(value.chunks)) throw new Error("seed manifest failed shape validation");
626
+ for (const key of ["versionLabel", "commit", "name", "description"]) if (value[key] !== void 0 && typeof value[key] !== "string") throw new Error("invalid seed manifest metadata");
627
+ if (value.repositoryUrl !== void 0 && (typeof value.repositoryUrl !== "string" || !/^https:\/\//.test(value.repositoryUrl) || new URL(value.repositoryUrl).username || new URL(value.repositoryUrl).password)) throw new Error("invalid seed repository URL");
628
+ if (value.dirty !== void 0 && typeof value.dirty !== "boolean") throw new Error("invalid seed manifest metadata");
629
+ const ids = /* @__PURE__ */ new Set(), paths = /* @__PURE__ */ new Set();
630
+ for (const chunk of value.chunks) {
631
+ if (!object(chunk) || typeof chunk.id !== "string" || !chunk.id || typeof chunk.path !== "string" || !/^chunks\/[a-zA-Z0-9._-]+\.json$/.test(chunk.path) || typeof chunk.sha256 !== "string" || !/^[0-9a-f]{64}$/.test(chunk.sha256) || !nonnegative(chunk.bytes) || !nonnegative(chunk.count) || chunk.kind !== void 0 && chunk.kind !== "notes" || ids.has(chunk.id) || paths.has(chunk.path)) throw new Error("seed manifest failed shape validation");
632
+ ids.add(chunk.id);
633
+ paths.add(chunk.path);
634
+ }
635
+ return value;
636
+ }
637
+ var init_validation = __esm({
638
+ "src/seedbuild/validation.ts"() {
639
+ init_contract();
640
+ }
641
+ });
617
642
 
618
643
  // src/seedbuild/compiler.ts
619
644
  var compiler_exports = {};
@@ -625,6 +650,7 @@ __export(compiler_exports, {
625
650
  compileSeedRow: () => compileSeedRow,
626
651
  discoverUncatalogedEfforts: () => discoverUncatalogedEfforts,
627
652
  feedDateToCreatedAt: () => feedDateToCreatedAt,
653
+ frontmatterDateToCreatedAt: () => frontmatterDateToCreatedAt,
628
654
  generateSeed: () => generateSeed,
629
655
  loadExistingEffortMap: () => loadExistingEffortMap,
630
656
  partitionIntoChunks: () => partitionIntoChunks,
@@ -726,6 +752,18 @@ function getFileCreatedAt(file, repoRoot) {
726
752
  return 0;
727
753
  }
728
754
  }
755
+ function frontmatterDateToCreatedAt(raw, path) {
756
+ const value = getScalar(parseFrontmatter(raw).meta, "date");
757
+ if (value === void 0 || value === "") return void 0;
758
+ const text = String(value);
759
+ const parts = text.match(/^(\d{4})-(\d{2})-(\d{2})$/);
760
+ const ts = parts ? Date.parse(`${text}T12:00:00Z`) : NaN;
761
+ const valid = !!parts && Number.isFinite(ts) && new Date(ts).getUTCMonth() === Number(parts[2]) - 1 && new Date(ts).getUTCDate() === Number(parts[3]);
762
+ if (!valid) {
763
+ throw new Error(`[wod build] invalid frontmatter date '${text}' in '${path}' \u2014 expected a real calendar day (YYYY-MM-DD)`);
764
+ }
765
+ return ts;
766
+ }
729
767
  function feedDateToCreatedAt(dateKey) {
730
768
  if (!/^\d{4}-\d{2}-\d{2}$/.test(dateKey)) return 0;
731
769
  const ts = Date.parse(`${dateKey}T00:00:00Z`);
@@ -753,7 +791,7 @@ function buildBlockIndexRows(markdownDir, corpusRoot) {
753
791
  const catalog = parts[2];
754
792
  const routeId = chunkId === "canvas" ? canvasRouteSlug(content) ?? void 0 : chunkId.startsWith("collection.") ? stem.toLowerCase() === "readme" ? catalog : `${catalog}/${stem}` : chunkId.startsWith("feed.") ? parts.slice(1).join("/").replace(/\.md$/, "") : void 0;
755
793
  const sourceId = seedNoteSourceId(path$1, content, chunkId);
756
- const createdAt = chunkId.startsWith("feed.") ? feedDateToCreatedAt(parts.at(-2) ?? "") : getFileCreatedAt(path$1, corpusRoot);
794
+ const createdAt = frontmatterDateToCreatedAt(content, path$1) ?? (chunkId.startsWith("feed.") ? feedDateToCreatedAt(parts.at(-2) ?? "") : getFileCreatedAt(path$1, corpusRoot));
757
795
  rows.push(...compileSeedRow(path$1, content, { routeId, sourceId, createdAt }).blockIndex);
758
796
  }
759
797
  return rows;
@@ -913,6 +951,13 @@ function generateSeed(options = {}) {
913
951
  const markdownDir = options.markdownDir ?? path.join(corpusRoot, "markdown");
914
952
  const outDir = options.outDir ?? path.join(corpusRoot, "dist");
915
953
  const gitVersion = options.version !== void 0 ? { ...resolveGitVersion(corpusRoot), version: options.version } : resolveGitVersion(corpusRoot);
954
+ const packageFile = path.join(corpusRoot, "package.json");
955
+ const packageMetadata = fs.existsSync(packageFile) ? JSON.parse(fs.readFileSync(packageFile, "utf8")) : {};
956
+ const metadata = packageMetadata && typeof packageMetadata === "object" ? packageMetadata : {};
957
+ const repository = "repository" in metadata ? metadata.repository : void 0;
958
+ const repositoryUrl = typeof repository === "string" ? repository : repository && typeof repository === "object" && "url" in repository && typeof repository.url === "string" ? repository.url : void 0;
959
+ const webRepositoryUrl = repositoryUrl?.replace(/^git\+/, "").replace(/\.git$/, "");
960
+ const repositoryLink = webRepositoryUrl && URL.canParse(webRepositoryUrl) ? new URL(webRepositoryUrl) : void 0;
916
961
  if (options.scaffoldDiscoveredEfforts) {
917
962
  const discovered = discoverUncatalogedEfforts(markdownDir, corpusRoot);
918
963
  if (discovered.length > 0) {
@@ -981,6 +1026,9 @@ ${shown}${more}`);
981
1026
  }
982
1027
  passMs.write = Date.now() - tWrite;
983
1028
  const manifest = {
1029
+ name: "name" in metadata && typeof metadata.name === "string" ? metadata.name : void 0,
1030
+ description: "description" in metadata && typeof metadata.description === "string" ? metadata.description : void 0,
1031
+ repositoryUrl: repositoryLink?.protocol === "https:" && !repositoryLink.username && !repositoryLink.password ? repositoryLink.href : void 0,
984
1032
  schema: SEED_SCHEMA2,
985
1033
  version: gitVersion.version,
986
1034
  versionLabel: gitVersion.versionLabel,
@@ -989,6 +1037,7 @@ ${shown}${more}`);
989
1037
  builtAt: (/* @__PURE__ */ new Date()).toISOString(),
990
1038
  chunks: manifestChunks
991
1039
  };
1040
+ assertSeedManifest2(manifest);
992
1041
  fs.writeFileSync(path.join(outDir, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
993
1042
  const reportPath = writeBuildReport(outDir, {
994
1043
  manifest,
@@ -1007,6 +1056,7 @@ var init_compiler = __esm({
1007
1056
  init_git_version();
1008
1057
  init_report();
1009
1058
  init_frontmatter();
1059
+ init_validation();
1010
1060
  NON_EFFORT_SLUGS = {
1011
1061
  "amrap": true,
1012
1062
  "emom": true,
@@ -1056,6 +1106,219 @@ var init_compiler = __esm({
1056
1106
  }
1057
1107
  });
1058
1108
 
1109
+ // src/version.ts
1110
+ var VERSION;
1111
+ var init_version = __esm({
1112
+ "src/version.ts"() {
1113
+ VERSION = "0.37.2137";
1114
+ }
1115
+ });
1116
+
1117
+ // src/cli/scaffoldCatalog.ts
1118
+ var scaffoldCatalog_exports = {};
1119
+ __export(scaffoldCatalog_exports, {
1120
+ scaffoldCatalog: () => scaffoldCatalog,
1121
+ validateCatalogName: () => validateCatalogName
1122
+ });
1123
+ function validateCatalogName(name) {
1124
+ if (name.length === 0 || name === "." || name === "..") {
1125
+ throw new Error("Catalog name must be a single non-empty directory name");
1126
+ }
1127
+ if (name.includes("/") || name.includes("\\") || name.includes("\0")) {
1128
+ throw new Error(`Invalid catalog name '${name}': must be a single directory name (no path separators or traversal)`);
1129
+ }
1130
+ if (/^(?:[A-Za-z]:)?[/\\]/.test(name)) {
1131
+ throw new Error(`Invalid catalog name '${name}': absolute paths are not allowed`);
1132
+ }
1133
+ if (name.length > 214 || !/^[a-z0-9][a-z0-9._-]*$/.test(name)) {
1134
+ throw new Error(
1135
+ `Invalid catalog name '${name}': must be a valid npm package name (lowercase letters, digits, dots, hyphens, underscores; no leading dot/hyphen)`
1136
+ );
1137
+ }
1138
+ return name;
1139
+ }
1140
+ function scaffoldCatalog(name, opts = {}) {
1141
+ validateCatalogName(name);
1142
+ const dir = path.resolve(opts.cwd ?? process.cwd(), name);
1143
+ try {
1144
+ fs.mkdirSync(dir);
1145
+ } catch (error) {
1146
+ if (error.code === "EEXIST") {
1147
+ throw new Error(`Refusing to overwrite '${dir}': destination already exists`, { cause: error });
1148
+ }
1149
+ throw error;
1150
+ }
1151
+ const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
1152
+ const files = [
1153
+ ["package.json", `${JSON.stringify(
1154
+ {
1155
+ name,
1156
+ version: "0.1.0",
1157
+ private: true,
1158
+ type: "module",
1159
+ description: `WOD Wiki workout catalog: ${name}`,
1160
+ scripts: {
1161
+ build: "wod build . --output dist"
1162
+ },
1163
+ devDependencies: {
1164
+ "@bitcobblers/wod-wiki-tools": `^${VERSION}`
1165
+ }
1166
+ },
1167
+ null,
1168
+ 2
1169
+ )}
1170
+ `],
1171
+ [".gitignore", "node_modules/\ndist/\n"],
1172
+ [".github/workflows/publish-catalog.yml", publishWorkflow()],
1173
+ ["markdown/collections/example/README.md", collectionReadme(name)],
1174
+ [`markdown/collections/example/morning-session.md`, exampleWorkout(date)],
1175
+ ["README.md", catalogReadme(name)]
1176
+ ];
1177
+ try {
1178
+ for (const [rel, content] of files) {
1179
+ const target = path.join(dir, rel);
1180
+ fs.mkdirSync(path.dirname(target), { recursive: true });
1181
+ fs.writeFileSync(target, content, "utf-8");
1182
+ }
1183
+ } catch (error) {
1184
+ fs.rmSync(dir, { recursive: true, force: true });
1185
+ throw error;
1186
+ }
1187
+ return { dir, files: files.map(([rel]) => rel) };
1188
+ }
1189
+ var publishWorkflow, collectionReadme, exampleWorkout, catalogReadme;
1190
+ var init_scaffoldCatalog = __esm({
1191
+ "src/cli/scaffoldCatalog.ts"() {
1192
+ init_version();
1193
+ publishWorkflow = () => `name: Publish catalog
1194
+
1195
+ # Compile the catalog with the wod CLI and deploy dist/ to GitHub Pages.
1196
+ # Requires Pages enabled: repo Settings \u2192 Pages \u2192 Source: "GitHub Actions".
1197
+
1198
+ on:
1199
+ push:
1200
+ branches: [main]
1201
+ workflow_dispatch:
1202
+
1203
+ permissions:
1204
+ contents: read
1205
+ pages: write
1206
+ id-token: write
1207
+
1208
+ concurrency:
1209
+ group: pages
1210
+ cancel-in-progress: true
1211
+
1212
+ jobs:
1213
+ publish:
1214
+ runs-on: ubuntu-latest
1215
+ environment:
1216
+ name: github-pages
1217
+ url: \${{ steps.deployment.outputs.page_url }}
1218
+ steps:
1219
+ - uses: actions/checkout@v4
1220
+ with:
1221
+ # Full history: seed manifest version stamps derive from git commit count.
1222
+ fetch-depth: 0
1223
+
1224
+ - name: Set up Bun
1225
+ uses: oven-sh/setup-bun@v2
1226
+ with:
1227
+ bun-version: 1.4.x
1228
+
1229
+ - name: Install dependencies
1230
+ run: bun install
1231
+
1232
+ - name: Build seed
1233
+ run: bunx wod build . --output dist
1234
+
1235
+ - uses: actions/configure-pages@v5
1236
+
1237
+ - name: Upload dist
1238
+ uses: actions/upload-pages-artifact@v3
1239
+ with:
1240
+ path: dist
1241
+
1242
+ - id: deployment
1243
+ uses: actions/deploy-pages@v4
1244
+ `;
1245
+ collectionReadme = (name) => `# ${name}
1246
+
1247
+ Example benchmark collection. Each \`.md\` file in this directory becomes part of
1248
+ the \`example\` collection chunk; \`README.md\` is the collection's landing page.
1249
+
1250
+ Add one file per workout:
1251
+
1252
+ \`\`\`\`markdown
1253
+ ---
1254
+ title: My workout
1255
+ date: 2026-01-01
1256
+ ---
1257
+
1258
+ # My workout
1259
+
1260
+ \`\`\`time
1261
+ (21-15-9)
1262
+ Thrusters @95lb
1263
+ Pull-ups
1264
+ \`\`\`
1265
+ \`\`\`\`
1266
+
1267
+ The \`date:\` frontmatter field sets the workout's chronological placement
1268
+ (regardless of filename or directory).
1269
+ `;
1270
+ exampleWorkout = (date) => `---
1271
+ title: Morning session
1272
+ date: ${date}
1273
+ ---
1274
+
1275
+ # Morning session
1276
+
1277
+ \`\`\`time
1278
+ (21-15-9)
1279
+ Thrusters @95lb
1280
+ Pull-ups
1281
+ \`\`\`
1282
+ `;
1283
+ catalogReadme = (name) => `# ${name}
1284
+
1285
+ A [WOD Wiki](https://github.com/bitcobblers/wod-wiki) workout catalog: plain
1286
+ Markdown workouts compiled to static, version-stamped seed chunks and published
1287
+ via GitHub Pages.
1288
+
1289
+ ## Develop
1290
+
1291
+ \`\`\`sh
1292
+ bun install
1293
+ bunx wod build . --output dist
1294
+ \`\`\`
1295
+
1296
+ Workouts live under \`markdown/\` \u2014 this scaffold starts with one example
1297
+ collection. Validate everything with \`bunx wod build\`: it gates every file on
1298
+ the Whiteboard grammar and corpus layout before writing \`dist/\`.
1299
+
1300
+ ## Publish
1301
+
1302
+ Push to GitHub and enable Pages (**Settings \u2192 Pages \u2192 Source: GitHub
1303
+ Actions**). The bundled workflow (\`.github/workflows/publish-catalog.yml\`)
1304
+ builds the catalog on every push to \`main\` and deploys \`dist/\`.
1305
+
1306
+ ## Share
1307
+
1308
+ Once published, anyone can subscribe to this catalog from a WOD Wiki instance
1309
+ at:
1310
+
1311
+ \`\`\`
1312
+ <app-url>/subscribe?source=<url-encoded published catalog URL>
1313
+ \`\`\`
1314
+
1315
+ For \`owner\`/\`repo\` on GitHub Pages the catalog URL is the deployed
1316
+ directory, \`https://owner.github.io/repo/\`, e.g.
1317
+ \`/subscribe?source=https%3A%2F%2Fowner.github.io%2Frepo%2F\`.
1318
+ `;
1319
+ }
1320
+ });
1321
+
1059
1322
  // src/server/errors.ts
1060
1323
  var HttpError;
1061
1324
  var init_errors = __esm({
@@ -1359,13 +1622,13 @@ __export(server_exports, {
1359
1622
  runCliServer: () => runCliServer
1360
1623
  });
1361
1624
  function withResolvers() {
1362
- let resolve2;
1625
+ let resolve3;
1363
1626
  let reject;
1364
1627
  const promise = new Promise((res, rej) => {
1365
- resolve2 = res;
1628
+ resolve3 = res;
1366
1629
  reject = rej;
1367
1630
  });
1368
- return { promise, resolve: resolve2, reject };
1631
+ return { promise, resolve: resolve3, reject };
1369
1632
  }
1370
1633
  function startHttpServer(handler, host, port) {
1371
1634
  const b = globalThis.Bun;
@@ -1373,7 +1636,7 @@ function startHttpServer(handler, host, port) {
1373
1636
  const s = b.serve({ port, hostname: host, fetch: handler });
1374
1637
  return Promise.resolve({ stop: () => s.stop() });
1375
1638
  }
1376
- const { promise, resolve: resolve2, reject } = withResolvers();
1639
+ const { promise, resolve: resolve3, reject } = withResolvers();
1377
1640
  const server = http__default.default.createServer(async (req, res) => {
1378
1641
  try {
1379
1642
  const url = new URL(req.url || "/", `http://${req.headers.host || `${host}:${port}`}`);
@@ -1398,7 +1661,7 @@ function startHttpServer(handler, host, port) {
1398
1661
  });
1399
1662
  server.once("error", reject);
1400
1663
  server.listen(port, host, () => {
1401
- resolve2({
1664
+ resolve3({
1402
1665
  stop: () => {
1403
1666
  const close = withResolvers();
1404
1667
  server.close(() => close.resolve());
@@ -1542,8 +1805,8 @@ async function loadLanguagePack(moduleSpec) {
1542
1805
  try {
1543
1806
  let importPath = moduleSpec;
1544
1807
  if (moduleSpec.startsWith(".") || moduleSpec.startsWith("/") || moduleSpec.includes("\\") || moduleSpec.endsWith(".ts") || moduleSpec.endsWith(".js") || moduleSpec.endsWith(".mjs")) {
1545
- const { resolve: resolve2, pathToFileURL } = getPathUtils();
1546
- const resolved = resolve2(process.cwd(), moduleSpec);
1808
+ const { resolve: resolve3, pathToFileURL } = getPathUtils();
1809
+ const resolved = resolve3(process.cwd(), moduleSpec);
1547
1810
  importPath = pathToFileURL(resolved).href;
1548
1811
  }
1549
1812
  const imported = await import(
@@ -2552,7 +2815,9 @@ async function publishCatalogSeed(db, options) {
2552
2815
  })) : compileSeedRow(notePath, note.rawContent, {
2553
2816
  routeId: `${catalogName}/${stem}`,
2554
2817
  sourceId: `collection:${catalogName}/${stem}`,
2555
- createdAt: note.createdAt,
2818
+ // Authored frontmatter date first — exports keep the post's own
2819
+ // day, never the import/publish time.
2820
+ createdAt: note.date ?? note.createdAt,
2556
2821
  noteTitle: note.title
2557
2822
  }).blockIndex;
2558
2823
  const blockEfforts = noteBlockEfforts.map((e) => ({
@@ -2894,10 +3159,8 @@ ${escapeCsvField(data.parsed.metric)},${data.scalar},${escapeCsvField(data.unit
2894
3159
  return lines.join("\n");
2895
3160
  }
2896
3161
 
2897
- // src/version.ts
2898
- var VERSION = "0.36.2135";
2899
-
2900
3162
  // src/cli/runner.ts
3163
+ init_version();
2901
3164
  function getFs2() {
2902
3165
  if (typeof globalThis.process?.versions?.node === "undefined" && typeof globalThis.Bun === "undefined") {
2903
3166
  throw new Error("File system operations are only supported in Node / Bun environments");
@@ -3049,7 +3312,7 @@ function parseCliArgs(args) {
3049
3312
  if (Number.isSafeInteger(v) && v >= 0) result.seedVersion = v;
3050
3313
  i++;
3051
3314
  } else if (!result.subcommand) {
3052
- if (arg === "parse" || arg === "run" || arg === "query" || arg === "download" || arg === "export" || arg === "import" || arg === "publish" || arg === "build" || arg === "seed" || arg === "reset" || arg === "init" || arg === "server" || arg === "serve" || arg === "playground" || arg === "help" || arg === "version") {
3315
+ if (arg === "parse" || arg === "run" || arg === "query" || arg === "download" || arg === "export" || arg === "import" || arg === "publish" || arg === "build" || arg === "seed" || arg === "reset" || arg === "init" || arg === "server" || arg === "serve" || arg === "playground" || arg === "init-catalog" || arg === "help" || arg === "version") {
3053
3316
  result.subcommand = arg;
3054
3317
  } else {
3055
3318
  result.target = arg;
@@ -3089,13 +3352,13 @@ function readBunStdinText() {
3089
3352
  async function readStdin() {
3090
3353
  const bunText = readBunStdinText();
3091
3354
  if (bunText) return bunText();
3092
- return new Promise((resolve2, reject) => {
3355
+ return new Promise((resolve3, reject) => {
3093
3356
  const chunks = [];
3094
3357
  process.stdin.on("data", (chunk) => {
3095
3358
  chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
3096
3359
  });
3097
3360
  process.stdin.on("end", () => {
3098
- resolve2(Buffer.concat(chunks).toString("utf-8"));
3361
+ resolve3(Buffer.concat(chunks).toString("utf-8"));
3099
3362
  });
3100
3363
  process.stdin.on("error", (err) => {
3101
3364
  reject(err);
@@ -3115,6 +3378,10 @@ USAGE:
3115
3378
  wod import <note|playground|session|effort|equipment> <file> [options]
3116
3379
  wod publish [catalog] [options]
3117
3380
  wod build [dir] [options]
3381
+ wod init-catalog <name>
3382
+ Scaffold a new catalog repository (package.json, example
3383
+ collection, GitHub Actions Pages workflow). Refuses an
3384
+ existing destination.
3118
3385
  wod seed [options]
3119
3386
  wod reset [options]
3120
3387
  wod init Initialize the configured database (idempotent)
@@ -3317,6 +3584,22 @@ async function cliMain(argv = process.argv.slice(2), io = {}) {
3317
3584
  }
3318
3585
  };
3319
3586
  try {
3587
+ if (parsed.subcommand === "init-catalog") {
3588
+ if (!parsed.target) {
3589
+ writeErr("[wod init-catalog] Error: Missing catalog name (a single new directory name)");
3590
+ return 1;
3591
+ }
3592
+ const { scaffoldCatalog: scaffoldCatalog2 } = await Promise.resolve().then(() => (init_scaffoldCatalog(), scaffoldCatalog_exports));
3593
+ try {
3594
+ const result = scaffoldCatalog2(parsed.target);
3595
+ writeOut(`[wod init-catalog] Created ${result.dir} (${result.files.length} files)`);
3596
+ writeOut(`Next: cd ${parsed.target} && bun install && bunx wod build . --output dist`);
3597
+ return 0;
3598
+ } catch (error) {
3599
+ writeErr(`[wod init-catalog] ${error.message}`);
3600
+ return 1;
3601
+ }
3602
+ }
3320
3603
  if (parsed.subcommand === "parse") {
3321
3604
  if (!parsed.target) {
3322
3605
  writeErr('[wod parse] Error: Missing input file or "-" for stdin');