markform 0.1.2 → 0.1.4
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 +97 -42
- package/dist/ai-sdk.d.mts +2 -2
- package/dist/ai-sdk.mjs +5 -5
- package/dist/{apply-BfAGTHMh.mjs → apply-C54EMAJ1.mjs} +383 -26
- package/dist/bin.mjs +6 -6
- package/dist/{cli-B3NVm6zL.mjs → cli-BhWhn6L9.mjs} +456 -141
- package/dist/cli.mjs +6 -6
- package/dist/{coreTypes-BXhhz9Iq.d.mts → coreTypes-cbNTYAcb.d.mts} +1878 -325
- package/dist/{coreTypes-Dful87E0.mjs → coreTypes-pyctKRgc.mjs} +79 -5
- package/dist/index.d.mts +146 -9
- 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-BXRkGFpG.mjs → src-BNh7Cx9P.mjs} +801 -121
- package/docs/markform-apis.md +194 -0
- package/{DOCS.md → docs/markform-reference.md} +111 -50
- package/{SPEC.md → docs/markform-spec.md} +342 -91
- package/examples/celebrity-deep-research/celebrity-deep-research.form.md +196 -141
- package/examples/earnings-analysis/earnings-analysis.form.md +236 -226
- package/examples/movie-research/movie-research-basic.form.md +25 -21
- package/examples/movie-research/movie-research-deep.form.md +74 -62
- package/examples/movie-research/movie-research-minimal.form.md +29 -34
- package/examples/simple/simple-mock-filled.form.md +93 -29
- package/examples/simple/simple-skipped-filled.form.md +91 -29
- package/examples/simple/simple-with-skips.session.yaml +93 -25
- package/examples/simple/simple.form.md +74 -20
- package/examples/simple/simple.session.yaml +98 -25
- package/examples/startup-deep-research/startup-deep-research.form.md +108 -81
- package/examples/startup-research/startup-research-mock-filled.form.md +43 -43
- package/examples/startup-research/startup-research.form.md +24 -24
- package/package.json +18 -27
- package/dist/session-DdAtY2Ni.mjs +0 -4
- package/dist/shared-D7gf27Tr.mjs +0 -3
|
@@ -1,19 +1,113 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { A as
|
|
3
|
-
import { a as resolveHarnessConfig, c as getProviderNames, f as createMockAgent, i as runResearch, l as resolveModel, m as createHarness, s as getProviderInfo, t as VERSION, u as createLiveAgent,
|
|
4
|
-
import { n as serializeSession } from "./session-
|
|
5
|
-
import { a as
|
|
1
|
+
import { L as PatchSchema } from "./coreTypes-pyctKRgc.mjs";
|
|
2
|
+
import { A as parseRolesFlag, D as deriveReportPath, E as deriveExportPath, F as hasWebSearchSupport, I as parseModelIdForDisplay, M as WEB_SEARCH_CONFIG, N as formatSuggestedLlms, O as detectFileType, T as USER_ROLE, _ as DEFAULT_MAX_TURNS, b as DEFAULT_RESEARCH_MAX_ISSUES_PER_TURN, d as serializeRawMarkdown, f as serializeReportMarkdown, g as DEFAULT_MAX_PATCHES_PER_TURN, h as DEFAULT_MAX_ISSUES_PER_TURN, j as SUGGESTED_LLMS, k as getFormsDir, m as DEFAULT_FORMS_DIR, p as AGENT_ROLE, r as inspect, t as applyPatches, u as serialize, v as DEFAULT_PORT, w as REPORT_EXTENSION, x as DEFAULT_RESEARCH_MAX_PATCHES_PER_TURN } from "./apply-C54EMAJ1.mjs";
|
|
3
|
+
import { a as resolveHarnessConfig, c as getProviderNames, f as createMockAgent, i as runResearch, l as resolveModel, m as createHarness, s as getProviderInfo, t as VERSION, u as createLiveAgent, w as parseForm } from "./src-BNh7Cx9P.mjs";
|
|
4
|
+
import { n as serializeSession } from "./session-uF0e6m6k.mjs";
|
|
5
|
+
import { a as formatPath, c as logError, d as logTiming, f as logVerbose, g as writeFile, i as formatOutput, l as logInfo, m as readFile$1, n as createSpinner, o as getCommandContext, p as logWarn, r as ensureFormsDir, s as logDryRun, t as OUTPUT_FORMATS, u as logSuccess } from "./shared-BqPnYXrn.mjs";
|
|
6
6
|
import YAML from "yaml";
|
|
7
7
|
import { basename, dirname, join, resolve } from "node:path";
|
|
8
8
|
import { Command } from "commander";
|
|
9
9
|
import pc from "picocolors";
|
|
10
|
-
import { readFile } from "node:fs/promises";
|
|
11
10
|
import { existsSync, readFileSync } from "node:fs";
|
|
12
11
|
import { fileURLToPath } from "node:url";
|
|
12
|
+
import { readFile } from "node:fs/promises";
|
|
13
13
|
import * as p from "@clack/prompts";
|
|
14
14
|
import { exec, spawn } from "node:child_process";
|
|
15
15
|
import { createServer } from "node:http";
|
|
16
16
|
|
|
17
|
+
//#region src/cli/commands/apis.ts
|
|
18
|
+
/**
|
|
19
|
+
* Get the path to the markform-apis.md file.
|
|
20
|
+
* Works both during development and when installed as a package.
|
|
21
|
+
*/
|
|
22
|
+
function getApisPath() {
|
|
23
|
+
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
24
|
+
if (thisDir.split(/[/\\]/).pop() === "dist") return join(dirname(thisDir), "docs", "markform-apis.md");
|
|
25
|
+
return join(dirname(dirname(dirname(thisDir))), "docs", "markform-apis.md");
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Load the APIs documentation content.
|
|
29
|
+
*/
|
|
30
|
+
function loadApis() {
|
|
31
|
+
const apisPath = getApisPath();
|
|
32
|
+
try {
|
|
33
|
+
return readFileSync(apisPath, "utf-8");
|
|
34
|
+
} catch (error) {
|
|
35
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
36
|
+
throw new Error(`Failed to load API docs from ${apisPath}: ${message}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Apply basic terminal formatting to markdown content.
|
|
41
|
+
* Colorizes headers, code blocks, and other elements for better readability.
|
|
42
|
+
*/
|
|
43
|
+
function formatMarkdown$4(content, useColors) {
|
|
44
|
+
if (!useColors) return content;
|
|
45
|
+
const lines = content.split("\n");
|
|
46
|
+
const formatted = [];
|
|
47
|
+
let inCodeBlock = false;
|
|
48
|
+
for (const line of lines) {
|
|
49
|
+
if (line.startsWith("```")) {
|
|
50
|
+
inCodeBlock = !inCodeBlock;
|
|
51
|
+
formatted.push(pc.dim(line));
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (inCodeBlock) {
|
|
55
|
+
formatted.push(pc.dim(line));
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (line.startsWith("# ")) {
|
|
59
|
+
formatted.push(pc.bold(pc.cyan(line)));
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (line.startsWith("## ")) {
|
|
63
|
+
formatted.push(pc.bold(pc.blue(line)));
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (line.startsWith("### ")) {
|
|
67
|
+
formatted.push(pc.bold(line));
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
let formattedLine = line.replace(/`([^`]+)`/g, (_match, code) => {
|
|
71
|
+
return pc.yellow(code);
|
|
72
|
+
});
|
|
73
|
+
formattedLine = formattedLine.replace(/\*\*([^*]+)\*\*/g, (_match, text) => {
|
|
74
|
+
return pc.bold(text);
|
|
75
|
+
});
|
|
76
|
+
formattedLine = formattedLine.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_match, text, url) => {
|
|
77
|
+
return `${pc.cyan(text)} ${pc.dim(`(${url})`)}`;
|
|
78
|
+
});
|
|
79
|
+
formatted.push(formattedLine);
|
|
80
|
+
}
|
|
81
|
+
return formatted.join("\n");
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Check if stdout is an interactive terminal.
|
|
85
|
+
*/
|
|
86
|
+
function isInteractive$4() {
|
|
87
|
+
return process.stdout.isTTY === true;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Display content.
|
|
91
|
+
*/
|
|
92
|
+
function displayContent$3(content) {
|
|
93
|
+
console.log(content);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Register the apis command.
|
|
97
|
+
*/
|
|
98
|
+
function registerApisCommand(program) {
|
|
99
|
+
program.command("apis").description("Display Markform TypeScript and AI SDK API documentation").option("--raw", "Output raw markdown without formatting").action((options, cmd) => {
|
|
100
|
+
const ctx = getCommandContext(cmd);
|
|
101
|
+
try {
|
|
102
|
+
displayContent$3(formatMarkdown$4(loadApis(), !options.raw && isInteractive$4() && !ctx.quiet));
|
|
103
|
+
} catch (error) {
|
|
104
|
+
logError(error instanceof Error ? error.message : String(error));
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
//#endregion
|
|
17
111
|
//#region src/cli/commands/apply.ts
|
|
18
112
|
/**
|
|
19
113
|
* Format state badge for console output.
|
|
@@ -146,13 +240,13 @@ function registerApplyCommand(program) {
|
|
|
146
240
|
//#endregion
|
|
147
241
|
//#region src/cli/commands/docs.ts
|
|
148
242
|
/**
|
|
149
|
-
* Get the path to the
|
|
243
|
+
* Get the path to the markform-reference.md file.
|
|
150
244
|
* Works both during development and when installed as a package.
|
|
151
245
|
*/
|
|
152
246
|
function getDocsPath() {
|
|
153
247
|
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
154
|
-
if (thisDir.split(/[/\\]/).pop() === "dist") return join(dirname(thisDir), "
|
|
155
|
-
return join(dirname(dirname(dirname(thisDir))), "
|
|
248
|
+
if (thisDir.split(/[/\\]/).pop() === "dist") return join(dirname(thisDir), "docs", "markform-reference.md");
|
|
249
|
+
return join(dirname(dirname(dirname(thisDir))), "docs", "markform-reference.md");
|
|
156
250
|
}
|
|
157
251
|
/**
|
|
158
252
|
* Load the docs content.
|
|
@@ -163,7 +257,7 @@ function loadDocs() {
|
|
|
163
257
|
return readFileSync(docsPath, "utf-8");
|
|
164
258
|
} catch (error) {
|
|
165
259
|
const message = error instanceof Error ? error.message : String(error);
|
|
166
|
-
throw new Error(`Failed to load
|
|
260
|
+
throw new Error(`Failed to load reference docs from ${docsPath}: ${message}`);
|
|
167
261
|
}
|
|
168
262
|
}
|
|
169
263
|
/**
|
|
@@ -334,23 +428,29 @@ function toNotesArray(form) {
|
|
|
334
428
|
* Derive export paths from a base form path.
|
|
335
429
|
* Uses centralized extension constants from settings.ts.
|
|
336
430
|
*
|
|
431
|
+
* Standard exports: report, values (yaml), form.
|
|
432
|
+
* Raw markdown is available via CLI but not in standard exports.
|
|
433
|
+
*
|
|
337
434
|
* @param basePath - Path to the .form.md file
|
|
338
435
|
* @returns Object with paths for all export formats
|
|
339
436
|
*/
|
|
340
437
|
function deriveExportPaths(basePath) {
|
|
341
438
|
return {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
439
|
+
reportPath: deriveReportPath(basePath),
|
|
440
|
+
yamlPath: deriveExportPath(basePath, "yaml"),
|
|
441
|
+
formPath: deriveExportPath(basePath, "form")
|
|
345
442
|
};
|
|
346
443
|
}
|
|
347
444
|
/**
|
|
348
445
|
* Export form to multiple formats.
|
|
349
446
|
*
|
|
350
|
-
*
|
|
447
|
+
* Standard exports:
|
|
448
|
+
* - Report format (.report.md) - filtered markdown (excludes instructions, report=false)
|
|
449
|
+
* - YAML values (.yml) - structured format with state and notes
|
|
351
450
|
* - Markform format (.form.md) - canonical form with directives
|
|
352
|
-
*
|
|
353
|
-
*
|
|
451
|
+
*
|
|
452
|
+
* Note: Raw markdown (.raw.md) is available via CLI `markform export --raw`
|
|
453
|
+
* but is not included in standard multi-format export.
|
|
354
454
|
*
|
|
355
455
|
* @param form - The parsed form to export
|
|
356
456
|
* @param basePath - Base path for the .form.md file (other paths are derived)
|
|
@@ -358,10 +458,8 @@ function deriveExportPaths(basePath) {
|
|
|
358
458
|
*/
|
|
359
459
|
async function exportMultiFormat(form, basePath) {
|
|
360
460
|
const paths = deriveExportPaths(basePath);
|
|
361
|
-
const
|
|
362
|
-
await writeFile(paths.
|
|
363
|
-
const rawContent = serializeRawMarkdown(form);
|
|
364
|
-
await writeFile(paths.rawPath, rawContent);
|
|
461
|
+
const reportContent = serializeReportMarkdown(form);
|
|
462
|
+
await writeFile(paths.reportPath, reportContent);
|
|
365
463
|
const values = toStructuredValues(form);
|
|
366
464
|
const notes = toNotesArray(form);
|
|
367
465
|
const exportData = {
|
|
@@ -370,6 +468,8 @@ async function exportMultiFormat(form, basePath) {
|
|
|
370
468
|
};
|
|
371
469
|
const yamlContent = YAML.stringify(exportData);
|
|
372
470
|
await writeFile(paths.yamlPath, yamlContent);
|
|
471
|
+
const formContent = serialize(form);
|
|
472
|
+
await writeFile(paths.formPath, formContent);
|
|
373
473
|
return paths;
|
|
374
474
|
}
|
|
375
475
|
|
|
@@ -474,6 +574,7 @@ function formatPatchValue(patch) {
|
|
|
474
574
|
case "set_url_list": return patch.items.length > 0 ? truncate(`[${patch.items.join(", ")}]`) : "(empty)";
|
|
475
575
|
case "set_date": return patch.value ? truncate(`"${patch.value}"`) : "(empty)";
|
|
476
576
|
case "set_year": return patch.value !== null ? String(patch.value) : "(empty)";
|
|
577
|
+
case "set_table": return patch.rows.length > 0 ? truncate(`[${patch.rows.length} rows]`) : "(empty)";
|
|
477
578
|
case "add_note": return truncate(`note: ${patch.text}`);
|
|
478
579
|
case "remove_note": return `(remove note ${patch.noteId})`;
|
|
479
580
|
}
|
|
@@ -496,6 +597,7 @@ function formatPatchType(patch) {
|
|
|
496
597
|
case "set_url_list": return "url_list";
|
|
497
598
|
case "set_date": return "date";
|
|
498
599
|
case "set_year": return "year";
|
|
600
|
+
case "set_table": return "table";
|
|
499
601
|
case "add_note": return "note";
|
|
500
602
|
case "remove_note": return "remove_note";
|
|
501
603
|
}
|
|
@@ -844,9 +946,10 @@ async function promptSkipOrFill(ctx) {
|
|
|
844
946
|
async function promptForString(ctx) {
|
|
845
947
|
const field = ctx.field;
|
|
846
948
|
const currentVal = ctx.currentValue?.kind === "string" ? ctx.currentValue.value : null;
|
|
949
|
+
const placeholderText = field.placeholder ?? currentVal ?? (ctx.description ? ctx.description.slice(0, 60) : void 0);
|
|
847
950
|
const result = await p.text({
|
|
848
951
|
message: formatFieldLabel(ctx),
|
|
849
|
-
placeholder:
|
|
952
|
+
placeholder: placeholderText,
|
|
850
953
|
initialValue: currentVal ?? "",
|
|
851
954
|
validate: (value) => {
|
|
852
955
|
if (field.required && !value.trim()) return "This field is required";
|
|
@@ -869,9 +972,10 @@ async function promptForString(ctx) {
|
|
|
869
972
|
async function promptForNumber(ctx) {
|
|
870
973
|
const field = ctx.field;
|
|
871
974
|
const currentVal = ctx.currentValue?.kind === "number" ? ctx.currentValue.value : null;
|
|
975
|
+
const placeholderText = field.placeholder ?? (currentVal !== null ? String(currentVal) : void 0);
|
|
872
976
|
const result = await p.text({
|
|
873
977
|
message: formatFieldLabel(ctx),
|
|
874
|
-
placeholder:
|
|
978
|
+
placeholder: placeholderText,
|
|
875
979
|
initialValue: currentVal !== null ? String(currentVal) : "",
|
|
876
980
|
validate: (value) => {
|
|
877
981
|
if (field.required && !value.trim()) return "This field is required";
|
|
@@ -897,7 +1001,10 @@ async function promptForNumber(ctx) {
|
|
|
897
1001
|
async function promptForStringList(ctx) {
|
|
898
1002
|
const field = ctx.field;
|
|
899
1003
|
const currentItems = ctx.currentValue?.kind === "string_list" ? ctx.currentValue.items : [];
|
|
900
|
-
|
|
1004
|
+
let hint;
|
|
1005
|
+
if (field.placeholder) hint = `${field.placeholder} (one item per line)`;
|
|
1006
|
+
else if (ctx.description) hint = `${ctx.description.slice(0, 50)}... (one item per line)`;
|
|
1007
|
+
else hint = "Enter items, one per line. Press Enter twice when done.";
|
|
901
1008
|
const result = await p.text({
|
|
902
1009
|
message: formatFieldLabel(ctx),
|
|
903
1010
|
placeholder: hint,
|
|
@@ -1071,9 +1178,10 @@ async function promptForCheckboxes(ctx) {
|
|
|
1071
1178
|
async function promptForUrl(ctx) {
|
|
1072
1179
|
const field = ctx.field;
|
|
1073
1180
|
const currentVal = ctx.currentValue?.kind === "url" ? ctx.currentValue.value : null;
|
|
1181
|
+
const placeholderText = field.placeholder ?? currentVal ?? "https://example.com";
|
|
1074
1182
|
const result = await p.text({
|
|
1075
1183
|
message: formatFieldLabel(ctx),
|
|
1076
|
-
placeholder:
|
|
1184
|
+
placeholder: placeholderText,
|
|
1077
1185
|
initialValue: currentVal ?? "",
|
|
1078
1186
|
validate: (value) => {
|
|
1079
1187
|
if (field.required && !value.trim()) return "This field is required";
|
|
@@ -1094,12 +1202,87 @@ async function promptForUrl(ctx) {
|
|
|
1094
1202
|
};
|
|
1095
1203
|
}
|
|
1096
1204
|
/**
|
|
1205
|
+
* Check if a string is a valid ISO 8601 date (YYYY-MM-DD).
|
|
1206
|
+
*/
|
|
1207
|
+
function isValidDate(str) {
|
|
1208
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(str)) return false;
|
|
1209
|
+
const date = new Date(str);
|
|
1210
|
+
if (isNaN(date.getTime())) return false;
|
|
1211
|
+
const [year, month, day] = str.split("-").map(Number);
|
|
1212
|
+
return date.getUTCFullYear() === year && date.getUTCMonth() + 1 === month && date.getUTCDate() === day;
|
|
1213
|
+
}
|
|
1214
|
+
/**
|
|
1215
|
+
* Prompt for a date field value.
|
|
1216
|
+
*/
|
|
1217
|
+
async function promptForDate(ctx) {
|
|
1218
|
+
const field = ctx.field;
|
|
1219
|
+
const currentVal = ctx.currentValue?.kind === "date" ? ctx.currentValue.value : null;
|
|
1220
|
+
const constraints = [];
|
|
1221
|
+
if (field.min) constraints.push(`min: ${field.min}`);
|
|
1222
|
+
if (field.max) constraints.push(`max: ${field.max}`);
|
|
1223
|
+
const formatHint = constraints.length > 0 ? ` (${constraints.join(", ")})` : "";
|
|
1224
|
+
const result = await p.text({
|
|
1225
|
+
message: formatFieldLabel(ctx),
|
|
1226
|
+
placeholder: currentVal ?? `YYYY-MM-DD${formatHint}`,
|
|
1227
|
+
initialValue: currentVal ?? "",
|
|
1228
|
+
validate: (value) => {
|
|
1229
|
+
if (field.required && !value.trim()) return "This field is required";
|
|
1230
|
+
if (!value.trim()) return;
|
|
1231
|
+
if (!isValidDate(value)) return "Please enter a valid date in YYYY-MM-DD format";
|
|
1232
|
+
if (field.min && value < field.min) return `Date must be on or after ${field.min}`;
|
|
1233
|
+
if (field.max && value > field.max) return `Date must be on or before ${field.max}`;
|
|
1234
|
+
}
|
|
1235
|
+
});
|
|
1236
|
+
if (p.isCancel(result)) return null;
|
|
1237
|
+
if (!result && !field.required) return null;
|
|
1238
|
+
return {
|
|
1239
|
+
op: "set_date",
|
|
1240
|
+
fieldId: field.id,
|
|
1241
|
+
value: result || null
|
|
1242
|
+
};
|
|
1243
|
+
}
|
|
1244
|
+
/** Default year range for validation */
|
|
1245
|
+
const DEFAULT_MIN_YEAR = 1e3;
|
|
1246
|
+
const DEFAULT_MAX_YEAR = 2500;
|
|
1247
|
+
/**
|
|
1248
|
+
* Prompt for a year field value.
|
|
1249
|
+
*/
|
|
1250
|
+
async function promptForYear(ctx) {
|
|
1251
|
+
const field = ctx.field;
|
|
1252
|
+
const currentVal = ctx.currentValue?.kind === "year" ? ctx.currentValue.value : null;
|
|
1253
|
+
const minYear = field.min ?? DEFAULT_MIN_YEAR;
|
|
1254
|
+
const maxYear = field.max ?? DEFAULT_MAX_YEAR;
|
|
1255
|
+
const result = await p.text({
|
|
1256
|
+
message: formatFieldLabel(ctx),
|
|
1257
|
+
placeholder: currentVal !== null ? String(currentVal) : `Year (${minYear}-${maxYear})`,
|
|
1258
|
+
initialValue: currentVal !== null ? String(currentVal) : "",
|
|
1259
|
+
validate: (value) => {
|
|
1260
|
+
if (field.required && !value.trim()) return "This field is required";
|
|
1261
|
+
if (!value.trim()) return;
|
|
1262
|
+
const num = Number(value);
|
|
1263
|
+
if (isNaN(num) || !Number.isInteger(num)) return "Please enter a valid year (e.g., 2025)";
|
|
1264
|
+
if (num < minYear) return `Year must be ${minYear} or later`;
|
|
1265
|
+
if (num > maxYear) return `Year must be ${maxYear} or earlier`;
|
|
1266
|
+
}
|
|
1267
|
+
});
|
|
1268
|
+
if (p.isCancel(result)) return null;
|
|
1269
|
+
if (!result && !field.required) return null;
|
|
1270
|
+
return {
|
|
1271
|
+
op: "set_year",
|
|
1272
|
+
fieldId: field.id,
|
|
1273
|
+
value: result ? Number(result) : null
|
|
1274
|
+
};
|
|
1275
|
+
}
|
|
1276
|
+
/**
|
|
1097
1277
|
* Prompt for a URL list field value.
|
|
1098
1278
|
*/
|
|
1099
1279
|
async function promptForUrlList(ctx) {
|
|
1100
1280
|
const field = ctx.field;
|
|
1101
1281
|
const currentItems = ctx.currentValue?.kind === "url_list" ? ctx.currentValue.items : [];
|
|
1102
|
-
|
|
1282
|
+
let hint;
|
|
1283
|
+
if (field.placeholder) hint = `${field.placeholder} (one URL per line)`;
|
|
1284
|
+
else if (ctx.description) hint = `${ctx.description.slice(0, 50)}... (one URL per line)`;
|
|
1285
|
+
else hint = "Enter URLs, one per line. Press Enter twice when done.";
|
|
1103
1286
|
const result = await p.text({
|
|
1104
1287
|
message: formatFieldLabel(ctx),
|
|
1105
1288
|
placeholder: hint,
|
|
@@ -1126,7 +1309,7 @@ async function promptForUrlList(ctx) {
|
|
|
1126
1309
|
};
|
|
1127
1310
|
}
|
|
1128
1311
|
/**
|
|
1129
|
-
* Prompt user for a single field value based on field
|
|
1312
|
+
* Prompt user for a single field value based on field kind.
|
|
1130
1313
|
* Returns a Patch to set the value, or null if skipped/cancelled.
|
|
1131
1314
|
*
|
|
1132
1315
|
* For optional fields, first offers a choice to skip or fill.
|
|
@@ -1145,6 +1328,8 @@ async function promptForField(ctx) {
|
|
|
1145
1328
|
case "checkboxes": return promptForCheckboxes(ctx);
|
|
1146
1329
|
case "url": return promptForUrl(ctx);
|
|
1147
1330
|
case "url_list": return promptForUrlList(ctx);
|
|
1331
|
+
case "date": return promptForDate(ctx);
|
|
1332
|
+
case "year": return promptForYear(ctx);
|
|
1148
1333
|
default: return null;
|
|
1149
1334
|
}
|
|
1150
1335
|
}
|
|
@@ -1380,29 +1565,42 @@ async function viewFile(filePath) {
|
|
|
1380
1565
|
/**
|
|
1381
1566
|
* Show an interactive file viewer chooser.
|
|
1382
1567
|
*
|
|
1383
|
-
* Presents a list of files to view
|
|
1384
|
-
*
|
|
1568
|
+
* Presents a list of files to view:
|
|
1569
|
+
* - "Show report:" for the report output (.report.md) at the top
|
|
1570
|
+
* - "Show source:" for other files (.form.md, .raw.md, .yml)
|
|
1571
|
+
* - "Quit" at the bottom
|
|
1572
|
+
*
|
|
1573
|
+
* Loops until the user selects Quit.
|
|
1385
1574
|
*
|
|
1386
1575
|
* @param files Array of file options to display
|
|
1387
1576
|
*/
|
|
1388
1577
|
async function showFileViewerChooser(files) {
|
|
1389
1578
|
if (!isInteractive$2()) return;
|
|
1390
1579
|
console.log("");
|
|
1580
|
+
const reportFile = files.find((f) => f.path.endsWith(".report.md"));
|
|
1581
|
+
const sourceFiles = files.filter((f) => !f.path.endsWith(".report.md"));
|
|
1391
1582
|
while (true) {
|
|
1392
|
-
const options = [
|
|
1583
|
+
const options = [];
|
|
1584
|
+
if (reportFile) options.push({
|
|
1585
|
+
value: reportFile.path,
|
|
1586
|
+
label: `Show report: ${pc.green(basename(reportFile.path))}`,
|
|
1587
|
+
hint: reportFile.hint ?? ""
|
|
1588
|
+
});
|
|
1589
|
+
for (const file of sourceFiles) options.push({
|
|
1393
1590
|
value: file.path,
|
|
1394
|
-
label: pc.green(basename(file.path))
|
|
1591
|
+
label: `Show source: ${pc.green(basename(file.path))}`,
|
|
1395
1592
|
hint: file.hint ?? ""
|
|
1396
|
-
})
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1593
|
+
});
|
|
1594
|
+
options.push({
|
|
1595
|
+
value: "quit",
|
|
1596
|
+
label: "Quit",
|
|
1597
|
+
hint: ""
|
|
1598
|
+
});
|
|
1401
1599
|
const selection = await p.select({
|
|
1402
|
-
message: "View
|
|
1600
|
+
message: "View files:",
|
|
1403
1601
|
options
|
|
1404
1602
|
});
|
|
1405
|
-
if (p.isCancel(selection) || selection === "
|
|
1603
|
+
if (p.isCancel(selection) || selection === "quit") break;
|
|
1406
1604
|
await viewFile(selection);
|
|
1407
1605
|
console.log("");
|
|
1408
1606
|
}
|
|
@@ -1534,54 +1732,75 @@ async function promptForWebSearchModel() {
|
|
|
1534
1732
|
* Accepts optional harness config overrides - research uses different defaults.
|
|
1535
1733
|
*/
|
|
1536
1734
|
async function runAgentFill(form, modelId, _outputPath, configOverrides) {
|
|
1537
|
-
const
|
|
1735
|
+
const { provider: providerName, model: modelName } = parseModelIdForDisplay(modelId);
|
|
1736
|
+
const resolveSpinner = createSpinner({
|
|
1737
|
+
type: "compute",
|
|
1738
|
+
operation: `Resolving model: ${modelId}`
|
|
1739
|
+
});
|
|
1740
|
+
let model, provider;
|
|
1538
1741
|
try {
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
maxTurns: configOverrides?.maxTurns ?? DEFAULT_MAX_TURNS,
|
|
1544
|
-
maxPatchesPerTurn: configOverrides?.maxPatchesPerTurn ?? DEFAULT_MAX_PATCHES_PER_TURN,
|
|
1545
|
-
maxIssuesPerTurn: configOverrides?.maxIssuesPerTurn ?? DEFAULT_MAX_ISSUES_PER_TURN,
|
|
1546
|
-
targetRoles: [AGENT_ROLE],
|
|
1547
|
-
fillMode: "continue"
|
|
1548
|
-
};
|
|
1549
|
-
console.log("");
|
|
1550
|
-
console.log(`Config: max_turns=${harnessConfig.maxTurns}, max_issues_per_turn=${harnessConfig.maxIssuesPerTurn}, max_patches_per_turn=${harnessConfig.maxPatchesPerTurn}`);
|
|
1551
|
-
const harness = createHarness(form, harnessConfig);
|
|
1552
|
-
const agent = createLiveAgent({
|
|
1553
|
-
model,
|
|
1554
|
-
provider,
|
|
1555
|
-
targetRole: AGENT_ROLE
|
|
1556
|
-
});
|
|
1557
|
-
p.log.step(pc.bold("Agent fill in progress..."));
|
|
1558
|
-
let stepResult = harness.step();
|
|
1559
|
-
while (!stepResult.isComplete && !harness.hasReachedMaxTurns()) {
|
|
1560
|
-
console.log(` ${pc.bold(`Turn ${stepResult.turnNumber}:`)} ${formatTurnIssues(stepResult.issues)}`);
|
|
1561
|
-
const { patches, stats } = await agent.generatePatches(stepResult.issues, harness.getForm(), harnessConfig.maxPatchesPerTurn);
|
|
1562
|
-
for (const patch of patches) {
|
|
1563
|
-
const typeName = formatPatchType(patch);
|
|
1564
|
-
const value = formatPatchValue(patch);
|
|
1565
|
-
const fieldId = "fieldId" in patch ? patch.fieldId : patch.op === "add_note" ? patch.ref : "";
|
|
1566
|
-
if (fieldId) console.log(` ${pc.cyan(fieldId)} (${typeName}) = ${pc.green(value)}`);
|
|
1567
|
-
else console.log(` (${typeName}) = ${pc.green(value)}`);
|
|
1568
|
-
}
|
|
1569
|
-
stepResult = harness.apply(patches, stepResult.issues);
|
|
1570
|
-
const tokenInfo = stats ? ` ${pc.dim(`(tokens: ↓${stats.inputTokens ?? 0} ↑${stats.outputTokens ?? 0})`)}` : "";
|
|
1571
|
-
console.log(` ${patches.length} patch(es) applied, ${stepResult.issues.length} remaining${tokenInfo}`);
|
|
1572
|
-
if (!stepResult.isComplete && !harness.hasReachedMaxTurns()) stepResult = harness.step();
|
|
1573
|
-
}
|
|
1574
|
-
if (stepResult.isComplete) p.log.success(pc.green(`Form completed in ${harness.getTurnNumber()} turn(s)`));
|
|
1575
|
-
else p.log.warn(pc.yellow(`Max turns reached (${harnessConfig.maxTurns})`));
|
|
1576
|
-
Object.assign(form, harness.getForm());
|
|
1577
|
-
return {
|
|
1578
|
-
success: stepResult.isComplete,
|
|
1579
|
-
turnCount: harness.getTurnNumber()
|
|
1580
|
-
};
|
|
1742
|
+
const result = await resolveModel(modelId);
|
|
1743
|
+
model = result.model;
|
|
1744
|
+
provider = result.provider;
|
|
1745
|
+
resolveSpinner.stop(`✓ Model resolved: ${modelId}`);
|
|
1581
1746
|
} catch (error) {
|
|
1582
|
-
|
|
1747
|
+
resolveSpinner.error("Model resolution failed");
|
|
1583
1748
|
throw error;
|
|
1584
1749
|
}
|
|
1750
|
+
const harnessConfig = {
|
|
1751
|
+
maxTurns: configOverrides?.maxTurns ?? DEFAULT_MAX_TURNS,
|
|
1752
|
+
maxPatchesPerTurn: configOverrides?.maxPatchesPerTurn ?? DEFAULT_MAX_PATCHES_PER_TURN,
|
|
1753
|
+
maxIssuesPerTurn: configOverrides?.maxIssuesPerTurn ?? DEFAULT_MAX_ISSUES_PER_TURN,
|
|
1754
|
+
targetRoles: [AGENT_ROLE],
|
|
1755
|
+
fillMode: "continue"
|
|
1756
|
+
};
|
|
1757
|
+
console.log("");
|
|
1758
|
+
console.log(`Config: max_turns=${harnessConfig.maxTurns}, max_issues_per_turn=${harnessConfig.maxIssuesPerTurn}, max_patches_per_turn=${harnessConfig.maxPatchesPerTurn}`);
|
|
1759
|
+
const harness = createHarness(form, harnessConfig);
|
|
1760
|
+
const agent = createLiveAgent({
|
|
1761
|
+
model,
|
|
1762
|
+
provider,
|
|
1763
|
+
targetRole: AGENT_ROLE,
|
|
1764
|
+
enableWebSearch: true
|
|
1765
|
+
});
|
|
1766
|
+
p.log.step(pc.bold("Agent fill in progress..."));
|
|
1767
|
+
let stepResult = harness.step();
|
|
1768
|
+
while (!stepResult.isComplete && !harness.hasReachedMaxTurns()) {
|
|
1769
|
+
console.log(` ${pc.bold(`Turn ${stepResult.turnNumber}:`)} ${formatTurnIssues(stepResult.issues)}`);
|
|
1770
|
+
const llmSpinner = createSpinner({
|
|
1771
|
+
type: "api",
|
|
1772
|
+
provider: providerName,
|
|
1773
|
+
model: modelName,
|
|
1774
|
+
turnNumber: stepResult.turnNumber
|
|
1775
|
+
});
|
|
1776
|
+
let response;
|
|
1777
|
+
try {
|
|
1778
|
+
response = await agent.generatePatches(stepResult.issues, harness.getForm(), harnessConfig.maxPatchesPerTurn);
|
|
1779
|
+
llmSpinner.stop();
|
|
1780
|
+
} catch (error) {
|
|
1781
|
+
llmSpinner.error("LLM call failed");
|
|
1782
|
+
throw error;
|
|
1783
|
+
}
|
|
1784
|
+
const { patches, stats } = response;
|
|
1785
|
+
for (const patch of patches) {
|
|
1786
|
+
const typeName = formatPatchType(patch);
|
|
1787
|
+
const value = formatPatchValue(patch);
|
|
1788
|
+
const fieldId = "fieldId" in patch ? patch.fieldId : patch.op === "add_note" ? patch.ref : "";
|
|
1789
|
+
if (fieldId) console.log(` ${pc.cyan(fieldId)} (${typeName}) = ${pc.green(value)}`);
|
|
1790
|
+
else console.log(` (${typeName}) = ${pc.green(value)}`);
|
|
1791
|
+
}
|
|
1792
|
+
stepResult = harness.apply(patches, stepResult.issues);
|
|
1793
|
+
const tokenInfo = stats ? ` ${pc.dim(`(tokens: ↓${stats.inputTokens ?? 0} ↑${stats.outputTokens ?? 0})`)}` : "";
|
|
1794
|
+
console.log(` ${patches.length} patch(es) applied, ${stepResult.issues.length} remaining${tokenInfo}`);
|
|
1795
|
+
if (!stepResult.isComplete && !harness.hasReachedMaxTurns()) stepResult = harness.step();
|
|
1796
|
+
}
|
|
1797
|
+
if (stepResult.isComplete) p.log.success(pc.green(`Form completed in ${harness.getTurnNumber()} turn(s)`));
|
|
1798
|
+
else p.log.warn(pc.yellow(`Max turns reached (${harnessConfig.maxTurns})`));
|
|
1799
|
+
Object.assign(form, harness.getForm());
|
|
1800
|
+
return {
|
|
1801
|
+
success: stepResult.isComplete,
|
|
1802
|
+
turnCount: harness.getTurnNumber()
|
|
1803
|
+
};
|
|
1585
1804
|
}
|
|
1586
1805
|
/**
|
|
1587
1806
|
* Run the interactive example scaffolding and filling flow.
|
|
@@ -1682,9 +1901,9 @@ async function runInteractiveFlow(preselectedId, formsDirOverride) {
|
|
|
1682
1901
|
showInteractiveOutro(patches.length, false);
|
|
1683
1902
|
console.log("");
|
|
1684
1903
|
p.log.success("Outputs:");
|
|
1685
|
-
console.log(` ${formatPath(userFillOutputs.
|
|
1686
|
-
console.log(` ${formatPath(userFillOutputs.
|
|
1687
|
-
console.log(` ${formatPath(userFillOutputs.
|
|
1904
|
+
console.log(` ${formatPath(userFillOutputs.reportPath)} ${pc.dim("(output report)")}`);
|
|
1905
|
+
console.log(` ${formatPath(userFillOutputs.yamlPath)} ${pc.dim("(output values)")}`);
|
|
1906
|
+
console.log(` ${formatPath(userFillOutputs.formPath)} ${pc.dim("(filled markform source)")}`);
|
|
1688
1907
|
logTiming({
|
|
1689
1908
|
verbose: false,
|
|
1690
1909
|
format: "console",
|
|
@@ -1710,19 +1929,19 @@ async function runInteractiveFlow(preselectedId, formsDirOverride) {
|
|
|
1710
1929
|
console.log(cliCommand);
|
|
1711
1930
|
if (userFillOutputs) await showFileViewerChooser([
|
|
1712
1931
|
{
|
|
1713
|
-
path: userFillOutputs.
|
|
1714
|
-
label: "
|
|
1715
|
-
hint: "
|
|
1932
|
+
path: userFillOutputs.reportPath,
|
|
1933
|
+
label: "Report",
|
|
1934
|
+
hint: "output report"
|
|
1716
1935
|
},
|
|
1717
1936
|
{
|
|
1718
|
-
path: userFillOutputs.
|
|
1719
|
-
label: "
|
|
1720
|
-
hint: "
|
|
1937
|
+
path: userFillOutputs.yamlPath,
|
|
1938
|
+
label: "Values",
|
|
1939
|
+
hint: "output values"
|
|
1721
1940
|
},
|
|
1722
1941
|
{
|
|
1723
|
-
path: userFillOutputs.
|
|
1724
|
-
label: "
|
|
1725
|
-
hint: "
|
|
1942
|
+
path: userFillOutputs.formPath,
|
|
1943
|
+
label: "Form",
|
|
1944
|
+
hint: "filled markform source"
|
|
1726
1945
|
}
|
|
1727
1946
|
]);
|
|
1728
1947
|
p.outro("Happy form filling!");
|
|
@@ -1760,29 +1979,29 @@ async function runInteractiveFlow(preselectedId, formsDirOverride) {
|
|
|
1760
1979
|
dryRun: false,
|
|
1761
1980
|
quiet: false
|
|
1762
1981
|
}, timingLabel, Date.now() - agentStartTime);
|
|
1763
|
-
const {
|
|
1982
|
+
const { reportPath, yamlPath, formPath } = await exportMultiFormat(form, agentOutputPath);
|
|
1764
1983
|
console.log("");
|
|
1765
1984
|
const successMessage = isResearchExample ? "Research complete. Outputs:" : "Agent fill complete. Outputs:";
|
|
1766
1985
|
p.log.success(successMessage);
|
|
1767
|
-
console.log(` ${formatPath(
|
|
1768
|
-
console.log(` ${formatPath(
|
|
1769
|
-
console.log(` ${formatPath(
|
|
1986
|
+
console.log(` ${formatPath(reportPath)} ${pc.dim("(output report)")}`);
|
|
1987
|
+
console.log(` ${formatPath(yamlPath)} ${pc.dim("(output values)")}`);
|
|
1988
|
+
console.log(` ${formatPath(formPath)} ${pc.dim("(filled markform source)")}`);
|
|
1770
1989
|
if (!success) p.log.warn("Agent did not complete all fields. You may need to run it again.");
|
|
1771
1990
|
await showFileViewerChooser([
|
|
1772
1991
|
{
|
|
1773
|
-
path:
|
|
1774
|
-
label: "
|
|
1775
|
-
hint: "
|
|
1992
|
+
path: reportPath,
|
|
1993
|
+
label: "Report",
|
|
1994
|
+
hint: "output report"
|
|
1776
1995
|
},
|
|
1777
1996
|
{
|
|
1778
|
-
path:
|
|
1779
|
-
label: "
|
|
1780
|
-
hint: "
|
|
1997
|
+
path: yamlPath,
|
|
1998
|
+
label: "Values",
|
|
1999
|
+
hint: "output values"
|
|
1781
2000
|
},
|
|
1782
2001
|
{
|
|
1783
|
-
path:
|
|
1784
|
-
label: "
|
|
1785
|
-
hint: "
|
|
2002
|
+
path: formPath,
|
|
2003
|
+
label: "Form",
|
|
2004
|
+
hint: "filled markform source"
|
|
1786
2005
|
}
|
|
1787
2006
|
]);
|
|
1788
2007
|
} catch (error) {
|
|
@@ -1864,7 +2083,9 @@ function registerExportCommand(program) {
|
|
|
1864
2083
|
...field.kind === "single_select" || field.kind === "multi_select" || field.kind === "checkboxes" ? { options: field.options.map((opt) => ({
|
|
1865
2084
|
id: opt.id,
|
|
1866
2085
|
label: opt.label
|
|
1867
|
-
})) } : {}
|
|
2086
|
+
})) } : {},
|
|
2087
|
+
...field.placeholder ? { placeholder: field.placeholder } : {},
|
|
2088
|
+
...field.examples && field.examples.length > 0 ? { examples: field.examples } : {}
|
|
1868
2089
|
}))
|
|
1869
2090
|
}))
|
|
1870
2091
|
};
|
|
@@ -1987,13 +2208,13 @@ function registerFillCommand(program) {
|
|
|
1987
2208
|
logInfo(ctx, `[DRY RUN] Would write form to: ${outputPath$1}`);
|
|
1988
2209
|
showInteractiveOutro(patches.length, false);
|
|
1989
2210
|
} else {
|
|
1990
|
-
const {
|
|
2211
|
+
const { reportPath, yamlPath, formPath } = await exportMultiFormat(form, outputPath$1);
|
|
1991
2212
|
showInteractiveOutro(patches.length, false);
|
|
1992
2213
|
console.log("");
|
|
1993
2214
|
p.log.success("Outputs:");
|
|
1994
|
-
console.log(` ${formatPath(
|
|
1995
|
-
console.log(` ${formatPath(
|
|
1996
|
-
console.log(` ${formatPath(
|
|
2215
|
+
console.log(` ${formatPath(reportPath)} ${pc.dim("(output report)")}`);
|
|
2216
|
+
console.log(` ${formatPath(yamlPath)} ${pc.dim("(output values)")}`);
|
|
2217
|
+
console.log(` ${formatPath(formPath)} ${pc.dim("(filled markform source)")}`);
|
|
1997
2218
|
}
|
|
1998
2219
|
logTiming(ctx, "Fill time", durationMs$1);
|
|
1999
2220
|
if (patches.length > 0) {
|
|
@@ -2026,14 +2247,18 @@ function registerFillCommand(program) {
|
|
|
2026
2247
|
const harness = createHarness(form, harnessConfig);
|
|
2027
2248
|
let agent;
|
|
2028
2249
|
let mockPath;
|
|
2250
|
+
let agentProvider;
|
|
2251
|
+
let agentModelName;
|
|
2029
2252
|
if (options.mock) {
|
|
2030
2253
|
mockPath = resolve(options.mockSource);
|
|
2031
2254
|
logVerbose(ctx, `Reading mock source: ${mockPath}`);
|
|
2032
2255
|
agent = createMockAgent(parseForm(await readFile$1(mockPath)));
|
|
2033
2256
|
} else {
|
|
2034
|
-
const
|
|
2035
|
-
logVerbose(ctx, `Resolving model: ${
|
|
2036
|
-
const { model, provider } = await resolveModel(
|
|
2257
|
+
const modelIdString = options.model;
|
|
2258
|
+
logVerbose(ctx, `Resolving model: ${modelIdString}`);
|
|
2259
|
+
const { model, provider, modelId } = await resolveModel(modelIdString);
|
|
2260
|
+
agentProvider = provider;
|
|
2261
|
+
agentModelName = modelId;
|
|
2037
2262
|
let systemPrompt;
|
|
2038
2263
|
if (options.instructions) {
|
|
2039
2264
|
systemPrompt = options.instructions;
|
|
@@ -2048,7 +2273,8 @@ function registerFillCommand(program) {
|
|
|
2048
2273
|
model,
|
|
2049
2274
|
provider,
|
|
2050
2275
|
systemPromptAddition: systemPrompt,
|
|
2051
|
-
targetRole: primaryRole
|
|
2276
|
+
targetRole: primaryRole,
|
|
2277
|
+
enableWebSearch: true
|
|
2052
2278
|
});
|
|
2053
2279
|
agent = liveAgent;
|
|
2054
2280
|
logInfo(ctx, `Available tools: ${liveAgent.getAvailableToolNames().join(", ")}`);
|
|
@@ -2064,7 +2290,22 @@ function registerFillCommand(program) {
|
|
|
2064
2290
|
let stepResult = harness.step();
|
|
2065
2291
|
logInfo(ctx, `${pc.bold(`Turn ${stepResult.turnNumber}:`)} ${formatTurnIssues(stepResult.issues)}`);
|
|
2066
2292
|
while (!stepResult.isComplete && !harness.hasReachedMaxTurns()) {
|
|
2067
|
-
|
|
2293
|
+
let spinner = null;
|
|
2294
|
+
if (!options.mock && agentProvider && agentModelName && process.stdout.isTTY && !ctx.quiet) spinner = createSpinner({
|
|
2295
|
+
type: "api",
|
|
2296
|
+
provider: agentProvider,
|
|
2297
|
+
model: agentModelName,
|
|
2298
|
+
turnNumber: stepResult.turnNumber
|
|
2299
|
+
});
|
|
2300
|
+
let response;
|
|
2301
|
+
try {
|
|
2302
|
+
response = await agent.generatePatches(stepResult.issues, harness.getForm(), harnessConfig.maxPatchesPerTurn);
|
|
2303
|
+
spinner?.stop();
|
|
2304
|
+
} catch (error) {
|
|
2305
|
+
spinner?.error("LLM call failed");
|
|
2306
|
+
throw error;
|
|
2307
|
+
}
|
|
2308
|
+
const { patches, stats } = response;
|
|
2068
2309
|
const tokenSuffix = stats ? ` ${pc.dim(`(tokens: ↓${stats.inputTokens ?? 0} ↑${stats.outputTokens ?? 0})`)}` : "";
|
|
2069
2310
|
logInfo(ctx, ` → ${pc.yellow(String(patches.length))} patches${tokenSuffix}:`);
|
|
2070
2311
|
for (const patch of patches) {
|
|
@@ -2487,13 +2728,13 @@ function registerReportCommand(program) {
|
|
|
2487
2728
|
//#endregion
|
|
2488
2729
|
//#region src/cli/commands/spec.ts
|
|
2489
2730
|
/**
|
|
2490
|
-
* Get the path to the
|
|
2731
|
+
* Get the path to the markform-spec.md file.
|
|
2491
2732
|
* Works both during development and when installed as a package.
|
|
2492
2733
|
*/
|
|
2493
2734
|
function getSpecPath() {
|
|
2494
2735
|
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
2495
|
-
if (thisDir.split(/[/\\]/).pop() === "dist") return join(dirname(thisDir), "
|
|
2496
|
-
return join(dirname(dirname(dirname(thisDir))), "
|
|
2736
|
+
if (thisDir.split(/[/\\]/).pop() === "dist") return join(dirname(thisDir), "docs", "markform-spec.md");
|
|
2737
|
+
return join(dirname(dirname(dirname(thisDir))), "docs", "markform-spec.md");
|
|
2497
2738
|
}
|
|
2498
2739
|
/**
|
|
2499
2740
|
* Load the spec content.
|
|
@@ -2884,6 +3125,34 @@ function formDataToPatches(formData, form) {
|
|
|
2884
3125
|
});
|
|
2885
3126
|
break;
|
|
2886
3127
|
}
|
|
3128
|
+
case "date": {
|
|
3129
|
+
const value = formData[fieldId];
|
|
3130
|
+
if (typeof value === "string" && value.trim() !== "") patches.push({
|
|
3131
|
+
op: "set_date",
|
|
3132
|
+
fieldId,
|
|
3133
|
+
value: value.trim()
|
|
3134
|
+
});
|
|
3135
|
+
else patches.push({
|
|
3136
|
+
op: "clear_field",
|
|
3137
|
+
fieldId
|
|
3138
|
+
});
|
|
3139
|
+
break;
|
|
3140
|
+
}
|
|
3141
|
+
case "year": {
|
|
3142
|
+
const value = formData[fieldId];
|
|
3143
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
3144
|
+
const num = parseInt(value, 10);
|
|
3145
|
+
if (!isNaN(num)) patches.push({
|
|
3146
|
+
op: "set_year",
|
|
3147
|
+
fieldId,
|
|
3148
|
+
value: num
|
|
3149
|
+
});
|
|
3150
|
+
} else patches.push({
|
|
3151
|
+
op: "clear_field",
|
|
3152
|
+
fieldId
|
|
3153
|
+
});
|
|
3154
|
+
break;
|
|
3155
|
+
}
|
|
2887
3156
|
}
|
|
2888
3157
|
}
|
|
2889
3158
|
return patches;
|
|
@@ -3228,7 +3497,13 @@ function renderFieldHtml(field, value, isSkipped) {
|
|
|
3228
3497
|
case "url_list":
|
|
3229
3498
|
inputHtml = renderUrlListInput(field, value, disabledAttr);
|
|
3230
3499
|
break;
|
|
3231
|
-
|
|
3500
|
+
case "date":
|
|
3501
|
+
inputHtml = renderDateInput(field, value, disabledAttr);
|
|
3502
|
+
break;
|
|
3503
|
+
case "year":
|
|
3504
|
+
inputHtml = renderYearInput(field, value, disabledAttr);
|
|
3505
|
+
break;
|
|
3506
|
+
default: inputHtml = "<div class=\"field-help\">(unknown field kind)</div>";
|
|
3232
3507
|
}
|
|
3233
3508
|
const skipButton = !field.required && !skipped ? `<div class="field-actions">
|
|
3234
3509
|
<button type="button" class="btn-skip" data-skip-field="${field.id}">Skip</button>
|
|
@@ -3250,7 +3525,8 @@ function renderStringInput(field, value, disabledAttr) {
|
|
|
3250
3525
|
const requiredAttr = field.required ? " required" : "";
|
|
3251
3526
|
const minLengthAttr = field.minLength !== void 0 ? ` minlength="${field.minLength}"` : "";
|
|
3252
3527
|
const maxLengthAttr = field.maxLength !== void 0 ? ` maxlength="${field.maxLength}"` : "";
|
|
3253
|
-
|
|
3528
|
+
const placeholderAttr = field.placeholder ? ` placeholder="${escapeHtml(field.placeholder)}"` : "";
|
|
3529
|
+
return `<input type="text" id="field-${field.id}" name="${field.id}" value="${escapeHtml(currentValue)}"${requiredAttr}${minLengthAttr}${maxLengthAttr}${placeholderAttr}${disabledAttr}>`;
|
|
3254
3530
|
}
|
|
3255
3531
|
/**
|
|
3256
3532
|
* Render a number field as number input.
|
|
@@ -3261,7 +3537,8 @@ function renderNumberInput(field, value, disabledAttr) {
|
|
|
3261
3537
|
const minAttr = field.min !== void 0 ? ` min="${field.min}"` : "";
|
|
3262
3538
|
const maxAttr = field.max !== void 0 ? ` max="${field.max}"` : "";
|
|
3263
3539
|
const stepAttr = field.integer ? " step=\"1\"" : "";
|
|
3264
|
-
|
|
3540
|
+
const placeholderAttr = field.placeholder ? ` placeholder="${escapeHtml(field.placeholder)}"` : "";
|
|
3541
|
+
return `<input type="number" id="field-${field.id}" name="${field.id}" value="${escapeHtml(currentValue)}"${requiredAttr}${minAttr}${maxAttr}${stepAttr}${placeholderAttr}${disabledAttr}>`;
|
|
3265
3542
|
}
|
|
3266
3543
|
/**
|
|
3267
3544
|
* Render a string list field as textarea.
|
|
@@ -3269,7 +3546,8 @@ function renderNumberInput(field, value, disabledAttr) {
|
|
|
3269
3546
|
function renderStringListInput(field, value, disabledAttr) {
|
|
3270
3547
|
const currentValue = (value?.kind === "string_list" ? value.items : []).join("\n");
|
|
3271
3548
|
const requiredAttr = field.required ? " required" : "";
|
|
3272
|
-
|
|
3549
|
+
const placeholderText = field.placeholder ? `${escapeHtml(field.placeholder)} (one item per line)` : "Enter one item per line";
|
|
3550
|
+
return `<textarea id="field-${field.id}" name="${field.id}" placeholder="${placeholderText}"${requiredAttr}${disabledAttr}>${escapeHtml(currentValue)}</textarea>`;
|
|
3273
3551
|
}
|
|
3274
3552
|
/**
|
|
3275
3553
|
* Render a URL field as url input.
|
|
@@ -3277,7 +3555,8 @@ function renderStringListInput(field, value, disabledAttr) {
|
|
|
3277
3555
|
function renderUrlInput(field, value, disabledAttr) {
|
|
3278
3556
|
const currentValue = value?.kind === "url" && value.value !== null ? value.value : "";
|
|
3279
3557
|
const requiredAttr = field.required ? " required" : "";
|
|
3280
|
-
|
|
3558
|
+
const placeholderText = field.placeholder ?? "https://example.com";
|
|
3559
|
+
return `<input type="url" id="field-${field.id}" name="${field.id}" value="${escapeHtml(currentValue)}" placeholder="${escapeHtml(placeholderText)}"${requiredAttr}${disabledAttr}>`;
|
|
3281
3560
|
}
|
|
3282
3561
|
/**
|
|
3283
3562
|
* Render a URL list field as textarea.
|
|
@@ -3285,7 +3564,28 @@ function renderUrlInput(field, value, disabledAttr) {
|
|
|
3285
3564
|
function renderUrlListInput(field, value, disabledAttr) {
|
|
3286
3565
|
const currentValue = (value?.kind === "url_list" ? value.items : []).join("\n");
|
|
3287
3566
|
const requiredAttr = field.required ? " required" : "";
|
|
3288
|
-
|
|
3567
|
+
const placeholderText = field.placeholder ? `${escapeHtml(field.placeholder)} (one URL per line)` : "Enter one URL per line";
|
|
3568
|
+
return `<textarea id="field-${field.id}" name="${field.id}" placeholder="${placeholderText}"${requiredAttr}${disabledAttr}>${escapeHtml(currentValue)}</textarea>`;
|
|
3569
|
+
}
|
|
3570
|
+
/**
|
|
3571
|
+
* Render a date field as date input.
|
|
3572
|
+
*/
|
|
3573
|
+
function renderDateInput(field, value, disabledAttr) {
|
|
3574
|
+
const currentValue = value?.kind === "date" && value.value !== null ? value.value : "";
|
|
3575
|
+
const requiredAttr = field.required ? " required" : "";
|
|
3576
|
+
const minAttr = field.min !== void 0 ? ` min="${field.min}"` : "";
|
|
3577
|
+
const maxAttr = field.max !== void 0 ? ` max="${field.max}"` : "";
|
|
3578
|
+
return `<input type="date" id="field-${field.id}" name="${field.id}" value="${escapeHtml(currentValue)}"${requiredAttr}${minAttr}${maxAttr}${disabledAttr}>`;
|
|
3579
|
+
}
|
|
3580
|
+
/**
|
|
3581
|
+
* Render a year field as number input.
|
|
3582
|
+
*/
|
|
3583
|
+
function renderYearInput(field, value, disabledAttr) {
|
|
3584
|
+
const currentValue = value?.kind === "year" && value.value !== null ? String(value.value) : "";
|
|
3585
|
+
const requiredAttr = field.required ? " required" : "";
|
|
3586
|
+
const minAttr = field.min !== void 0 ? ` min="${field.min}"` : " min=\"1000\"";
|
|
3587
|
+
const maxAttr = field.max !== void 0 ? ` max="${field.max}"` : " max=\"2500\"";
|
|
3588
|
+
return `<input type="number" id="field-${field.id}" name="${field.id}" value="${escapeHtml(currentValue)}" step="1" placeholder="YYYY"${requiredAttr}${minAttr}${maxAttr}${disabledAttr}>`;
|
|
3289
3589
|
}
|
|
3290
3590
|
/**
|
|
3291
3591
|
* Render a single-select field as select element.
|
|
@@ -3671,7 +3971,8 @@ function registerResearchCommand(program) {
|
|
|
3671
3971
|
process.exit(1);
|
|
3672
3972
|
}
|
|
3673
3973
|
const modelId = options.model;
|
|
3674
|
-
|
|
3974
|
+
const { provider, model: modelName } = parseModelIdForDisplay(modelId);
|
|
3975
|
+
if (!hasWebSearchSupport(provider)) {
|
|
3675
3976
|
const webSearchProviders = Object.entries(WEB_SEARCH_CONFIG).filter(([, config]) => config.supported).map(([p$1]) => p$1);
|
|
3676
3977
|
logError(`Model "${modelId}" does not support web search.`);
|
|
3677
3978
|
console.log("");
|
|
@@ -3709,27 +4010,40 @@ function registerResearchCommand(program) {
|
|
|
3709
4010
|
logVerbose(ctx, `Max turns: ${maxTurns}`);
|
|
3710
4011
|
logVerbose(ctx, `Max patches/turn: ${maxPatchesPerTurn}`);
|
|
3711
4012
|
logVerbose(ctx, `Max issues/turn: ${maxIssuesPerTurn}`);
|
|
3712
|
-
const
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
4013
|
+
const spinner = process.stdout.isTTY && !ctx.quiet ? createSpinner({
|
|
4014
|
+
type: "api",
|
|
4015
|
+
provider,
|
|
4016
|
+
model: modelName
|
|
4017
|
+
}) : null;
|
|
4018
|
+
let result;
|
|
4019
|
+
try {
|
|
4020
|
+
result = await runResearch(form, {
|
|
4021
|
+
model: modelId,
|
|
4022
|
+
enableWebSearch: true,
|
|
4023
|
+
maxTurns,
|
|
4024
|
+
maxPatchesPerTurn,
|
|
4025
|
+
maxIssuesPerTurn,
|
|
4026
|
+
targetRoles: [AGENT_ROLE],
|
|
4027
|
+
fillMode: "continue"
|
|
4028
|
+
});
|
|
4029
|
+
spinner?.stop();
|
|
4030
|
+
} catch (error) {
|
|
4031
|
+
spinner?.error("Research failed");
|
|
4032
|
+
throw error;
|
|
4033
|
+
}
|
|
3720
4034
|
if (result.availableTools) logInfo(ctx, `Tools: ${result.availableTools.join(", ")}`);
|
|
3721
4035
|
logInfo(ctx, `Status: ${(result.status === "completed" ? pc.green : result.status === "max_turns_reached" ? pc.yellow : pc.red)(result.status)}`);
|
|
3722
4036
|
logInfo(ctx, `Turns: ${result.totalTurns}`);
|
|
3723
4037
|
if (result.inputTokens || result.outputTokens) logVerbose(ctx, `Tokens: ${result.inputTokens ?? 0} in, ${result.outputTokens ?? 0} out`);
|
|
3724
|
-
const {
|
|
4038
|
+
const { reportPath, yamlPath, formPath } = await exportMultiFormat(result.form, outputPath);
|
|
3725
4039
|
logSuccess(ctx, "Outputs:");
|
|
3726
|
-
console.log(` ${
|
|
3727
|
-
console.log(` ${
|
|
3728
|
-
console.log(` ${
|
|
4040
|
+
console.log(` ${reportPath} ${pc.dim("(output report)")}`);
|
|
4041
|
+
console.log(` ${yamlPath} ${pc.dim("(output values)")}`);
|
|
4042
|
+
console.log(` ${formPath} ${pc.dim("(filled markform source)")}`);
|
|
3729
4043
|
if (options.transcript && result.transcript) {
|
|
3730
|
-
const { serializeSession: serializeSession$1 } = await import("./session-
|
|
4044
|
+
const { serializeSession: serializeSession$1 } = await import("./session-B_stoXQn.mjs");
|
|
3731
4045
|
const transcriptPath = outputPath.replace(/\.form\.md$/, ".session.yaml");
|
|
3732
|
-
const { writeFile: writeFile$1 } = await import("./shared-
|
|
4046
|
+
const { writeFile: writeFile$1 } = await import("./shared-CZsyShck.mjs");
|
|
3733
4047
|
await writeFile$1(transcriptPath, serializeSession$1(result.transcript));
|
|
3734
4048
|
logInfo(ctx, `Transcript: ${transcriptPath}`);
|
|
3735
4049
|
}
|
|
@@ -3875,10 +4189,11 @@ function withColoredHelp(cmd) {
|
|
|
3875
4189
|
*/
|
|
3876
4190
|
function createProgram() {
|
|
3877
4191
|
const program = withColoredHelp(new Command());
|
|
3878
|
-
program.name("markform").description("Agent-friendly, human-readable, editable forms").version(VERSION).showHelpAfterError().option("--verbose", "Enable verbose output").option("--quiet", "Suppress non-essential output").option("--dry-run", "Show what would be done without making changes").option("--format <format>", `Output format: ${OUTPUT_FORMATS.join(", ")}`, "console").option("--forms-dir <dir>", `Directory for form output (default: ${DEFAULT_FORMS_DIR})`);
|
|
4192
|
+
program.name("markform").description("Agent-friendly, human-readable, editable forms").version(VERSION, "--version", "output the version number").showHelpAfterError().option("--verbose", "Enable verbose output").option("--quiet", "Suppress non-essential output").option("--dry-run", "Show what would be done without making changes").option("--format <format>", `Output format: ${OUTPUT_FORMATS.join(", ")}`, "console").option("--forms-dir <dir>", `Directory for form output (default: ${DEFAULT_FORMS_DIR})`);
|
|
3879
4193
|
registerReadmeCommand(program);
|
|
3880
4194
|
registerDocsCommand(program);
|
|
3881
4195
|
registerSpecCommand(program);
|
|
4196
|
+
registerApisCommand(program);
|
|
3882
4197
|
registerApplyCommand(program);
|
|
3883
4198
|
registerDumpCommand(program);
|
|
3884
4199
|
registerExamplesCommand(program);
|