markform 0.1.21 → 0.1.22

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 (33) hide show
  1. package/README.md +32 -4
  2. package/dist/ai-sdk.d.mts +1 -1
  3. package/dist/ai-sdk.mjs +1 -1
  4. package/dist/{apply-CD-t7ovb.mjs → apply-C7mO7VkZ.mjs} +100 -74
  5. package/dist/apply-C7mO7VkZ.mjs.map +1 -0
  6. package/dist/bin.mjs +1 -1
  7. package/dist/{cli-ChdIy1a7.mjs → cli-C8F9yDsv.mjs} +17 -1213
  8. package/dist/cli-C8F9yDsv.mjs.map +1 -0
  9. package/dist/cli.mjs +1 -1
  10. package/dist/{coreTypes-BQrWf_Wt.d.mts → coreTypes-BlsJkU1w.d.mts} +1 -1
  11. package/dist/fillRecord-DTl5lnK0.d.mts +345 -0
  12. package/dist/fillRecordRenderer-CruJrLkj.mjs +1256 -0
  13. package/dist/fillRecordRenderer-CruJrLkj.mjs.map +1 -0
  14. package/dist/index.d.mts +5 -342
  15. package/dist/index.mjs +3 -3
  16. package/dist/render.d.mts +74 -0
  17. package/dist/render.mjs +4 -0
  18. package/dist/{session-ZgegwtkT.mjs → session-BCcltrLA.mjs} +1 -1
  19. package/dist/{session-ZgegwtkT.mjs.map → session-BCcltrLA.mjs.map} +1 -1
  20. package/dist/{session-BPuQ-ok0.mjs → session-VeSkVrck.mjs} +1 -1
  21. package/dist/{shared-DwdyWmvE.mjs → shared-CsdT2T7k.mjs} +1 -1
  22. package/dist/{shared-DwdyWmvE.mjs.map → shared-CsdT2T7k.mjs.map} +1 -1
  23. package/dist/{shared-BTR35aMz.mjs → shared-fb0nkzQi.mjs} +1 -1
  24. package/dist/{src-DOPe4tmu.mjs → src-CbRnGzMK.mjs} +16 -11
  25. package/dist/{src-DOPe4tmu.mjs.map → src-CbRnGzMK.mjs.map} +1 -1
  26. package/dist/urlFormat-lls7CsEP.mjs +71 -0
  27. package/dist/urlFormat-lls7CsEP.mjs.map +1 -0
  28. package/docs/markform-apis.md +53 -0
  29. package/examples/simple/simple-skipped-filled.report.md +8 -8
  30. package/examples/twitter-thread/twitter-thread.form.md +373 -0
  31. package/package.json +5 -1
  32. package/dist/apply-CD-t7ovb.mjs.map +0 -1
  33. package/dist/cli-ChdIy1a7.mjs.map +0 -1
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Markform
2
2
 
