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