markform 0.1.3 → 0.1.5

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 (36) hide show
  1. package/README.md +110 -70
  2. package/dist/ai-sdk.d.mts +2 -2
  3. package/dist/ai-sdk.mjs +5 -5
  4. package/dist/{apply-00UmzDKL.mjs → apply-BCCiJzQr.mjs} +371 -26
  5. package/dist/bin.mjs +6 -6
  6. package/dist/{cli-D--Lel-e.mjs → cli-D469amuk.mjs} +386 -96
  7. package/dist/cli.mjs +6 -6
  8. package/dist/{coreTypes-BXhhz9Iq.d.mts → coreTypes-9XZSNOv6.d.mts} +1878 -325
  9. package/dist/{coreTypes-Dful87E0.mjs → coreTypes-pyctKRgc.mjs} +79 -5
  10. package/dist/index.d.mts +142 -5
  11. package/dist/index.mjs +5 -5
  12. package/dist/session-B_stoXQn.mjs +4 -0
  13. package/dist/{session-Bqnwi9wp.mjs → session-uF0e6m6k.mjs} +9 -5
  14. package/dist/{shared-N_s1M-_K.mjs → shared-BqPnYXrn.mjs} +82 -1
  15. package/dist/shared-CZsyShck.mjs +3 -0
  16. package/dist/{src-Dm8jZ5dl.mjs → src-Df0XX7UB.mjs} +818 -125
  17. package/docs/markform-apis.md +194 -0
  18. package/{DOCS.md → docs/markform-reference.md} +130 -69
  19. package/{SPEC.md → docs/markform-spec.md} +359 -108
  20. package/examples/earnings-analysis/earnings-analysis.form.md +88 -800
  21. package/examples/earnings-analysis/earnings-analysis.valid.ts +16 -148
  22. package/examples/movie-research/movie-research-basic.form.md +41 -37
  23. package/examples/movie-research/movie-research-deep.form.md +110 -98
  24. package/examples/movie-research/movie-research-minimal.form.md +29 -15
  25. package/examples/simple/simple-mock-filled.form.md +105 -41
  26. package/examples/simple/simple-skipped-filled.form.md +103 -41
  27. package/examples/simple/simple-with-skips.session.yaml +93 -25
  28. package/examples/simple/simple.form.md +86 -32
  29. package/examples/simple/simple.session.yaml +98 -25
  30. package/examples/startup-deep-research/startup-deep-research.form.md +130 -103
  31. package/examples/startup-research/startup-research-mock-filled.form.md +55 -55
  32. package/examples/startup-research/startup-research.form.md +36 -36
  33. package/package.json +18 -19
  34. package/dist/session-DdAtY2Ni.mjs +0 -4
  35. package/dist/shared-D7gf27Tr.mjs +0 -3
  36. package/examples/celebrity-deep-research/celebrity-deep-research.form.md +0 -912