3
- [![CI](https://github.com/jlevy/markform/actions/workflows/ci.yml/badge.svg)](https://github.com/jlevy/markform/actions/runs/21611973252)
4
- [![Coverage](https://raw.githubusercontent.com/jlevy/markform/main/badges/packages/markform/coverage-total.svg)](https://github.com/jlevy/markform/actions/runs/21611973252)
3
+ [![CI](https://github.com/jlevy/markform/actions/workflows/ci.yml/badge.svg)](https://github.com/jlevy/markform/actions/runs/21885509869)
4
+ [![Coverage](https://raw.githubusercontent.com/jlevy/markform/main/badges/packages/markform/coverage-total.svg)](https://github.com/jlevy/markform/actions/runs/21885509869)
5
5
  [![npm version](https://img.shields.io/npm/v/markform)](https://www.npmjs.com/package/markform)
6
6
  [![X Follow](https://img.shields.io/twitter/follow/ojoshe)](https://x.com/ojoshe)
7
7
 
@@ -551,8 +551,8 @@ If unsure, try `gpt-5-mini` first as it’s fast and supports web search.
551
551
 
552
552
  ## Programmatic Usage
553
553
 
554
- Markform exports a parsing engine and AI SDK integration for use in your own
555
- applications.
554
+ Markform exports a parsing engine, rendering functions, and AI SDK integration for use in
555
+ your own applications.
556
556
 
557
557
  ### Basic Parsing
558
558
 
@@ -594,6 +594,34 @@ See the
594
594
  [API documentation](https://github.com/jlevy/markform/blob/main/docs/markform-apis.md)
595
595
  for options like parallel execution, callbacks, and checkpointing.
596
596
 
597
+ ### Rendering API
598
+
599
+ Import from the `markform/render` subpath to render forms and fill records as HTML
600
+ fragments — the same output as `markform serve`, without pulling in CLI/server
601
+ dependencies:
602
+
603
+ ```typescript
604
+ import {
605
+ renderViewContent,
606
+ renderFillRecordContent,
607
+ FILL_RECORD_STYLES,
608
+ FILL_RECORD_SCRIPTS,
609
+ } from "markform/render";
610
+
611
+ // Render a filled form as read-only HTML
612
+ const formHtml = renderViewContent(parsedForm);
613
+
614
+ // Render a fill record dashboard
615
+ const dashboardHtml = renderFillRecordContent(fillRecord);
616
+ ```
617
+
618
+ Also exports `renderSourceContent`, `renderMarkdownContent`, `renderYamlContent`,
619
+ `renderJsonContent`, `escapeHtml`, `formatDuration`, and `formatTokens`.
620
+
621
+ See the
622
+ [API documentation](https://github.com/jlevy/markform/blob/main/docs/markform-apis.md#rendering-api)
623
+ for full details.
624
+
597
625
  ### AI SDK Integration
598
626
 
599
627
  Markform provides tools compatible with the [Vercel AI SDK](https://sdk.vercel.ai/):
package/dist/ai-sdk.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- import { At as ParsedForm, Nt as PatchSchema, U as FieldResponse, Y as FormSchema, at as InspectResult, et as Id, hr as ValidatorRegistry, jt as Patch, r as ApplyResult } from "./coreTypes-BQrWf_Wt.mjs";
2
+ import { At as ParsedForm, Nt as PatchSchema, U as FieldResponse, Y as FormSchema, at as InspectResult, et as Id, hr as ValidatorRegistry, jt as Patch, r as ApplyResult } from "./coreTypes-BlsJkU1w.mjs";
3
3
  import { z } from "zod";
4
4
 
5
5
  //#region src/integrations/toolTypes.d.ts
package/dist/ai-sdk.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
 
2
2
  import { R as PatchSchema } from "./coreTypes-CTLr-NGd.mjs";
3
- import { d as serializeForm, i as inspect, t as applyPatches } from "./apply-CD-t7ovb.mjs";
3
+ import { d as serializeForm, i as inspect, t as applyPatches } from "./apply-C7mO7VkZ.mjs";
4
4
  import { z } from "zod";
5
5
 
6
6
  //#region src/integrations/vercelAiSdkTools.ts
@@ -1,8 +1,9 @@
1
1
 
2
+ import { n as formatUrlAsMarkdownLink } from "./urlFormat-lls7CsEP.mjs";
2
3
  import YAML from "yaml";
3
4
 
4
5
  //#region src/errors.ts
5
- const VERSION = "0.1.21";
6
+ const VERSION = "0.1.22";
6
7
  /**
7
8
  * Base error class for all markform errors.
8
9
  * Consumers can catch this to handle any markform error.
@@ -94,12 +95,15 @@ var MarkformLlmError = class extends MarkformError {
94
95
  statusCode;
95
96
  /** Whether this error is retryable */
96
97
  retryable;
98
+ /** Raw response body from the API (for debugging) */
99
+ responseBody;
97
100
  constructor(message, context) {
98
101
  super(message, { cause: context.cause });
99
102
  this.provider = context.provider;
100
103
  this.model = context.model;
101
104
  this.statusCode = context.statusCode;
102
105
  this.retryable = context.retryable ?? false;
106
+ this.responseBody = context.responseBody;
103
107
  }
104
108
  };
105
109
  /**
@@ -173,6 +177,86 @@ function isRetryableError(error) {
173
177
  return isLlmError(error) && error.retryable;
174
178
  }
175
179
  /**
180
+ * Check if an error looks like a Vercel AI SDK APICallError.
181
+ * These errors have statusCode, responseBody, and isRetryable properties.
182
+ */
183
+ function isApiCallError(error) {
184
+ return error instanceof Error && ("statusCode" in error || "responseBody" in error || "isRetryable" in error);
185
+ }
186
+ /**
187
+ * Generate troubleshooting hints based on error status and message.
188
+ */
189
+ function getTroubleshootingHints(statusCode, message, provider, model) {
190
+ const hints = [];
191
+ const lowerMessage = message.toLowerCase();
192
+ if (statusCode === 404 || lowerMessage.includes("not found")) {
193
+ hints.push(`Check if model "${model}" exists and is available for the ${provider} API`);
194
+ hints.push("Verify the model ID is spelled correctly (check provider documentation)");
195
+ hints.push("Some models require specific API tier access or waitlist approval");
196
+ } else if (statusCode === 403 || lowerMessage.includes("forbidden")) {
197
+ hints.push(`Your API key may not have permission to use model "${model}"`);
198
+ hints.push("Check that your API key has the required access tier/plan");
199
+ hints.push("Some preview models require explicit opt-in via provider dashboard");
200
+ } else if (statusCode === 401 || lowerMessage.includes("unauthorized") || lowerMessage.includes("invalid api key")) {
201
+ hints.push("Verify your API key is correct and not expired");
202
+ hints.push(`Check that the correct env var is set (e.g., ${provider.toUpperCase()}_API_KEY)`);
203
+ } else if (statusCode === 429 || lowerMessage.includes("rate limit")) {
204
+ hints.push("You have hit the rate limit - wait a moment and retry");
205
+ hints.push("Consider using a model with higher rate limits or upgrading your plan");
206
+ } else if (statusCode === 500 || statusCode === 502 || statusCode === 503) {
207
+ hints.push("The API provider is experiencing issues - retry in a few minutes");
208
+ hints.push("Check the provider status page for any ongoing incidents");
209
+ }
210
+ if (hints.length === 0) {
211
+ hints.push("Check your API key and model availability");
212
+ hints.push(`Run "markform models" to see available models for ${provider}`);
213
+ }
214
+ return hints;
215
+ }
216
+ /**
217
+ * Wrap an API error with rich context for debugging.
218
+ *
219
+ * Extracts details from Vercel AI SDK APICallError and creates a MarkformLlmError
220
+ * with actionable information including model ID, status code, response body, and
221
+ * troubleshooting hints.
222
+ *
223
+ * @param error - The original error from the API call
224
+ * @param provider - The LLM provider name (e.g., 'anthropic', 'openai')
225
+ * @param model - The model identifier that was requested
226
+ * @returns A MarkformLlmError with rich context
227
+ */
228
+ function wrapApiError(error, provider, model) {
229
+ let statusCode;
230
+ let responseBody;
231
+ let retryable = false;
232
+ let originalMessage = "Unknown error";
233
+ if (isApiCallError(error)) {
234
+ statusCode = error.statusCode;
235
+ responseBody = error.responseBody;
236
+ retryable = error.isRetryable ?? false;
237
+ originalMessage = error.message;
238
+ } else if (error instanceof Error) originalMessage = error.message;
239
+ else originalMessage = String(error);
240
+ const parts = [];
241
+ parts.push(`API call failed for model "${provider}/${model}"`);
242
+ if (statusCode !== void 0) parts.push(`HTTP ${statusCode}`);
243
+ parts.push(originalMessage);
244
+ if (responseBody) {
245
+ const truncated = responseBody.length > 200 ? responseBody.slice(0, 200) + "..." : responseBody;
246
+ parts.push(`Response: ${truncated}`);
247
+ }
248
+ const hints = getTroubleshootingHints(statusCode, originalMessage, provider, model);
249
+ if (hints.length > 0) parts.push(`\n\nTroubleshooting:\n - ${hints.join("\n - ")}`);
250
+ return new MarkformLlmError(parts.join(": "), {
251
+ provider,
252
+ model,
253
+ statusCode,
254
+ retryable,
255
+ responseBody,
256
+ cause: error instanceof Error ? error : void 0
257
+ });
258
+ }
259
+ /**
176
260
  * Alias for MarkformParseError.
177
261
  * @deprecated Use MarkformParseError instead. ParseError will be removed in a future version.
178
262
  */
@@ -707,6 +791,7 @@ function extractTableContent(node) {
707
791
  function extractTextFromNode(n) {
708
792
  if (!n || typeof n !== "object") return "";
709
793
  if (n.type === "text" && typeof n.attributes?.content === "string") return n.attributes.content;
794
+ if (n.type === "link" && typeof n.attributes?.href === "string") return `[${n.children?.map(extractTextFromNode).join("") ?? ""}](${n.attributes.href})`;
710
795
  if (n.children && Array.isArray(n.children)) return n.children.map(extractTextFromNode).join("");
711
796
  return "";
712
797
  }
@@ -1357,74 +1442,6 @@ function priorityKeyComparator(priorityKeys) {
1357
1442
  });
1358
1443
  }
1359
1444
 
1360
- //#endregion
1361
- //#region src/utils/urlFormat.ts
1362
- /**
1363
- * Create a friendly abbreviated display name for a URL.
1364
- * - Drops "www." prefix from domain
1365
- * - Adds first portion of path (up to maxPathChars) if present
1366
- * - Adds ellipsis (…) if path is truncated
1367
- *
1368
- * @param url - The URL to abbreviate
1369
- * @param maxPathChars - Maximum characters to include from the path (default: 12)
1370
- * @returns Friendly abbreviated URL (e.g., "example.com/docs/api…")
1371
- */
1372
- function friendlyUrlAbbrev(url, maxPathChars = 12) {
1373
- try {
1374
- const parsed = new URL(url);
1375
- let hostname = parsed.hostname;
1376
- if (hostname.startsWith("www.")) hostname = hostname.slice(4);
1377
- const path = parsed.pathname.slice(1);
1378
- if (!path) return hostname;
1379
- if (path.length <= maxPathChars) return `${hostname}/${path}`;
1380
- return `${hostname}/${path.slice(0, maxPathChars)}…`;
1381
- } catch {
1382
- let result = url;
1383
- result = result.replace(/^https?:\/\//, "");
1384
- result = result.replace(/^www\./, "");
1385
- const maxLen = 30;
1386
- if (result.length > maxLen) return result.slice(0, maxLen) + "…";
1387
- return result;
1388
- }
1389
- }
1390
- /**
1391
- * Format a URL as a markdown link with a friendly abbreviated display text.
1392
- * The full URL is preserved as the link target.
1393
- *
1394
- * @param url - The URL to format
1395
- * @returns Markdown link in format [friendly-abbrev](url)
1396
- */
1397
- function formatUrlAsMarkdownLink(url) {
1398
- return `[${friendlyUrlAbbrev(url)}](${url})`;
1399
- }
1400
- /**
1401
- * Format bare URLs in text as HTML links with abbreviated display text.
1402
- * Also handles markdown-style links [text](url) for consistency.
1403
- *
1404
- * Processing order:
1405
- * 1. Escape all HTML to prevent XSS
1406
- * 2. Convert markdown links [text](url) to <a> tags
1407
- * 3. Convert bare URLs (not already in links) to <a> tags with abbreviated display
1408
- *
1409
- * @param text - The raw text containing URLs (will be HTML-escaped)
1410
- * @param escapeHtml - Function to escape HTML entities
1411
- * @returns HTML-safe text with URLs converted to <a> tags
1412
- */
1413
- function formatBareUrlsAsHtmlLinks(text, escapeHtml) {
1414
- let result = escapeHtml(text);
1415
- result = result.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_match, linkText, url) => {
1416
- const cleanUrl = url.replace(/&amp;/g, "&");
1417
- return `<a href="${escapeHtml(cleanUrl)}" target="_blank" class="url-link" data-url="${escapeHtml(cleanUrl)}">${linkText}</a>`;
1418
- });
1419
- result = result.replace(/(?<!href="|data-url="|">)(?:https?:\/\/|www\.)[^\s<>"]+(?<![.,;:!?'")])/g, (url) => {
1420
- const cleanUrl = url.replace(/&amp;/g, "&");
1421
- const fullUrl = cleanUrl.startsWith("www.") ? `https://${cleanUrl}` : cleanUrl;
1422
- const display = friendlyUrlAbbrev(fullUrl);
1423
- return `<a href="${escapeHtml(fullUrl)}" target="_blank" class="url-link" data-url="${escapeHtml(fullUrl)}">${escapeHtml(display)}</a>`;
1424
- });
1425
- return result;
1426
- }
1427
-
1428
1445
  //#endregion
1429
1446
  //#region src/engine/serialize.ts
1430
1447
  /**
@@ -1976,14 +1993,13 @@ function serializeYearField(field, response) {
1976
1993
  }
1977
1994
  /**
1978
1995
  * Serialize a cell value for table output.
1979
- * URL-typed columns are formatted as markdown links with domain as display text.
1996
+ * Values are serialized as-is; HTML rendering handles display formatting.
1980
1997
  */
1981
- function serializeCellValue(cell, columnType) {
1998
+ function serializeCellValue(cell, _columnType) {
1982
1999
  if (cell.state === "skipped") return cell.reason ? `%SKIP:${cell.reason}%` : "%SKIP%";
1983
2000
  if (cell.state === "aborted") return cell.reason ? `%ABORT:${cell.reason}%` : "%ABORT%";
1984
2001
  if (cell.value === void 0 || cell.value === null) return "";
1985
2002
  if (typeof cell.value === "number") return String(cell.value);
1986
- if (columnType === "url") return formatUrlAsMarkdownLink(cell.value);
1987
2003
  return cell.value;
1988
2004
  }
1989
2005
  /**
@@ -2383,6 +2399,16 @@ function serializeFieldRaw(field, responses) {
2383
2399
  const lines = [];
2384
2400
  lines.push(`**${field.label}:**`);
2385
2401
  lines.push("");
2402
+ if (response?.state === "skipped") {
2403
+ const text = response.reason ? `_(skipped: ${response.reason})_` : "_(skipped)_";
2404
+ lines.push(text);
2405
+ return lines.join("\n");
2406
+ }
2407
+ if (response?.state === "aborted") {
2408
+ const text = response.reason ? `_(aborted: ${response.reason})_` : "_(aborted)_";
2409
+ lines.push(text);
2410
+ return lines.join("\n");
2411
+ }
2386
2412
  const value = response?.state === "answered" ? response.value : void 0;
2387
2413
  switch (field.kind) {
2388
2414
  case "string": {
@@ -4330,5 +4356,5 @@ function applyPatches(form, patches) {
4330
4356
  }
4331
4357
 
4332
4358
  //#endregion
4333
- export { transformHarnessConfigToTs as $, parseOptionText as A, DEFAULT_RESEARCH_MAX_ISSUES_PER_TURN as B, extractTableContent as C, getStringAttr as D, getStringArrayAttr as E, DEFAULT_MAX_PATCHES_PER_TURN as F, REPORT_EXTENSION as G, DEFAULT_ROLES as H, DEFAULT_MAX_STEPS_PER_TURN as I, deriveFillRecordPath as J, USER_ROLE as K, DEFAULT_MAX_TURNS as L, DEFAULT_FORMS_DIR as M, DEFAULT_MAX_ISSUES_PER_TURN as N, getValidateAttr as O, DEFAULT_MAX_PARALLEL_AGENTS as P, parseRolesFlag as Q, DEFAULT_PORT as R, extractOptionItems as S, getNumberAttr as T, DEFAULT_ROLE_INSTRUCTIONS as U, DEFAULT_RESEARCH_MAX_PATCHES_PER_TURN as V, MAX_FORMS_IN_MENU as W, deriveSchemaPath as X, deriveReportPath as Y, detectFileType as Z, preprocessCommentSyntax as _, isMarkformError as _t, validate as a, parseModelIdForDisplay as at, CHECKBOX_MARKERS as b, isRetryableError as bt, computeProgressSummary as c, MarkformError as ct, serializeForm as d, MarkformPatchError as dt, SUGGESTED_LLMS as et, serializeRawMarkdown as f, MarkformValidationError as ft, detectSyntaxStyle as g, isLlmError as gt, friendlyUrlAbbrev as h, isConfigError as ht, inspect as i, hasWebSearchSupport as it, AGENT_ROLE as j, isTagNode as k, computeStructureSummary as l, MarkformLlmError as lt, formatBareUrlsAsHtmlLinks as m, isAbortError as mt, getAllFields as n, formatSuggestedLlms as nt, computeAllSummaries as o, MarkformAbortError as ot, serializeReport as p, ParseError as pt, deriveExportPath as q, getFieldsForRoles as r, getWebSearchConfig as rt, computeFormState as s, MarkformConfigError as st, applyPatches as t, WEB_SEARCH_CONFIG as tt, isFormComplete as u, MarkformParseError as ut, validateSyntaxConsistency as v, isParseError as vt, getBooleanAttr as w, extractFenceValue as x, isValidationError as xt, tryParseSentinelResponse as y, isPatchError as yt, DEFAULT_PRIORITY as z };
4334
- //# sourceMappingURL=apply-CD-t7ovb.mjs.map
4359
+ export { WEB_SEARCH_CONFIG as $, DEFAULT_FORMS_DIR as A, DEFAULT_ROLES as B, getNumberAttr as C, isTagNode as D, getValidateAttr as E, DEFAULT_MAX_TURNS as F, deriveExportPath as G, MAX_FORMS_IN_MENU as H, DEFAULT_PORT as I, deriveSchemaPath as J, deriveFillRecordPath as K, DEFAULT_PRIORITY as L, DEFAULT_MAX_PARALLEL_AGENTS as M, DEFAULT_MAX_PATCHES_PER_TURN as N, parseOptionText as O, DEFAULT_MAX_STEPS_PER_TURN as P, SUGGESTED_LLMS as Q, DEFAULT_RESEARCH_MAX_ISSUES_PER_TURN as R, getBooleanAttr as S, getStringAttr as T, REPORT_EXTENSION as U, DEFAULT_ROLE_INSTRUCTIONS as V, USER_ROLE as W, parseRolesFlag as X, detectFileType as Y, transformHarnessConfigToTs as Z, tryParseSentinelResponse as _, isPatchError as _t, validate as a, MarkformConfigError as at, extractOptionItems as b, wrapApiError as bt, computeProgressSummary as c, MarkformParseError as ct, serializeForm as d, ParseError as dt, formatSuggestedLlms as et, serializeRawMarkdown as f, isAbortError as ft, validateSyntaxConsistency as g, isParseError as gt, preprocessCommentSyntax as h, isMarkformError as ht, inspect as i, MarkformAbortError as it, DEFAULT_MAX_ISSUES_PER_TURN as j, AGENT_ROLE as k, computeStructureSummary as l, MarkformPatchError as lt, detectSyntaxStyle as m, isLlmError as mt, getAllFields as n, hasWebSearchSupport as nt, computeAllSummaries as o, MarkformError as ot, serializeReport as p, isConfigError as pt, deriveReportPath as q, getFieldsForRoles as r, parseModelIdForDisplay as rt, computeFormState as s, MarkformLlmError as st, applyPatches as t, getWebSearchConfig as tt, isFormComplete as u, MarkformValidationError as ut, CHECKBOX_MARKERS as v, isRetryableError as vt, getStringArrayAttr as w, extractTableContent as x, extractFenceValue as y, isValidationError as yt, DEFAULT_RESEARCH_MAX_PATCHES_PER_TURN as z };
4360
+ //# sourceMappingURL=apply-C7mO7VkZ.mjs.map