@bitcobblers/wod-wiki-tools 0.0.0-stage → 0.36.2133

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 (74) hide show
  1. package/dist/bin/wod.cjs +3532 -0
  2. package/dist/bin/wod.cjs.map +1 -0
  3. package/dist/bin/wod.d.cts +1 -0
  4. package/dist/bin/wod.d.ts +1 -0
  5. package/dist/bin/wod.mjs +17 -0
  6. package/dist/bin/wod.mjs.map +1 -0
  7. package/dist/chunk-6L5ZDZJ7.mjs +15 -0
  8. package/dist/chunk-6L5ZDZJ7.mjs.map +1 -0
  9. package/dist/chunk-DZESCGCM.mjs +123 -0
  10. package/dist/chunk-DZESCGCM.mjs.map +1 -0
  11. package/dist/chunk-EUQWEX6Z.mjs +253 -0
  12. package/dist/chunk-EUQWEX6Z.mjs.map +1 -0
  13. package/dist/chunk-K3GZZFAC.mjs +300 -0
  14. package/dist/chunk-K3GZZFAC.mjs.map +1 -0
  15. package/dist/chunk-OZI2FYPF.mjs +2061 -0
  16. package/dist/chunk-OZI2FYPF.mjs.map +1 -0
  17. package/dist/compiler-KT6P3AC6.mjs +730 -0
  18. package/dist/compiler-KT6P3AC6.mjs.map +1 -0
  19. package/dist/index.cjs +3548 -0
  20. package/dist/index.cjs.map +1 -0
  21. package/dist/index.d.cts +356 -0
  22. package/dist/index.d.ts +356 -0
  23. package/dist/index.mjs +12 -0
  24. package/dist/index.mjs.map +1 -0
  25. package/dist/server/app.cjs +299 -0
  26. package/dist/server/app.cjs.map +1 -0
  27. package/dist/server/app.d.cts +24 -0
  28. package/dist/server/app.d.ts +24 -0
  29. package/dist/server/app.mjs +9 -0
  30. package/dist/server/app.mjs.map +1 -0
  31. package/dist/server-Z2T7IGZX.mjs +11 -0
  32. package/dist/server-Z2T7IGZX.mjs.map +1 -0
  33. package/dist/web/playground/ai-skills/parse.md +656 -0
  34. package/dist/web/playground/assets/main-Bh_mthqc.js +212 -0
  35. package/dist/web/playground/assets/main-Bhn9NHgQ.css +2 -0
  36. package/dist/web/playground/assets/rolldown-runtime-C0FnF6B9.js +1 -0
  37. package/dist/web/playground/assets/statementBuilderFlow-gvzmxfiE.js +2 -0
  38. package/dist/web/playground/assets/vendor-BlJqgFME.js +201 -0
  39. package/dist/web/playground/assets/vendor-codemirror-BtJgoBjf.js +40 -0
  40. package/dist/web/playground/fonts/Inter-Regular.woff2 +0 -0
  41. package/dist/web/playground/fonts/Inter-SemiBold.woff2 +0 -0
  42. package/dist/web/playground/images/hero/editing.png +0 -0
  43. package/dist/web/playground/images/hero/journal.png +0 -0
  44. package/dist/web/playground/images/hero/results.png +0 -0
  45. package/dist/web/playground/images/hero/timer.png +0 -0
  46. package/dist/web/playground/images/wod-wiki-logo-dark.png +0 -0
  47. package/dist/web/playground/images/wod-wiki-logo-light.png +0 -0
  48. package/dist/web/playground/index.html +60 -0
  49. package/dist/web/playground/seed/core/chunks/canvas.d2a5e6e7.json +1 -0
  50. package/dist/web/playground/seed/core/chunks/template.e5e0e82d.json +1 -0
  51. package/dist/web/playground/seed/core/manifest.json +24 -0
  52. package/dist/web/playground/seed/core/report.html +43 -0
  53. package/dist/web/server/ai-skills/parse.md +656 -0
  54. package/dist/web/server/assets/main-Bhn9NHgQ.css +2 -0
  55. package/dist/web/server/assets/main-CPRFCrjV.js +211 -0
  56. package/dist/web/server/assets/rolldown-runtime-C0FnF6B9.js +1 -0
  57. package/dist/web/server/assets/statementBuilderFlow-DT1tyXgc.js +2 -0
  58. package/dist/web/server/assets/vendor-BlJqgFME.js +201 -0
  59. package/dist/web/server/assets/vendor-codemirror-BtJgoBjf.js +40 -0
  60. package/dist/web/server/fonts/Inter-Regular.woff2 +0 -0
  61. package/dist/web/server/fonts/Inter-SemiBold.woff2 +0 -0
  62. package/dist/web/server/images/hero/editing.png +0 -0
  63. package/dist/web/server/images/hero/journal.png +0 -0
  64. package/dist/web/server/images/hero/results.png +0 -0
  65. package/dist/web/server/images/hero/timer.png +0 -0
  66. package/dist/web/server/images/wod-wiki-logo-dark.png +0 -0
  67. package/dist/web/server/images/wod-wiki-logo-light.png +0 -0
  68. package/dist/web/server/index.html +60 -0
  69. package/dist/web/server/seed/core/chunks/canvas.d2a5e6e7.json +1 -0
  70. package/dist/web/server/seed/core/chunks/template.e5e0e82d.json +1 -0
  71. package/dist/web/server/seed/core/manifest.json +24 -0
  72. package/dist/web/server/seed/core/report.html +43 -0
  73. package/package.json +46 -4
  74. package/README.md +0 -3