@@ -0,0 +1,194 @@
1
+ # Markform APIs
2
+
3
+ Markform provides TypeScript APIs for parsing, validating, and manipulating forms
4
+ programmatically.
5
+
6
+ ## CLI
7
+
8
+ The `markform` CLI is self-documenting:
9
+
10
+ ```bash
11
+ markform --help # List all commands
12
+ markform <command> --help # Help for specific command
13
+ markform docs # Form syntax quick reference
14
+ markform spec # Full specification
15
+ markform apis # This document
16
+ ```
17
+
18
+ **Form syntax:** See [markform-reference.md](markform-reference.md) for the quick
19
+ reference on form syntax, field kinds, and attributes.
20
+
21
+ ## Project Installation
22
+
23
+ ```bash
24
+ npm install markform
25
+ ```
26
+
27
+ ## Core Engine API
28
+
29
+ Import from the main package:
30
+
31
+ ```typescript
32
+ import {
33
+ parseForm,
34
+ serialize,
35
+ validate,
36
+ inspect,
37
+ applyPatches,
38
+ } from 'markform';
39
+ ```
40
+
41
+ ### parseForm(content: string): ParsedForm
42
+
43
+ Parse a `.form.md` file into a structured form object.
44
+
45
+ ### serialize(form: ParsedForm, options?: SerializeOptions): string
46
+
47
+ Convert a parsed form back to Markdown.
48
+
49
+ ### validate(form: ParsedForm, options?: ValidateOptions): ValidateResult
50
+
51
+ Validate form syntax and constraints.
52
+
53
+ ### inspect(form: ParsedForm, options?: InspectOptions): InspectResult
54
+
55
+ Get form state including structure, progress, and validation issues.
56
+
57
+ ### applyPatches(form: ParsedForm, patches: Patch[]): ApplyResult
58
+
59
+ Apply value changes to a form.
60
+ Modifies the form in place.
61
+
62
+ ## Vercel AI SDK Integration
63
+
64
+ Import from the ai-sdk subpath:
65
+
66
+ ```typescript
67
+ import {
68
+ createMarkformTools,
69
+ MarkformSessionStore
70
+ } from 'markform/ai-sdk';
71
+ ```
72
+
73
+ ### MarkformSessionStore
74
+
75
+ Session store for managing form state during AI interactions.
76
+
77
+ ```typescript
78
+ const store = new MarkformSessionStore(parsedForm);
79
+ ```
80
+
81
+ ### createMarkformTools(options): MarkformToolSet
82
+
83
+ Create AI SDK compatible tools for agent-driven form filling.
84
+
85
+ ```typescript
86
+ import { generateText } from 'ai';
87
+
88
+ const tools = createMarkformTools({ sessionStore: store });
89
+ const { text } = await generateText({
90
+ model: yourModel,
91
+ tools,
92
+ prompt: 'Fill out this form...',
93
+ });
94
+ ```
95
+
96
+ **Available tools:**
97
+
98
+ | Tool | Description |
99
+ | --- | --- |
100
+ | `markform_inspect` | Get form state, structure, progress, issues |
101
+ | `markform_apply` | Apply patches to update field values |
102
+ | `markform_export` | Export schema and values as JSON |
103
+ | `markform_get_markdown` | Get canonical Markdown representation |
104
+
105
+ See [vercelAiSdkTools.ts](../packages/markform/src/integrations/vercelAiSdkTools.ts) for
106
+ full details.
107
+
108
+ ### Patch Operations
109
+
110
+ Use `markform_apply` with an array of patches.
111
+ Each patch has an `op` and `fieldId`.
112
+
113
+ | Operation | Fields | Value Format |
114
+ | --- | --- | --- |
115
+ | `set_string` | string | `{ "op": "set_string", "fieldId": "name", "value": "Alice" }` |
116
+ | `set_number` | number | `{ "op": "set_number", "fieldId": "age", "value": 25 }` |
117
+ | `set_string_list` | string_list | `{ "op": "set_string_list", "fieldId": "tags", "items": ["a", "b"] }` |
118
+ | `set_single_select` | single_select | `{ "op": "set_single_select", "fieldId": "rating", "selected": "high" }` |
119
+ | `set_multi_select` | multi_select | `{ "op": "set_multi_select", "fieldId": "cats", "selected": ["a", "b"] }` |
120
+ | `set_checkboxes` | checkboxes | `{ "op": "set_checkboxes", "fieldId": "tasks", "values": {"item1": "done"} }` |
121
+ | `set_url` | url | `{ "op": "set_url", "fieldId": "website", "value": "https://..." }` |
122
+ | `set_url_list` | url_list | `{ "op": "set_url_list", "fieldId": "sources", "items": ["https://..."] }` |
123
+ | `set_date` | date | `{ "op": "set_date", "fieldId": "deadline", "value": "2024-06-15" }` |
124
+ | `set_year` | year | `{ "op": "set_year", "fieldId": "founded", "value": 2015 }` |
125
+ | `clear_field` | any | `{ "op": "clear_field", "fieldId": "name" }` |
126
+ | `skip_field` | optional | `{ "op": "skip_field", "fieldId": "notes", "reason": "Not applicable" }` |
127
+ | `abort_field` | any | `{ "op": "abort_field", "fieldId": "data", "reason": "Unable to find" }` |
128
+
129
+ ### Checkbox Values
130
+
131
+ For `set_checkboxes`, values depend on the checkbox mode:
132
+
133
+ - **multi** (default): `todo`, `done`, `incomplete`, `active`, `na`
134
+
135
+ - **simple**: `todo`, `done`
136
+
137
+ - **explicit**: `unfilled`, `yes`, `no`
138
+
139
+ ## Form Harness API
140
+
141
+ The harness manages step-by-step form filling sessions.
142
+
143
+ ```typescript
144
+ import { FormHarness, createHarness, fillForm } from 'markform';
145
+ ```
146
+
147
+ ### fillForm(options: FillOptions): Promise<FillResult>
148
+
149
+ High-level API for filling a form with an AI model.
150
+
151
+ ```typescript
152
+ const result = await fillForm({
153
+ form: parsedForm,
154
+ model: 'anthropic/claude-sonnet-4-5',
155
+ roles: ['agent'],
156
+ });
157
+ ```
158
+
159
+ ### createHarness(form, config?): FormHarness
160
+
161
+ Create a harness for manual control over the fill loop.
162
+
163
+ See [harness.ts](../packages/markform/src/harness/harness.ts) for full details.
164
+
165
+ ## Research API
166
+
167
+ For research-type forms that run extended data gathering sessions.
168
+
169
+ ```typescript
170
+ import { runResearch, isResearchForm } from 'markform';
171
+ ```
172
+
173
+ ### runResearch(options: ResearchOptions): Promise<ResearchResult>
174
+
175
+ Run a research session on a research-type form.
176
+
177
+ See [runResearch.ts](../packages/markform/src/research/runResearch.ts) for full details.
178
+
179
+ ## Type Exports
180
+
181
+ All Zod schemas and TypeScript types are exported from the main package:
182
+
183
+ ```typescript
184
+ import type {
185
+ ParsedForm,
186
+ Field,
187
+ FieldValue,
188
+ Patch,
189
+ InspectResult,
190
+ // ... many more
191
+ } from 'markform';
192
+ ```
193
+
194
+ See [src/index.ts](../packages/markform/src/index.ts) for the complete list of exports.
@@ -7,8 +7,8 @@ Files combine YAML frontmatter with [Markdoc](https://markdoc.dev/) tags to defi
7
7
  typed, validated fields.
8
8
 
9
9
  **More info:** [Project README](https://github.com/jlevy/markform) |
10
- [Full Specification](https://github.com/jlevy/markform/blob/main/SPEC.md) (`markform
11
- spec`)
10
+ [Full Specification](markform-spec.md) (`markform spec`) |
11
+ [API Documentation](markform-apis.md) (`markform apis`)
12
12
 
13
13
  ## Installation
14
14
 
@@ -39,11 +39,11 @@ markform:
39
39
 
40
40
  {% form id="form_id" title="Form Title" %}
41
41
 
42
- {% field-group id="group_id" title="Group Title" %}
42
+ {% group id="group_id" title="Group Title" %}
43
43
 
44
44
  <!-- fields go here -->
45
45
 
46
- {% /field-group %}
46
+ {% /group %}
47
47
 
48
48
  {% /form %}
49
49
  ```
@@ -53,20 +53,24 @@ markform:
53
53
  Use `.form.md` for Markform files.
54
54
  They are Markdoc syntax, which is a superset of Markdown.
55
55
 
56
- ## Field Types
56
+ ## Field Kinds
57
+
58
+ Markform uses the term **field kind** to refer to the type of a field (e.g., `string`,
59
+ `number`, `checkboxes`). The term **data type** refers to the underlying value
60
+ representation. See the Type System section in SPEC.md for full details.
57
61
 
58
62
  ### String Field
59
63
 
60
64
  Single-line or multi-line text.
61
65
 
62
66
  ````markdown
63
- {% string-field id="name" label="Name" required=true minLength=2 maxLength=100 %}{% /string-field %}
67
+ {% field kind="string" id="name" label="Name" required=true minLength=2 maxLength=100 %}{% /field %}
64
68
 
65
- {% string-field id="bio" label="Biography" pattern="^[A-Z].*" %}
69
+ {% field kind="string" id="bio" label="Biography" pattern="^[A-Z].*" %}
66
70
  ```value
67
71
  Existing value here
68
72
  ````
69
- {% /string-field %}
73
+ {% /field %}
70
74
  ````
71
75
 
72
76
  | Attribute | Type | Description |
@@ -80,13 +84,13 @@ Existing value here
80
84
  Numeric values with optional constraints.
81
85
 
82
86
  ```markdown
83
- {% number-field id="age" label="Age" required=true min=0 max=150 integer=true %}{% /number-field %}
87
+ {% field kind="number" id="age" label="Age" required=true min=0 max=150 integer=true %}{% /field %}
84
88
 
85
- {% number-field id="price" label="Price" min=0.01 max=999999.99 %}
89
+ {% field kind="number" id="price" label="Price" min=0.01 max=999999.99 %}
86
90
  ```value
87
91
  49.99
88
92
  ````
89
- {% /number-field %}
93
+ {% /field %}
90
94
  ````
91
95
 
92
96
  | Attribute | Type | Description |
@@ -100,15 +104,15 @@ Numeric values with optional constraints.
100
104
  Array of strings, one per line.
101
105
 
102
106
  ```markdown
103
- {% string-list id="tags" label="Tags" required=true minItems=1 maxItems=10 uniqueItems=true %}{% /string-list %}
107
+ {% field kind="string_list" id="tags" label="Tags" required=true minItems=1 maxItems=10 uniqueItems=true %}{% /field %}
104
108
 
105
- {% string-list id="features" label="Key Features" minItems=3 itemMinLength=10 %}
109
+ {% field kind="string_list" id="features" label="Key Features" minItems=3 itemMinLength=10 %}
106
110
  ```value
107
111
  Feature one description
108
112
  Feature two description
109
113
  Feature three description
110
114
  ````
111
- {% /string-list %}
115
+ {% /field %}
112
116
  ````
113
117
 
114
118
  | Attribute | Type | Description |
@@ -124,11 +128,11 @@ Feature three description
124
128
  Choose exactly one option.
125
129
 
126
130
  ```markdown
127
- {% single-select id="rating" label="Rating" required=true %}
131
+ {% field kind="single_select" id="rating" label="Rating" required=true %}
128
132
  - [ ] Low {% #low %}
129
133
  - [ ] Medium {% #medium %}
130
134
  - [x] High {% #high %}
131
- {% /single-select %}
135
+ {% /field %}
132
136
  ````
133
137
 
134
138
  Options use `[ ]` (unselected) or `[x]` (selected).
@@ -139,12 +143,12 @@ Each option needs `{% #id %}`.
139
143
  Choose multiple options.
140
144
 
141
145
  ```markdown
142
- {% multi-select id="categories" label="Categories" required=true minSelections=1 maxSelections=3 %}
146
+ {% field kind="multi_select" id="categories" label="Categories" required=true minSelections=1 maxSelections=3 %}
143
147
  - [x] Frontend {% #frontend %}
144
148
  - [x] Backend {% #backend %}
145
149
  - [ ] Database {% #database %}
146
150
  - [ ] DevOps {% #devops %}
147
- {% /multi-select %}
151
+ {% /field %}
148
152
  ```
149
153
 
150
154
  | Attribute | Type | Description |
@@ -159,13 +163,13 @@ Stateful checklists with three modes.
159
163
  **Multi Mode** (default) - 5 states for workflow tracking:
160
164
 
161
165
  ```markdown
162
- {% checkboxes id="tasks" label="Tasks" required=true checkboxMode="multi" %}
166
+ {% field kind="checkboxes" id="tasks" label="Tasks" required=true checkboxMode="multi" %}
163
167
  - [ ] Research {% #research %}
164
168
  - [x] Design {% #design %}
165
169
  - [/] Implementation {% #impl %}
166
170
  - [*] Testing {% #test %}
167
171
  - [-] N/A item {% #na %}
168
- {% /checkboxes %}
172
+ {% /field %}
169
173
  ```
170
174
 
171
175
  | Token | State | Meaning |
@@ -179,20 +183,20 @@ Stateful checklists with three modes.
179
183
  **Simple Mode** - 2 states (GFM compatible):
180
184
 
181
185
  ```markdown
182
- {% checkboxes id="agreements" label="Agreements" checkboxMode="simple" required=true %}
186
+ {% field kind="checkboxes" id="agreements" label="Agreements" checkboxMode="simple" required=true %}
183
187
  - [x] I agree to terms {% #terms %}
184
188
  - [ ] Subscribe to newsletter {% #news %}
185
- {% /checkboxes %}
189
+ {% /field %}
186
190
  ```
187
191
 
188
192
  **Explicit Mode** - Requires yes/no for each:
189
193
 
190
194
  ```markdown
191
- {% checkboxes id="confirmations" label="Confirmations" checkboxMode="explicit" required=true %}
195
+ {% field kind="checkboxes" id="confirmations" label="Confirmations" checkboxMode="explicit" required=true %}
192
196
  - [y] Backup completed {% #backup %}
193
197
  - [n] Stakeholders notified {% #notify %}
194
198
  - [ ] Deployment ready {% #deploy %}
195
- {% /checkboxes %}
199
+ {% /field %}
196
200
  ```
197
201
 
198
202
  | Token | Value | Meaning |
@@ -206,13 +210,13 @@ Stateful checklists with three modes.
206
210
  Single URL with format validation.
207
211
 
208
212
  ````markdown
209
- {% url-field id="website" label="Website" required=true %}{% /url-field %}
213
+ {% field kind="url" id="website" label="Website" required=true %}{% /field %}
210
214
 
211
- {% url-field id="repo" label="Repository" %}
215
+ {% field kind="url" id="repo" label="Repository" %}
212
216
  ```value
213
217
  https://github.com/example/repo
214
218
  ````
215
- {% /url-field %}
219
+ {% /field %}
216
220
  ````
217
221
 
218
222
  ### URL List
@@ -220,14 +224,52 @@ https://github.com/example/repo
220
224
  Array of URLs.
221
225
 
222
226
  ```markdown
223
- {% url-list id="sources" label="Sources" required=true minItems=1 maxItems=10 uniqueItems=true %}
227
+ {% field kind="url_list" id="sources" label="Sources" required=true minItems=1 maxItems=10 uniqueItems=true %}
224
228
  ```value
225
229
  https://example.com/source1
226
230
  https://example.com/source2
227
231
  ````
228
- {% /url-list %}
232
+ {% /field %}
233
+ `````
234
+
235
+ ### Date Field
236
+
237
+ Date value in ISO 8601 format (YYYY-MM-DD).
238
+
239
+ ````markdown
240
+ {% field kind="date" id="deadline" label="Deadline" required=true %}{% /field %}
241
+
242
+ {% field kind="date" id="start_date" label="Start Date" min="2020-01-01" max="2030-12-31" %}
243
+ ```value
244
+ 2024-06-15
245
+ `````
246
+ {% /field %}
247
+ `````
248
+
249
+ | Attribute | Type | Description |
250
+ |-----------|------|-------------|
251
+ | `min` | string | Minimum date (ISO 8601: YYYY-MM-DD) |
252
+ | `max` | string | Maximum date (ISO 8601: YYYY-MM-DD) |
253
+
254
+ ### Year Field
255
+
256
+ Integer year with optional constraints.
257
+
258
+ ````markdown
259
+ {% field kind="year" id="release_year" label="Release Year" required=true min=1888 max=2030 %}{% /field %}
260
+
261
+ {% field kind="year" id="founded" label="Year Founded" %}
262
+ ```value
263
+ 2015
264
+ `````
265
+ {% /field %}
229
266
  ````
230
267
 
268
+ | Attribute | Type | Description |
269
+ |-----------|------|-------------|
270
+ | `min` | number | Minimum year (inclusive) |
271
+ | `max` | number | Maximum year (inclusive) |
272
+
231
273
  ## Common Attributes
232
274
 
233
275
  All fields support these attributes:
@@ -240,6 +282,20 @@ All fields support these attributes:
240
282
  | `role` | string | - | Target actor (`user`, `agent`) |
241
283
  | `priority` | string | medium | `high`, `medium`, `low` |
242
284
 
285
+ **Text-entry fields only** (string, number, string-list, url, url-list):
286
+
287
+ | Attribute | Type | Description |
288
+ |-----------|------|-------------|
289
+ | `placeholder` | string | Hint text shown in empty fields |
290
+ | `examples` | string[] | Example values (helps LLMs understand expected format) |
291
+
292
+ ```markdown
293
+ {% field kind="string" id="name" label="Name" placeholder="Enter your name" examples=["John Doe", "Jane Smith"] %}{% /field %}
294
+ {% field kind="number" id="revenue" label="Revenue" placeholder="1000000" examples=["500000", "1000000"] %}{% /field %}
295
+ ```
296
+
297
+ Note: `placeholder` and `examples` are NOT valid on chooser fields (single-select, multi-select, checkboxes).
298
+
243
299
  ## Documentation Blocks
244
300
 
245
301
  Add context to fields, groups, or the form.
@@ -283,8 +339,8 @@ roles:
283
339
  ```
284
340
 
285
341
  ```markdown
286
- {% string-field id="query" label="Search Query" role="user" %}{% /string-field %}
287
- {% string-field id="summary" label="AI Summary" role="agent" %}{% /string-field %}
342
+ {% field kind="string" id="query" label="Search Query" role="user" %}{% /field %}
343
+ {% field kind="string" id="summary" label="AI Summary" role="agent" %}{% /field %}
288
344
  ```
289
345
 
290
346
  ## Value Encoding
@@ -292,17 +348,17 @@ roles:
292
348
  Values use fenced code blocks with language `value`:
293
349
 
294
350
  ````markdown
295
- {% string-field id="name" label="Name" %}
351
+ {% field kind="string" id="name" label="Name" %}
296
352
  ```value
297
353
  John Smith
298
354
  ````
299
- {% /string-field %}
355
+ {% /field %}
300
356
  ````
301
357
 
302
358
  Empty fields omit the value block entirely:
303
359
 
304
360
  ```markdown
305
- {% string-field id="name" label="Name" %}{% /string-field %}
361
+ {% field kind="string" id="name" label="Name" %}{% /field %}
306
362
  ````
307
363
 
308
364
  ## Complete Example
@@ -343,124 +399,124 @@ A focused research form for gathering ratings and key statistics for any film.
343
399
  Pulls from IMDB, Rotten Tomatoes, and Metacritic.
344
400
  {% /description %}
345
401
 
346
- {% field-group id="movie_input" title="Movie Identification" %}
402
+ {% group id="movie_input" title="Movie Identification" %}
347
403
 
348
- {% string-field id="movie" label="Movie" role="user" required=true minLength=1 maxLength=300 %}{% /string-field %}
404
+ {% field kind="string" id="movie" label="Movie" role="user" required=true minLength=1 maxLength=300 %}{% /field %}
349
405
 
350
406
  {% instructions ref="movie" %}
351
407
  Enter the movie title (add any details to help identify, like "Barbie 2023" or "the Batman movie with Robert Pattinson")
352
408
  {% /instructions %}
353
409
 
354
- {% /field-group %}
410
+ {% /group %}
355
411
 
356
- {% field-group id="title_identification" title="Title Identification" %}
412
+ {% group id="title_identification" title="Title Identification" %}
357
413
 
358
- {% string-field id="full_title" label="Full Title" role="agent" required=true %}{% /string-field %}
414
+ {% field kind="string" id="full_title" label="Full Title" role="agent" required=true %}{% /field %}
359
415
 
360
416
  {% instructions ref="full_title" %}
361
417
  Look up what film the user had in mind and fill in the official title including subtitle if any (e.g., "The Lord of the Rings: The Fellowship of the Ring").
362
418
  {% /instructions %}
363
419
 
364
- {% /field-group %}
420
+ {% /group %}
365
421
 
366
- {% field-group id="sources" title="Sources" %}
422
+ {% group id="sources" title="Sources" %}
367
423
 
368
- {% url-field id="imdb_url" label="IMDB URL" role="agent" required=true %}{% /url-field %}
424
+ {% field kind="url" id="imdb_url" label="IMDB URL" role="agent" required=true %}{% /field %}
369
425
 
370
426
  {% instructions ref="imdb_url" %}
371
427
  Direct link to the movie's IMDB page (e.g., https://www.imdb.com/title/tt0111161/).
372
428
  {% /instructions %}
373
429
 
374
- {% url-field id="rt_url" label="Rotten Tomatoes URL" role="agent" %}{% /url-field %}
430
+ {% field kind="url" id="rt_url" label="Rotten Tomatoes URL" role="agent" %}{% /field %}
375
431
 
376
432
  {% instructions ref="rt_url" %}
377
433
  Direct link to the movie's Rotten Tomatoes page.
378
434
  {% /instructions %}
379
435
 
380
- {% url-field id="metacritic_url" label="Metacritic URL" role="agent" %}{% /url-field %}
436
+ {% field kind="url" id="metacritic_url" label="Metacritic URL" role="agent" %}{% /field %}
381
437
 
382
438
  {% instructions ref="metacritic_url" %}
383
439
  Direct link to the movie's Metacritic page.
384
440
  {% /instructions %}
385
441
 
386
- {% /field-group %}
442
+ {% /group %}
387
443
 
388
- {% field-group id="basic_details" title="Basic Details" %}
444
+ {% group id="basic_details" title="Basic Details" %}
389
445
 
390
- {% number-field id="year" label="Release Year" role="agent" required=true min=1888 max=2030 %}{% /number-field %}
446
+ {% field kind="number" id="year" label="Release Year" role="agent" required=true min=1888 max=2030 %}{% /field %}
391
447
 
392
- {% string-list id="directors" label="Director(s)" role="agent" required=true %}{% /string-list %}
448
+ {% field kind="string_list" id="directors" label="Director(s)" role="agent" required=true %}{% /field %}
393
449
 
394
450
  {% instructions ref="directors" %}
395
451
  One director per line. Most films have one; some have two or more co-directors.
396
452
  {% /instructions %}
397
453
 
398
- {% number-field id="runtime_minutes" label="Runtime (minutes)" role="agent" min=1 max=1000 %}{% /number-field %}
454
+ {% field kind="number" id="runtime_minutes" label="Runtime (minutes)" role="agent" min=1 max=1000 %}{% /field %}
399
455
 
400
- {% single-select id="mpaa_rating" label="MPAA Rating" role="agent" %}
456
+ {% field kind="single_select" id="mpaa_rating" label="MPAA Rating" role="agent" %}
401
457
  - [ ] G {% #g %}
402
458
  - [ ] PG {% #pg %}
403
459
  - [ ] PG-13 {% #pg_13 %}
404
460
  - [ ] R {% #r %}
405
461
  - [ ] NC-17 {% #nc_17 %}
406
462
  - [ ] NR/Unrated {% #nr %}
407
- {% /single-select %}
463
+ {% /field %}
408
464
 
409
- {% /field-group %}
465
+ {% /group %}
410
466
 
411
- {% field-group id="imdb_ratings" title="IMDB Ratings" %}
467
+ {% group id="imdb_ratings" title="IMDB Ratings" %}
412
468
 
413
- {% number-field id="imdb_rating" label="IMDB Rating" role="agent" min=1.0 max=10.0 %}{% /number-field %}
469
+ {% field kind="number" id="imdb_rating" label="IMDB Rating" role="agent" min=1.0 max=10.0 %}{% /field %}
414
470
 
415
471
  {% instructions ref="imdb_rating" %}
416
472
  IMDB user rating (1.0-10.0 scale).
417
473
  {% /instructions %}
418
474
 
419
- {% number-field id="imdb_votes" label="IMDB Vote Count" role="agent" min=0 %}{% /number-field %}
475
+ {% field kind="number" id="imdb_votes" label="IMDB Vote Count" role="agent" min=0 %}{% /field %}
420
476
 
421
477
  {% instructions ref="imdb_votes" %}
422
478
  Number of IMDB user votes (e.g., 2800000 for a popular film).
423
479
  {% /instructions %}
424
480
 
425
- {% /field-group %}
481
+ {% /group %}
426
482
 
427
- {% field-group id="rotten_tomatoes_ratings" title="Rotten Tomatoes Ratings" %}
483
+ {% group id="rotten_tomatoes_ratings" title="Rotten Tomatoes Ratings" %}
428
484
 
429
- {% number-field id="rt_critics_score" label="Tomatometer (Critics)" role="agent" min=0 max=100 %}{% /number-field %}
485
+ {% field kind="number" id="rt_critics_score" label="Tomatometer (Critics)" role="agent" min=0 max=100 %}{% /field %}
430
486
 
431
487
  {% instructions ref="rt_critics_score" %}
432
488
  Tomatometer percentage (0-100).
433
489
  {% /instructions %}
434
490
 
435
- {% number-field id="rt_critics_count" label="Critics Review Count" role="agent" min=0 %}{% /number-field %}
491
+ {% field kind="number" id="rt_critics_count" label="Critics Review Count" role="agent" min=0 %}{% /field %}
436
492
 
437
- {% number-field id="rt_audience_score" label="Audience Score" role="agent" min=0 max=100 %}{% /number-field %}
493
+ {% field kind="number" id="rt_audience_score" label="Audience Score" role="agent" min=0 max=100 %}{% /field %}
438
494
 
439
495
  {% instructions ref="rt_audience_score" %}
440
496
  Audience Score percentage (0-100).
441
497
  {% /instructions %}
442
498
 
443
- {% /field-group %}
499
+ {% /group %}
444
500
 
445
- {% field-group id="metacritic_ratings" title="Metacritic Ratings" %}
501
+ {% group id="metacritic_ratings" title="Metacritic Ratings" %}
446
502
 
447
- {% number-field id="metacritic_score" label="Metacritic Score" role="agent" min=0 max=100 %}{% /number-field %}
503
+ {% field kind="number" id="metacritic_score" label="Metacritic Score" role="agent" min=0 max=100 %}{% /field %}
448
504
 
449
505
  {% instructions ref="metacritic_score" %}
450
506
  Metascore (0-100 scale). Leave empty if not available.
451
507
  {% /instructions %}
452
508
 
453
- {% /field-group %}
509
+ {% /group %}
454
510
 
455
- {% field-group id="summary" title="Summary" %}
511
+ {% group id="summary" title="Summary" %}
456
512
 
457
- {% string-field id="logline" label="One-Line Summary" role="agent" maxLength=300 %}{% /string-field %}
513
+ {% field kind="string" id="logline" label="One-Line Summary" role="agent" maxLength=300 %}{% /field %}
458
514
 
459
515
  {% instructions ref="logline" %}
460
516
  Brief plot summary in 1-2 sentences, no spoilers.
461
517
  {% /instructions %}
462
518
 
463
- {% string-list id="notable_awards" label="Notable Awards" role="agent" %}{% /string-list %}
519
+ {% field kind="string_list" id="notable_awards" label="Notable Awards" role="agent" %}{% /field %}
464
520
 
465
521
  {% instructions ref="notable_awards" %}
466
522
  Major awards won. One per line.
@@ -468,7 +524,7 @@ Format: Award | Category | Year
468
524
  Example: "Oscar | Best Picture | 1995"
469
525
  {% /instructions %}
470
526
 
471
- {% /field-group %}
527
+ {% /group %}
472
528
 
473
529
  {% /form %}
474
530
  ```
@@ -539,8 +595,13 @@ markform fill template.form.md --mock --mock-source filled.form.md
539
595
 
540
596
  3. **Set constraints**: Use `min`, `max`, `minLength`, `pattern` to validate
541
597
 
542
- 4. **Group logically**: Related fields in the same `field-group`
598
+ 4. **Group logically**: Related fields in the same `group`
543
599
 
544
600
  5. **Assign roles**: Separate user input from agent research
545
601
 
546
602
  6. **Document thoroughly**: Use `{% instructions %}` for complex fields
603
+
604
+ ## Programmatic API
605
+
606
+ For TypeScript and AI SDK integration, run `markform apis` or see
607
+ [markform-apis.md](markform-apis.md).