@shivaedev/work-board 0.8.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +20 -1
  3. package/assets/agent.js +14 -14
  4. package/assets/native.js +49 -27
  5. package/dist/browser/responses/Prompt.d.ts +10 -0
  6. package/dist/browser/responses/Prompt.d.ts.map +1 -0
  7. package/dist/browser/responses/Prompt.js +2 -0
  8. package/dist/browser/responses/Prompt.js.map +1 -0
  9. package/dist/browser/responses/answerBody.d.ts +4 -0
  10. package/dist/browser/responses/answerBody.d.ts.map +1 -0
  11. package/dist/browser/responses/answerBody.js +9 -0
  12. package/dist/browser/responses/answerBody.js.map +1 -0
  13. package/dist/browser/responses/draftSession.d.ts.map +1 -1
  14. package/dist/browser/responses/draftSession.js +23 -10
  15. package/dist/browser/responses/draftSession.js.map +1 -1
  16. package/dist/browser/responses/drafts.d.ts +5 -0
  17. package/dist/browser/responses/drafts.d.ts.map +1 -1
  18. package/dist/browser/responses/drafts.js +20 -1
  19. package/dist/browser/responses/drafts.js.map +1 -1
  20. package/dist/browser/responses/preview.d.ts +2 -1
  21. package/dist/browser/responses/preview.d.ts.map +1 -1
  22. package/dist/browser/responses/preview.js +3 -2
  23. package/dist/browser/responses/preview.js.map +1 -1
  24. package/dist/browser/responses/questionnaireControls.d.ts +10 -0
  25. package/dist/browser/responses/questionnaireControls.d.ts.map +1 -0
  26. package/dist/browser/responses/questionnaireControls.js +68 -0
  27. package/dist/browser/responses/questionnaireControls.js.map +1 -0
  28. package/dist/browser/responses/schema.d.ts +41 -0
  29. package/dist/browser/responses/schema.d.ts.map +1 -1
  30. package/dist/browser/responses/schema.js +11 -1
  31. package/dist/browser/responses/schema.js.map +1 -1
  32. package/dist/browser/responses/setup.d.ts.map +1 -1
  33. package/dist/browser/responses/setup.js +34 -4
  34. package/dist/browser/responses/setup.js.map +1 -1
  35. package/dist/browser/responses/submissionFields.d.ts +15 -0
  36. package/dist/browser/responses/submissionFields.d.ts.map +1 -0
  37. package/dist/browser/responses/submissionFields.js +14 -0
  38. package/dist/browser/responses/submissionFields.js.map +1 -0
  39. package/dist/browser/responses/submit.d.ts.map +1 -1
  40. package/dist/browser/responses/submit.js +3 -9
  41. package/dist/browser/responses/submit.js.map +1 -1
  42. package/dist/metadata/backlinks.d.ts.map +1 -1
  43. package/dist/metadata/backlinks.js +3 -0
  44. package/dist/metadata/backlinks.js.map +1 -1
  45. package/dist/metadata/fields.d.ts +6 -0
  46. package/dist/metadata/fields.d.ts.map +1 -1
  47. package/dist/metadata/problems.d.ts.map +1 -1
  48. package/dist/metadata/problems.js +1 -0
  49. package/dist/metadata/problems.js.map +1 -1
  50. package/dist/metadata/schema.d.ts +6 -0
  51. package/dist/metadata/schema.d.ts.map +1 -1
  52. package/dist/page/metadataDetails.js +1 -1
  53. package/dist/page/metadataDetails.js.map +1 -1
  54. package/dist/page/style.d.ts.map +1 -1
  55. package/dist/page/style.js +1 -0
  56. package/dist/page/style.js.map +1 -1
  57. package/dist/render/markdown.d.ts +1 -0
  58. package/dist/render/markdown.d.ts.map +1 -1
  59. package/dist/render/markdown.js +4 -2
  60. package/dist/render/markdown.js.map +1 -1
  61. package/dist/render/questionnaireDocuments.d.ts +3 -0
  62. package/dist/render/questionnaireDocuments.d.ts.map +1 -0
  63. package/dist/render/questionnaireDocuments.js +11 -0
  64. package/dist/render/questionnaireDocuments.js.map +1 -0
  65. package/dist/render/visualDocuments.d.ts.map +1 -1
  66. package/dist/render/visualDocuments.js +3 -0
  67. package/dist/render/visualDocuments.js.map +1 -1
  68. package/dist/responses/agent.d.ts +12 -0
  69. package/dist/responses/agent.d.ts.map +1 -1
  70. package/dist/responses/await.d.ts +6 -0
  71. package/dist/responses/await.d.ts.map +1 -1
  72. package/dist/responses/composerHtml.d.ts +27 -0
  73. package/dist/responses/composerHtml.d.ts.map +1 -0
  74. package/dist/responses/composerHtml.js +14 -0
  75. package/dist/responses/composerHtml.js.map +1 -0
  76. package/dist/responses/history.d.ts +20 -0
  77. package/dist/responses/history.d.ts.map +1 -0
  78. package/dist/responses/history.js +16 -0
  79. package/dist/responses/history.js.map +1 -0
  80. package/dist/responses/locate.d.ts.map +1 -1
  81. package/dist/responses/locate.js +5 -0
  82. package/dist/responses/locate.js.map +1 -1
  83. package/dist/responses/make.d.ts +3 -0
  84. package/dist/responses/make.d.ts.map +1 -0
  85. package/dist/responses/make.js +16 -0
  86. package/dist/responses/make.js.map +1 -0
  87. package/dist/responses/outcome.d.ts +6 -0
  88. package/dist/responses/outcome.d.ts.map +1 -1
  89. package/dist/responses/page.d.ts +1 -1
  90. package/dist/responses/page.d.ts.map +1 -1
  91. package/dist/responses/page.js +7 -10
  92. package/dist/responses/page.js.map +1 -1
  93. package/dist/responses/read.d.ts +6 -0
  94. package/dist/responses/read.d.ts.map +1 -1
  95. package/dist/responses/records.d.ts.map +1 -1
  96. package/dist/responses/records.js +4 -1
  97. package/dist/responses/records.js.map +1 -1
  98. package/dist/responses/retry.d.ts +12 -0
  99. package/dist/responses/retry.d.ts.map +1 -1
  100. package/dist/responses/service.d.ts +37 -1
  101. package/dist/responses/service.d.ts.map +1 -1
  102. package/dist/responses/service.js +19 -14
  103. package/dist/responses/service.js.map +1 -1
  104. package/dist/responses/template.d.ts +7 -0
  105. package/dist/responses/template.d.ts.map +1 -0
  106. package/dist/responses/template.js +58 -0
  107. package/dist/responses/template.js.map +1 -0
  108. package/dist/responses/templateHtml.d.ts +7 -0
  109. package/dist/responses/templateHtml.d.ts.map +1 -0
  110. package/dist/responses/templateHtml.js +18 -0
  111. package/dist/responses/templateHtml.js.map +1 -0
  112. package/dist/responses/templateLinks.d.ts +3 -0
  113. package/dist/responses/templateLinks.d.ts.map +1 -0
  114. package/dist/responses/templateLinks.js +45 -0
  115. package/dist/responses/templateLinks.js.map +1 -0
  116. package/dist/responses/templateNodes.d.ts +17 -0
  117. package/dist/responses/templateNodes.d.ts.map +1 -0
  118. package/dist/responses/templateNodes.js +47 -0
  119. package/dist/responses/templateNodes.js.map +1 -0
  120. package/dist/responses/validateSupersedes.d.ts +5 -0
  121. package/dist/responses/validateSupersedes.d.ts.map +1 -0
  122. package/dist/responses/validateSupersedes.js +21 -0
  123. package/dist/responses/validateSupersedes.js.map +1 -0
  124. package/dist/responses/validatedInput.d.ts +3 -0
  125. package/dist/responses/validatedInput.d.ts.map +1 -0
  126. package/dist/responses/validatedInput.js +43 -0
  127. package/dist/responses/validatedInput.js.map +1 -0
  128. package/dist/responses/waitFor.d.ts +6 -0
  129. package/dist/responses/waitFor.d.ts.map +1 -1
  130. package/dist/rpc/contract.d.ts +24 -0
  131. package/dist/rpc/contract.d.ts.map +1 -1
  132. package/dist/rpc/responseContract.d.ts +48 -0
  133. package/dist/rpc/responseContract.d.ts.map +1 -1
  134. package/dist/serve-cli.js +1 -1
  135. package/package.json +2 -2
  136. package/src/browser/responses/Prompt.ts +6 -0
  137. package/src/browser/responses/answerBody.ts +11 -0
  138. package/src/browser/responses/draftSession.ts +23 -10
  139. package/src/browser/responses/drafts.ts +25 -1
  140. package/src/browser/responses/preview.ts +4 -2
  141. package/src/browser/responses/questionnaireControls.ts +74 -0
  142. package/src/browser/responses/schema.ts +12 -1
  143. package/src/browser/responses/setup.ts +34 -4
  144. package/src/browser/responses/submissionFields.ts +15 -0
  145. package/src/browser/responses/submit.ts +3 -11
  146. package/src/metadata/backlinks.ts +3 -0
  147. package/src/metadata/problems.ts +1 -0
  148. package/src/page/metadataDetails.ts +1 -1
  149. package/src/page/style.ts +1 -0
  150. package/src/render/markdown.ts +5 -2
  151. package/src/render/questionnaireDocuments.ts +12 -0
  152. package/src/render/visualDocuments.ts +3 -0
  153. package/src/responses/composerHtml.ts +28 -0
  154. package/src/responses/history.ts +18 -0
  155. package/src/responses/locate.ts +5 -0
  156. package/src/responses/make.ts +17 -0
  157. package/src/responses/page.ts +7 -18
  158. package/src/responses/records.ts +6 -1
  159. package/src/responses/service.ts +26 -15
  160. package/src/responses/template.ts +72 -0
  161. package/src/responses/templateHtml.ts +26 -0
  162. package/src/responses/templateLinks.ts +43 -0
  163. package/src/responses/templateNodes.ts +55 -0
  164. package/src/responses/validateSupersedes.ts +24 -0
  165. package/src/responses/validatedInput.ts +45 -0
  166. package/src/serve-cli.ts +1 -1