@@ -0,0 +1,2061 @@
1
+ import { createRequire } from 'module';
2
+ import { openCliDb, nodeFs, sqliteIndexStore, sqliteEffortStore, sqliteBlockStore, sqliteNoteStore, sqliteEventStore, getNoteBundle, getSessionBundle, getEffortBySlug, listTagsByType, nodePath, loadSql, uuid5, CLI_UUID_NAMESPACE, stableNoteId, randomId } from './chunk-EUQWEX6Z.mjs';
3
+ import { __require } from './chunk-6L5ZDZJ7.mjs';
4
+ import { createParser, frozenNow, createCompiler, createMockClock, RuntimeStack, EventBus, ScriptRuntime, createAnalyticsEngineForBlock, StartSessionAction, NextEvent, toStoredOutputStatement, bundledEfforts, parseScript } from '@bitcobblers/wod-wiki-lang';
5
+ import { buildStatementTree, createIRFile, registerLanguagePack, extractNoteIdentity, extractTypedTags, composeNoteMarkdown, sessionToCsv, effortToMarkdown, equipmentToCsv, markdownToEffort, csvToEquipmentLabels, csvToSessionRows, isIRFile, inMemoryEventStore, assertSeedManifest, seedMetaKey, SEED_SCHEMA, emptySeedMeta, CANVAS_CHUNK_ID, assertSeedRows, seedNoteId, applySeedChunk } from '@bitcobblers/wod-wiki-engine';
6
+ import { toEventRows, toSummaryEventRows, parseQuery, QueryService, isPipelineQuery, isFindQuery } from '@bitcobblers/wod-wiki-wql';
7
+ import { validateNoteBlocks, parseDocumentSections, blockContentId, reconcileNoteBlocks, KNOWN_SECTION_WIDGETS } from '@bitcobblers/wod-wiki-core';
8
+ import { rowsEqual } from '@bitcobblers/wod-wiki-storage';
9
+
10
+ if (typeof globalThis.require === 'undefined') {
11
+ try { globalThis.require = createRequire(import.meta.url); } catch {}
12
+ }
13
+ var ParseSyntaxError = class extends Error {
14
+ constructor(errors, message = "Parse error in Whiteboard Script") {
15
+ const details = errors.map((e) => e.message).join("; ");
16
+ super(`${message}: ${details}`);
17
+ this.errors = errors;
18
+ this.name = "ParseSyntaxError";
19
+ }
20
+ errors;
21
+ };
22
+ function runParse(source, options = {}) {
23
+ const parser = createParser();
24
+ const script = parser.read(source, options.sport);
25
+ if (script.errors && script.errors.length > 0) {
26
+ throw new ParseSyntaxError(script.errors);
27
+ }
28
+ const tree = buildStatementTree(script);
29
+ return createIRFile("parse-tree", tree, { source: options.sourceLabel ?? "cli:wod parse" });
30
+ }
31
+ function buildWorkoutResults(outputs, options) {
32
+ return {
33
+ startTime: options.startTime ?? options.now.nowMs(),
34
+ endTime: options.now.nowMs(),
35
+ duration: options.elapsedTime,
36
+ completed: options.completed,
37
+ logs: outputs.map(toStoredOutputStatement)
38
+ };
39
+ }
40
+ async function runExecution(source, options = {}) {
41
+ const parser = createParser();
42
+ const script = parser.read(source, options.sport);
43
+ if (script.errors && script.errors.length > 0) {
44
+ throw new ParseSyntaxError(script.errors);
45
+ }
46
+ const startTime = options.startTime ?? Date.now();
47
+ const now = options.now ?? frozenNow(new Date(startTime));
48
+ const compiler = createCompiler();
49
+ const clock = createMockClock(new Date(startTime));
50
+ const stack = new RuntimeStack();
51
+ const eventBus = new EventBus();
52
+ const runtime = new ScriptRuntime(
53
+ script,
54
+ compiler,
55
+ { stack, clock, eventBus },
56
+ {},
57
+ now
58
+ );
59
+ const { engine, analyticsContext } = createAnalyticsEngineForBlock({
60
+ id: options.blockId ?? "cli-block",
61
+ content: source,
62
+ statements: script.statements,
63
+ sport: options.sport,
64
+ version: options.blockVersion ?? 1,
65
+ startLine: 0,
66
+ endLine: 0,
67
+ dialect: options.dialect ?? "time",
68
+ state: "idle",
69
+ createdAt: Date.now()
70
+ });
71
+ runtime.analyticsContext = analyticsContext;
72
+ runtime.setAnalyticsEngine(engine);
73
+ const outputs = [];
74
+ runtime.subscribeToOutput((out) => {
75
+ outputs.push(out);
76
+ });
77
+ runtime.do(new StartSessionAction());
78
+ const maxSteps = options.maxIterations ?? 5e3;
79
+ let steps = 0;
80
+ while (stack.count > 0 && steps < maxSteps) {
81
+ runtime.handle(new NextEvent(void 0, runtime.nowProvider));
82
+ clock.advance(1e3);
83
+ steps++;
84
+ }
85
+ const workoutResults = buildWorkoutResults(outputs, {
86
+ startTime,
87
+ elapsedTime: steps * 1e3,
88
+ completed: stack.count === 0,
89
+ now
90
+ });
91
+ const logs = outputs.map(toStoredOutputStatement);
92
+ return createIRFile(
93
+ "execution-log",
94
+ {
95
+ results: workoutResults,
96
+ logs,
97
+ statements: logs,
98
+ sourceScript: source
99
+ },
100
+ { source: options.sourceLabel ?? "cli:wod run" }
101
+ );
102
+ }
103
+ function getFs() {
104
+ if (typeof globalThis.process?.versions?.node === "undefined" && typeof globalThis.Bun === "undefined") {
105
+ throw new Error("File system operations are only supported in Node / Bun environments");
106
+ }
107
+ return __require("fs");
108
+ }
109
+ var WqlSyntaxError = class extends Error {
110
+ constructor(query, syntaxError) {
111
+ super(`WQL Syntax Error in "${query}": ${syntaxError}`);
112
+ this.query = query;
113
+ this.syntaxError = syntaxError;
114
+ this.name = "WqlSyntaxError";
115
+ }
116
+ query;
117
+ syntaxError;
118
+ };
119
+ function extractPayload(rawJson) {
120
+ try {
121
+ const parsed = JSON.parse(rawJson);
122
+ if (isIRFile(parsed)) {
123
+ return parsed.data;
124
+ }
125
+ return parsed;
126
+ } catch (err) {
127
+ throw new Error(`Failed to parse JSON input: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
128
+ }
129
+ }
130
+ function buildStoresFromData(data) {
131
+ const events = [
132
+ ...data.eventRecords,
133
+ ...data.facts.map((f, i) => ({
134
+ id: `fact:${f.resultId ?? "stdin"}:${f.metricKey ?? f.type}:${i}`,
135
+ resultId: f.resultId ?? "stdin-result-1",
136
+ noteId: f.noteId ?? "stdin-note-1",
137
+ blockContentId: f.blockContentId,
138
+ pageId: f.pageId,
139
+ origin: f.origin,
140
+ timestamp: f.timestamp,
141
+ grain: f.grain === "event" ? "event" : "summary",
142
+ outputType: "analytics",
143
+ effortSlug: f.effortSlug,
144
+ metrics: [{
145
+ type: f.metricKey ?? f.type,
146
+ value: f.value,
147
+ ...f.unit ? { unit: f.unit } : {},
148
+ origin: "engine"
149
+ }],
150
+ blockId: f.blockId,
151
+ blockVersion: f.blockVersion
152
+ }))
153
+ ];
154
+ const eventStore = inMemoryEventStore(events);
155
+ const noteStore = {
156
+ getAllNotes: async () => [...data.notes],
157
+ getNotesByIds: async (ids) => {
158
+ const selected = new Set(ids);
159
+ return data.notes.filter((note) => selected.has(note.id));
160
+ },
161
+ getNoteIdsForTag: async (tag) => {
162
+ const matching = /* @__PURE__ */ new Set();
163
+ for (const [noteId, tags] of Object.entries(data.noteTags)) {
164
+ if (tags.includes(tag)) matching.add(noteId);
165
+ }
166
+ return matching;
167
+ },
168
+ getNoteTagLabels: async (noteId) => [...data.noteTags[noteId] ?? []]
169
+ };
170
+ const blockStore = {
171
+ getAllBlocks: async () => [...data.blocks]
172
+ };
173
+ const effortStore = {
174
+ getAllEfforts: async () => [...data.efforts]
175
+ };
176
+ return { eventStore, noteStore, blockStore, effortStore };
177
+ }
178
+ function loadQueryData(options) {
179
+ const data = {
180
+ eventRecords: [],
181
+ facts: [],
182
+ results: [],
183
+ notes: [],
184
+ blocks: [],
185
+ revisions: [],
186
+ efforts: [...bundledEfforts],
187
+ noteTags: {}
188
+ };
189
+ if (options.corpusPath) {
190
+ const fs = getFs();
191
+ if (!fs.existsSync(options.corpusPath)) {
192
+ throw new Error(`Corpus file not found at path: ${options.corpusPath}`);
193
+ }
194
+ const raw = fs.readFileSync(options.corpusPath, "utf-8");
195
+ const payload = extractPayload(raw);
196
+ if (Array.isArray(payload)) {
197
+ data.facts = payload;
198
+ } else if (payload && typeof payload === "object") {
199
+ const obj = payload;
200
+ if ("segments" in obj) throw new Error("Incompatible corpus segments; whole Notes and block revisions are required");
201
+ if (obj.kind === "event-journal" || Array.isArray(obj.records)) {
202
+ if (Array.isArray(obj.blockRevisions)) data.revisions = obj.blockRevisions;
203
+ if (Array.isArray(obj.sessions)) data.results = obj.sessions;
204
+ if (Array.isArray(obj.records)) {
205
+ data.eventRecords = obj.records;
206
+ }
207
+ if (Array.isArray(obj.notes)) {
208
+ data.notes = obj.notes;
209
+ for (const n of obj.notes) {
210
+ if (n && typeof n === "object" && n.id && Array.isArray(n.tags)) {
211
+ data.noteTags[n.id] = n.tags;
212
+ }
213
+ }
214
+ }
215
+ } else if (obj.facts || obj.results || obj.notes || obj.blocks || obj.efforts || obj.tags || obj.logs) {
216
+ const corpus = payload;
217
+ if (corpus.facts) data.facts = corpus.facts;
218
+ if (Array.isArray(corpus.results)) data.results = corpus.results;
219
+ if (corpus.notes) data.notes = corpus.notes;
220
+ if (corpus.blocks) data.blocks = corpus.blocks;
221
+ if (corpus.efforts) data.efforts = corpus.efforts;
222
+ if (corpus.revisions) data.revisions = corpus.revisions;
223
+ if (corpus.tags) data.noteTags = corpus.tags;
224
+ if (corpus.logs) {
225
+ const identity = {
226
+ noteId: "corpus-note-1",
227
+ resultId: "corpus-result-1",
228
+ origin: "journal",
229
+ workoutTimestamp: corpus.logs[0]?.timeSpan?.started ?? Date.now()
230
+ };
231
+ data.eventRecords = [...toEventRows(corpus.logs, identity), ...toSummaryEventRows(corpus.logs, identity)];
232
+ }
233
+ } else {
234
+ throw new Error(`Unrecognized corpus payload shape in "${options.corpusPath}" (expected event-journal, fact-set, or CorpusIRData)`);
235
+ }
236
+ }
237
+ } else if (options.stdinLog) {
238
+ const payload = extractPayload(options.stdinLog);
239
+ let executionLog;
240
+ const rawLogs = payload.logs ?? ("statements" in payload ? payload.statements : void 0) ?? [];
241
+ if ("results" in payload) {
242
+ executionLog = { results: payload.results, logs: rawLogs, statements: rawLogs };
243
+ } else {
244
+ executionLog = { results: payload, logs: rawLogs, statements: rawLogs };
245
+ }
246
+ const identity = {
247
+ noteId: "stdin-note-1",
248
+ resultId: "stdin-result-1",
249
+ origin: "journal",
250
+ workoutTimestamp: executionLog.results.startTime
251
+ };
252
+ data.eventRecords = [
253
+ ...toEventRows(executionLog.logs, identity),
254
+ ...toSummaryEventRows(executionLog.logs, identity)
255
+ ];
256
+ } else if (options.stdinFacts) {
257
+ const payload = extractPayload(options.stdinFacts);
258
+ if (Array.isArray(payload)) {
259
+ data.facts = payload;
260
+ } else if (payload && typeof payload === "object" && "facts" in payload) {
261
+ data.facts = payload.facts;
262
+ }
263
+ } else {
264
+ throw new Error("No input dataset provided. Specify --corpus <path>, --stdin-log, or --stdin-facts.");
265
+ }
266
+ for (const note of data.notes) {
267
+ if (typeof note.rawContent !== "string" || !Number.isSafeInteger(note.sourceRevision) || note.sourceRevision < 1) {
268
+ throw new Error("Incompatible corpus Note; exact rawContent and sourceRevision are required");
269
+ }
270
+ validateNoteBlocks(note.rawContent, note.blocks, note.sourceRevision);
271
+ if (!data.blocks.some((row) => row.noteId === note.id)) {
272
+ for (const [position, block] of Object.values(note.blocks).sort((a, b) => a.from - b.from).entries()) {
273
+ const rawContent = note.rawContent.slice(block.from, block.to);
274
+ const body = parseDocumentSections(rawContent)[0]?.displayContent ?? rawContent;
275
+ data.blocks.push({
276
+ id: `${note.id}:${block.id}`,
277
+ noteId: note.id,
278
+ blockId: block.id,
279
+ blockVersion: block.version,
280
+ sourceRevision: note.sourceRevision,
281
+ from: block.from,
282
+ to: block.to,
283
+ position,
284
+ dataType: block.kind,
285
+ rawContent,
286
+ noteTitle: note.title,
287
+ createdAt: note.createdAt,
288
+ ...block.kind === "wod" ? { blockContentId: blockContentId(body) } : {}
289
+ });
290
+ }
291
+ }
292
+ }
293
+ for (const session of data.results) {
294
+ if ("segmentId" in session || "segmentVersion" in session || "data" in session || "version" in session || "rawContent" in session || !session.blockId || !Number.isSafeInteger(session.blockVersion) || session.blockVersion < 1) {
295
+ throw new Error("Incompatible corpus Session; exact blockId/blockVersion pin required");
296
+ }
297
+ if (!data.revisions.some((row) => row.noteId === session.noteId && row.blockId === session.blockId && row.version === session.blockVersion && row.kind === "wod")) {
298
+ throw new Error("Corpus Session immutable revision is unavailable");
299
+ }
300
+ }
301
+ for (const row of [...data.eventRecords, ...data.facts, ...data.blocks]) {
302
+ if ("segmentId" in row || "segmentVersion" in row) throw new Error("Incompatible corpus segment pin");
303
+ }
304
+ const revisions = new Map(data.revisions.map((row) => [JSON.stringify([row.noteId, row.blockId, row.version]), row]));
305
+ for (const row of data.eventRecords) {
306
+ if (row.blockId !== void 0 || row.blockVersion !== void 0) {
307
+ if (!row.blockId || !Number.isSafeInteger(row.blockVersion) || !revisions.has(JSON.stringify([row.noteId, row.blockId, row.blockVersion]))) {
308
+ throw new Error("Corpus Event immutable revision is unavailable");
309
+ }
310
+ }
311
+ }
312
+ for (const row of data.blocks) {
313
+ const note = data.notes.find((candidate) => candidate.id === row.noteId);
314
+ const block = note?.blocks[row.blockId];
315
+ if (!note || !block || row.blockVersion !== block.version || row.sourceRevision !== note.sourceRevision || row.from !== block.from || row.to !== block.to || row.dataType !== block.kind) {
316
+ throw new Error("Corpus block projection does not match current Note source");
317
+ }
318
+ row.rawContent = note.rawContent.slice(block.from, block.to);
319
+ }
320
+ return data;
321
+ }
322
+ async function runQueryCli(wqlString, options = {}) {
323
+ const parsed = parseQuery(wqlString);
324
+ if (parsed.error) {
325
+ throw new WqlSyntaxError(wqlString, parsed.error);
326
+ }
327
+ const stores = options.stores ?? buildStoresFromData(loadQueryData(options));
328
+ const service = new QueryService(stores);
329
+ if (isPipelineQuery(parsed)) {
330
+ const result2 = await service.runPipeline(wqlString, {
331
+ preferredUnit: options.preferredUnit
332
+ });
333
+ return createIRFile("pipeline-result", result2, { source: options.sourceLabel ?? "cli:wod query" });
334
+ }
335
+ if (isFindQuery(parsed)) {
336
+ const result2 = await service.runFind(parsed);
337
+ const kind = parsed.target === "session" ? "rows-result" : "find-result";
338
+ return createIRFile(kind, result2, { source: options.sourceLabel ?? "cli:wod query" });
339
+ }
340
+ const result = await service.runQuery(wqlString, {
341
+ preferredUnit: options.preferredUnit
342
+ });
343
+ return createIRFile("query-result", result, { source: options.sourceLabel ?? "cli:wod query" });
344
+ }
345
+ function getPathUtils() {
346
+ if (typeof globalThis.process?.versions?.node === "undefined" && !("Bun" in globalThis)) {
347
+ throw new Error("Path resolution is only supported in Node / Bun environments");
348
+ }
349
+ return {
350
+ // eslint-disable-next-line no-restricted-syntax -- lazy CJS require keeps node builtins out of browser bundles
351
+ resolve: __require("path").resolve,
352
+ // eslint-disable-next-line no-restricted-syntax -- lazy CJS require keeps node builtins out of browser bundles
353
+ pathToFileURL: __require("url").pathToFileURL
354
+ };
355
+ }
356
+ var PackLoadError = class extends Error {
357
+ constructor(spec, originalError) {
358
+ const msg = originalError instanceof Error ? originalError.message : String(originalError);
359
+ super(`Failed to load Language Pack "${spec}": ${msg}`);
360
+ this.spec = spec;
361
+ this.originalError = originalError;
362
+ this.name = "PackLoadError";
363
+ }
364
+ spec;
365
+ originalError;
366
+ };
367
+ async function loadLanguagePack(moduleSpec) {
368
+ try {
369
+ let importPath = moduleSpec;
370
+ if (moduleSpec.startsWith(".") || moduleSpec.startsWith("/") || moduleSpec.includes("\\") || moduleSpec.endsWith(".ts") || moduleSpec.endsWith(".js") || moduleSpec.endsWith(".mjs")) {
371
+ const { resolve, pathToFileURL } = getPathUtils();
372
+ const resolved = resolve(process.cwd(), moduleSpec);
373
+ importPath = pathToFileURL(resolved).href;
374
+ }
375
+ const imported = await import(
376
+ /* @vite-ignore */
377
+ importPath
378
+ );
379
+ const candidate = imported.default ?? imported.pack ?? imported;
380
+ if (!candidate || typeof candidate !== "object") {
381
+ throw new Error(`Module "${moduleSpec}" does not export a valid LanguagePack object`);
382
+ }
383
+ const pack = candidate;
384
+ registerLanguagePack(pack);
385
+ return pack;
386
+ } catch (error) {
387
+ if (error instanceof PackLoadError) throw error;
388
+ throw new PackLoadError(moduleSpec, error);
389
+ }
390
+ }
391
+
392
+ // src/cli/config.ts
393
+ function nodeRequire() {
394
+ const g = globalThis;
395
+ if (typeof g.process?.versions?.node === "undefined" && typeof g.Bun === "undefined") {
396
+ throw new Error("database configuration is only supported in Node / Bun environments");
397
+ }
398
+ const req = g.require ?? (typeof __require === "function" ? __require : void 0);
399
+ if (!req) throw new Error("database configuration requires a CJS require shim (missing tsup banner?)");
400
+ return req;
401
+ }
402
+ var nodePath2 = () => nodeRequire()("node:path");
403
+ var nodeFs2 = () => nodeRequire()("node:fs");
404
+ function parseEnvFile(content) {
405
+ return nodeRequire()("node:util").parseEnv(content);
406
+ }
407
+ function loadCliEnvironment(envFile) {
408
+ const fs = nodeFs2();
409
+ const path = nodePath2();
410
+ let file;
411
+ if (envFile !== void 0) {
412
+ file = path.resolve(process.cwd(), envFile);
413
+ if (!fs.existsSync(file)) throw new Error(`[wod] --env-file: file not found: ${envFile}`);
414
+ } else {
415
+ let dir = process.cwd();
416
+ for (; ; ) {
417
+ const candidate = path.join(dir, ".env");
418
+ if (fs.existsSync(candidate)) {
419
+ file = candidate;
420
+ break;
421
+ }
422
+ const parent = path.dirname(dir);
423
+ if (parent === dir) break;
424
+ dir = parent;
425
+ }
426
+ }
427
+ return file !== void 0 ? parseEnvFile(fs.readFileSync(file, "utf-8")) : {};
428
+ }
429
+ var SQLITE_URL_SCHEMES = /^(libsql|https?|wss?|file):\/\//;
430
+ var PG_URL_SCHEMES = /^postgres(ql)?:\/\//;
431
+ var TURSO_URL_SCHEMES = /^(libsql|https?|wss?):\/\//;
432
+ var PG_FLAG_KEYS = [
433
+ "postgresHost",
434
+ "postgresPort",
435
+ "postgresUser",
436
+ "postgresPassword",
437
+ "postgresDatabase",
438
+ "postgresSsl"
439
+ ];
440
+ var PG_ENV_KEYS = [
441
+ "POSTGRES_HOST",
442
+ "POSTGRES_PORT",
443
+ "POSTGRES_USER",
444
+ "POSTGRES_PASSWORD",
445
+ "POSTGRES_DATABASE",
446
+ "POSTGRES_SSL"
447
+ ];
448
+ var isBackend = (v) => v === "sqlite" || v === "postgres" || v === "turso";
449
+ function setEnv(v) {
450
+ return v !== void 0 && v.trim() !== "" ? v : void 0;
451
+ }
452
+ function fail(message) {
453
+ throw new Error(`[wod] ${message}`);
454
+ }
455
+ function parsePort(v, source) {
456
+ if (!/^\d+$/.test(v)) fail(`${source} must be an integer 1..65535, got ${JSON.stringify(v)}`);
457
+ const port = Number(v);
458
+ if (port < 1 || port > 65535) fail(`${source} must be an integer 1..65535, got ${v}`);
459
+ return port;
460
+ }
461
+ function sslMode(v, source) {
462
+ if (v === void 0 || v === "") return "prefer";
463
+ if (typeof v === "boolean") return v ? "require" : "disable";
464
+ const s = v.trim().toLowerCase();
465
+ if (["true", "1", "yes", "require"].includes(s)) return "require";
466
+ if (["false", "0", "no", "disable", "disabled"].includes(s)) return "disable";
467
+ if (s === "prefer" || s === "verify-ca" || s === "verify-full") return s;
468
+ fail(`${source} must be disable|prefer|require|verify-ca|verify-full, got ${JSON.stringify(v)}`);
469
+ }
470
+ function safeUrlLabel(raw) {
471
+ try {
472
+ const u = new URL(raw);
473
+ return `${u.protocol}//${u.host}${u.pathname}`;
474
+ } catch {
475
+ return "<unparsable-url>";
476
+ }
477
+ }
478
+ function postgresFromUrl(url, source) {
479
+ if (!PG_URL_SCHEMES.test(url)) {
480
+ fail(`${source} must start with postgres:// or postgresql://`);
481
+ }
482
+ let u;
483
+ try {
484
+ u = new URL(url);
485
+ } catch {
486
+ fail(`${source} is not a parseable URL`);
487
+ }
488
+ const database = decodeURIComponent(u.pathname.replace(/^\/+|\/+$/g, ""));
489
+ if (database === "") fail(`${source} has no database name`);
490
+ return {
491
+ kind: "postgres",
492
+ label: safeUrlLabel(url),
493
+ url,
494
+ host: u.hostname,
495
+ port: u.port !== "" ? parsePort(u.port, `${source} port`) : 5432,
496
+ database,
497
+ user: u.username !== "" ? decodeURIComponent(u.username) : void 0,
498
+ password: u.password !== "" ? decodeURIComponent(u.password) : void 0,
499
+ ssl: sslMode(u.searchParams.get("sslmode") ?? u.searchParams.get("ssl") ?? void 0, `${source} ssl parameter`)
500
+ };
501
+ }
502
+ function resolvePostgres(options, e, databaseUrl) {
503
+ const flagProps = PG_FLAG_KEYS.some((k) => options[k] !== void 0);
504
+ const envProps = PG_ENV_KEYS.some((k) => setEnv(e[k]) !== void 0);
505
+ if (flagProps || envProps && databaseUrl === void 0) {
506
+ const host = options.postgresHost ?? setEnv(e.POSTGRES_HOST) ?? "127.0.0.1";
507
+ const port = parsePort(options.postgresPort ?? setEnv(e.POSTGRES_PORT) ?? "5432", "--postgres-port/POSTGRES_PORT");
508
+ const database = options.postgresDatabase ?? setEnv(e.POSTGRES_DATABASE);
509
+ if (database === void 0) {
510
+ fail("--postgres-database (or POSTGRES_DATABASE) is required for per-property PostgreSQL configuration");
511
+ }
512
+ const user = options.postgresUser ?? setEnv(e.POSTGRES_USER);
513
+ const password = options.postgresPassword ?? setEnv(e.POSTGRES_PASSWORD);
514
+ const ssl = sslMode(options.postgresSsl ?? setEnv(e.POSTGRES_SSL), "--postgres-ssl/POSTGRES_SSL");
515
+ try {
516
+ const u = new URL(`postgresql://${host}`);
517
+ u.port = String(port);
518
+ u.pathname = `/${database}`;
519
+ if (user !== void 0) u.username = user;
520
+ if (password !== void 0) u.password = password;
521
+ u.searchParams.set("sslmode", ssl);
522
+ return {
523
+ kind: "postgres",
524
+ label: `postgresql://${u.host}${u.pathname}`,
525
+ url: u.toString(),
526
+ host,
527
+ port,
528
+ database,
529
+ user,
530
+ password,
531
+ ssl
532
+ };
533
+ } catch {
534
+ fail("invalid PostgreSQL host/database in per-property configuration");
535
+ }
536
+ }
537
+ if (databaseUrl !== void 0) return postgresFromUrl(databaseUrl, "DB_DRIVER=postgres DATABASE_URL");
538
+ fail("DB_DRIVER=postgres requires DATABASE_URL starting with postgres:// or postgresql:// \u2014 or per-property --postgres-host/--postgres-database flags");
539
+ }
540
+ function resolveTurso(options, e, databaseUrl) {
541
+ const url = options.tursoUrl ?? setEnv(e.TURSO_DATABASE_URL) ?? databaseUrl;
542
+ if (url === void 0) {
543
+ fail("DB_DRIVER=turso requires --turso-url / TURSO_DATABASE_URL or DATABASE_URL (libsql:// or https://)");
544
+ }
545
+ if (PG_URL_SCHEMES.test(url)) fail("DB_DRIVER=turso cannot use a postgres:// URL");
546
+ if (!TURSO_URL_SCHEMES.test(url)) fail("DB_DRIVER=turso requires a libsql://, https://, http:// or wss:// URL");
547
+ return {
548
+ kind: "turso",
549
+ label: safeUrlLabel(url),
550
+ url,
551
+ authToken: options.tursoAuthToken ?? setEnv(e.TURSO_AUTH_TOKEN) ?? setEnv(e.LIBSQL_AUTH_TOKEN)
552
+ };
553
+ }
554
+ function resolveSqlite(options, e, databaseUrl) {
555
+ if (options.db !== void 0) {
556
+ const path2 = nodePath2().resolve(process.cwd(), options.db);
557
+ return { kind: "sqlite", label: path2, path: path2 };
558
+ }
559
+ if (databaseUrl !== void 0) {
560
+ if (PG_URL_SCHEMES.test(databaseUrl)) fail("DB_DRIVER=sqlite cannot use a postgres:// DATABASE_URL");
561
+ if (databaseUrl.startsWith("file:")) {
562
+ const path2 = nodePath2().resolve(process.cwd(), decodeURIComponent(databaseUrl.slice("file:".length).split("?")[0]));
563
+ return { kind: "sqlite", label: path2, path: path2 };
564
+ }
565
+ if (!SQLITE_URL_SCHEMES.test(databaseUrl)) {
566
+ fail("DB_DRIVER=sqlite requires a libsql://, https://, http://, wss:// or file:// DATABASE_URL");
567
+ }
568
+ return { kind: "turso", label: safeUrlLabel(databaseUrl), url: databaseUrl, authToken: setEnv(e.LIBSQL_AUTH_TOKEN) };
569
+ }
570
+ const path = nodePath2().resolve(process.cwd(), setEnv(e.WOD_WIKI_DB) ?? setEnv(e.SQLITE_PATH) ?? "./wodwiki.db");
571
+ return { kind: "sqlite", label: path, path };
572
+ }
573
+ function envBackend(e) {
574
+ const kinds = /* @__PURE__ */ new Set();
575
+ if (setEnv(e.WOD_WIKI_DB) !== void 0 || setEnv(e.SQLITE_PATH) !== void 0) kinds.add("sqlite");
576
+ const dbUrl = setEnv(e.DATABASE_URL);
577
+ if (PG_ENV_KEYS.some((k) => setEnv(e[k]) !== void 0) || dbUrl !== void 0 && PG_URL_SCHEMES.test(dbUrl)) {
578
+ kinds.add("postgres");
579
+ } else if (dbUrl !== void 0 && SQLITE_URL_SCHEMES.test(dbUrl)) {
580
+ kinds.add("sqlite");
581
+ }
582
+ if (setEnv(e.TURSO_DATABASE_URL) !== void 0) kinds.add("turso");
583
+ if (kinds.size > 1) {
584
+ fail(`ambiguous database configuration: ${[...kinds].join(" + ")} env vars are both set \u2014 set DB_DRIVER (or pass explicit flags) to choose`);
585
+ }
586
+ return kinds.values().next().value ?? "sqlite";
587
+ }
588
+ function resolveDatabaseConfig(options = {}, env) {
589
+ const e = env ?? { ...loadCliEnvironment(options.envFile), ...process.env };
590
+ const driverFlag = options.dbDriver;
591
+ if (driverFlag !== void 0 && !isBackend(driverFlag)) {
592
+ fail(`--db-driver must be sqlite|postgres|turso, got ${JSON.stringify(driverFlag)}`);
593
+ }
594
+ const pinned = [];
595
+ if (options.db !== void 0) pinned.push(["--db", "sqlite"]);
596
+ if (driverFlag !== void 0) pinned.push([`--db-driver ${driverFlag}`, driverFlag]);
597
+ if (options.tursoUrl !== void 0) pinned.push(["--turso-url", "turso"]);
598
+ if (options.tursoAuthToken !== void 0) pinned.push(["--turso-auth-token", "turso"]);
599
+ if (PG_FLAG_KEYS.some((k) => options[k] !== void 0)) pinned.push(["--postgres-*", "postgres"]);
600
+ const kinds = new Set(pinned.map(([, kind]) => kind));
601
+ if (kinds.size > 1) {
602
+ fail(`conflicting database selectors: ${pinned.map(([flag, kind]) => `${flag} (${kind})`).join(", ")}`);
603
+ }
604
+ if (options.databaseUrl !== void 0) {
605
+ if (options.db !== void 0) fail("conflicting database selectors: --db and --database-url are both set");
606
+ if (options.tursoUrl !== void 0) fail("conflicting database selectors: --turso-url and --database-url are both set");
607
+ if (PG_FLAG_KEYS.some((k) => options[k] !== void 0)) {
608
+ fail("conflicting database selectors: --database-url and --postgres-* flags are both set");
609
+ }
610
+ }
611
+ let backend;
612
+ if (pinned.length > 0) {
613
+ backend = pinned[0][1];
614
+ } else {
615
+ const envDriver = setEnv(e.DB_DRIVER);
616
+ if (envDriver !== void 0) {
617
+ if (!isBackend(envDriver)) {
618
+ fail(`Unsupported DB_DRIVER: ${envDriver} (expected sqlite|turso|postgres)`);
619
+ }
620
+ backend = envDriver;
621
+ } else {
622
+ backend = envBackend(e);
623
+ }
624
+ }
625
+ const databaseUrl = options.databaseUrl ?? setEnv(e.DATABASE_URL);
626
+ switch (backend) {
627
+ case "postgres":
628
+ return resolvePostgres(options, e, databaseUrl);
629
+ case "turso":
630
+ return resolveTurso(options, e, databaseUrl);
631
+ case "sqlite":
632
+ return resolveSqlite(options, e, databaseUrl);
633
+ }
634
+ }
635
+
636
+ // src/cli/formatters.ts
637
+ function escapeCsvField(field) {
638
+ if (field === void 0 || field === null) return "";
639
+ const str = String(field);
640
+ if (str.includes(",") || str.includes('"') || str.includes("\n") || str.includes("\r")) {
641
+ return `"${str.replace(/"/g, '""')}"`;
642
+ }
643
+ return str;
644
+ }
645
+ function formatDate(ts) {
646
+ if (!ts) return "-";
647
+ try {
648
+ return new Date(ts).toISOString();
649
+ } catch {
650
+ return String(ts);
651
+ }
652
+ }
653
+ function formatMetricValue(m) {
654
+ if (m.image) return m.image;
655
+ if (m.value === void 0 || m.value === null) return "";
656
+ if (typeof m.value === "object") return JSON.stringify(m.value);
657
+ return String(m.value);
658
+ }
659
+ function formatMetric(m) {
660
+ const t = m.type || "metric";
661
+ return `${t}:${formatMetricValue(m)}`;
662
+ }
663
+ function formatParseOutput(ir, format, pretty = true) {
664
+ if (format === "json") {
665
+ return JSON.stringify(ir, null, pretty ? 2 : void 0);
666
+ }
667
+ const flatNodes = [];
668
+ const walk = (node) => {
669
+ flatNodes.push(node);
670
+ for (const child of node.children ?? []) {
671
+ walk(child);
672
+ }
673
+ };
674
+ walk(ir.data);
675
+ if (format === "csv") {
676
+ const header = "id,type,from,to,raw,metrics,hints";
677
+ const rows = flatNodes.map(
678
+ (n) => [
679
+ n.id,
680
+ n.type,
681
+ n.from,
682
+ n.to,
683
+ escapeCsvField(n.raw),
684
+ escapeCsvField(n.metrics.map(formatMetric).join("; ")),
685
+ escapeCsvField(n.hints?.join("; ") ?? "")
686
+ ].join(",")
687
+ );
688
+ return [header, ...rows].join("\n");
689
+ }
690
+ const lines = [];
691
+ lines.push(`Parse Tree (${flatNodes.length} statements):`);
692
+ lines.push("\u2500".repeat(80));
693
+ lines.push(
694
+ ["ID".padEnd(6), "Type".padEnd(12), "Span".padEnd(12), "Hints".padEnd(20), "Raw Text"].join(" | ")
695
+ );
696
+ lines.push("\u2500".repeat(80));
697
+ for (const n of flatNodes) {
698
+ const span = `${n.from}-${n.to}`;
699
+ const hints = (n.hints ?? []).join(", ");
700
+ lines.push(
701
+ [
702
+ String(n.id).padEnd(6),
703
+ n.type.padEnd(12),
704
+ span.padEnd(12),
705
+ (hints.length > 18 ? hints.slice(0, 15) + "..." : hints).padEnd(20),
706
+ n.raw.replace(/\n/g, "\\n")
707
+ ].join(" | ")
708
+ );
709
+ }
710
+ return lines.join("\n");
711
+ }
712
+ function formatExecutionOutput(ir, format, pretty = true) {
713
+ if (format === "json") {
714
+ return JSON.stringify(ir, null, pretty ? 2 : void 0);
715
+ }
716
+ const { results, logs } = ir.data;
717
+ const statements = logs;
718
+ if (format === "csv") {
719
+ const header = "id,outputType,started,ended,sourceBlockKey,stackLevel,completionReason,metrics,hints";
720
+ const rows = statements.map(
721
+ (s) => [
722
+ s.id,
723
+ s.outputType,
724
+ s.timeSpan?.started ?? s.timestamp ?? 0,
725
+ s.timeSpan?.ended ?? "",
726
+ escapeCsvField(s.sourceBlockKey),
727
+ s.stackLevel,
728
+ escapeCsvField(s.completionReason ?? ""),
729
+ escapeCsvField(s.metrics.map(formatMetric).join("; ")),
730
+ escapeCsvField(s.hints?.join("; ") ?? "")
731
+ ].join(",")
732
+ );
733
+ return [header, ...rows].join("\n");
734
+ }
735
+ const lines = [];
736
+ lines.push(`Workout Execution Results:`);
737
+ lines.push(` Duration: ${results.duration}ms`);
738
+ lines.push(` Completed: ${results.completed ? "yes" : "no"}`);
739
+ lines.push(` Start: ${formatDate(results.startTime)}`);
740
+ lines.push(` End: ${formatDate(results.endTime)}`);
741
+ lines.push(` Logs: ${statements.length} statements emitted`);
742
+ lines.push("\u2500".repeat(80));
743
+ lines.push(
744
+ ["ID".padEnd(8), "Type".padEnd(12), "Block".padEnd(18), "Reason".padEnd(15), "Metrics"].join(" | ")
745
+ );
746
+ lines.push("\u2500".repeat(80));
747
+ for (const s of statements) {
748
+ const metricsStr = s.metrics.map(formatMetric).join(", ");
749
+ lines.push(
750
+ [
751
+ String(s.id).padEnd(8),
752
+ (s.outputType ?? "segment").padEnd(12),
753
+ (s.sourceBlockKey ?? "-").padEnd(18),
754
+ (s.completionReason ?? "-").padEnd(15),
755
+ metricsStr
756
+ ].join(" | ")
757
+ );
758
+ }
759
+ return lines.join("\n");
760
+ }
761
+ function formatQueryOutput(ir, format, pretty = true) {
762
+ if (format === "json") {
763
+ return JSON.stringify(ir, null, pretty ? 2 : void 0);
764
+ }
765
+ const kind = ir.kind;
766
+ if (kind === "rows-result") {
767
+ const data2 = ir.data;
768
+ if (format === "csv") {
769
+ const header = "resultId,timestamp,outputType,eventId,metrics";
770
+ const rows = [];
771
+ for (const run of data2.runs) {
772
+ for (const event of run.events) {
773
+ rows.push(
774
+ [
775
+ run.resultId,
776
+ run.timestamp,
777
+ event.outputType,
778
+ event.id,
779
+ escapeCsvField(event.metrics.map(formatMetric).join("; "))
780
+ ].join(",")
781
+ );
782
+ }
783
+ }
784
+ return [header, ...rows].join("\n");
785
+ }
786
+ const lines2 = [];
787
+ lines2.push(`Rows Query Result (${data2.runs.length} runs):`);
788
+ if (data2.error) lines2.push(`Error: ${data2.error}`);
789
+ lines2.push("\u2500".repeat(80));
790
+ lines2.push(
791
+ ["Result ID".padEnd(20), "Date".padEnd(24), "Events"].join(" | ")
792
+ );
793
+ lines2.push("\u2500".repeat(80));
794
+ for (const run of data2.runs) {
795
+ lines2.push(
796
+ [
797
+ run.resultId.padEnd(20),
798
+ formatDate(run.timestamp).padEnd(24),
799
+ String(run.events.length)
800
+ ].join(" | ")
801
+ );
802
+ }
803
+ return lines2.join("\n");
804
+ }
805
+ if (kind === "find-result") {
806
+ const data2 = ir.data;
807
+ if (format === "csv") {
808
+ const header = "target,id,title_or_label,sourceId";
809
+ const rows = [];
810
+ for (const n of data2.notes) {
811
+ rows.push(["note", n.id, escapeCsvField(n.title), escapeCsvField(n.sourceId ?? "")].join(","));
812
+ }
813
+ for (const b of data2.blocks) {
814
+ rows.push(["block", b.id, escapeCsvField(b.noteTitle), escapeCsvField(b.sourceId ?? "")].join(","));
815
+ }
816
+ for (const e of data2.efforts ?? []) {
817
+ rows.push(["effort", e.id, escapeCsvField(e.label), escapeCsvField(e.slug)].join(","));
818
+ }
819
+ return [header, ...rows].join("\n");
820
+ }
821
+ const lines2 = [];
822
+ const matchedCount2 = data2.stages?.matched ?? 0;
823
+ const selectedCount = data2.stages?.selected ?? 0;
824
+ lines2.push(`Find Result (matched: ${matchedCount2} / selected: ${selectedCount}):`);
825
+ lines2.push("\u2500".repeat(80));
826
+ lines2.push(["Type".padEnd(8), "ID".padEnd(24), "Title / Label".padEnd(30), "Source ID"].join(" | "));
827
+ lines2.push("\u2500".repeat(80));
828
+ for (const n of data2.notes) {
829
+ lines2.push(["note".padEnd(8), n.id.padEnd(24), n.title.padEnd(30), n.sourceId ?? "-"].join(" | "));
830
+ }
831
+ for (const b of data2.blocks) {
832
+ lines2.push(["block".padEnd(8), b.id.padEnd(24), b.noteTitle.padEnd(30), b.sourceId ?? "-"].join(" | "));
833
+ }
834
+ for (const e of data2.efforts ?? []) {
835
+ lines2.push(["effort".padEnd(8), e.id.padEnd(24), e.label.padEnd(30), e.slug].join(" | "));
836
+ }
837
+ return lines2.join("\n");
838
+ }
839
+ if (kind === "pipeline-result") {
840
+ const data2 = ir.data;
841
+ const sink = data2.chart ? `:${data2.chart.head}` : "data";
842
+ if (format === "csv") {
843
+ const header = "series,timestamp,date,value,unit";
844
+ const rows = [];
845
+ for (const s of data2.series ?? []) {
846
+ for (const pt of s.points) {
847
+ rows.push(
848
+ [
849
+ escapeCsvField(s.label || s.key),
850
+ pt.ts,
851
+ formatDate(pt.ts),
852
+ pt.value,
853
+ escapeCsvField(s.unit ?? data2.unit ?? "")
854
+ ].join(",")
855
+ );
856
+ }
857
+ }
858
+ return [header, ...rows].join("\n");
859
+ }
860
+ const lines2 = [];
861
+ if (data2.error) lines2.push(`Pipeline Error: ${data2.error}`);
862
+ lines2.push(`Pipeline Result (\u2192 ${sink}):`);
863
+ lines2.push("\u2500".repeat(80));
864
+ if (data2.table) {
865
+ lines2.push(`Table: ${data2.table.rows.length} of ${data2.table.totalCount} rows`);
866
+ }
867
+ for (const s of data2.series ?? []) {
868
+ lines2.push(`${s.label || s.key}: ${s.points.length} points${s.unit ? ` (${s.unit})` : ""}`);
869
+ }
870
+ if (data2.notes?.length) lines2.push(`notes: ${data2.notes.length}`);
871
+ if (data2.blocks?.length) lines2.push(`blocks: ${data2.blocks.length}`);
872
+ if (data2.efforts?.length) lines2.push(`efforts: ${data2.efforts.length}`);
873
+ if (data2.runs?.length) lines2.push(`runs: ${data2.runs.length}`);
874
+ return lines2.join("\n");
875
+ }
876
+ const data = ir.data;
877
+ if (data.scalar !== void 0 && data.series.length === 0) {
878
+ if (format === "csv") {
879
+ return `metric,scalar,unit
880
+ ${escapeCsvField(data.parsed.metric)},${data.scalar},${escapeCsvField(data.unit ?? "")}`;
881
+ }
882
+ return `Scalar Result: ${data.scalar}${data.unit ? " " + data.unit : ""}`;
883
+ }
884
+ if (format === "csv") {
885
+ const header = "series,timestamp,date,value,unit";
886
+ const rows = [];
887
+ for (const s of data.series) {
888
+ for (const pt of s.points) {
889
+ rows.push(
890
+ [
891
+ escapeCsvField(s.label || s.key),
892
+ pt.ts,
893
+ formatDate(pt.ts),
894
+ pt.value,
895
+ escapeCsvField(s.unit ?? data.unit ?? "")
896
+ ].join(",")
897
+ );
898
+ }
899
+ }
900
+ return [header, ...rows].join("\n");
901
+ }
902
+ const lines = [];
903
+ const matchedCount = data.stages?.matched ?? data.stages?.selected ?? data.matched?.length ?? 0;
904
+ lines.push(`Query Result (${data.series.length} series, ${matchedCount} matched facts):`);
905
+ lines.push(
906
+ ["Series / Group".padEnd(20), "Timestamp".padEnd(16), "Date".padEnd(24), "Value".padEnd(12), "Unit"].join(" | ")
907
+ );
908
+ lines.push("\u2500".repeat(80));
909
+ for (const s of data.series) {
910
+ for (const pt of s.points) {
911
+ lines.push(
912
+ [
913
+ (s.label || s.key).padEnd(20),
914
+ String(pt.ts).padEnd(16),
915
+ formatDate(pt.ts).padEnd(24),
916
+ String(pt.value).padEnd(12),
917
+ s.unit ?? data.unit ?? "-"
918
+ ].join(" | ")
919
+ );
920
+ }
921
+ }
922
+ return lines.join("\n");
923
+ }
924
+ var registeredWidgets = KNOWN_SECTION_WIDGETS;
925
+ var saveStores = ["notes", "block_revisions", "block_index", "block_efforts", "tags", "note_tags", "sessions"];
926
+ function extractEffortSlugs(statements) {
927
+ const slugs = /* @__PURE__ */ new Set();
928
+ for (const statement of statements) {
929
+ if (statement.exerciseId) {
930
+ slugs.add(statement.exerciseId);
931
+ } else {
932
+ const effortMetric = statement.metrics?.getMetric?.("effort");
933
+ const effort = effortMetric && "effort" in effortMetric ? effortMetric.effort : void 0;
934
+ if (typeof effort === "string") {
935
+ const slug = effort.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
936
+ if (slug) slugs.add(slug);
937
+ }
938
+ }
939
+ }
940
+ return [...slugs];
941
+ }
942
+ function titleFromLabel(sourceLabel) {
943
+ if (!sourceLabel || sourceLabel === "-") return "stdin";
944
+ const stem = sourceLabel.split("/").pop() ?? sourceLabel;
945
+ return stem.replace(/\.[^.]+$/, "") || sourceLabel;
946
+ }
947
+ async function saveSource(db, options, startTime) {
948
+ const noteId = options.noteId ?? stableNoteId(options.sourceLabel, options.source);
949
+ const observed = await db.storage.readonly("notes").get(noteId);
950
+ const expected = options.expectedSourceRevision ?? observed?.sourceRevision ?? 0;
951
+ const now = options.now ?? Date.now();
952
+ return db.storage.transaction([...saveStores], "readwrite", async (tx) => {
953
+ const existing = await tx.readonly("notes").get(noteId);
954
+ if ((existing?.sourceRevision ?? 0) !== expected) {
955
+ throw new Error(`Stale Note source revision for ${noteId}; expected ${expected}`);
956
+ }
957
+ let sourceRevision = existing?.rawContent === options.source ? existing.sourceRevision : (existing?.sourceRevision ?? 0) + 1;
958
+ const blocks = reconcileNoteBlocks(options.source, {
959
+ sourceRevision,
960
+ blocks: options.blocks ?? (existing?.rawContent === options.source ? existing.blocks : void 0),
961
+ registeredWidgets,
962
+ createId: randomId
963
+ });
964
+ validateNoteBlocks(options.source, blocks, sourceRevision, { registeredWidgets });
965
+ const history = await tx.readonly("block_revisions").getAllFromIndex("by-note", noteId);
966
+ const latest = /* @__PURE__ */ new Map();
967
+ for (const row of history) {
968
+ if ((latest.get(row.blockId)?.version ?? 0) < row.version) latest.set(row.blockId, row);
969
+ }
970
+ const revisions = [];
971
+ for (const block of Object.values(blocks)) {
972
+ const saved = latest.get(block.id);
973
+ const rawContent = options.source.slice(block.from, block.to);
974
+ const unchanged = saved && saved.rawContent === rawContent && saved.kind === block.kind && saved.dialect === block.dialect && saved.sport === block.sport;
975
+ block.version = unchanged ? saved.version : (saved?.version ?? 0) + 1;
976
+ if (!unchanged) revisions.push({
977
+ noteId,
978
+ blockId: block.id,
979
+ version: block.version,
980
+ kind: block.kind,
981
+ rawContent,
982
+ savedAt: now,
983
+ ...block.dialect ? { dialect: block.dialect } : {},
984
+ ...block.sport ? { sport: block.sport } : {}
985
+ });
986
+ }
987
+ if (existing?.rawContent === options.source && !rowsEqual(existing.blocks, blocks)) {
988
+ sourceRevision++;
989
+ for (const block of Object.values(blocks)) block.sourceRevision = sourceRevision;
990
+ }
991
+ const note = {
992
+ ...existing,
993
+ id: noteId,
994
+ title: options.title ?? existing?.title ?? titleFromLabel(options.sourceLabel),
995
+ type: existing?.type ?? "note",
996
+ date: existing?.date ?? now,
997
+ createdAt: existing?.createdAt ?? now,
998
+ rawContent: options.source,
999
+ sourceRevision,
1000
+ blocks
1001
+ };
1002
+ const priorRows = await tx.readonly("block_index").getAllFromIndex("by-note", noteId);
1003
+ const priorEfforts = await tx.readonly("block_efforts").getAllFromIndex("by-note", noteId);
1004
+ const priorById = new Map(priorRows.map((row) => [row.id, row]));
1005
+ const nextRows = [];
1006
+ const nextEfforts = [];
1007
+ for (const [position, block] of Object.values(blocks).sort((a, b) => a.from - b.from).entries()) {
1008
+ const id = `${noteId}:${block.id}`;
1009
+ const prior = priorById.get(id);
1010
+ const rawContent = options.source.slice(block.from, block.to);
1011
+ const body = parseDocumentSections(rawContent, { registeredWidgets })[0]?.displayContent ?? rawContent;
1012
+ const fingerprint = block.kind === "wod" ? blockContentId(body) : void 0;
1013
+ nextRows.push({
1014
+ ...prior,
1015
+ id,
1016
+ noteId,
1017
+ blockId: block.id,
1018
+ blockVersion: block.version,
1019
+ dataType: block.kind,
1020
+ sourceRevision,
1021
+ from: block.from,
1022
+ to: block.to,
1023
+ position,
1024
+ rawContent,
1025
+ noteTitle: note.title,
1026
+ createdAt: prior?.createdAt ?? now,
1027
+ blockContentId: fingerprint,
1028
+ ...note.sourceId ? { sourceId: note.sourceId } : {}
1029
+ });
1030
+ if (block.kind === "wod") {
1031
+ for (const effortSlug of extractEffortSlugs(parseScript(body, { sport: block.sport, dialect: block.dialect }).statements)) {
1032
+ const effortId = `${noteId}:${block.id}:${effortSlug}`;
1033
+ const previous = priorEfforts.find((row) => row.id === effortId);
1034
+ nextEfforts.push({
1035
+ ...previous,
1036
+ id: effortId,
1037
+ noteId,
1038
+ blockId: block.id,
1039
+ blockContentId: fingerprint,
1040
+ effortSlug,
1041
+ createdAt: previous?.createdAt ?? now
1042
+ });
1043
+ }
1044
+ }
1045
+ }
1046
+ if (!rowsEqual(existing, note)) await tx.readwrite("notes").put(note);
1047
+ if (revisions.length) await tx.readwrite("block_revisions").putMany(revisions);
1048
+ await diffProjection(tx, "block_index", priorRows, nextRows);
1049
+ await diffProjection(tx, "block_efforts", priorEfforts, nextEfforts);
1050
+ const tags = await saveTags(tx, noteId, options.tags, now);
1051
+ let session;
1052
+ let revision;
1053
+ if (startTime !== void 0) {
1054
+ const block = Object.values(blocks).filter((row) => row.kind === "wod").sort((a, b) => a.from - b.from)[0];
1055
+ if (!block) throw new Error("[wod run] Note has no actionable workout block");
1056
+ revision = revisions.find((row) => row.blockId === block.id) ?? latest.get(block.id);
1057
+ if (!revision) throw new Error("[wod run] Saved workout revision is unavailable");
1058
+ session = {
1059
+ id: randomId(),
1060
+ noteId,
1061
+ blockId: block.id,
1062
+ blockVersion: block.version,
1063
+ blockContentId: blockContentId(parseDocumentSections(revision.rawContent)[0]?.displayContent ?? revision.rawContent),
1064
+ origin: "journal",
1065
+ status: "in-progress",
1066
+ startTime,
1067
+ endTime: startTime,
1068
+ duration: 0,
1069
+ completed: false,
1070
+ createdAt: startTime
1071
+ };
1072
+ await tx.readwrite("sessions").put(session);
1073
+ }
1074
+ return { noteId, note, blocks: nextRows.length, efforts: nextEfforts.length, tags, created: !existing, session, revision };
1075
+ });
1076
+ }
1077
+ async function diffProjection(tx, store, previous, next) {
1078
+ const nextIds = new Set(next.map((row) => row.id));
1079
+ for (const row of previous) if (!nextIds.has(row.id)) await tx.readwrite(store).delete(row.id);
1080
+ const old = new Map(previous.map((row) => [row.id, row]));
1081
+ for (const row of next) {
1082
+ if (!rowsEqual(old.get(row.id), row)) {
1083
+ if (store === "block_index" && "blockVersion" in row) await tx.readwrite("block_index").put(row);
1084
+ if (store === "block_efforts" && "effortSlug" in row) await tx.readwrite("block_efforts").put(row);
1085
+ }
1086
+ }
1087
+ }
1088
+ async function saveTags(tx, noteId, items, now) {
1089
+ const previous = await tx.readonly("note_tags").getAllFromIndex("by-note", noteId);
1090
+ if (!items) return previous.length;
1091
+ const next = /* @__PURE__ */ new Map();
1092
+ for (const item of items) {
1093
+ const label = item.label.trim();
1094
+ if (!label) continue;
1095
+ const found = (await tx.readonly("tags").getAllFromIndex("by-label", label))[0];
1096
+ const tag = found ? { ...found, ...!found.type && item.type ? { type: item.type } : {} } : { id: uuid5(CLI_UUID_NAMESPACE, `tag:${label}`), label, type: item.type, createdAt: now };
1097
+ if (!rowsEqual(found, tag)) await tx.readwrite("tags").put(tag);
1098
+ const id = previous.find((link) => link.tagId === tag.id)?.id ?? uuid5(CLI_UUID_NAMESPACE, `note-tag:${noteId}:${tag.id}`);
1099
+ next.set(id, { ...previous.find((link) => link.id === id), id, noteId, tagId: tag.id });
1100
+ }
1101
+ for (const link of previous) if (!next.has(link.id)) await tx.readwrite("note_tags").delete(link.id);
1102
+ const old = new Map(previous.map((row) => [row.id, row]));
1103
+ for (const link of next.values()) if (!rowsEqual(old.get(link.id), link)) await tx.readwrite("note_tags").put(link);
1104
+ return next.size;
1105
+ }
1106
+ function persistNote(db, options) {
1107
+ return saveSource(db, options);
1108
+ }
1109
+ async function startRun(db, options, startTime = Date.now()) {
1110
+ const saved = await saveSource(db, options, startTime);
1111
+ if (!saved.session || !saved.revision) throw new Error("[wod run] Atomic run capture failed");
1112
+ return { session: saved.session, revision: saved.revision, noteCreated: saved.created };
1113
+ }
1114
+ async function persistRun(db, started, executionLog) {
1115
+ const results = executionLog.results;
1116
+ const session = {
1117
+ ...started.session,
1118
+ status: results.completed ? "completed" : "in-progress",
1119
+ endTime: results.endTime,
1120
+ duration: results.duration ?? 0,
1121
+ completed: results.completed
1122
+ };
1123
+ const identity = {
1124
+ noteId: session.noteId,
1125
+ resultId: session.id,
1126
+ blockId: session.blockId,
1127
+ blockVersion: session.blockVersion,
1128
+ blockContentId: session.blockContentId,
1129
+ origin: session.origin,
1130
+ pageId: session.pageId,
1131
+ workoutTimestamp: session.createdAt
1132
+ };
1133
+ const events = toEventRows(executionLog.logs, identity);
1134
+ const summaries = toSummaryEventRows(executionLog.logs, identity);
1135
+ await db.storage.transaction(["sessions", "events", "block_revisions"], "readwrite", async (tx) => {
1136
+ const saved = await tx.readonly("sessions").get(session.id);
1137
+ if (!saved || saved.noteId !== session.noteId || saved.blockId !== session.blockId || saved.blockVersion !== session.blockVersion) {
1138
+ throw new Error("[wod run] Captured Session source pin changed");
1139
+ }
1140
+ const revision = await tx.readonly("block_revisions").get([session.noteId, session.blockId, session.blockVersion]);
1141
+ if (!revision || !rowsEqual(revision, started.revision)) throw new Error("[wod run] Captured revision is unavailable");
1142
+ await tx.readwrite("sessions").put(session);
1143
+ await tx.readwrite("events").putMany([...events, ...summaries]);
1144
+ });
1145
+ return { noteId: session.noteId, resultId: session.id, session, events: events.length, summaries: summaries.length, noteCreated: started.noteCreated };
1146
+ }
1147
+ async function persistSessionBundle(db, session, events) {
1148
+ if ("segmentId" in session || "segmentVersion" in session || "version" in session || "rawContent" in session || "data" in session || !session.noteId || !session.blockId || !Number.isSafeInteger(session.blockVersion) || session.blockVersion < 1) {
1149
+ throw new Error("[wod import] Session requires an exact block revision pin");
1150
+ }
1151
+ await db.storage.transaction(["sessions", "events", "block_revisions"], "readwrite", async (tx) => {
1152
+ const revision = await tx.readonly("block_revisions").get([session.noteId, session.blockId, session.blockVersion]);
1153
+ if (!revision || revision.kind !== "wod") throw new Error("[wod import] Exact immutable workout revision is unavailable");
1154
+ for (const event of events) {
1155
+ if (event.resultId !== session.id || event.noteId !== session.noteId || event.blockId !== session.blockId || event.blockVersion !== session.blockVersion) {
1156
+ throw new Error("[wod import] Event source pin differs from Session source pin");
1157
+ }
1158
+ }
1159
+ const existing = await tx.readonly("sessions").get(session.id);
1160
+ if (existing && !rowsEqual(existing, session)) throw new Error("[wod import] Session identity conflicts with existing row");
1161
+ if (!existing) await tx.readwrite("sessions").put(session);
1162
+ for (const event of events) {
1163
+ const old = await tx.readonly("events").get(event.id);
1164
+ if (old && !rowsEqual(old, event)) throw new Error("[wod import] Event identity conflicts with existing row");
1165
+ if (!old) await tx.readwrite("events").put(event);
1166
+ }
1167
+ });
1168
+ return { events: events.length };
1169
+ }
1170
+ async function persistEffort(db, effort) {
1171
+ const sql = await loadSql();
1172
+ await db.db.transaction(async (tx) => {
1173
+ await sql.putRow(tx, sql.storeDefinition("efforts"), effort);
1174
+ });
1175
+ }
1176
+ async function persistTypedTags(db, labels, type) {
1177
+ return db.storage.transaction(["tags"], "readwrite", async (tx) => {
1178
+ let added = 0;
1179
+ for (const raw of labels) {
1180
+ const label = raw.trim();
1181
+ if (!label) continue;
1182
+ const tag = (await tx.readonly("tags").getAllFromIndex("by-label", label))[0];
1183
+ if (!tag) {
1184
+ await tx.readwrite("tags").put({ id: uuid5(CLI_UUID_NAMESPACE, `tag:${label}`), label, type, createdAt: Date.now() });
1185
+ added++;
1186
+ } else if (!tag.type) {
1187
+ await tx.readwrite("tags").put({ ...tag, type });
1188
+ }
1189
+ }
1190
+ return added;
1191
+ });
1192
+ }
1193
+ async function importSeedDir(db, dir, sourceId) {
1194
+ const fs = nodeFs(), path = nodePath();
1195
+ const source = sourceId || path.basename(dir);
1196
+ const manifest = assertSeedManifest(JSON.parse(fs.readFileSync(path.resolve(dir, "manifest.json"), "utf-8")));
1197
+ const key = seedMetaKey(source);
1198
+ let expected = await db.storage.readonly("meta").get(key);
1199
+ if (expected && expected.schema !== SEED_SCHEMA) throw new Error("Incompatible seed checkpoint; recreate storage");
1200
+ let meta = expected ?? emptySeedMeta(key);
1201
+ if (manifest.version < meta.seedVersion) throw new Error("Seed manifest is older than stored checkpoint");
1202
+ const summary = { sourceId: source, version: manifest.version, notes: 0, efforts: 0, blocks: 0, blockEfforts: 0, deleted: 0, skippedUserOwned: 0 };
1203
+ const metadataChanged = manifest.version !== meta.seedVersion || manifest.builtAt !== meta.builtAt;
1204
+ const plan = manifest.chunks.filter((c) => metadataChanged || meta.chunks[c.id]?.sha256 !== c.sha256);
1205
+ plan.sort((a, b) => Number(b.id === CANVAS_CHUNK_ID) - Number(a.id === CANVAS_CHUNK_ID));
1206
+ const accumulate = (result) => {
1207
+ summary.notes += result.totalNotes;
1208
+ summary.efforts += result.totalEfforts;
1209
+ summary.blocks += result.totalBlocks;
1210
+ summary.deleted += result.deleted;
1211
+ summary.skippedUserOwned += result.skippedUserOwned;
1212
+ };
1213
+ for (const chunk of plan) {
1214
+ const rows = assertSeedRows(JSON.parse(fs.readFileSync(path.resolve(dir, chunk.path), "utf-8")));
1215
+ if (rows.length !== chunk.count) throw new Error("Seed chunk count mismatch");
1216
+ const next = { ...meta, chunks: { ...meta.chunks, [chunk.id]: { sha256: chunk.sha256, noteIds: await Promise.all(rows.map((r) => seedNoteId(r.path))) } }, importedAt: Date.now() };
1217
+ accumulate(await applySeedChunk(db.storage, { chunkId: chunk.id, rows, manifest, expectedMeta: expected, meta: next }));
1218
+ summary.blockEfforts += rows.reduce((count, row) => count + row.blockEfforts.length, 0);
1219
+ meta = next;
1220
+ expected = next;
1221
+ }
1222
+ for (const chunkId of Object.keys(meta.chunks)) {
1223
+ if (manifest.chunks.some((c) => c.id === chunkId)) continue;
1224
+ const chunks = { ...meta.chunks };
1225
+ delete chunks[chunkId];
1226
+ const next = { ...meta, chunks, importedAt: Date.now() };
1227
+ accumulate(await applySeedChunk(db.storage, { chunkId, rows: [], manifest, expectedMeta: expected, meta: next }));
1228
+ meta = next;
1229
+ expected = next;
1230
+ }
1231
+ if (plan.length || metadataChanged || Object.keys(meta.chunks).length !== Object.keys(expected?.chunks ?? {}).length) {
1232
+ const next = { ...meta, schema: SEED_SCHEMA, seedVersion: manifest.version, builtAt: manifest.builtAt, importedAt: Date.now() };
1233
+ await applySeedChunk(db.storage, { chunkId: null, rows: [], manifest, expectedMeta: expected, meta: next });
1234
+ }
1235
+ return summary;
1236
+ }
1237
+ function defaultSeedDirs(cwd) {
1238
+ const base = nodePath().resolve(cwd, "apps", "playground", "public", "seed");
1239
+ return ["core", "community"].map((id) => nodePath().resolve(base, id)).filter((dir) => nodeFs().existsSync(nodePath().resolve(dir, "manifest.json")));
1240
+ }
1241
+
1242
+ // src/version.ts
1243
+ var VERSION = "0.36.2133";
1244
+
1245
+ // src/cli/runner.ts
1246
+ function getFs2() {
1247
+ if (typeof globalThis.process?.versions?.node === "undefined" && typeof globalThis.Bun === "undefined") {
1248
+ throw new Error("File system operations are only supported in Node / Bun environments");
1249
+ }
1250
+ return __require("fs");
1251
+ }
1252
+ function cliNoteSource(source, label, sport) {
1253
+ if (/\.(?:md|markdown)$/i.test(label) || parseDocumentSections(source).some((section) => section.scriptBlock)) return source;
1254
+ let length = 3;
1255
+ for (const match of source.matchAll(/`+/g)) length = Math.max(length, match[0].length + 1);
1256
+ const fence = "`".repeat(length);
1257
+ return `${fence}time${sport ? `:${sport}` : ""}
1258
+ ${source}
1259
+ ${fence}
1260
+ `;
1261
+ }
1262
+ var VALUE_FLAGS = [
1263
+ ["--db", "db"],
1264
+ ["--db-driver", "dbDriver"],
1265
+ ["--database-url", "databaseUrl"],
1266
+ ["--postgres-host", "postgresHost"],
1267
+ ["--postgres-port", "postgresPort"],
1268
+ ["--postgres-user", "postgresUser"],
1269
+ ["--postgres-password", "postgresPassword"],
1270
+ ["--postgres-database", "postgresDatabase"],
1271
+ ["--postgres-ssl", "postgresSsl"],
1272
+ ["--turso-url", "tursoUrl"],
1273
+ ["--turso-auth-token", "tursoAuthToken"],
1274
+ ["--env-file", "envFile"],
1275
+ ["--host", "host"],
1276
+ ["--port", "port"],
1277
+ ["--dist", "dist"]
1278
+ ];
1279
+ function readValueFlag(args, i, flag) {
1280
+ const inline = args[i].startsWith(`${flag}=`);
1281
+ if (inline) {
1282
+ const value2 = args[i].slice(flag.length + 1);
1283
+ if (!value2) throw new Error(`Missing value for ${flag}`);
1284
+ return { value: value2, next: i + 1 };
1285
+ }
1286
+ const value = args[i + 1];
1287
+ if (value === void 0 || value === "" || value.startsWith("--")) throw new Error(`Missing value for ${flag}`);
1288
+ return { value, next: i + 2 };
1289
+ }
1290
+ function parseCliArgs(args) {
1291
+ const result = {
1292
+ format: "json",
1293
+ packs: [],
1294
+ sources: []
1295
+ };
1296
+ const valueArgs = result;
1297
+ let i = 0;
1298
+ while (i < args.length) {
1299
+ const arg = args[i];
1300
+ if (arg === "--at" || arg.startsWith("--at=")) throw new Error("Unknown option --at; whole Note exports have no as-of version");
1301
+ const valueFlag = VALUE_FLAGS.find(([flag]) => arg === flag || arg.startsWith(`${flag}=`));
1302
+ if (valueFlag) {
1303
+ const [flag, key] = valueFlag;
1304
+ const { value, next } = readValueFlag(args, i, flag);
1305
+ valueArgs[key] = value;
1306
+ i = next;
1307
+ continue;
1308
+ }
1309
+ if (arg === "--help" || arg === "-h") {
1310
+ result.help = true;
1311
+ i++;
1312
+ } else if (arg === "--version" || arg === "-v") {
1313
+ result.version = true;
1314
+ i++;
1315
+ } else if (arg === "--corpus" || arg === "-c") {
1316
+ result.corpus = args[++i];
1317
+ i++;
1318
+ } else if (arg === "--stdin-log") {
1319
+ result.stdinLog = true;
1320
+ i++;
1321
+ } else if (arg === "--stdin-facts") {
1322
+ result.stdinFacts = true;
1323
+ i++;
1324
+ } else if (arg === "--format") {
1325
+ const fmt = args[++i];
1326
+ if (fmt === "json" || fmt === "table" || fmt === "csv") {
1327
+ result.format = fmt;
1328
+ }
1329
+ i++;
1330
+ } else if (arg.startsWith("--format=")) {
1331
+ const fmt = arg.slice("--format=".length);
1332
+ if (fmt === "json" || fmt === "table" || fmt === "csv") {
1333
+ result.format = fmt;
1334
+ }
1335
+ i++;
1336
+ } else if (arg === "-o" || arg === "--output") {
1337
+ result.output = args[++i];
1338
+ i++;
1339
+ } else if (arg.startsWith("--output=")) {
1340
+ result.output = arg.slice("--output=".length);
1341
+ i++;
1342
+ } else if (arg === "--pack") {
1343
+ const packSpec = args[++i];
1344
+ if (packSpec) result.packs.push(packSpec);
1345
+ i++;
1346
+ } else if (arg.startsWith("--pack=")) {
1347
+ result.packs.push(arg.slice("--pack=".length));
1348
+ i++;
1349
+ } else if (arg === "--sport") {
1350
+ result.sport = args[++i];
1351
+ i++;
1352
+ } else if (arg === "--unit") {
1353
+ result.unit = args[++i];
1354
+ i++;
1355
+ } else if (arg === "--debug") {
1356
+ result.debug = "";
1357
+ i++;
1358
+ } else if (arg.startsWith("--debug=")) {
1359
+ result.debug = arg.slice("--debug=".length);
1360
+ i++;
1361
+ } else if (arg === "--source") {
1362
+ const source = args[++i];
1363
+ if (source) result.sources.push(source);
1364
+ i++;
1365
+ } else if (arg.startsWith("--source=")) {
1366
+ result.sources.push(arg.slice("--source=".length));
1367
+ i++;
1368
+ } else if (arg === "--scaffold") {
1369
+ result.scaffold = true;
1370
+ i++;
1371
+ } else if (arg === "--template") {
1372
+ result.template = args[++i];
1373
+ i++;
1374
+ } else if (arg.startsWith("--template=")) {
1375
+ result.template = arg.slice("--template=".length);
1376
+ i++;
1377
+ } else if (arg === "--out") {
1378
+ result.output = args[++i];
1379
+ i++;
1380
+ } else if (arg.startsWith("--out=")) {
1381
+ result.output = arg.slice("--out=".length);
1382
+ i++;
1383
+ } else if (arg === "--dir") {
1384
+ result.dir = args[++i];
1385
+ i++;
1386
+ } else if (arg.startsWith("--dir=")) {
1387
+ result.dir = arg.slice("--dir=".length);
1388
+ i++;
1389
+ } else if (!result.subcommand) {
1390
+ if (arg === "parse" || arg === "run" || arg === "query" || arg === "download" || arg === "import" || arg === "build" || arg === "seed" || arg === "reset" || arg === "init" || arg === "server" || arg === "serve" || arg === "playground" || arg === "help" || arg === "version") {
1391
+ result.subcommand = arg;
1392
+ } else {
1393
+ result.target = arg;
1394
+ }
1395
+ i++;
1396
+ } else if (!result.target) {
1397
+ result.target = arg;
1398
+ i++;
1399
+ } else if (!result.target2) {
1400
+ result.target2 = arg;
1401
+ i++;
1402
+ } else {
1403
+ i++;
1404
+ }
1405
+ }
1406
+ for (const [flag, value] of [["--port", result.port], ["--postgres-port", result.postgresPort]]) {
1407
+ if (value !== void 0 && (!/^\d+$/.test(value) || Number(value) < 1 || Number(value) > 65535)) {
1408
+ throw new Error(`Invalid value for ${flag}: "${value}" is not a TCP port (1-65535)`);
1409
+ }
1410
+ }
1411
+ return result;
1412
+ }
1413
+ function readBunStdinText() {
1414
+ const g = globalThis;
1415
+ if (g && typeof g === "object" && "Bun" in g) {
1416
+ const bun = g.Bun;
1417
+ if (bun && typeof bun === "object" && "stdin" in bun) {
1418
+ const stdin = bun.stdin;
1419
+ if (stdin && typeof stdin === "object" && "text" in stdin) {
1420
+ const text = stdin.text;
1421
+ if (typeof text === "function") return text;
1422
+ }
1423
+ }
1424
+ }
1425
+ return void 0;
1426
+ }
1427
+ async function readStdin() {
1428
+ const bunText = readBunStdinText();
1429
+ if (bunText) return bunText();
1430
+ return new Promise((resolve, reject) => {
1431
+ const chunks = [];
1432
+ process.stdin.on("data", (chunk) => {
1433
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
1434
+ });
1435
+ process.stdin.on("end", () => {
1436
+ resolve(Buffer.concat(chunks).toString("utf-8"));
1437
+ });
1438
+ process.stdin.on("error", (err) => {
1439
+ reject(err);
1440
+ });
1441
+ });
1442
+ }
1443
+ function getUsageText() {
1444
+ return `
1445
+ wod CLI \u2014 Headless Whiteboard Language & WQL Engine runner (v${VERSION})
1446
+
1447
+ USAGE:
1448
+ wod parse <file | -> [options]
1449
+ wod run <file | -> [options]
1450
+ wod query '<wql-string>' [options]
1451
+ wod download <note|session|effort|equipment> <id> [options]
1452
+ wod import <note|session|effort|equipment> <file> [options]
1453
+ wod build [dir] [options]
1454
+ wod seed [options]
1455
+ wod reset [options]
1456
+ wod init Initialize the configured database (idempotent)
1457
+ wod server [options]
1458
+ Serve the app (HTTP API + packaged frontend; alias: serve)
1459
+ wod playground [options]
1460
+ Serve the local playground (browser IndexedDB build)
1461
+
1462
+ SUBCOMMANDS:
1463
+ parse <file | -> Parse Whiteboard Script, create a note in the database
1464
+ (whole source + saved revisions + sparse block index), optionally emit parse-tree IR.
1465
+ Markdown carrying a 'wod-note-id' frontmatter stamp
1466
+ (see download) imports back into that same note.
1467
+ run <file | -> Execute the script state machine, record a session +
1468
+ event/summary rows in the database, optionally emit
1469
+ execution-log IR
1470
+ query '<wql>' Evaluate WQL against the database (or an explicit
1471
+ dataset) and emit QueryResult / RowsQueryResult IR
1472
+ download note <id> Export the note's authored markdown to <id>.md.
1473
+ The frontmatter identity stamp allows re-import.
1474
+ Frontmatter tags (general + typed equipment/quality/\u2026)
1475
+ are materialized into the tag graph on parse/import.
1476
+ download session <id>
1477
+ Write the session's generated metrics (event + summary
1478
+ rows, one CSV row per metric) to <id>.csv. Import back
1479
+ with 'wod import session'.
1480
+ download effort <slug>
1481
+ Write the effort registry row as corpus-style markdown
1482
+ (frontmatter id/slug/label/aliases/baseAttributes + body)
1483
+ to <slug>.md. Import back with 'wod import effort'.
1484
+ download equipment
1485
+ Write every equipment tag as a one-column CSV to
1486
+ equipment.csv. Import back with 'wod import equipment'.
1487
+ import note <file> Re-import exported note markdown (same as parse with
1488
+ the stamped identity).
1489
+ import session <file>
1490
+ Import a session + its events from a downloaded CSV.
1491
+ The exact saved block revision must already exist.
1492
+ import effort <file>
1493
+ Upsert an effort registry row from corpus-style markdown.
1494
+ import equipment <file>
1495
+ Create/upgrade equipment tags (CSV, one label per row).
1496
+ build [dir] Compile a seed corpus: validate every .md under
1497
+ <dir>/markdown (parity gate + dashboard format gate) and
1498
+ write manifest.json + content-hashed chunks/ +
1499
+ report.html to <dir>/dist with source/maps/projections bundled
1500
+ (override with -o/--output). Version stamps derive from
1501
+ <dir>'s git history (full checkout required).
1502
+
1503
+ BUILD OPTIONS (for 'build'):
1504
+ --scaffold Also scaffold markdown/efforts/discovered/*.md stubs for
1505
+ workout effort labels missing from the registry.
1506
+ --template <file> Compile an extra markdown file as the 'template' chunk
1507
+ (the host's playground intake template).
1508
+
1509
+ INPUT SOURCING (for 'query'; without these, the database is queried):
1510
+ -c, --corpus <path> Load historical corpus dataset from a versioned JSON file
1511
+ --stdin-log Read kind: 'execution-log' JSON payload from stdin
1512
+ --stdin-facts Read raw kind: 'fact-set' JSON payload from stdin
1513
+
1514
+ DATABASE (init / parse / run / query / download / import / seed / reset / server):
1515
+ Pick one backend; explicit flags win over the process environment, the
1516
+ process environment wins over .env files. Run 'wod init' once first: it
1517
+ creates the SQLite file + schema, creates the PostgreSQL database when
1518
+ your role is authorized to (otherwise use an existing/provisioned one),
1519
+ or installs the schema in your provisioned Turso database \u2014 idempotently,
1520
+ preserving existing notes. 'wod server' refuses an uninitialized database.
1521
+ --db <path> Local SQLite database file (default: $WOD_WIKI_DB
1522
+ or ./wodwiki.db). Passing --db always selects SQLite,
1523
+ even when the environment names a remote backend.
1524
+ Created on demand by parse/run/seed.
1525
+ --db-driver <name> Backend driver: sqlite (default), postgres, or
1526
+ turso ($DB_DRIVER)
1527
+ --database-url <url> Database connection URL ($DATABASE_URL); accepts
1528
+ PostgreSQL, local SQLite, and remote libSQL URLs.
1529
+ --postgres-host <host> PostgreSQL host ($POSTGRES_HOST)
1530
+ --postgres-port <port> PostgreSQL port ($POSTGRES_PORT)
1531
+ --postgres-user <user> PostgreSQL user ($POSTGRES_USER)
1532
+ --postgres-password <pw> PostgreSQL password ($POSTGRES_PASSWORD)
1533
+ --postgres-database <db> PostgreSQL database name ($POSTGRES_DATABASE)
1534
+ --postgres-ssl <mode> PostgreSQL SSL mode, e.g. disable/prefer/require
1535
+ ($POSTGRES_SSL)
1536
+ --turso-url <url> Turso/libSQL database URL (libsql://\u2026). The
1537
+ hosted database must already exist (provisioned on
1538
+ Turso); only its schema is installed.
1539
+ --turso-auth-token <token> Turso authentication token (never printed)
1540
+ --env-file <path> Load variables from this file; a missing file is
1541
+ an error. Without it: ./.env when present, otherwise
1542
+ the workspace-root .env. Credentials are never logged.
1543
+
1544
+ HOSTING (for 'server' / 'playground'; requires the Bun runtime):
1545
+ server HTTP API over the configured database plus the frontend
1546
+ packaged inside the engine module. Requires an
1547
+ initialized database. ('serve' is an accepted alias.)
1548
+ playground Serves the playground app backed by browser IndexedDB;
1549
+ the configured database is not touched.
1550
+ --host <host> Bind address (default 127.0.0.1, $HOST)
1551
+ --port <port> TCP port (default 3000, $PORT)
1552
+ --dist <dir> Serve this prebuilt frontend directory instead of the
1553
+ packaged one ($PLAYGROUND_DIST); must already contain
1554
+ index.html.
1555
+
1556
+ SEED SOURCING (for 'seed'):
1557
+ --source <dir> Extra compiled seed directory (manifest.json + chunks);
1558
+ repeatable. Defaults to the bundled core + community
1559
+ artifacts under apps/playground/public/seed.
1560
+
1561
+ OPTIONS:
1562
+ --format <fmt> Output format: json (default), table, csv
1563
+ -o, --output <path> Write the IR payload (or download) to a path instead of
1564
+ the default; '-' writes to stdout
1565
+ --debug[=name] Also write the IR payload to <name> (default
1566
+ wod-<subcommand>-debug.json); without it, parse/run
1567
+ print a one-line summary of what was persisted
1568
+ --pack <module> Dynamically load and register a Language Pack (repeatable)
1569
+ --sport <sport> Target sport fence dialect (e.g. climbing, crossfit)
1570
+ --unit <unit> Preferred display unit for queries (e.g. lb, kg)
1571
+ -h, --help Show help message
1572
+ -v, --version Show version number
1573
+
1574
+ ENVIRONMENT (flags > process env > .env):
1575
+ WOD_WIKI_DB, SQLITE_PATH SQLite file path
1576
+ DB_DRIVER sqlite | postgres | turso
1577
+ DATABASE_URL PostgreSQL connection URL
1578
+ POSTGRES_HOST, POSTGRES_PORT, POSTGRES_USER,
1579
+ POSTGRES_PASSWORD, POSTGRES_DATABASE, POSTGRES_SSL
1580
+ TURSO_DATABASE_URL, TURSO_AUTH_TOKEN, LIBSQL_AUTH_TOKEN
1581
+ HOST, PORT, PLAYGROUND_DIST server / playground defaults
1582
+
1583
+ EXIT CODES:
1584
+ 0 - OK / Success
1585
+ 1 - General / Runtime error
1586
+ 2 - Parse / Syntax error
1587
+ 3 - Language Pack load error
1588
+ `;
1589
+ }
1590
+ function printUsage(writer) {
1591
+ const text = getUsageText();
1592
+ if (writer) {
1593
+ writer(text);
1594
+ } else {
1595
+ process.stdout.write(text.endsWith("\n") ? text : text + "\n");
1596
+ }
1597
+ }
1598
+ async function cliMain(argv = process.argv.slice(2), io = {}) {
1599
+ const writeOut = io.stdout ?? ((t) => process.stdout.write(t + "\n"));
1600
+ const writeErr = io.stderr ?? ((t) => process.stderr.write(t + "\n"));
1601
+ const getStdin = io.readStdinFn ?? readStdin;
1602
+ let envLoaded;
1603
+ const cliEnv = () => envLoaded ??= { ...loadCliEnvironment(parsed.envFile), ...process.env };
1604
+ let dbResolved;
1605
+ const cliDb = () => dbResolved ??= resolveDatabaseConfig(parsed, cliEnv());
1606
+ let parsed;
1607
+ try {
1608
+ parsed = parseCliArgs(argv);
1609
+ } catch (error) {
1610
+ writeErr(error instanceof Error ? error.message : String(error));
1611
+ return 1;
1612
+ }
1613
+ if (parsed.version || parsed.subcommand === "version") {
1614
+ writeOut(`wod v${VERSION}`);
1615
+ return 0;
1616
+ }
1617
+ if (parsed.help || parsed.subcommand === "help") {
1618
+ printUsage(writeOut);
1619
+ return 0;
1620
+ }
1621
+ if (!parsed.subcommand) {
1622
+ printUsage(writeErr);
1623
+ return 1;
1624
+ }
1625
+ for (const packSpec of parsed.packs) {
1626
+ try {
1627
+ await loadLanguagePack(packSpec);
1628
+ } catch (err) {
1629
+ if (err instanceof PackLoadError) {
1630
+ writeErr(err.message);
1631
+ } else {
1632
+ writeErr(`[wod] Language pack load error: ${err instanceof Error ? err.message : String(err)}`);
1633
+ }
1634
+ return 3;
1635
+ }
1636
+ }
1637
+ const deliverOutput = (formattedText) => {
1638
+ if (parsed.output && parsed.output !== "-") {
1639
+ getFs2().writeFileSync(parsed.output, formattedText, "utf-8");
1640
+ } else {
1641
+ writeOut(formattedText);
1642
+ }
1643
+ };
1644
+ const debugTarget = (subcommand) => parsed.debug === void 0 ? void 0 : parsed.debug || `wod-${subcommand}-debug.json`;
1645
+ const writeDebugFile = (subcommand, ir) => {
1646
+ const target = debugTarget(subcommand);
1647
+ if (target !== void 0) {
1648
+ getFs2().writeFileSync(target, JSON.stringify(ir, null, 2) + "\n", "utf-8");
1649
+ }
1650
+ };
1651
+ try {
1652
+ if (parsed.subcommand === "parse") {
1653
+ if (!parsed.target) {
1654
+ writeErr('[wod parse] Error: Missing input file or "-" for stdin');
1655
+ return 1;
1656
+ }
1657
+ let source;
1658
+ if (parsed.target === "-") {
1659
+ source = await getStdin();
1660
+ } else {
1661
+ const fs = getFs2();
1662
+ if (!fs.existsSync(parsed.target)) {
1663
+ writeErr(`[wod parse] File not found: ${parsed.target}`);
1664
+ return 1;
1665
+ }
1666
+ source = fs.readFileSync(parsed.target, "utf-8");
1667
+ }
1668
+ const noteSource = cliNoteSource(source, parsed.target, parsed.sport);
1669
+ const ir = runParse(source, { sport: parsed.sport, sourceLabel: `cli:wod parse ${parsed.target}` });
1670
+ const identity = extractNoteIdentity(source);
1671
+ const tags = extractTypedTags(source);
1672
+ const db = await openCliDb(cliDb());
1673
+ try {
1674
+ const persisted = await persistNote(db, {
1675
+ source: noteSource,
1676
+ sourceLabel: parsed.target,
1677
+ noteId: identity?.noteId,
1678
+ title: identity?.title,
1679
+ tags
1680
+ });
1681
+ writeOut(
1682
+ `[wod parse] note ${persisted.noteId}${persisted.created ? "" : " (updated)"} \u2014 ${persisted.blocks} blocks, ${persisted.efforts} efforts${persisted.tags > 0 ? `, ${persisted.tags} tags` : ""} \u2192 ${db.path}`
1683
+ );
1684
+ } finally {
1685
+ await db.close();
1686
+ }
1687
+ if (parsed.output) {
1688
+ const pretty = Boolean(process.stdout.isTTY);
1689
+ deliverOutput(formatParseOutput(ir, parsed.format, pretty));
1690
+ }
1691
+ writeDebugFile("parse", ir);
1692
+ return 0;
1693
+ }
1694
+ if (parsed.subcommand === "run") {
1695
+ if (!parsed.target) {
1696
+ writeErr('[wod run] Error: Missing input file or "-" for stdin');
1697
+ return 1;
1698
+ }
1699
+ let source;
1700
+ if (parsed.target === "-") {
1701
+ source = await getStdin();
1702
+ } else {
1703
+ const fs = getFs2();
1704
+ if (!fs.existsSync(parsed.target)) {
1705
+ writeErr(`[wod run] File not found: ${parsed.target}`);
1706
+ return 1;
1707
+ }
1708
+ source = fs.readFileSync(parsed.target, "utf-8");
1709
+ }
1710
+ const identity = extractNoteIdentity(source);
1711
+ const db = await openCliDb(cliDb());
1712
+ let ir;
1713
+ try {
1714
+ const started = await startRun(db, {
1715
+ source: cliNoteSource(source, parsed.target, parsed.sport),
1716
+ sourceLabel: parsed.target,
1717
+ noteId: identity?.noteId,
1718
+ title: identity?.title,
1719
+ tags: extractTypedTags(source)
1720
+ });
1721
+ const section = parseDocumentSections(started.revision.rawContent).find((row) => row.scriptBlock);
1722
+ if (!section?.scriptBlock) throw new Error("[wod run] Captured workout source cannot be parsed");
1723
+ const bare = !/\.(?:md|markdown)$/i.test(parsed.target) && !parseDocumentSections(source).some((row) => row.scriptBlock);
1724
+ const frozenBody = bare ? section.scriptBlock.content.slice(0, -1) : section.scriptBlock.content;
1725
+ ir = await runExecution(frozenBody, {
1726
+ sport: started.revision.sport,
1727
+ dialect: started.revision.dialect,
1728
+ blockId: started.session.blockId,
1729
+ blockVersion: started.session.blockVersion,
1730
+ startTime: started.session.startTime,
1731
+ sourceLabel: `cli:wod run ${parsed.target}`
1732
+ });
1733
+ const persisted = await persistRun(db, started, ir.data);
1734
+ writeOut(
1735
+ `[wod run] session ${persisted.resultId} \u2014 ${persisted.events} events, ${persisted.summaries} summaries on note ${persisted.noteId} \u2192 ${db.path}`
1736
+ );
1737
+ } finally {
1738
+ await db.close();
1739
+ }
1740
+ if (parsed.output) {
1741
+ const pretty = Boolean(process.stdout.isTTY);
1742
+ deliverOutput(formatExecutionOutput(ir, parsed.format, pretty));
1743
+ }
1744
+ writeDebugFile("run", ir);
1745
+ return 0;
1746
+ }
1747
+ if (parsed.subcommand === "query") {
1748
+ if (!parsed.target) {
1749
+ writeErr("[wod query] Error: Missing WQL query argument");
1750
+ return 1;
1751
+ }
1752
+ const useDb = !parsed.corpus && !parsed.stdinLog && !parsed.stdinFacts;
1753
+ let stdinLogContent;
1754
+ let stdinFactsContent;
1755
+ if (parsed.stdinLog) {
1756
+ stdinLogContent = await getStdin();
1757
+ } else if (parsed.stdinFacts) {
1758
+ stdinFactsContent = await getStdin();
1759
+ }
1760
+ let ir;
1761
+ if (useDb) {
1762
+ const config = cliDb();
1763
+ if (config.kind === "sqlite" && !nodeFs().existsSync(config.path)) {
1764
+ writeErr(
1765
+ `[wod query] No input dataset and no database at ${config.path}. Specify --corpus <file>, --stdin-log, --stdin-facts, or run \`wod seed\` first.`
1766
+ );
1767
+ return 1;
1768
+ }
1769
+ const db = await openCliDb(config, config.kind === "sqlite" ? void 0 : { initialize: false });
1770
+ try {
1771
+ ir = await runQueryCli(parsed.target, {
1772
+ preferredUnit: parsed.unit,
1773
+ sourceLabel: `cli:wod query "${parsed.target}"`,
1774
+ stores: {
1775
+ eventStore: sqliteEventStore(db.db),
1776
+ noteStore: sqliteNoteStore(db.db),
1777
+ blockStore: sqliteBlockStore(db.db),
1778
+ effortStore: sqliteEffortStore(db.db),
1779
+ blockEffortsStore: sqliteIndexStore(db.db, "block_efforts"),
1780
+ tagsStore: sqliteIndexStore(db.db, "tags"),
1781
+ noteTagsStore: sqliteIndexStore(db.db, "note_tags")
1782
+ }
1783
+ });
1784
+ } finally {
1785
+ await db.close();
1786
+ }
1787
+ } else {
1788
+ if (!parsed.corpus && !stdinLogContent && !stdinFactsContent) {
1789
+ writeErr("[wod query] Error: Query requires input data. Specify --corpus <file>, --stdin-log, or --stdin-facts.");
1790
+ return 1;
1791
+ }
1792
+ ir = await runQueryCli(parsed.target, {
1793
+ corpusPath: parsed.corpus,
1794
+ stdinLog: stdinLogContent,
1795
+ stdinFacts: stdinFactsContent,
1796
+ preferredUnit: parsed.unit,
1797
+ sourceLabel: `cli:wod query "${parsed.target}"`
1798
+ });
1799
+ }
1800
+ const pretty = !parsed.output && Boolean(process.stdout.isTTY);
1801
+ const formatted = formatQueryOutput(ir, parsed.format, pretty);
1802
+ deliverOutput(formatted);
1803
+ writeDebugFile("query", ir);
1804
+ return 0;
1805
+ }
1806
+ if (parsed.subcommand === "download") {
1807
+ const kind = parsed.target;
1808
+ const id = parsed.target2;
1809
+ const DOWNLOAD_KINDS = ["note", "session", "effort", "equipment"];
1810
+ if (!id && kind !== "equipment" || !DOWNLOAD_KINDS.includes(kind ?? "")) {
1811
+ writeErr("[wod download] Usage: wod download <note|session|effort|equipment> <id> [-o <path>]");
1812
+ return 1;
1813
+ }
1814
+ const targetId = id;
1815
+ const db = await openCliDb(cliDb());
1816
+ try {
1817
+ if (kind === "note") {
1818
+ const bundle = await getNoteBundle(db.db, targetId);
1819
+ if (!bundle.note) {
1820
+ writeErr(`[wod download] Note not found in ${db.path}: ${id}`);
1821
+ return 1;
1822
+ }
1823
+ const markdown = composeNoteMarkdown(bundle.note);
1824
+ const target = parsed.output && parsed.output !== "-" ? parsed.output : `${id}.md`;
1825
+ if (parsed.output === "-") {
1826
+ writeOut(markdown);
1827
+ } else {
1828
+ nodeFs().writeFileSync(target, markdown, "utf-8");
1829
+ }
1830
+ if (parsed.output !== "-") writeOut(`[wod download] note ${id} \u2192 ${target}`);
1831
+ } else if (kind === "session") {
1832
+ const bundle = await getSessionBundle(db.db, targetId);
1833
+ if (!bundle) {
1834
+ writeErr(`[wod download] Session not found in ${db.path}: ${id}`);
1835
+ return 1;
1836
+ }
1837
+ const csv = sessionToCsv(bundle.session, bundle.events);
1838
+ const target = parsed.output && parsed.output !== "-" ? parsed.output : `${id}.csv`;
1839
+ if (parsed.output === "-") {
1840
+ writeOut(csv);
1841
+ } else {
1842
+ nodeFs().writeFileSync(target, csv, "utf-8");
1843
+ }
1844
+ if (parsed.output !== "-") writeOut(`[wod download] session ${id} \u2192 ${target} (${bundle.events.length} events)`);
1845
+ } else if (kind === "effort") {
1846
+ const effort = await getEffortBySlug(db.db, targetId);
1847
+ if (!effort) {
1848
+ writeErr(`[wod download] Effort not found in ${db.path}: ${id}`);
1849
+ return 1;
1850
+ }
1851
+ const markdown = effortToMarkdown(effort);
1852
+ const target = parsed.output && parsed.output !== "-" ? parsed.output : `${id}.md`;
1853
+ if (parsed.output === "-") {
1854
+ writeOut(markdown);
1855
+ } else {
1856
+ nodeFs().writeFileSync(target, markdown, "utf-8");
1857
+ }
1858
+ writeOut(`[wod download] effort ${id} \u2192 ${target}`);
1859
+ } else {
1860
+ const labels = (await listTagsByType(db.db, "equipment")).map((t) => t.label).sort();
1861
+ const csv = equipmentToCsv(labels);
1862
+ const target = parsed.output && parsed.output !== "-" ? parsed.output : "equipment.csv";
1863
+ if (parsed.output === "-") {
1864
+ writeOut(csv);
1865
+ } else {
1866
+ nodeFs().writeFileSync(target, csv, "utf-8");
1867
+ }
1868
+ writeOut(`[wod download] equipment \u2192 ${target} (${labels.length} labels)`);
1869
+ }
1870
+ } finally {
1871
+ await db.close();
1872
+ }
1873
+ return 0;
1874
+ }
1875
+ if (parsed.subcommand === "import") {
1876
+ const kind = parsed.target;
1877
+ const file = parsed.target2;
1878
+ const IMPORT_KINDS = ["note", "session", "effort", "equipment"];
1879
+ if (!file || !IMPORT_KINDS.includes(kind ?? "")) {
1880
+ writeErr("[wod import] Usage: wod import <note|session|effort|equipment> <file>");
1881
+ return 1;
1882
+ }
1883
+ const fs = nodeFs();
1884
+ if (!fs.existsSync(file)) {
1885
+ writeErr(`[wod import] File not found: ${file}`);
1886
+ return 1;
1887
+ }
1888
+ if (kind === "note") {
1889
+ const source = fs.readFileSync(file, "utf-8");
1890
+ const identity = extractNoteIdentity(source);
1891
+ const tags = extractTypedTags(source);
1892
+ const db2 = await openCliDb(cliDb());
1893
+ try {
1894
+ const persisted = await persistNote(db2, {
1895
+ source,
1896
+ sourceLabel: file,
1897
+ noteId: identity?.noteId,
1898
+ title: identity?.title,
1899
+ tags
1900
+ });
1901
+ writeOut(
1902
+ `[wod import] note ${persisted.noteId}${persisted.created ? "" : " (updated)"} \u2014 ${persisted.blocks} blocks${persisted.tags > 0 ? `, ${persisted.tags} tags` : ""} \u2192 ${db2.path}`
1903
+ );
1904
+ } finally {
1905
+ await db2.close();
1906
+ }
1907
+ return 0;
1908
+ }
1909
+ if (kind === "effort") {
1910
+ const effort = markdownToEffort(fs.readFileSync(file, "utf-8"));
1911
+ if (!effort) {
1912
+ writeErr(`[wod import] Unparseable effort file (need frontmatter id/slug/label + numeric baseAttributes.met): ${file}`);
1913
+ return 1;
1914
+ }
1915
+ const db2 = await openCliDb(cliDb());
1916
+ try {
1917
+ await persistEffort(db2, { ...effort, registrySource: "user" });
1918
+ writeOut(`[wod import] effort ${effort.slug} \u2014 ${effort.label} \u2192 ${db2.path}`);
1919
+ } finally {
1920
+ await db2.close();
1921
+ }
1922
+ return 0;
1923
+ }
1924
+ if (kind === "equipment") {
1925
+ const labels = csvToEquipmentLabels(fs.readFileSync(file, "utf-8"));
1926
+ if (labels.length === 0) {
1927
+ writeErr(`[wod import] No equipment labels found in ${file}`);
1928
+ return 1;
1929
+ }
1930
+ const db2 = await openCliDb(cliDb());
1931
+ try {
1932
+ const added = await persistTypedTags(db2, labels, "equipment");
1933
+ writeOut(`[wod import] equipment \u2014 ${labels.length} labels (${added} new) \u2192 ${db2.path}`);
1934
+ } finally {
1935
+ await db2.close();
1936
+ }
1937
+ return 0;
1938
+ }
1939
+ const { session, events } = csvToSessionRows(fs.readFileSync(file, "utf-8"));
1940
+ const db = await openCliDb(cliDb());
1941
+ try {
1942
+ const result = await persistSessionBundle(db, session, events);
1943
+ writeOut(
1944
+ `[wod import] session ${session.id} \u2014 ${result.events} events \u2192 ${db.path}`
1945
+ );
1946
+ } finally {
1947
+ await db.close();
1948
+ }
1949
+ return 0;
1950
+ }
1951
+ if (parsed.subcommand === "build") {
1952
+ const cwd = process.cwd();
1953
+ const root = nodePath().resolve(cwd, parsed.dir ?? parsed.target ?? ".");
1954
+ if (!nodeFs().existsSync(nodePath().resolve(root, "markdown"))) {
1955
+ writeErr(`[wod build] No markdown/ corpus under ${root} \u2014 run from the content repo root (or pass its directory).`);
1956
+ return 1;
1957
+ }
1958
+ const { generateSeed } = await import('./compiler-KT6P3AC6.mjs');
1959
+ try {
1960
+ const result = generateSeed({
1961
+ corpusRoot: root,
1962
+ ...parsed.output ? { outDir: nodePath().resolve(cwd, parsed.output) } : {},
1963
+ ...parsed.template ? { templateFile: nodePath().resolve(cwd, parsed.template) } : {},
1964
+ scaffoldDiscoveredEfforts: parsed.scaffold === true
1965
+ });
1966
+ const kb = (n) => `${(n / 1024).toFixed(0)} KB`;
1967
+ const totalBytes = result.manifest.chunks.reduce((s, c) => s + c.bytes, 0);
1968
+ writeOut(`[wod build] schema v${result.manifest.schema} version ${result.manifest.version} (${result.manifest.versionLabel}) \u2192 ${result.outDir}`);
1969
+ for (const c of result.manifest.chunks) {
1970
+ writeOut(` ${c.id.padEnd(48)} ${String(c.count).padStart(4)} files ${kb(c.bytes).padStart(8)}`);
1971
+ }
1972
+ writeOut(`[wod build] ${result.manifest.chunks.length} chunks, ${result.totalFiles} files, ${kb(totalBytes)} total \u2014 scan ${result.passMs.scan}ms, block-index ${result.passMs.blockIndex}ms, block-efforts ${result.passMs.blockEfforts}ms, write ${result.passMs.write}ms`);
1973
+ return 0;
1974
+ } catch (error) {
1975
+ writeErr(`[wod build] ${error.message}`);
1976
+ return 1;
1977
+ }
1978
+ }
1979
+ if (parsed.subcommand === "seed") {
1980
+ const dirs = parsed.sources.length > 0 ? parsed.sources.map((s) => nodePath().resolve(process.cwd(), s)) : defaultSeedDirs(process.cwd());
1981
+ if (dirs.length === 0) {
1982
+ writeErr(
1983
+ "[wod seed] No seed sources found. Run `bun scripts/sync-seed.ts` to build the bundled artifacts, or pass --source <dir> pointing at a compiled seed directory (manifest.json + chunks)."
1984
+ );
1985
+ return 1;
1986
+ }
1987
+ const db = await openCliDb(cliDb());
1988
+ try {
1989
+ for (const dir of dirs) {
1990
+ const summary = await importSeedDir(db, dir, "");
1991
+ writeOut(
1992
+ `[wod seed] ${summary.sourceId}: v${summary.version} \u2014 ${summary.notes} notes, ${summary.efforts} efforts, ${summary.blocks} blocks, ${summary.blockEfforts} block-efforts (${summary.deleted} deleted, ${summary.skippedUserOwned} user-owned skipped) \u2192 ${db.path}`
1993
+ );
1994
+ }
1995
+ } finally {
1996
+ await db.close();
1997
+ }
1998
+ return 0;
1999
+ }
2000
+ if (parsed.subcommand === "reset") {
2001
+ const config = cliDb();
2002
+ if (config.kind !== "sqlite") {
2003
+ writeErr(
2004
+ `[wod reset] refusing to reset the configured ${config.kind} database \u2014 reset only removes a local SQLite file. Clear remote databases through their provider.`
2005
+ );
2006
+ return 1;
2007
+ }
2008
+ const target = config.path;
2009
+ if (nodeFs().existsSync(target)) {
2010
+ nodeFs().unlinkSync(target);
2011
+ writeOut(`[wod reset] removed ${target}`);
2012
+ } else {
2013
+ writeOut(`[wod reset] nothing to reset \u2014 ${target} does not exist`);
2014
+ }
2015
+ return 0;
2016
+ }
2017
+ if (parsed.subcommand === "init") {
2018
+ const db = await openCliDb(cliDb(), { create: true });
2019
+ try {
2020
+ writeOut(`[wod init] database ready at ${db.path}`);
2021
+ } finally {
2022
+ await db.close();
2023
+ }
2024
+ return 0;
2025
+ }
2026
+ if (parsed.subcommand === "server" || parsed.subcommand === "serve" || parsed.subcommand === "playground") {
2027
+ if (!("Bun" in globalThis)) {
2028
+ writeErr(
2029
+ `[wod ${parsed.subcommand}] Error: hosting requires the Bun runtime (https://bun.sh) \u2014 run \`bun wod server\` / \`bun wod playground\`.`
2030
+ );
2031
+ return 1;
2032
+ }
2033
+ const mode = parsed.subcommand === "playground" ? "playground" : "server";
2034
+ const { runCliServer } = await import('./server-Z2T7IGZX.mjs');
2035
+ const options = { env: cliEnv() };
2036
+ if (mode === "server") options.database = cliDb();
2037
+ if (parsed.host !== void 0) options.host = parsed.host;
2038
+ if (parsed.port !== void 0) options.port = parsed.port;
2039
+ if (parsed.dist !== void 0) options.dist = parsed.dist;
2040
+ await runCliServer(mode, options, writeOut);
2041
+ return 0;
2042
+ }
2043
+ writeErr(`[wod] Unknown subcommand "${parsed.subcommand}"`);
2044
+ return 1;
2045
+ } catch (error) {
2046
+ if (error instanceof ParseSyntaxError || error instanceof WqlSyntaxError) {
2047
+ writeErr(error.message);
2048
+ return 2;
2049
+ }
2050
+ if (error instanceof PackLoadError) {
2051
+ writeErr(error.message);
2052
+ return 3;
2053
+ }
2054
+ writeErr(`[wod] Error: ${error instanceof Error ? error.message : String(error)}`);
2055
+ return 1;
2056
+ }
2057
+ }
2058
+
2059
+ export { PackLoadError, ParseSyntaxError, WqlSyntaxError, cliMain, formatExecutionOutput, formatParseOutput, formatQueryOutput, loadCliEnvironment, loadLanguagePack, loadQueryData, parseCliArgs, readStdin, resolveDatabaseConfig, runExecution, runParse, runQueryCli };
2060
+ //# sourceMappingURL=chunk-OZI2FYPF.mjs.map
2061
+ //# sourceMappingURL=chunk-OZI2FYPF.mjs.map