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
|
@@ -1,19 +1,113 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { A as parseRolesFlag, D as deriveReportPath, E as deriveExportPath, F as hasWebSearchSupport, 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-
|
|
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-BCCiJzQr.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-Df0XX7UB.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
|
/**
|
|
@@ -480,6 +574,7 @@ function formatPatchValue(patch) {
|
|
|
480
574
|
case "set_url_list": return patch.items.length > 0 ? truncate(`[${patch.items.join(", ")}]`) : "(empty)";
|
|
481
575
|
case "set_date": return patch.value ? truncate(`"${patch.value}"`) : "(empty)";
|
|
482
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)";
|
|
483
578
|
case "add_note": return truncate(`note: ${patch.text}`);
|
|
484
579
|
case "remove_note": return `(remove note ${patch.noteId})`;
|
|
485
580
|
}
|
|
@@ -502,6 +597,7 @@ function formatPatchType(patch) {
|
|
|
502
597
|
case "set_url_list": return "url_list";
|
|
503
598
|
case "set_date": return "date";
|
|
504
599
|
case "set_year": return "year";
|
|
600
|
+
case "set_table": return "table";
|
|
505
601
|
case "add_note": return "note";
|
|
506
602
|
case "remove_note": return "remove_note";
|
|
507
603
|
}
|
|
@@ -588,12 +684,6 @@ const EXAMPLE_DEFINITIONS = [
|
|
|
588
684
|
filename: "startup-deep-research.form.md",
|
|
589
685
|
path: "startup-deep-research/startup-deep-research.form.md",
|
|
590
686
|
type: "research"
|
|
591
|
-
},
|
|
592
|
-
{
|
|
593
|
-
id: "celebrity-deep-research",
|
|
594
|
-
filename: "celebrity-deep-research.form.md",
|
|
595
|
-
path: "celebrity-deep-research/celebrity-deep-research.form.md",
|
|
596
|
-
type: "research"
|
|
597
687
|
}
|
|
598
688
|
];
|
|
599
689
|
/**
|
|
@@ -607,7 +697,7 @@ function getExamplesDir() {
|
|
|
607
697
|
}
|
|
608
698
|
/**
|
|
609
699
|
* Load the content of an example form.
|
|
610
|
-
* @param exampleId - The example ID (e.g., 'simple', '
|
|
700
|
+
* @param exampleId - The example ID (e.g., 'simple', 'movie-research-deep')
|
|
611
701
|
* @returns The form content as a string
|
|
612
702
|
* @throws Error if the example is not found
|
|
613
703
|
*/
|
|
@@ -629,7 +719,7 @@ function getExampleById(id) {
|
|
|
629
719
|
}
|
|
630
720
|
/**
|
|
631
721
|
* Get the absolute path to an example's source file.
|
|
632
|
-
* @param exampleId - The example ID (e.g., 'simple', '
|
|
722
|
+
* @param exampleId - The example ID (e.g., 'simple', 'movie-research-deep')
|
|
633
723
|
* @returns The absolute path to the example form file
|
|
634
724
|
* @throws Error if the example is not found
|
|
635
725
|
*/
|
|
@@ -654,7 +744,7 @@ function extractFrontmatter(content) {
|
|
|
654
744
|
}
|
|
655
745
|
/**
|
|
656
746
|
* Load metadata (title, description) from an example's YAML frontmatter.
|
|
657
|
-
* @param exampleId - The example ID (e.g., 'simple', '
|
|
747
|
+
* @param exampleId - The example ID (e.g., 'simple', 'movie-research-deep')
|
|
658
748
|
* @returns Object with title and description from frontmatter
|
|
659
749
|
*/
|
|
660
750
|
function loadExampleMetadata(exampleId) {
|
|
@@ -850,9 +940,10 @@ async function promptSkipOrFill(ctx) {
|
|
|
850
940
|
async function promptForString(ctx) {
|
|
851
941
|
const field = ctx.field;
|
|
852
942
|
const currentVal = ctx.currentValue?.kind === "string" ? ctx.currentValue.value : null;
|
|
943
|
+
const placeholderText = field.placeholder ?? currentVal ?? (ctx.description ? ctx.description.slice(0, 60) : void 0);
|
|
853
944
|
const result = await p.text({
|
|
854
945
|
message: formatFieldLabel(ctx),
|
|
855
|
-
placeholder:
|
|
946
|
+
placeholder: placeholderText,
|
|
856
947
|
initialValue: currentVal ?? "",
|
|
857
948
|
validate: (value) => {
|
|
858
949
|
if (field.required && !value.trim()) return "This field is required";
|
|
@@ -875,9 +966,10 @@ async function promptForString(ctx) {
|
|
|
875
966
|
async function promptForNumber(ctx) {
|
|
876
967
|
const field = ctx.field;
|
|
877
968
|
const currentVal = ctx.currentValue?.kind === "number" ? ctx.currentValue.value : null;
|
|
969
|
+
const placeholderText = field.placeholder ?? (currentVal !== null ? String(currentVal) : void 0);
|
|
878
970
|
const result = await p.text({
|
|
879
971
|
message: formatFieldLabel(ctx),
|
|
880
|
-
placeholder:
|
|
972
|
+
placeholder: placeholderText,
|
|
881
973
|
initialValue: currentVal !== null ? String(currentVal) : "",
|
|
882
974
|
validate: (value) => {
|
|
883
975
|
if (field.required && !value.trim()) return "This field is required";
|
|
@@ -903,7 +995,10 @@ async function promptForNumber(ctx) {
|
|
|
903
995
|
async function promptForStringList(ctx) {
|
|
904
996
|
const field = ctx.field;
|
|
905
997
|
const currentItems = ctx.currentValue?.kind === "string_list" ? ctx.currentValue.items : [];
|
|
906
|
-
|
|
998
|
+
let hint;
|
|
999
|
+
if (field.placeholder) hint = `${field.placeholder} (one item per line)`;
|
|
1000
|
+
else if (ctx.description) hint = `${ctx.description.slice(0, 50)}... (one item per line)`;
|
|
1001
|
+
else hint = "Enter items, one per line. Press Enter twice when done.";
|
|
907
1002
|
const result = await p.text({
|
|
908
1003
|
message: formatFieldLabel(ctx),
|
|
909
1004
|
placeholder: hint,
|
|
@@ -1077,9 +1172,10 @@ async function promptForCheckboxes(ctx) {
|
|
|
1077
1172
|
async function promptForUrl(ctx) {
|
|
1078
1173
|
const field = ctx.field;
|
|
1079
1174
|
const currentVal = ctx.currentValue?.kind === "url" ? ctx.currentValue.value : null;
|
|
1175
|
+
const placeholderText = field.placeholder ?? currentVal ?? "https://example.com";
|
|
1080
1176
|
const result = await p.text({
|
|
1081
1177
|
message: formatFieldLabel(ctx),
|
|
1082
|
-
placeholder:
|
|
1178
|
+
placeholder: placeholderText,
|
|
1083
1179
|
initialValue: currentVal ?? "",
|
|
1084
1180
|
validate: (value) => {
|
|
1085
1181
|
if (field.required && !value.trim()) return "This field is required";
|
|
@@ -1100,12 +1196,87 @@ async function promptForUrl(ctx) {
|
|
|
1100
1196
|
};
|
|
1101
1197
|
}
|
|
1102
1198
|
/**
|
|
1199
|
+
* Check if a string is a valid ISO 8601 date (YYYY-MM-DD).
|
|
1200
|
+
*/
|
|
1201
|
+
function isValidDate(str) {
|
|
1202
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(str)) return false;
|
|
1203
|
+
const date = new Date(str);
|
|
1204
|
+
if (isNaN(date.getTime())) return false;
|
|
1205
|
+
const [year, month, day] = str.split("-").map(Number);
|
|
1206
|
+
return date.getUTCFullYear() === year && date.getUTCMonth() + 1 === month && date.getUTCDate() === day;
|
|
1207
|
+
}
|
|
1208
|
+
/**
|
|
1209
|
+
* Prompt for a date field value.
|
|
1210
|
+
*/
|
|
1211
|
+
async function promptForDate(ctx) {
|
|
1212
|
+
const field = ctx.field;
|
|
1213
|
+
const currentVal = ctx.currentValue?.kind === "date" ? ctx.currentValue.value : null;
|
|
1214
|
+
const constraints = [];
|
|
1215
|
+
if (field.min) constraints.push(`min: ${field.min}`);
|
|
1216
|
+
if (field.max) constraints.push(`max: ${field.max}`);
|
|
1217
|
+
const formatHint = constraints.length > 0 ? ` (${constraints.join(", ")})` : "";
|
|
1218
|
+
const result = await p.text({
|
|
1219
|
+
message: formatFieldLabel(ctx),
|
|
1220
|
+
placeholder: currentVal ?? `YYYY-MM-DD${formatHint}`,
|
|
1221
|
+
initialValue: currentVal ?? "",
|
|
1222
|
+
validate: (value) => {
|
|
1223
|
+
if (field.required && !value.trim()) return "This field is required";
|
|
1224
|
+
if (!value.trim()) return;
|
|
1225
|
+
if (!isValidDate(value)) return "Please enter a valid date in YYYY-MM-DD format";
|
|
1226
|
+
if (field.min && value < field.min) return `Date must be on or after ${field.min}`;
|
|
1227
|
+
if (field.max && value > field.max) return `Date must be on or before ${field.max}`;
|
|
1228
|
+
}
|
|
1229
|
+
});
|
|
1230
|
+
if (p.isCancel(result)) return null;
|
|
1231
|
+
if (!result && !field.required) return null;
|
|
1232
|
+
return {
|
|
1233
|
+
op: "set_date",
|
|
1234
|
+
fieldId: field.id,
|
|
1235
|
+
value: result || null
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
1238
|
+
/** Default year range for validation */
|
|
1239
|
+
const DEFAULT_MIN_YEAR = 1e3;
|
|
1240
|
+
const DEFAULT_MAX_YEAR = 2500;
|
|
1241
|
+
/**
|
|
1242
|
+
* Prompt for a year field value.
|
|
1243
|
+
*/
|
|
1244
|
+
async function promptForYear(ctx) {
|
|
1245
|
+
const field = ctx.field;
|
|
1246
|
+
const currentVal = ctx.currentValue?.kind === "year" ? ctx.currentValue.value : null;
|
|
1247
|
+
const minYear = field.min ?? DEFAULT_MIN_YEAR;
|
|
1248
|
+
const maxYear = field.max ?? DEFAULT_MAX_YEAR;
|
|
1249
|
+
const result = await p.text({
|
|
1250
|
+
message: formatFieldLabel(ctx),
|
|
1251
|
+
placeholder: currentVal !== null ? String(currentVal) : `Year (${minYear}-${maxYear})`,
|
|
1252
|
+
initialValue: currentVal !== null ? String(currentVal) : "",
|
|
1253
|
+
validate: (value) => {
|
|
1254
|
+
if (field.required && !value.trim()) return "This field is required";
|
|
1255
|
+
if (!value.trim()) return;
|
|
1256
|
+
const num = Number(value);
|
|
1257
|
+
if (isNaN(num) || !Number.isInteger(num)) return "Please enter a valid year (e.g., 2025)";
|
|
1258
|
+
if (num < minYear) return `Year must be ${minYear} or later`;
|
|
1259
|
+
if (num > maxYear) return `Year must be ${maxYear} or earlier`;
|
|
1260
|
+
}
|
|
1261
|
+
});
|
|
1262
|
+
if (p.isCancel(result)) return null;
|
|
1263
|
+
if (!result && !field.required) return null;
|
|
1264
|
+
return {
|
|
1265
|
+
op: "set_year",
|
|
1266
|
+
fieldId: field.id,
|
|
1267
|
+
value: result ? Number(result) : null
|
|
1268
|
+
};
|
|
1269
|
+
}
|
|
1270
|
+
/**
|
|
1103
1271
|
* Prompt for a URL list field value.
|
|
1104
1272
|
*/
|
|
1105
1273
|
async function promptForUrlList(ctx) {
|
|
1106
1274
|
const field = ctx.field;
|
|
1107
1275
|
const currentItems = ctx.currentValue?.kind === "url_list" ? ctx.currentValue.items : [];
|
|
1108
|
-
|
|
1276
|
+
let hint;
|
|
1277
|
+
if (field.placeholder) hint = `${field.placeholder} (one URL per line)`;
|
|
1278
|
+
else if (ctx.description) hint = `${ctx.description.slice(0, 50)}... (one URL per line)`;
|
|
1279
|
+
else hint = "Enter URLs, one per line. Press Enter twice when done.";
|
|
1109
1280
|
const result = await p.text({
|
|
1110
1281
|
message: formatFieldLabel(ctx),
|
|
1111
1282
|
placeholder: hint,
|
|
@@ -1132,7 +1303,7 @@ async function promptForUrlList(ctx) {
|
|
|
1132
1303
|
};
|
|
1133
1304
|
}
|
|
1134
1305
|
/**
|
|
1135
|
-
* Prompt user for a single field value based on field
|
|
1306
|
+
* Prompt user for a single field value based on field kind.
|
|
1136
1307
|
* Returns a Patch to set the value, or null if skipped/cancelled.
|
|
1137
1308
|
*
|
|
1138
1309
|
* For optional fields, first offers a choice to skip or fill.
|
|
@@ -1151,6 +1322,8 @@ async function promptForField(ctx) {
|
|
|
1151
1322
|
case "checkboxes": return promptForCheckboxes(ctx);
|
|
1152
1323
|
case "url": return promptForUrl(ctx);
|
|
1153
1324
|
case "url_list": return promptForUrlList(ctx);
|
|
1325
|
+
case "date": return promptForDate(ctx);
|
|
1326
|
+
case "year": return promptForYear(ctx);
|
|
1154
1327
|
default: return null;
|
|
1155
1328
|
}
|
|
1156
1329
|
}
|
|
@@ -1553,54 +1726,75 @@ async function promptForWebSearchModel() {
|
|
|
1553
1726
|
* Accepts optional harness config overrides - research uses different defaults.
|
|
1554
1727
|
*/
|
|
1555
1728
|
async function runAgentFill(form, modelId, _outputPath, configOverrides) {
|
|
1556
|
-
const
|
|
1729
|
+
const { provider: providerName, model: modelName } = parseModelIdForDisplay(modelId);
|
|
1730
|
+
const resolveSpinner = createSpinner({
|
|
1731
|
+
type: "compute",
|
|
1732
|
+
operation: `Resolving model: ${modelId}`
|
|
1733
|
+
});
|
|
1734
|
+
let model, provider;
|
|
1557
1735
|
try {
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
maxTurns: configOverrides?.maxTurns ?? DEFAULT_MAX_TURNS,
|
|
1563
|
-
maxPatchesPerTurn: configOverrides?.maxPatchesPerTurn ?? DEFAULT_MAX_PATCHES_PER_TURN,
|
|
1564
|
-
maxIssuesPerTurn: configOverrides?.maxIssuesPerTurn ?? DEFAULT_MAX_ISSUES_PER_TURN,
|
|
1565
|
-
targetRoles: [AGENT_ROLE],
|
|
1566
|
-
fillMode: "continue"
|
|
1567
|
-
};
|
|
1568
|
-
console.log("");
|
|
1569
|
-
console.log(`Config: max_turns=${harnessConfig.maxTurns}, max_issues_per_turn=${harnessConfig.maxIssuesPerTurn}, max_patches_per_turn=${harnessConfig.maxPatchesPerTurn}`);
|
|
1570
|
-
const harness = createHarness(form, harnessConfig);
|
|
1571
|
-
const agent = createLiveAgent({
|
|
1572
|
-
model,
|
|
1573
|
-
provider,
|
|
1574
|
-
targetRole: AGENT_ROLE
|
|
1575
|
-
});
|
|
1576
|
-
p.log.step(pc.bold("Agent fill in progress..."));
|
|
1577
|
-
let stepResult = harness.step();
|
|
1578
|
-
while (!stepResult.isComplete && !harness.hasReachedMaxTurns()) {
|
|
1579
|
-
console.log(` ${pc.bold(`Turn ${stepResult.turnNumber}:`)} ${formatTurnIssues(stepResult.issues)}`);
|
|
1580
|
-
const { patches, stats } = await agent.generatePatches(stepResult.issues, harness.getForm(), harnessConfig.maxPatchesPerTurn);
|
|
1581
|
-
for (const patch of patches) {
|
|
1582
|
-
const typeName = formatPatchType(patch);
|
|
1583
|
-
const value = formatPatchValue(patch);
|
|
1584
|
-
const fieldId = "fieldId" in patch ? patch.fieldId : patch.op === "add_note" ? patch.ref : "";
|
|
1585
|
-
if (fieldId) console.log(` ${pc.cyan(fieldId)} (${typeName}) = ${pc.green(value)}`);
|
|
1586
|
-
else console.log(` (${typeName}) = ${pc.green(value)}`);
|
|
1587
|
-
}
|
|
1588
|
-
stepResult = harness.apply(patches, stepResult.issues);
|
|
1589
|
-
const tokenInfo = stats ? ` ${pc.dim(`(tokens: ↓${stats.inputTokens ?? 0} ↑${stats.outputTokens ?? 0})`)}` : "";
|
|
1590
|
-
console.log(` ${patches.length} patch(es) applied, ${stepResult.issues.length} remaining${tokenInfo}`);
|
|
1591
|
-
if (!stepResult.isComplete && !harness.hasReachedMaxTurns()) stepResult = harness.step();
|
|
1592
|
-
}
|
|
1593
|
-
if (stepResult.isComplete) p.log.success(pc.green(`Form completed in ${harness.getTurnNumber()} turn(s)`));
|
|
1594
|
-
else p.log.warn(pc.yellow(`Max turns reached (${harnessConfig.maxTurns})`));
|
|
1595
|
-
Object.assign(form, harness.getForm());
|
|
1596
|
-
return {
|
|
1597
|
-
success: stepResult.isComplete,
|
|
1598
|
-
turnCount: harness.getTurnNumber()
|
|
1599
|
-
};
|
|
1736
|
+
const result = await resolveModel(modelId);
|
|
1737
|
+
model = result.model;
|
|
1738
|
+
provider = result.provider;
|
|
1739
|
+
resolveSpinner.stop(`✓ Model resolved: ${modelId}`);
|
|
1600
1740
|
} catch (error) {
|
|
1601
|
-
|
|
1741
|
+
resolveSpinner.error("Model resolution failed");
|
|
1602
1742
|
throw error;
|
|
1603
1743
|
}
|
|
1744
|
+
const harnessConfig = {
|
|
1745
|
+
maxTurns: configOverrides?.maxTurns ?? DEFAULT_MAX_TURNS,
|
|
1746
|
+
maxPatchesPerTurn: configOverrides?.maxPatchesPerTurn ?? DEFAULT_MAX_PATCHES_PER_TURN,
|
|
1747
|
+
maxIssuesPerTurn: configOverrides?.maxIssuesPerTurn ?? DEFAULT_MAX_ISSUES_PER_TURN,
|
|
1748
|
+
targetRoles: [AGENT_ROLE],
|
|
1749
|
+
fillMode: "continue"
|
|
1750
|
+
};
|
|
1751
|
+
console.log("");
|
|
1752
|
+
console.log(`Config: max_turns=${harnessConfig.maxTurns}, max_issues_per_turn=${harnessConfig.maxIssuesPerTurn}, max_patches_per_turn=${harnessConfig.maxPatchesPerTurn}`);
|
|
1753
|
+
const harness = createHarness(form, harnessConfig);
|
|
1754
|
+
const agent = createLiveAgent({
|
|
1755
|
+
model,
|
|
1756
|
+
provider,
|
|
1757
|
+
targetRole: AGENT_ROLE,
|
|
1758
|
+
enableWebSearch: true
|
|
1759
|
+
});
|
|
1760
|
+
p.log.step(pc.bold("Agent fill in progress..."));
|
|
1761
|
+
let stepResult = harness.step();
|
|
1762
|
+
while (!stepResult.isComplete && !harness.hasReachedMaxTurns()) {
|
|
1763
|
+
console.log(` ${pc.bold(`Turn ${stepResult.turnNumber}:`)} ${formatTurnIssues(stepResult.issues)}`);
|
|
1764
|
+
const llmSpinner = createSpinner({
|
|
1765
|
+
type: "api",
|
|
1766
|
+
provider: providerName,
|
|
1767
|
+
model: modelName,
|
|
1768
|
+
turnNumber: stepResult.turnNumber
|
|
1769
|
+
});
|
|
1770
|
+
let response;
|
|
1771
|
+
try {
|
|
1772
|
+
response = await agent.generatePatches(stepResult.issues, harness.getForm(), harnessConfig.maxPatchesPerTurn);
|
|
1773
|
+
llmSpinner.stop();
|
|
1774
|
+
} catch (error) {
|
|
1775
|
+
llmSpinner.error("LLM call failed");
|
|
1776
|
+
throw error;
|
|
1777
|
+
}
|
|
1778
|
+
const { patches, stats } = response;
|
|
1779
|
+
for (const patch of patches) {
|
|
1780
|
+
const typeName = formatPatchType(patch);
|
|
1781
|
+
const value = formatPatchValue(patch);
|
|
1782
|
+
const fieldId = "fieldId" in patch ? patch.fieldId : patch.op === "add_note" ? patch.ref : "";
|
|
1783
|
+
if (fieldId) console.log(` ${pc.cyan(fieldId)} (${typeName}) = ${pc.green(value)}`);
|
|
1784
|
+
else console.log(` (${typeName}) = ${pc.green(value)}`);
|
|
1785
|
+
}
|
|
1786
|
+
stepResult = harness.apply(patches, stepResult.issues);
|
|
1787
|
+
const tokenInfo = stats ? ` ${pc.dim(`(tokens: ↓${stats.inputTokens ?? 0} ↑${stats.outputTokens ?? 0})`)}` : "";
|
|
1788
|
+
console.log(` ${patches.length} patch(es) applied, ${stepResult.issues.length} remaining${tokenInfo}`);
|
|
1789
|
+
if (!stepResult.isComplete && !harness.hasReachedMaxTurns()) stepResult = harness.step();
|
|
1790
|
+
}
|
|
1791
|
+
if (stepResult.isComplete) p.log.success(pc.green(`Form completed in ${harness.getTurnNumber()} turn(s)`));
|
|
1792
|
+
else p.log.warn(pc.yellow(`Max turns reached (${harnessConfig.maxTurns})`));
|
|
1793
|
+
Object.assign(form, harness.getForm());
|
|
1794
|
+
return {
|
|
1795
|
+
success: stepResult.isComplete,
|
|
1796
|
+
turnCount: harness.getTurnNumber()
|
|
1797
|
+
};
|
|
1604
1798
|
}
|
|
1605
1799
|
/**
|
|
1606
1800
|
* Run the interactive example scaffolding and filling flow.
|
|
@@ -1883,7 +2077,9 @@ function registerExportCommand(program) {
|
|
|
1883
2077
|
...field.kind === "single_select" || field.kind === "multi_select" || field.kind === "checkboxes" ? { options: field.options.map((opt) => ({
|
|
1884
2078
|
id: opt.id,
|
|
1885
2079
|
label: opt.label
|
|
1886
|
-
})) } : {}
|
|
2080
|
+
})) } : {},
|
|
2081
|
+
...field.placeholder ? { placeholder: field.placeholder } : {},
|
|
2082
|
+
...field.examples && field.examples.length > 0 ? { examples: field.examples } : {}
|
|
1887
2083
|
}))
|
|
1888
2084
|
}))
|
|
1889
2085
|
};
|
|
@@ -2045,14 +2241,18 @@ function registerFillCommand(program) {
|
|
|
2045
2241
|
const harness = createHarness(form, harnessConfig);
|
|
2046
2242
|
let agent;
|
|
2047
2243
|
let mockPath;
|
|
2244
|
+
let agentProvider;
|
|
2245
|
+
let agentModelName;
|
|
2048
2246
|
if (options.mock) {
|
|
2049
2247
|
mockPath = resolve(options.mockSource);
|
|
2050
2248
|
logVerbose(ctx, `Reading mock source: ${mockPath}`);
|
|
2051
2249
|
agent = createMockAgent(parseForm(await readFile$1(mockPath)));
|
|
2052
2250
|
} else {
|
|
2053
|
-
const
|
|
2054
|
-
logVerbose(ctx, `Resolving model: ${
|
|
2055
|
-
const { model, provider } = await resolveModel(
|
|
2251
|
+
const modelIdString = options.model;
|
|
2252
|
+
logVerbose(ctx, `Resolving model: ${modelIdString}`);
|
|
2253
|
+
const { model, provider, modelId } = await resolveModel(modelIdString);
|
|
2254
|
+
agentProvider = provider;
|
|
2255
|
+
agentModelName = modelId;
|
|
2056
2256
|
let systemPrompt;
|
|
2057
2257
|
if (options.instructions) {
|
|
2058
2258
|
systemPrompt = options.instructions;
|
|
@@ -2067,7 +2267,8 @@ function registerFillCommand(program) {
|
|
|
2067
2267
|
model,
|
|
2068
2268
|
provider,
|
|
2069
2269
|
systemPromptAddition: systemPrompt,
|
|
2070
|
-
targetRole: primaryRole
|
|
2270
|
+
targetRole: primaryRole,
|
|
2271
|
+
enableWebSearch: true
|
|
2071
2272
|
});
|
|
2072
2273
|
agent = liveAgent;
|
|
2073
2274
|
logInfo(ctx, `Available tools: ${liveAgent.getAvailableToolNames().join(", ")}`);
|
|
@@ -2083,7 +2284,22 @@ function registerFillCommand(program) {
|
|
|
2083
2284
|
let stepResult = harness.step();
|
|
2084
2285
|
logInfo(ctx, `${pc.bold(`Turn ${stepResult.turnNumber}:`)} ${formatTurnIssues(stepResult.issues)}`);
|
|
2085
2286
|
while (!stepResult.isComplete && !harness.hasReachedMaxTurns()) {
|
|
2086
|
-
|
|
2287
|
+
let spinner = null;
|
|
2288
|
+
if (!options.mock && agentProvider && agentModelName && process.stdout.isTTY && !ctx.quiet) spinner = createSpinner({
|
|
2289
|
+
type: "api",
|
|
2290
|
+
provider: agentProvider,
|
|
2291
|
+
model: agentModelName,
|
|
2292
|
+
turnNumber: stepResult.turnNumber
|
|
2293
|
+
});
|
|
2294
|
+
let response;
|
|
2295
|
+
try {
|
|
2296
|
+
response = await agent.generatePatches(stepResult.issues, harness.getForm(), harnessConfig.maxPatchesPerTurn);
|
|
2297
|
+
spinner?.stop();
|
|
2298
|
+
} catch (error) {
|
|
2299
|
+
spinner?.error("LLM call failed");
|
|
2300
|
+
throw error;
|
|
2301
|
+
}
|
|
2302
|
+
const { patches, stats } = response;
|
|
2087
2303
|
const tokenSuffix = stats ? ` ${pc.dim(`(tokens: ↓${stats.inputTokens ?? 0} ↑${stats.outputTokens ?? 0})`)}` : "";
|
|
2088
2304
|
logInfo(ctx, ` → ${pc.yellow(String(patches.length))} patches${tokenSuffix}:`);
|
|
2089
2305
|
for (const patch of patches) {
|
|
@@ -2506,13 +2722,13 @@ function registerReportCommand(program) {
|
|
|
2506
2722
|
//#endregion
|
|
2507
2723
|
//#region src/cli/commands/spec.ts
|
|
2508
2724
|
/**
|
|
2509
|
-
* Get the path to the
|
|
2725
|
+
* Get the path to the markform-spec.md file.
|
|
2510
2726
|
* Works both during development and when installed as a package.
|
|
2511
2727
|
*/
|
|
2512
2728
|
function getSpecPath() {
|
|
2513
2729
|
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
2514
|
-
if (thisDir.split(/[/\\]/).pop() === "dist") return join(dirname(thisDir), "
|
|
2515
|
-
return join(dirname(dirname(dirname(thisDir))), "
|
|
2730
|
+
if (thisDir.split(/[/\\]/).pop() === "dist") return join(dirname(thisDir), "docs", "markform-spec.md");
|
|
2731
|
+
return join(dirname(dirname(dirname(thisDir))), "docs", "markform-spec.md");
|
|
2516
2732
|
}
|
|
2517
2733
|
/**
|
|
2518
2734
|
* Load the spec content.
|
|
@@ -2903,6 +3119,34 @@ function formDataToPatches(formData, form) {
|
|
|
2903
3119
|
});
|
|
2904
3120
|
break;
|
|
2905
3121
|
}
|
|
3122
|
+
case "date": {
|
|
3123
|
+
const value = formData[fieldId];
|
|
3124
|
+
if (typeof value === "string" && value.trim() !== "") patches.push({
|
|
3125
|
+
op: "set_date",
|
|
3126
|
+
fieldId,
|
|
3127
|
+
value: value.trim()
|
|
3128
|
+
});
|
|
3129
|
+
else patches.push({
|
|
3130
|
+
op: "clear_field",
|
|
3131
|
+
fieldId
|
|
3132
|
+
});
|
|
3133
|
+
break;
|
|
3134
|
+
}
|
|
3135
|
+
case "year": {
|
|
3136
|
+
const value = formData[fieldId];
|
|
3137
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
3138
|
+
const num = parseInt(value, 10);
|
|
3139
|
+
if (!isNaN(num)) patches.push({
|
|
3140
|
+
op: "set_year",
|
|
3141
|
+
fieldId,
|
|
3142
|
+
value: num
|
|
3143
|
+
});
|
|
3144
|
+
} else patches.push({
|
|
3145
|
+
op: "clear_field",
|
|
3146
|
+
fieldId
|
|
3147
|
+
});
|
|
3148
|
+
break;
|
|
3149
|
+
}
|
|
2906
3150
|
}
|
|
2907
3151
|
}
|
|
2908
3152
|
return patches;
|
|
@@ -3247,7 +3491,13 @@ function renderFieldHtml(field, value, isSkipped) {
|
|
|
3247
3491
|
case "url_list":
|
|
3248
3492
|
inputHtml = renderUrlListInput(field, value, disabledAttr);
|
|
3249
3493
|
break;
|
|
3250
|
-
|
|
3494
|
+
case "date":
|
|
3495
|
+
inputHtml = renderDateInput(field, value, disabledAttr);
|
|
3496
|
+
break;
|
|
3497
|
+
case "year":
|
|
3498
|
+
inputHtml = renderYearInput(field, value, disabledAttr);
|
|
3499
|
+
break;
|
|
3500
|
+
default: inputHtml = "<div class=\"field-help\">(unknown field kind)</div>";
|
|
3251
3501
|
}
|
|
3252
3502
|
const skipButton = !field.required && !skipped ? `<div class="field-actions">
|
|
3253
3503
|
<button type="button" class="btn-skip" data-skip-field="${field.id}">Skip</button>
|
|
@@ -3269,7 +3519,8 @@ function renderStringInput(field, value, disabledAttr) {
|
|
|
3269
3519
|
const requiredAttr = field.required ? " required" : "";
|
|
3270
3520
|
const minLengthAttr = field.minLength !== void 0 ? ` minlength="${field.minLength}"` : "";
|
|
3271
3521
|
const maxLengthAttr = field.maxLength !== void 0 ? ` maxlength="${field.maxLength}"` : "";
|
|
3272
|
-
|
|
3522
|
+
const placeholderAttr = field.placeholder ? ` placeholder="${escapeHtml(field.placeholder)}"` : "";
|
|
3523
|
+
return `<input type="text" id="field-${field.id}" name="${field.id}" value="${escapeHtml(currentValue)}"${requiredAttr}${minLengthAttr}${maxLengthAttr}${placeholderAttr}${disabledAttr}>`;
|
|
3273
3524
|
}
|
|
3274
3525
|
/**
|
|
3275
3526
|
* Render a number field as number input.
|
|
@@ -3280,7 +3531,8 @@ function renderNumberInput(field, value, disabledAttr) {
|
|
|
3280
3531
|
const minAttr = field.min !== void 0 ? ` min="${field.min}"` : "";
|
|
3281
3532
|
const maxAttr = field.max !== void 0 ? ` max="${field.max}"` : "";
|
|
3282
3533
|
const stepAttr = field.integer ? " step=\"1\"" : "";
|
|
3283
|
-
|
|
3534
|
+
const placeholderAttr = field.placeholder ? ` placeholder="${escapeHtml(field.placeholder)}"` : "";
|
|
3535
|
+
return `<input type="number" id="field-${field.id}" name="${field.id}" value="${escapeHtml(currentValue)}"${requiredAttr}${minAttr}${maxAttr}${stepAttr}${placeholderAttr}${disabledAttr}>`;
|
|
3284
3536
|
}
|
|
3285
3537
|
/**
|
|
3286
3538
|
* Render a string list field as textarea.
|
|
@@ -3288,7 +3540,8 @@ function renderNumberInput(field, value, disabledAttr) {
|
|
|
3288
3540
|
function renderStringListInput(field, value, disabledAttr) {
|
|
3289
3541
|
const currentValue = (value?.kind === "string_list" ? value.items : []).join("\n");
|
|
3290
3542
|
const requiredAttr = field.required ? " required" : "";
|
|
3291
|
-
|
|
3543
|
+
const placeholderText = field.placeholder ? `${escapeHtml(field.placeholder)} (one item per line)` : "Enter one item per line";
|
|
3544
|
+
return `<textarea id="field-${field.id}" name="${field.id}" placeholder="${placeholderText}"${requiredAttr}${disabledAttr}>${escapeHtml(currentValue)}</textarea>`;
|
|
3292
3545
|
}
|
|
3293
3546
|
/**
|
|
3294
3547
|
* Render a URL field as url input.
|
|
@@ -3296,7 +3549,8 @@ function renderStringListInput(field, value, disabledAttr) {
|
|
|
3296
3549
|
function renderUrlInput(field, value, disabledAttr) {
|
|
3297
3550
|
const currentValue = value?.kind === "url" && value.value !== null ? value.value : "";
|
|
3298
3551
|
const requiredAttr = field.required ? " required" : "";
|
|
3299
|
-
|
|
3552
|
+
const placeholderText = field.placeholder ?? "https://example.com";
|
|
3553
|
+
return `<input type="url" id="field-${field.id}" name="${field.id}" value="${escapeHtml(currentValue)}" placeholder="${escapeHtml(placeholderText)}"${requiredAttr}${disabledAttr}>`;
|
|
3300
3554
|
}
|
|
3301
3555
|
/**
|
|
3302
3556
|
* Render a URL list field as textarea.
|
|
@@ -3304,7 +3558,28 @@ function renderUrlInput(field, value, disabledAttr) {
|
|
|
3304
3558
|
function renderUrlListInput(field, value, disabledAttr) {
|
|
3305
3559
|
const currentValue = (value?.kind === "url_list" ? value.items : []).join("\n");
|
|
3306
3560
|
const requiredAttr = field.required ? " required" : "";
|
|
3307
|
-
|
|
3561
|
+
const placeholderText = field.placeholder ? `${escapeHtml(field.placeholder)} (one URL per line)` : "Enter one URL per line";
|
|
3562
|
+
return `<textarea id="field-${field.id}" name="${field.id}" placeholder="${placeholderText}"${requiredAttr}${disabledAttr}>${escapeHtml(currentValue)}</textarea>`;
|
|
3563
|
+
}
|
|
3564
|
+
/**
|
|
3565
|
+
* Render a date field as date input.
|
|
3566
|
+
*/
|
|
3567
|
+
function renderDateInput(field, value, disabledAttr) {
|
|
3568
|
+
const currentValue = value?.kind === "date" && value.value !== null ? value.value : "";
|
|
3569
|
+
const requiredAttr = field.required ? " required" : "";
|
|
3570
|
+
const minAttr = field.min !== void 0 ? ` min="${field.min}"` : "";
|
|
3571
|
+
const maxAttr = field.max !== void 0 ? ` max="${field.max}"` : "";
|
|
3572
|
+
return `<input type="date" id="field-${field.id}" name="${field.id}" value="${escapeHtml(currentValue)}"${requiredAttr}${minAttr}${maxAttr}${disabledAttr}>`;
|
|
3573
|
+
}
|
|
3574
|
+
/**
|
|
3575
|
+
* Render a year field as number input.
|
|
3576
|
+
*/
|
|
3577
|
+
function renderYearInput(field, value, disabledAttr) {
|
|
3578
|
+
const currentValue = value?.kind === "year" && value.value !== null ? String(value.value) : "";
|
|
3579
|
+
const requiredAttr = field.required ? " required" : "";
|
|
3580
|
+
const minAttr = field.min !== void 0 ? ` min="${field.min}"` : " min=\"1000\"";
|
|
3581
|
+
const maxAttr = field.max !== void 0 ? ` max="${field.max}"` : " max=\"2500\"";
|
|
3582
|
+
return `<input type="number" id="field-${field.id}" name="${field.id}" value="${escapeHtml(currentValue)}" step="1" placeholder="YYYY"${requiredAttr}${minAttr}${maxAttr}${disabledAttr}>`;
|
|
3308
3583
|
}
|
|
3309
3584
|
/**
|
|
3310
3585
|
* Render a single-select field as select element.
|
|
@@ -3690,7 +3965,8 @@ function registerResearchCommand(program) {
|
|
|
3690
3965
|
process.exit(1);
|
|
3691
3966
|
}
|
|
3692
3967
|
const modelId = options.model;
|
|
3693
|
-
|
|
3968
|
+
const { provider, model: modelName } = parseModelIdForDisplay(modelId);
|
|
3969
|
+
if (!hasWebSearchSupport(provider)) {
|
|
3694
3970
|
const webSearchProviders = Object.entries(WEB_SEARCH_CONFIG).filter(([, config]) => config.supported).map(([p$1]) => p$1);
|
|
3695
3971
|
logError(`Model "${modelId}" does not support web search.`);
|
|
3696
3972
|
console.log("");
|
|
@@ -3728,14 +4004,27 @@ function registerResearchCommand(program) {
|
|
|
3728
4004
|
logVerbose(ctx, `Max turns: ${maxTurns}`);
|
|
3729
4005
|
logVerbose(ctx, `Max patches/turn: ${maxPatchesPerTurn}`);
|
|
3730
4006
|
logVerbose(ctx, `Max issues/turn: ${maxIssuesPerTurn}`);
|
|
3731
|
-
const
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
4007
|
+
const spinner = process.stdout.isTTY && !ctx.quiet ? createSpinner({
|
|
4008
|
+
type: "api",
|
|
4009
|
+
provider,
|
|
4010
|
+
model: modelName
|
|
4011
|
+
}) : null;
|
|
4012
|
+
let result;
|
|
4013
|
+
try {
|
|
4014
|
+
result = await runResearch(form, {
|
|
4015
|
+
model: modelId,
|
|
4016
|
+
enableWebSearch: true,
|
|
4017
|
+
maxTurns,
|
|
4018
|
+
maxPatchesPerTurn,
|
|
4019
|
+
maxIssuesPerTurn,
|
|
4020
|
+
targetRoles: [AGENT_ROLE],
|
|
4021
|
+
fillMode: "continue"
|
|
4022
|
+
});
|
|
4023
|
+
spinner?.stop();
|
|
4024
|
+
} catch (error) {
|
|
4025
|
+
spinner?.error("Research failed");
|
|
4026
|
+
throw error;
|
|
4027
|
+
}
|
|
3739
4028
|
if (result.availableTools) logInfo(ctx, `Tools: ${result.availableTools.join(", ")}`);
|
|
3740
4029
|
logInfo(ctx, `Status: ${(result.status === "completed" ? pc.green : result.status === "max_turns_reached" ? pc.yellow : pc.red)(result.status)}`);
|
|
3741
4030
|
logInfo(ctx, `Turns: ${result.totalTurns}`);
|
|
@@ -3746,9 +4035,9 @@ function registerResearchCommand(program) {
|
|
|
3746
4035
|
console.log(` ${yamlPath} ${pc.dim("(output values)")}`);
|
|
3747
4036
|
console.log(` ${formPath} ${pc.dim("(filled markform source)")}`);
|
|
3748
4037
|
if (options.transcript && result.transcript) {
|
|
3749
|
-
const { serializeSession: serializeSession$1 } = await import("./session-
|
|
4038
|
+
const { serializeSession: serializeSession$1 } = await import("./session-B_stoXQn.mjs");
|
|
3750
4039
|
const transcriptPath = outputPath.replace(/\.form\.md$/, ".session.yaml");
|
|
3751
|
-
const { writeFile: writeFile$1 } = await import("./shared-
|
|
4040
|
+
const { writeFile: writeFile$1 } = await import("./shared-CZsyShck.mjs");
|
|
3752
4041
|
await writeFile$1(transcriptPath, serializeSession$1(result.transcript));
|
|
3753
4042
|
logInfo(ctx, `Transcript: ${transcriptPath}`);
|
|
3754
4043
|
}
|
|
@@ -3894,10 +4183,11 @@ function withColoredHelp(cmd) {
|
|
|
3894
4183
|
*/
|
|
3895
4184
|
function createProgram() {
|
|
3896
4185
|
const program = withColoredHelp(new Command());
|
|
3897
|
-
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})`);
|
|
4186
|
+
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})`);
|
|
3898
4187
|
registerReadmeCommand(program);
|
|
3899
4188
|
registerDocsCommand(program);
|
|
3900
4189
|
registerSpecCommand(program);
|
|
4190
|
+
registerApisCommand(program);
|
|
3901
4191
|
registerApplyCommand(program);
|
|
3902
4192
|
registerDumpCommand(program);
|
|
3903
4193
|
registerExamplesCommand(program);
|