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.
- package/README.md +110 -70
- package/dist/ai-sdk.d.mts +2 -2
- package/dist/ai-sdk.mjs +5 -5
- package/dist/{apply-00UmzDKL.mjs → apply-BCCiJzQr.mjs} +371 -26
- package/dist/bin.mjs +6 -6
- package/dist/{cli-D--Lel-e.mjs → cli-D469amuk.mjs} +386 -96
- package/dist/cli.mjs +6 -6
- package/dist/{coreTypes-BXhhz9Iq.d.mts → coreTypes-9XZSNOv6.d.mts} +1878 -325
- package/dist/{coreTypes-Dful87E0.mjs → coreTypes-pyctKRgc.mjs} +79 -5
- package/dist/index.d.mts +142 -5
- package/dist/index.mjs +5 -5
- package/dist/session-B_stoXQn.mjs +4 -0
- package/dist/{session-Bqnwi9wp.mjs → session-uF0e6m6k.mjs} +9 -5
- package/dist/{shared-N_s1M-_K.mjs → shared-BqPnYXrn.mjs} +82 -1
- package/dist/shared-CZsyShck.mjs +3 -0
- package/dist/{src-Dm8jZ5dl.mjs → src-Df0XX7UB.mjs} +818 -125
- package/docs/markform-apis.md +194 -0
- package/{DOCS.md → docs/markform-reference.md} +130 -69
- package/{SPEC.md → docs/markform-spec.md} +359 -108
- package/examples/earnings-analysis/earnings-analysis.form.md +88 -800
- package/examples/earnings-analysis/earnings-analysis.valid.ts +16 -148
- package/examples/movie-research/movie-research-basic.form.md +41 -37
- package/examples/movie-research/movie-research-deep.form.md +110 -98
- package/examples/movie-research/movie-research-minimal.form.md +29 -15
- package/examples/simple/simple-mock-filled.form.md +105 -41
- package/examples/simple/simple-skipped-filled.form.md +103 -41
- package/examples/simple/simple-with-skips.session.yaml +93 -25
- package/examples/simple/simple.form.md +86 -32
- package/examples/simple/simple.session.yaml +98 -25
- package/examples/startup-deep-research/startup-deep-research.form.md +130 -103
- package/examples/startup-research/startup-research-mock-filled.form.md +55 -55
- package/examples/startup-research/startup-research.form.md +36 -36
- package/package.json +18 -19
- package/dist/session-DdAtY2Ni.mjs +0 -4
- package/dist/shared-D7gf27Tr.mjs +0 -3
- package/examples/celebrity-deep-research/celebrity-deep-research.form.md +0 -912
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* These validators demonstrate parameterized validation patterns.
|
|
5
5
|
* Parameters are passed via ctx.params from the validate attribute:
|
|
6
6
|
*
|
|
7
|
-
* validate=[{id: "min_words", min:
|
|
7
|
+
* validate=[{id: "min_words", min: 20}]
|
|
8
8
|
*
|
|
9
|
-
* The validator receives { min:
|
|
9
|
+
* The validator receives { min: 20 } in ctx.params.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import type { ValidatorContext, ValidationIssue } from 'markform';
|
|
@@ -28,27 +28,17 @@ function getStringValue(values: Record<string, unknown>, fieldId: string): strin
|
|
|
28
28
|
return field?.kind === 'string' && field.value ? field.value : null;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
function getNumberValue(values: Record<string, unknown>, fieldId: string): number | null {
|
|
32
|
-
const field = values[fieldId] as { kind: string; value?: number | null } | undefined;
|
|
33
|
-
return field?.kind === 'number' && field.value != null ? field.value : null;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
31
|
function getStringListItems(values: Record<string, unknown>, fieldId: string): string[] {
|
|
37
32
|
const field = values[fieldId] as { kind: string; items?: string[] } | undefined;
|
|
38
33
|
return field?.kind === 'string_list' && field.items ? field.items : [];
|
|
39
34
|
}
|
|
40
35
|
|
|
41
|
-
function getMultiSelectSelections(values: Record<string, unknown>, fieldId: string): string[] {
|
|
42
|
-
const field = values[fieldId] as { kind: string; selected?: string[] } | undefined;
|
|
43
|
-
return field?.kind === 'multi_select' && field.selected ? field.selected : [];
|
|
44
|
-
}
|
|
45
|
-
|
|
46
36
|
function getSingleSelectValue(values: Record<string, unknown>, fieldId: string): string | null {
|
|
47
37
|
const field = values[fieldId] as { kind: string; selected?: string | null } | undefined;
|
|
48
38
|
return field?.kind === 'single_select' && field.selected ? field.selected : null;
|
|
49
39
|
}
|
|
50
40
|
|
|
51
|
-
//
|
|
41
|
+
// Validators
|
|
52
42
|
|
|
53
43
|
/**
|
|
54
44
|
* Validate minimum word count.
|
|
@@ -118,45 +108,6 @@ function maxWords(ctx: ValidatorContext): ValidationIssue[] {
|
|
|
118
108
|
return [];
|
|
119
109
|
}
|
|
120
110
|
|
|
121
|
-
/**
|
|
122
|
-
* Validate that specified number fields sum to a target.
|
|
123
|
-
* Params: { fields: string[], target?: number, tolerance?: number }
|
|
124
|
-
*/
|
|
125
|
-
function sumTo(ctx: ValidatorContext): ValidationIssue[] {
|
|
126
|
-
const fields = ctx.params.fields as string[];
|
|
127
|
-
const target = (ctx.params.target as number) ?? 100;
|
|
128
|
-
const tolerance = (ctx.params.tolerance as number) ?? 0.1;
|
|
129
|
-
|
|
130
|
-
if (!Array.isArray(fields)) {
|
|
131
|
-
return [
|
|
132
|
-
{
|
|
133
|
-
severity: 'error',
|
|
134
|
-
message: 'sum_to requires "fields" array parameter',
|
|
135
|
-
ref: ctx.targetId,
|
|
136
|
-
source: 'code',
|
|
137
|
-
},
|
|
138
|
-
];
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const values = fields.map((fieldId) => getNumberValue(ctx.values, fieldId) ?? 0);
|
|
142
|
-
const sum = values.reduce((a, b) => a + b, 0);
|
|
143
|
-
|
|
144
|
-
// Only validate if at least one value is set
|
|
145
|
-
if (values.every((v) => v === 0)) return [];
|
|
146
|
-
|
|
147
|
-
if (Math.abs(sum - target) > tolerance) {
|
|
148
|
-
return [
|
|
149
|
-
{
|
|
150
|
-
severity: 'error',
|
|
151
|
-
message: `Fields must sum to ${target}% (currently ${sum.toFixed(1)}%)`,
|
|
152
|
-
ref: fields[0],
|
|
153
|
-
source: 'code',
|
|
154
|
-
},
|
|
155
|
-
];
|
|
156
|
-
}
|
|
157
|
-
return [];
|
|
158
|
-
}
|
|
159
|
-
|
|
160
111
|
/**
|
|
161
112
|
* Validate that string-list items with "Label: XX%" format sum to target.
|
|
162
113
|
* Params: { target?: number }
|
|
@@ -197,117 +148,38 @@ function sumToPercentList(ctx: ValidatorContext): ValidationIssue[] {
|
|
|
197
148
|
}
|
|
198
149
|
|
|
199
150
|
/**
|
|
200
|
-
* Require field when another field has
|
|
201
|
-
* Params: { when: string
|
|
151
|
+
* Require field when another field has any value set.
|
|
152
|
+
* Params: { when: string }
|
|
202
153
|
*/
|
|
203
|
-
function
|
|
154
|
+
function requiredIfSet(ctx: ValidatorContext): ValidationIssue[] {
|
|
204
155
|
const triggerField = ctx.params.when as string;
|
|
205
|
-
const targetField = (ctx.params.then as string) ?? ctx.targetId;
|
|
206
156
|
|
|
207
157
|
if (!triggerField) {
|
|
208
158
|
return [
|
|
209
159
|
{
|
|
210
160
|
severity: 'error',
|
|
211
|
-
message: '
|
|
161
|
+
message: 'required_if_set requires "when" parameter',
|
|
212
162
|
ref: ctx.targetId,
|
|
213
163
|
source: 'code',
|
|
214
164
|
},
|
|
215
165
|
];
|
|
216
166
|
}
|
|
217
167
|
|
|
218
|
-
|
|
219
|
-
const target = ctx.values[targetField] as Record<string, unknown> | undefined;
|
|
220
|
-
|
|
168
|
+
// Check if trigger field has a value
|
|
221
169
|
const triggerHasValue =
|
|
222
|
-
(
|
|
223
|
-
(
|
|
224
|
-
(
|
|
170
|
+
getStringValue(ctx.values, triggerField) !== null ||
|
|
171
|
+
getSingleSelectValue(ctx.values, triggerField) !== null ||
|
|
172
|
+
getStringListItems(ctx.values, triggerField).length > 0;
|
|
225
173
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
(target.kind === 'number' && target.value == null);
|
|
174
|
+
// Check if target field is empty
|
|
175
|
+
const targetValue = getStringValue(ctx.values, ctx.targetId);
|
|
176
|
+
const targetEmpty = !targetValue || targetValue.trim().length === 0;
|
|
230
177
|
|
|
231
178
|
if (triggerHasValue && targetEmpty) {
|
|
232
179
|
return [
|
|
233
180
|
{
|
|
234
181
|
severity: 'error',
|
|
235
|
-
message: `This field is required when ${triggerField} has a value`,
|
|
236
|
-
ref: targetField,
|
|
237
|
-
source: 'code',
|
|
238
|
-
},
|
|
239
|
-
];
|
|
240
|
-
}
|
|
241
|
-
return [];
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* Require field when another field equals a specific value.
|
|
246
|
-
* Params: { when: string, equals: string, then?: string }
|
|
247
|
-
*/
|
|
248
|
-
function requiredIfEquals(ctx: ValidatorContext): ValidationIssue[] {
|
|
249
|
-
const triggerField = ctx.params.when as string;
|
|
250
|
-
const expectedValue = ctx.params.equals as string;
|
|
251
|
-
const targetField = (ctx.params.then as string) ?? ctx.targetId;
|
|
252
|
-
|
|
253
|
-
if (!triggerField || expectedValue === undefined) {
|
|
254
|
-
return [
|
|
255
|
-
{
|
|
256
|
-
severity: 'error',
|
|
257
|
-
message: 'required_if_equals requires "when" and "equals" parameters',
|
|
258
|
-
ref: ctx.targetId,
|
|
259
|
-
source: 'code',
|
|
260
|
-
},
|
|
261
|
-
];
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
const triggerValue = getSingleSelectValue(ctx.values, triggerField);
|
|
265
|
-
const target = getStringValue(ctx.values, targetField);
|
|
266
|
-
|
|
267
|
-
if (triggerValue === expectedValue && (!target || target.trim().length === 0)) {
|
|
268
|
-
return [
|
|
269
|
-
{
|
|
270
|
-
severity: 'error',
|
|
271
|
-
message: `This field is required when ${triggerField} is "${expectedValue}"`,
|
|
272
|
-
ref: targetField,
|
|
273
|
-
source: 'code',
|
|
274
|
-
},
|
|
275
|
-
];
|
|
276
|
-
}
|
|
277
|
-
return [];
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* Validate that list items match a regex pattern.
|
|
282
|
-
* Params: { pattern: string, example?: string }
|
|
283
|
-
*/
|
|
284
|
-
function itemFormat(ctx: ValidatorContext): ValidationIssue[] {
|
|
285
|
-
const pattern = ctx.params.pattern as string;
|
|
286
|
-
const example = (ctx.params.example as string) ?? '';
|
|
287
|
-
|
|
288
|
-
if (!pattern) {
|
|
289
|
-
return [
|
|
290
|
-
{
|
|
291
|
-
severity: 'error',
|
|
292
|
-
message: 'item_format requires "pattern" parameter',
|
|
293
|
-
ref: ctx.targetId,
|
|
294
|
-
source: 'code',
|
|
295
|
-
},
|
|
296
|
-
];
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
const items = getStringListItems(ctx.values, ctx.targetId);
|
|
300
|
-
if (items.length === 0) return [];
|
|
301
|
-
|
|
302
|
-
const regex = new RegExp(pattern);
|
|
303
|
-
const malformed = items.filter((item) => !regex.test(item));
|
|
304
|
-
|
|
305
|
-
if (malformed.length > 0) {
|
|
306
|
-
const hint = example ? ` Expected format: "${example}"` : '';
|
|
307
|
-
return [
|
|
308
|
-
{
|
|
309
|
-
severity: 'warning',
|
|
310
|
-
message: `${malformed.length} item(s) don't match expected format.${hint}`,
|
|
182
|
+
message: `This field is required when "${triggerField}" has a value`,
|
|
311
183
|
ref: ctx.targetId,
|
|
312
184
|
source: 'code',
|
|
313
185
|
},
|
|
@@ -319,12 +191,8 @@ function itemFormat(ctx: ValidatorContext): ValidationIssue[] {
|
|
|
319
191
|
// Exported Validators Registry
|
|
320
192
|
|
|
321
193
|
export const validators: Record<string, (ctx: ValidatorContext) => ValidationIssue[]> = {
|
|
322
|
-
// Parameterized validators
|
|
323
194
|
min_words: minWords,
|
|
324
195
|
max_words: maxWords,
|
|
325
|
-
sum_to: sumTo,
|
|
326
196
|
sum_to_percent_list: sumToPercentList,
|
|
327
|
-
|
|
328
|
-
required_if_equals: requiredIfEquals,
|
|
329
|
-
item_format: itemFormat,
|
|
197
|
+
required_if_set: requiredIfSet,
|
|
330
198
|
};
|
|
@@ -34,131 +34,135 @@ markform:
|
|
|
34
34
|
Standard research form for gathering ratings and key statistics for any film. Pulls from IMDB, Rotten Tomatoes, and Metacritic.
|
|
35
35
|
{% /description %}
|
|
36
36
|
|
|
37
|
-
{%
|
|
37
|
+
{% group id="movie_input" title="Movie Identification" %}
|
|
38
38
|
|
|
39
|
-
{%
|
|
39
|
+
{% field kind="string" id="movie" label="Movie" role="user" required=true minLength=1 maxLength=300 %}{% /field %}
|
|
40
40
|
|
|
41
41
|
{% instructions ref="movie" %}
|
|
42
42
|
Enter the movie title (add any details to help identify, like "Barbie 2023" or "the Batman movie with Robert Pattinson")
|
|
43
43
|
{% /instructions %}
|
|
44
44
|
|
|
45
|
-
{% /
|
|
45
|
+
{% /group %}
|
|
46
46
|
|
|
47
|
-
{%
|
|
47
|
+
{% group id="title_identification" title="Title Identification" %}
|
|
48
48
|
|
|
49
|
-
{%
|
|
49
|
+
{% field kind="string" id="full_title" label="Full Title" role="agent" required=true %}{% /field %}
|
|
50
50
|
|
|
51
51
|
{% instructions ref="full_title" %}
|
|
52
52
|
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").
|
|
53
53
|
{% /instructions %}
|
|
54
54
|
|
|
55
|
-
{% /
|
|
55
|
+
{% /group %}
|
|
56
56
|
|
|
57
|
-
{%
|
|
57
|
+
{% group id="sources" title="Sources" %}
|
|
58
58
|
|
|
59
|
-
{%
|
|
59
|
+
{% field kind="url" id="imdb_url" label="IMDB URL" role="agent" required=true %}{% /field %}
|
|
60
60
|
|
|
61
61
|
{% instructions ref="imdb_url" %}
|
|
62
62
|
Direct link to the movie's IMDB page (e.g., https://www.imdb.com/title/tt0111161/).
|
|
63
63
|
{% /instructions %}
|
|
64
64
|
|
|
65
|
-
{%
|
|
65
|
+
{% field kind="url" id="rt_url" label="Rotten Tomatoes URL" role="agent" %}{% /field %}
|
|
66
66
|
|
|
67
67
|
{% instructions ref="rt_url" %}
|
|
68
68
|
Direct link to the movie's Rotten Tomatoes page.
|
|
69
69
|
{% /instructions %}
|
|
70
70
|
|
|
71
|
-
{%
|
|
71
|
+
{% field kind="url" id="metacritic_url" label="Metacritic URL" role="agent" %}{% /field %}
|
|
72
72
|
|
|
73
73
|
{% instructions ref="metacritic_url" %}
|
|
74
74
|
Direct link to the movie's Metacritic page.
|
|
75
75
|
{% /instructions %}
|
|
76
76
|
|
|
77
|
-
{% /
|
|
77
|
+
{% /group %}
|
|
78
78
|
|
|
79
|
-
{%
|
|
79
|
+
{% group id="basic_details" title="Basic Details" %}
|
|
80
80
|
|
|
81
|
-
{%
|
|
81
|
+
{% field kind="number" id="year" label="Release Year" role="agent" required=true min=1888 max=2030 %}{% /field %}
|
|
82
82
|
|
|
83
|
-
{%
|
|
83
|
+
{% field kind="string_list" id="directors" label="Director(s)" role="agent" required=true %}{% /field %}
|
|
84
84
|
|
|
85
85
|
{% instructions ref="directors" %}
|
|
86
86
|
One director per line. Most films have one; some have two or more co-directors.
|
|
87
87
|
{% /instructions %}
|
|
88
88
|
|
|
89
|
-
{%
|
|
89
|
+
{% field kind="number" id="runtime_minutes" label="Runtime (minutes)" role="agent" min=1 max=1000 %}{% /field %}
|
|
90
90
|
|
|
91
|
-
{%
|
|
91
|
+
{% field kind="single_select" id="mpaa_rating" label="MPAA Rating" role="agent" %}
|
|
92
92
|
- [ ] G {% #g %}
|
|
93
93
|
- [ ] PG {% #pg %}
|
|
94
94
|
- [ ] PG-13 {% #pg_13 %}
|
|
95
95
|
- [ ] R {% #r %}
|
|
96
96
|
- [ ] NC-17 {% #nc_17 %}
|
|
97
97
|
- [ ] NR/Unrated {% #nr %}
|
|
98
|
-
{% /
|
|
98
|
+
{% /field %}
|
|
99
99
|
|
|
100
|
-
{% /
|
|
100
|
+
{% /group %}
|
|
101
101
|
|
|
102
|
-
{%
|
|
102
|
+
{% group id="imdb_ratings" title="IMDB Ratings" %}
|
|
103
103
|
|
|
104
|
-
{%
|
|
104
|
+
{% field kind="number" id="imdb_rating" label="IMDB Rating" role="agent" min=1.0 max=10.0 %}{% /field %}
|
|
105
105
|
|
|
106
106
|
{% instructions ref="imdb_rating" %}
|
|
107
107
|
IMDB user rating (1.0-10.0 scale).
|
|
108
108
|
{% /instructions %}
|
|
109
109
|
|
|
110
|
-
{%
|
|
110
|
+
{% field kind="number" id="imdb_votes" label="IMDB Vote Count" role="agent" min=0 %}{% /field %}
|
|
111
111
|
|
|
112
112
|
{% instructions ref="imdb_votes" %}
|
|
113
113
|
Number of IMDB user votes (e.g., 2800000 for a popular film).
|
|
114
114
|
{% /instructions %}
|
|
115
115
|
|
|
116
|
-
{% /
|
|
116
|
+
{% /group %}
|
|
117
117
|
|
|
118
|
-
{%
|
|
118
|
+
{% group id="rotten_tomatoes_ratings" title="Rotten Tomatoes Ratings" %}
|
|
119
119
|
|
|
120
|
-
{%
|
|
120
|
+
{% field kind="number" id="rt_critics_score" label="Tomatometer (Critics)" role="agent" min=0 max=100 %}{% /field %}
|
|
121
121
|
|
|
122
122
|
{% instructions ref="rt_critics_score" %}
|
|
123
123
|
Tomatometer percentage (0-100).
|
|
124
124
|
{% /instructions %}
|
|
125
125
|
|
|
126
|
-
{%
|
|
126
|
+
{% field kind="number" id="rt_critics_count" label="Critics Review Count" role="agent" min=0 %}{% /field %}
|
|
127
127
|
|
|
128
|
-
{%
|
|
128
|
+
{% field kind="number" id="rt_audience_score" label="Audience Score" role="agent" min=0 max=100 %}{% /field %}
|
|
129
129
|
|
|
130
130
|
{% instructions ref="rt_audience_score" %}
|
|
131
131
|
Audience Score percentage (0-100).
|
|
132
132
|
{% /instructions %}
|
|
133
133
|
|
|
134
|
-
{% /
|
|
134
|
+
{% /group %}
|
|
135
135
|
|
|
136
|
-
{%
|
|
136
|
+
{% group id="metacritic_ratings" title="Metacritic Ratings" %}
|
|
137
137
|
|
|
138
|
-
{%
|
|
138
|
+
{% field kind="number" id="metacritic_score" label="Metacritic Score" role="agent" min=0 max=100 %}{% /field %}
|
|
139
139
|
|
|
140
140
|
{% instructions ref="metacritic_score" %}
|
|
141
141
|
Metascore (0-100 scale). Leave empty if not available.
|
|
142
142
|
{% /instructions %}
|
|
143
143
|
|
|
144
|
-
{% /
|
|
144
|
+
{% /group %}
|
|
145
145
|
|
|
146
|
-
{%
|
|
146
|
+
{% group id="summary" title="Summary" %}
|
|
147
147
|
|
|
148
|
-
{%
|
|
148
|
+
{% field kind="string" id="logline" label="One-Line Summary" role="agent" maxLength=300 %}{% /field %}
|
|
149
149
|
|
|
150
150
|
{% instructions ref="logline" %}
|
|
151
151
|
Brief plot summary in 1-2 sentences, no spoilers.
|
|
152
152
|
{% /instructions %}
|
|
153
153
|
|
|
154
|
-
{%
|
|
154
|
+
{% field kind="table" id="notable_awards" label="Notable Awards" role="agent"
|
|
155
|
+
columnIds=["award", "category", "year"]
|
|
156
|
+
columnTypes=["string", "string", "year"] %}
|
|
157
|
+
| Award | Category | Year |
|
|
158
|
+
|-------|----------|------|
|
|
159
|
+
{% /field %}
|
|
155
160
|
|
|
156
161
|
{% instructions ref="notable_awards" %}
|
|
157
|
-
Major awards won.
|
|
158
|
-
|
|
159
|
-
Example: "Oscar | Best Picture | 1995"
|
|
162
|
+
Major awards won.
|
|
163
|
+
Example: Oscar | Best Picture | 1995
|
|
160
164
|
{% /instructions %}
|
|
161
165
|
|
|
162
|
-
{% /
|
|
166
|
+
{% /group %}
|
|
163
167
|
|
|
164
168
|
{% /form %}
|