@@ -0,0 +1,72 @@
1
+ import type { RootContent } from "mdast";
2
+ import type { ContainerDirective } from "mdast-util-directive";
3
+ import remarkDirective from "remark-directive";
4
+ import remarkParse from "remark-parse";
5
+ import { unified } from "unified";
6
+ import type { Prompt } from "#browser/responses/Prompt.ts";
7
+ import { metadataParse } from "#metadata/parse.ts";
8
+ import { templateLinks } from "./templateLinks.ts";
9
+ import { isContainer, promptFrom, sliceNode } from "./templateNodes.ts";
10
+
11
+ export interface Template {
12
+ readonly context: string;
13
+ readonly prompts: readonly Prompt[];
14
+ }
15
+ function nestedControl(node: RootContent): boolean {
16
+ return (
17
+ ((node.type === "leafDirective" || node.type === "textDirective" || node.type === "containerDirective")
18
+ && ["question", "option"].includes(node.name))
19
+ || ("children" in node && node.children.some(nestedControl))
20
+ );
21
+ }
22
+ function scopedPrompt(
23
+ node: ContainerDirective,
24
+ source: string,
25
+ requests: readonly { readonly id: string; readonly state: string }[],
26
+ request: string,
27
+ ) {
28
+ const result = promptFrom(node, source);
29
+ if (result.request !== undefined && !requests.some((entry) => entry.id === result.request)) {
30
+ throw new Error("A question's request= must name an attention request in this document.");
31
+ }
32
+ if (result.request === undefined && requests.filter((entry) => entry.state === "open").length > 1) {
33
+ throw new Error("Use request= on question directives when a document has several open attention requests.");
34
+ }
35
+ return result.request === undefined || result.request === request ? result.prompt : undefined;
36
+ }
37
+ export function questionTemplate(context: string, request: string, file?: string): Template {
38
+ const parsed = metadataParse(context);
39
+ const parser = unified().use(remarkParse).use(remarkDirective);
40
+ const body = parsed.body.replace(/\r\n?/gu, "\n");
41
+ const original = parser.parse(body);
42
+ const source = file === undefined ? body : templateLinks(body, original.children, file);
43
+ const tree = parser.parse(source);
44
+ const definitions = tree.children
45
+ .filter((node) => node.type === "definition")
46
+ .map((node) => sliceNode(source, node))
47
+ .join("\n\n");
48
+ const prompts: Prompt[] = [];
49
+ const plain: string[] = [];
50
+
51
+ for (const node of tree.children) {
52
+ if (isContainer(node) && node.name === "question") {
53
+ const prompt = scopedPrompt(node, source, parsed.fields.attention ?? [], request);
54
+ if (prompt) {
55
+ prompts.push({
56
+ ...prompt,
57
+ options: prompt.options.map((option) => ({ ...option, source: `${option.source}\n\n${definitions}`.trim() })),
58
+ source: `${prompt.source}\n\n${definitions}`.trim(),
59
+ });
60
+ }
61
+ } else {
62
+ if (nestedControl(node)) {
63
+ throw new Error("Use top-level question containers with direct option children.");
64
+ }
65
+ plain.push(sliceNode(source, node));
66
+ }
67
+ }
68
+ if (prompts.length > 32 || new Set(prompts.map((prompt) => prompt.id)).size !== prompts.length) {
69
+ throw new Error("Use at most 32 questions with unique IDs per attention request.");
70
+ }
71
+ return { context: plain.join("\n\n"), prompts };
72
+ }
@@ -0,0 +1,26 @@
1
+ import { Effect } from "effect";
2
+ import { escapeHtml } from "#page/escape.ts";
3
+ import { renderMarkdown } from "#render/markdown.ts";
4
+ import { questionTemplate } from "./template.ts";
5
+
6
+ export const templateHtml = Effect.fn("WorkBoard.templateHtml")(function* (context: string, request: string, file: string) {
7
+ const template = questionTemplate(context, request, file);
8
+ function render(source: string) {
9
+ return renderMarkdown(source, { file, safe: true });
10
+ }
11
+ const background = yield* render(template.context);
12
+ const controls = yield* Effect.forEach(template.prompts, (prompt, index) =>
13
+ Effect.gen(function* () {
14
+ const description = yield* render(prompt.source);
15
+ const options = yield* Effect.forEach(prompt.options, (option, choice) =>
16
+ Effect.map(
17
+ render(option.source),
18
+ (html) =>
19
+ `<div class="question-option"><input id="question-${index}-${choice}" type="${prompt.mode === "one" ? "radio" : "checkbox"}" name="choice-${escapeHtml(prompt.id)}" value="${escapeHtml(option.id)}" data-option-source="${escapeHtml(option.source)}"><div><label for="question-${index}-${choice}">Option ${escapeHtml(option.id)}</label>${html}</div></div>`,
20
+ ),
21
+ );
22
+ return `<fieldset data-prompt="${escapeHtml(prompt.id)}" data-mode="${prompt.mode}" data-source="${escapeHtml(prompt.source)}"><legend>Question ${index + 1} · ${escapeHtml(prompt.id)}</legend>${description}<p>${prompt.mode === "text" ? "Answer in your own words" : `${prompt.mode === "one" ? "Choose one option" : "Choose any combination"}, or answer in your own words`}. Nothing is selected for you.</p>${options.join("")}${prompt.mode === "text" ? "" : '<button type="button" data-clear-choices>Clear selections</button>'}<label>Additional text / another answer<textarea data-answer-text maxlength="8192" rows="3"></textarea></label></fieldset>`;
23
+ }),
24
+ );
25
+ return { background, controls: controls.join(""), hasPrompts: template.prompts.length > 0 };
26
+ });
@@ -0,0 +1,43 @@
1
+ import type { RootContent } from "mdast";
2
+ import { fileUrl } from "#files/url.ts";
3
+
4
+ function destination(url: string, file: string) {
5
+ if (url.startsWith("//") || url.search(/^[a-z][a-z\d+.-]*:/iu) === 0) {
6
+ return url;
7
+ }
8
+ const resolved = new URL(url, `http://work-board.local${fileUrl(file)}`);
9
+ return resolved.pathname + resolved.search + resolved.hash;
10
+ }
11
+ function titleSuffix(title: string | null | undefined) {
12
+ return title ? ` "${title.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"` : "";
13
+ }
14
+ export function templateLinks(source: string, nodes: readonly RootContent[], file: string) {
15
+ const edits: { readonly start: number; readonly end: number; readonly text: string }[] = [];
16
+ function visit(node: RootContent) {
17
+ const start = node.position?.start.offset;
18
+ const end = node.position?.end.offset;
19
+ if (start === undefined || end === undefined) {
20
+ return;
21
+ }
22
+ if (node.type === "link") {
23
+ const label = source.slice(node.children.at(0)?.position?.start.offset, node.children.at(-1)?.position?.end.offset);
24
+ edits.push({ end, start, text: `[${label}](<${destination(node.url, file)}>${titleSuffix(node.title)})` });
25
+ } else if (node.type === "image") {
26
+ edits.push({
27
+ end,
28
+ start,
29
+ text: `![${(node.alt ?? "").replaceAll("[", "\\[").replaceAll("]", "\\]")}](<${destination(node.url, file)}>${titleSuffix(node.title)})`,
30
+ });
31
+ } else if (node.type === "definition") {
32
+ edits.push({ end, start, text: `[${node.label ?? node.identifier}]: <${destination(node.url, file)}>${titleSuffix(node.title)}` });
33
+ } else if ("children" in node) {
34
+ node.children.forEach(visit);
35
+ }
36
+ }
37
+ nodes.forEach(visit);
38
+ let result = source;
39
+ for (const edit of edits.sort((a, b) => b.start - a.start)) {
40
+ result = result.slice(0, edit.start) + edit.text + result.slice(edit.end);
41
+ }
42
+ return result;
43
+ }
@@ -0,0 +1,55 @@
1
+ import { Schema } from "effect";
2
+ import type { RootContent } from "mdast";
3
+ import type { ContainerDirective } from "mdast-util-directive";
4
+ import type { Prompt } from "#browser/responses/Prompt.ts";
5
+ import { Identity } from "#path/Identity.ts";
6
+
7
+ const Id = Identity.check(Schema.isMaxLength(128));
8
+ const PromptFields = Schema.Struct({ id: Id, request: Schema.optional(Identity), select: Schema.optional(Schema.Literals(["one", "many", "text"])) });
9
+ const OptionFields = Schema.Struct({ id: Id });
10
+
11
+ export function sliceNode(source: string, node: RootContent) {
12
+ return source.slice(node.position?.start.offset, node.position?.end.offset);
13
+ }
14
+ export function isContainer(node: RootContent): node is ContainerDirective {
15
+ return node.type === "containerDirective";
16
+ }
17
+ function hasControl(node: RootContent): boolean {
18
+ return (
19
+ ((node.type === "containerDirective" || node.type === "leafDirective" || node.type === "textDirective")
20
+ && ["question", "option"].includes(node.name))
21
+ || ("children" in node && node.children.some(hasControl))
22
+ );
23
+ }
24
+ export function promptFrom(node: ContainerDirective, source: string) {
25
+ const attrs = Schema.decodeUnknownSync(PromptFields, { onExcessProperty: "error" })(node.attributes ?? {});
26
+ const options = node.children.filter((child) => isContainer(child) && child.name === "option");
27
+ const description = node.children.filter((child) => !(isContainer(child) && child.name === "option"));
28
+ if (
29
+ description.some(hasControl)
30
+ || description.length === 0
31
+ || options.length > 32
32
+ || (attrs.select === "text" ? options.length > 0 : options.length === 0)
33
+ ) {
34
+ throw new Error("A question needs Markdown context and 1–32 direct options (or select=text without options), without nested questions.");
35
+ }
36
+ const choices = options.map((option) => {
37
+ if (!isContainer(option) || option.children.some(hasControl) || option.children.length === 0) {
38
+ throw new Error("Options need Markdown descriptions and cannot nest response controls.");
39
+ }
40
+ const fields = Schema.decodeUnknownSync(OptionFields, { onExcessProperty: "error" })(option.attributes ?? {});
41
+ return { id: fields.id, source: option.children.map((child) => sliceNode(source, child)).join("\n\n") };
42
+ });
43
+ if (new Set(choices.map((option) => option.id)).size !== choices.length) {
44
+ throw new Error("Option IDs must be unique within each question.");
45
+ }
46
+ return {
47
+ prompt: {
48
+ id: attrs.id,
49
+ mode: attrs.select ?? "one",
50
+ options: choices,
51
+ source: description.map((child) => sliceNode(source, child)).join("\n\n"),
52
+ } satisfies Prompt,
53
+ request: attrs.request,
54
+ };
55
+ }
@@ -0,0 +1,24 @@
1
+ import { Effect } from "effect";
2
+ import { type DraftInput, type Question, ResponseFailed } from "#browser/responses/schema.ts";
3
+ import type { Snapshot } from "#search/snapshot.ts";
4
+ import { questionFrom, responseFrom } from "./records.ts";
5
+
6
+ export function validateSupersedes(input: DraftInput, question: Question, data: Snapshot) {
7
+ if (input.supersedes === undefined) {
8
+ return Effect.void;
9
+ }
10
+ const matches = data.model.ids.get(input.supersedes);
11
+ const previous = matches?.length === 1 && matches[0] ? responseFrom(matches[0]) : undefined;
12
+ const context = previous && data.model.ids.get(previous.response.question);
13
+ const old = context?.length === 1 && context[0] ? questionFrom(context[0]) : undefined;
14
+ return old
15
+ && previous
16
+ && previous.response.reviewedRevision === old.question.reviewedRevision
17
+ && old.question.item === question.question.item
18
+ && old.question.request === question.question.request
19
+ && input.supersedes !== input.id
20
+ ? Effect.void
21
+ : Effect.fail(
22
+ new ResponseFailed({ code: "Conflict", message: "Supersedes must identify one recorded response to this item's attention request." }),
23
+ );
24
+ }
@@ -0,0 +1,45 @@
1
+ import { answerBody } from "#browser/responses/answerBody.ts";
2
+ import type { Prompt } from "#browser/responses/Prompt.ts";
3
+ import type { DraftInput, PromptAnswer } from "#browser/responses/schema.ts";
4
+ import { questionTemplate } from "./template.ts";
5
+
6
+ function answerFor(prompt: Prompt, answer: PromptAnswer | undefined, required: boolean) {
7
+ if (!answer || answer.prompt !== prompt.id) {
8
+ throw new Error("Submit one answer per question, in template order.");
9
+ }
10
+ if (new Set(answer.selected).size !== answer.selected.length || (prompt.mode === "one" && answer.selected.length > 1)) {
11
+ throw new Error("The selected options do not match the question's selection mode.");
12
+ }
13
+ if (answer.selected.some((id) => !prompt.options.some((option) => option.id === id))) {
14
+ throw new Error("A selected option is not in the reviewed template.");
15
+ }
16
+ if (required && answer.selected.length === 0 && !answer.text.trim()) {
17
+ throw new Error("Answer every question with a selection or additional text, or use Clarify / Not now.");
18
+ }
19
+ }
20
+ export function validatedInput(input: DraftInput, context: string, request: string, file?: string): DraftInput {
21
+ const template = questionTemplate(context, request, file);
22
+ if (input.answers === undefined) {
23
+ if (template.prompts.length > 0 && input.type === "answer") {
24
+ throw new Error("This question packet requires answers for all prompts.");
25
+ }
26
+ if (!input.body.trim()) {
27
+ throw new Error("A response needs text or answers to the question packet.");
28
+ }
29
+ return input;
30
+ }
31
+ if (input.answers.length !== template.prompts.length || template.prompts.length === 0) {
32
+ throw new Error("The answer packet does not match the reviewed questions.");
33
+ }
34
+ template.prompts.forEach((prompt, index) => {
35
+ answerFor(prompt, input.answers?.[index], input.type === "answer");
36
+ });
37
+ if (input.type !== "answer" && !input.body.trim() && !input.answers.some((answer) => answer.text.trim())) {
38
+ throw new Error("Clarify / Not now needs explanatory text.");
39
+ }
40
+ const body = answerBody(template.prompts, input.answers, input.body);
41
+ if (body.length > 32_768) {
42
+ throw new Error("The complete recorded response exceeds 32768 characters. Shorten the response or split the source request.");
43
+ }
44
+ return { ...input, body };
45
+ }
package/src/serve-cli.ts CHANGED
@@ -4,7 +4,7 @@ import { Effect, Layer, Option, Path } from "effect";
4
4
  import { Argument, Command, Flag } from "effect/unstable/cli";
5
5
  import { HOST, serveBoard } from "./serve.ts";
6
6
 
7
- const VERSION = "0.8.0";
7
+ const VERSION = "0.9.1";
8
8
 
9
9
  const serve = Effect.fn("WorkBoard.serve")(function* (input: {
10
10
  readonly dir: string;