markform 0.1.3 → 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 +81 -28
- package/dist/ai-sdk.d.mts +2 -2
- package/dist/ai-sdk.mjs +5 -5
- package/dist/{apply-00UmzDKL.mjs → apply-C54EMAJ1.mjs} +371 -26
- package/dist/bin.mjs +6 -6
- package/dist/{cli-D--Lel-e.mjs → cli-BhWhn6L9.mjs} +383 -87
- 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 +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-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 +25 -11
- 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 -19
- package/dist/session-DdAtY2Ni.mjs +0 -4
- package/dist/shared-D7gf27Tr.mjs +0 -3
|
@@ -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-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
|
/**
|
|
@@ -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
|
}
|
|
@@ -850,9 +946,10 @@ async function promptSkipOrFill(ctx) {
|
|
|
850
946
|
async function promptForString(ctx) {
|
|
851
947
|
const field = ctx.field;
|
|
852
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);
|
|
853
950
|
const result = await p.text({
|
|
854
951
|
message: formatFieldLabel(ctx),
|
|
855
|
-
placeholder:
|
|
952
|
+
placeholder: placeholderText,
|
|
856
953
|
initialValue: currentVal ?? "",
|
|
857
954
|
validate: (value) => {
|
|
858
955
|
if (field.required && !value.trim()) return "This field is required";
|
|
@@ -875,9 +972,10 @@ async function promptForString(ctx) {
|
|
|
875
972
|
async function promptForNumber(ctx) {
|
|
876
973
|
const field = ctx.field;
|
|
877
974
|
const currentVal = ctx.currentValue?.kind === "number" ? ctx.currentValue.value : null;
|
|
975
|
+
const placeholderText = field.placeholder ?? (currentVal !== null ? String(currentVal) : void 0);
|
|
878
976
|
const result = await p.text({
|
|
879
977
|
message: formatFieldLabel(ctx),
|
|
880
|
-
placeholder:
|
|
978
|
+
placeholder: placeholderText,
|
|
881
979
|
initialValue: currentVal !== null ? String(currentVal) : "",
|
|
882
980
|
validate: (value) => {
|
|
883
981
|
if (field.required && !value.trim()) return "This field is required";
|
|
@@ -903,7 +1001,10 @@ async function promptForNumber(ctx) {
|
|
|
903
1001
|
async function promptForStringList(ctx) {
|
|
904
1002
|
const field = ctx.field;
|
|
905
1003
|
const currentItems = ctx.currentValue?.kind === "string_list" ? ctx.currentValue.items : [];
|
|
906
|
-
|
|
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.";
|
|
907
1008
|
const result = await p.text({
|
|
908
1009
|
message: formatFieldLabel(ctx),
|
|
909
1010
|
placeholder: hint,
|
|
@@ -1077,9 +1178,10 @@ async function promptForCheckboxes(ctx) {
|
|
|
1077
1178
|
async function promptForUrl(ctx) {
|
|
1078
1179
|
const field = ctx.field;
|
|
1079
1180
|
const currentVal = ctx.currentValue?.kind === "url" ? ctx.currentValue.value : null;
|
|
1181
|
+
const placeholderText = field.placeholder ?? currentVal ?? "https://example.com";
|
|
1080
1182
|
const result = await p.text({
|
|
1081
1183
|
message: formatFieldLabel(ctx),
|
|
1082
|
-
placeholder:
|
|
1184
|
+
placeholder: placeholderText,
|
|
1083
1185
|
initialValue: currentVal ?? "",
|
|
1084
1186
|
validate: (value) => {
|
|
1085
1187
|
if (field.required && !value.trim()) return "This field is required";
|
|
@@ -1100,12 +1202,87 @@ async function promptForUrl(ctx) {
|
|
|
1100
1202
|
};
|
|
1101
1203
|
}
|
|
1102
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
|
+
/**
|
|
1103
1277
|
* Prompt for a URL list field value.
|
|
1104
1278
|
*/
|
|
1105
1279
|
async function promptForUrlList(ctx) {
|
|
1106
1280
|
const field = ctx.field;
|
|
1107
1281
|
const currentItems = ctx.currentValue?.kind === "url_list" ? ctx.currentValue.items : [];
|
|
1108
|
-
|
|
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.";
|
|
1109
1286
|
const result = await p.text({
|
|
1110
1287
|
message: formatFieldLabel(ctx),
|
|
1111
1288
|
placeholder: hint,
|
|
@@ -1132,7 +1309,7 @@ async function promptForUrlList(ctx) {
|
|
|
1132
1309
|
};
|
|
1133
1310
|
}
|
|
1134
1311
|
/**
|
|
1135
|
-
* Prompt user for a single field value based on field
|
|
1312
|
+
* Prompt user for a single field value based on field kind.
|
|
1136
1313
|
* Returns a Patch to set the value, or null if skipped/cancelled.
|
|
1137
1314
|
*
|
|
1138
1315
|
* For optional fields, first offers a choice to skip or fill.
|
|
@@ -1151,6 +1328,8 @@ async function promptForField(ctx) {
|
|
|
1151
1328
|
case "checkboxes": return promptForCheckboxes(ctx);
|
|
1152
1329
|
case "url": return promptForUrl(ctx);
|
|
1153
1330
|
case "url_list": return promptForUrlList(ctx);
|
|
1331
|
+
case "date": return promptForDate(ctx);
|
|
1332
|
+
case "year": return promptForYear(ctx);
|
|
1154
1333
|
default: return null;
|
|
1155
1334
|
}
|
|
1156
1335
|
}
|
|
@@ -1553,54 +1732,75 @@ async function promptForWebSearchModel() {
|
|
|
1553
1732
|
* Accepts optional harness config overrides - research uses different defaults.
|
|
1554
1733
|
*/
|
|
1555
1734
|
async function runAgentFill(form, modelId, _outputPath, configOverrides) {
|
|
1556
|
-
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;
|
|
1557
1741
|
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
|
-
};
|
|
1742
|
+
const result = await resolveModel(modelId);
|
|
1743
|
+
model = result.model;
|
|
1744
|
+
provider = result.provider;
|
|
1745
|
+
resolveSpinner.stop(`✓ Model resolved: ${modelId}`);
|
|
1600
1746
|
} catch (error) {
|
|
1601
|
-
|
|
1747
|
+
resolveSpinner.error("Model resolution failed");
|
|
1602
1748
|
throw error;
|
|
1603
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
|
+
};
|
|
1604
1804
|
}
|
|
1605
1805
|
/**
|
|
1606
1806
|
* Run the interactive example scaffolding and filling flow.
|
|
@@ -1883,7 +2083,9 @@ function registerExportCommand(program) {
|
|
|
1883
2083
|
...field.kind === "single_select" || field.kind === "multi_select" || field.kind === "checkboxes" ? { options: field.options.map((opt) => ({
|
|
1884
2084
|
id: opt.id,
|
|
1885
2085
|
label: opt.label
|
|
1886
|
-
})) } : {}
|
|
2086
|
+
})) } : {},
|
|
2087
|
+
...field.placeholder ? { placeholder: field.placeholder } : {},
|
|
2088
|
+
...field.examples && field.examples.length > 0 ? { examples: field.examples } : {}
|
|
1887
2089
|
}))
|
|
1888
2090
|
}))
|
|
1889
2091
|
};
|
|
@@ -2045,14 +2247,18 @@ function registerFillCommand(program) {
|
|
|
2045
2247
|
const harness = createHarness(form, harnessConfig);
|
|
2046
2248
|
let agent;
|
|
2047
2249
|
let mockPath;
|
|
2250
|
+
let agentProvider;
|
|
2251
|
+
let agentModelName;
|
|
2048
2252
|
if (options.mock) {
|
|
2049
2253
|
mockPath = resolve(options.mockSource);
|
|
2050
2254
|
logVerbose(ctx, `Reading mock source: ${mockPath}`);
|
|
2051
2255
|
agent = createMockAgent(parseForm(await readFile$1(mockPath)));
|
|
2052
2256
|
} else {
|
|
2053
|
-
const
|
|
2054
|
-
logVerbose(ctx, `Resolving model: ${
|
|
2055
|
-
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;
|
|
2056
2262
|
let systemPrompt;
|
|
2057
2263
|
if (options.instructions) {
|
|
2058
2264
|
systemPrompt = options.instructions;
|
|
@@ -2067,7 +2273,8 @@ function registerFillCommand(program) {
|
|
|
2067
2273
|
model,
|
|
2068
2274
|
provider,
|
|
2069
2275
|
systemPromptAddition: systemPrompt,
|
|
2070
|
-
targetRole: primaryRole
|
|
2276
|
+
targetRole: primaryRole,
|
|
2277
|
+
enableWebSearch: true
|
|
2071
2278
|
});
|
|
2072
2279
|
agent = liveAgent;
|
|
2073
2280
|
logInfo(ctx, `Available tools: ${liveAgent.getAvailableToolNames().join(", ")}`);
|
|
@@ -2083,7 +2290,22 @@ function registerFillCommand(program) {
|
|
|
2083
2290
|
let stepResult = harness.step();
|
|
2084
2291
|
logInfo(ctx, `${pc.bold(`Turn ${stepResult.turnNumber}:`)} ${formatTurnIssues(stepResult.issues)}`);
|
|
2085
2292
|
while (!stepResult.isComplete && !harness.hasReachedMaxTurns()) {
|
|
2086
|
-
|
|
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;
|
|
2087
2309
|
const tokenSuffix = stats ? ` ${pc.dim(`(tokens: ↓${stats.inputTokens ?? 0} ↑${stats.outputTokens ?? 0})`)}` : "";
|
|
2088
2310
|
logInfo(ctx, ` → ${pc.yellow(String(patches.length))} patches${tokenSuffix}:`);
|
|
2089
2311
|
for (const patch of patches) {
|
|
@@ -2506,13 +2728,13 @@ function registerReportCommand(program) {
|
|
|
2506
2728
|
//#endregion
|
|
2507
2729
|
//#region src/cli/commands/spec.ts
|
|
2508
2730
|
/**
|
|
2509
|
-
* Get the path to the
|
|
2731
|
+
* Get the path to the markform-spec.md file.
|
|
2510
2732
|
* Works both during development and when installed as a package.
|
|
2511
2733
|
*/
|
|
2512
2734
|
function getSpecPath() {
|
|
2513
2735
|
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
2514
|
-
if (thisDir.split(/[/\\]/).pop() === "dist") return join(dirname(thisDir), "
|
|
2515
|
-
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");
|
|
2516
2738
|
}
|
|
2517
2739
|
/**
|
|
2518
2740
|
* Load the spec content.
|
|
@@ -2903,6 +3125,34 @@ function formDataToPatches(formData, form) {
|
|
|
2903
3125
|
});
|
|
2904
3126
|
break;
|
|
2905
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
|
+
}
|
|
2906
3156
|
}
|
|
2907
3157
|
}
|
|
2908
3158
|
return patches;
|
|
@@ -3247,7 +3497,13 @@ function renderFieldHtml(field, value, isSkipped) {
|
|
|
3247
3497
|
case "url_list":
|
|
3248
3498
|
inputHtml = renderUrlListInput(field, value, disabledAttr);
|
|
3249
3499
|
break;
|
|
3250
|
-
|
|
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>";
|
|
3251
3507
|
}
|
|
3252
3508
|
const skipButton = !field.required && !skipped ? `<div class="field-actions">
|
|
3253
3509
|
<button type="button" class="btn-skip" data-skip-field="${field.id}">Skip</button>
|
|
@@ -3269,7 +3525,8 @@ function renderStringInput(field, value, disabledAttr) {
|
|
|
3269
3525
|
const requiredAttr = field.required ? " required" : "";
|
|
3270
3526
|
const minLengthAttr = field.minLength !== void 0 ? ` minlength="${field.minLength}"` : "";
|
|
3271
3527
|
const maxLengthAttr = field.maxLength !== void 0 ? ` maxlength="${field.maxLength}"` : "";
|
|
3272
|
-
|
|
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}>`;
|
|
3273
3530
|
}
|
|
3274
3531
|
/**
|
|
3275
3532
|
* Render a number field as number input.
|
|
@@ -3280,7 +3537,8 @@ function renderNumberInput(field, value, disabledAttr) {
|
|
|
3280
3537
|
const minAttr = field.min !== void 0 ? ` min="${field.min}"` : "";
|
|
3281
3538
|
const maxAttr = field.max !== void 0 ? ` max="${field.max}"` : "";
|
|
3282
3539
|
const stepAttr = field.integer ? " step=\"1\"" : "";
|
|
3283
|
-
|
|
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}>`;
|
|
3284
3542
|
}
|
|
3285
3543
|
/**
|
|
3286
3544
|
* Render a string list field as textarea.
|
|
@@ -3288,7 +3546,8 @@ function renderNumberInput(field, value, disabledAttr) {
|
|
|
3288
3546
|
function renderStringListInput(field, value, disabledAttr) {
|
|
3289
3547
|
const currentValue = (value?.kind === "string_list" ? value.items : []).join("\n");
|
|
3290
3548
|
const requiredAttr = field.required ? " required" : "";
|
|
3291
|
-
|
|
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>`;
|
|
3292
3551
|
}
|
|
3293
3552
|
/**
|
|
3294
3553
|
* Render a URL field as url input.
|
|
@@ -3296,7 +3555,8 @@ function renderStringListInput(field, value, disabledAttr) {
|
|
|
3296
3555
|
function renderUrlInput(field, value, disabledAttr) {
|
|
3297
3556
|
const currentValue = value?.kind === "url" && value.value !== null ? value.value : "";
|
|
3298
3557
|
const requiredAttr = field.required ? " required" : "";
|
|
3299
|
-
|
|
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}>`;
|
|
3300
3560
|
}
|
|
3301
3561
|
/**
|
|
3302
3562
|
* Render a URL list field as textarea.
|
|
@@ -3304,7 +3564,28 @@ function renderUrlInput(field, value, disabledAttr) {
|
|
|
3304
3564
|
function renderUrlListInput(field, value, disabledAttr) {
|
|
3305
3565
|
const currentValue = (value?.kind === "url_list" ? value.items : []).join("\n");
|
|
3306
3566
|
const requiredAttr = field.required ? " required" : "";
|
|
3307
|
-
|
|
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}>`;
|
|
3308
3589
|
}
|
|
3309
3590
|
/**
|
|
3310
3591
|
* Render a single-select field as select element.
|
|
@@ -3690,7 +3971,8 @@ function registerResearchCommand(program) {
|
|
|
3690
3971
|
process.exit(1);
|
|
3691
3972
|
}
|
|
3692
3973
|
const modelId = options.model;
|
|
3693
|
-
|
|
3974
|
+
const { provider, model: modelName } = parseModelIdForDisplay(modelId);
|
|
3975
|
+
if (!hasWebSearchSupport(provider)) {
|
|
3694
3976
|
const webSearchProviders = Object.entries(WEB_SEARCH_CONFIG).filter(([, config]) => config.supported).map(([p$1]) => p$1);
|
|
3695
3977
|
logError(`Model "${modelId}" does not support web search.`);
|
|
3696
3978
|
console.log("");
|
|
@@ -3728,14 +4010,27 @@ function registerResearchCommand(program) {
|
|
|
3728
4010
|
logVerbose(ctx, `Max turns: ${maxTurns}`);
|
|
3729
4011
|
logVerbose(ctx, `Max patches/turn: ${maxPatchesPerTurn}`);
|
|
3730
4012
|
logVerbose(ctx, `Max issues/turn: ${maxIssuesPerTurn}`);
|
|
3731
|
-
const
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
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
|
+
}
|
|
3739
4034
|
if (result.availableTools) logInfo(ctx, `Tools: ${result.availableTools.join(", ")}`);
|
|
3740
4035
|
logInfo(ctx, `Status: ${(result.status === "completed" ? pc.green : result.status === "max_turns_reached" ? pc.yellow : pc.red)(result.status)}`);
|
|
3741
4036
|
logInfo(ctx, `Turns: ${result.totalTurns}`);
|
|
@@ -3746,9 +4041,9 @@ function registerResearchCommand(program) {
|
|
|
3746
4041
|
console.log(` ${yamlPath} ${pc.dim("(output values)")}`);
|
|
3747
4042
|
console.log(` ${formPath} ${pc.dim("(filled markform source)")}`);
|
|
3748
4043
|
if (options.transcript && result.transcript) {
|
|
3749
|
-
const { serializeSession: serializeSession$1 } = await import("./session-
|
|
4044
|
+
const { serializeSession: serializeSession$1 } = await import("./session-B_stoXQn.mjs");
|
|
3750
4045
|
const transcriptPath = outputPath.replace(/\.form\.md$/, ".session.yaml");
|
|
3751
|
-
const { writeFile: writeFile$1 } = await import("./shared-
|
|
4046
|
+
const { writeFile: writeFile$1 } = await import("./shared-CZsyShck.mjs");
|
|
3752
4047
|
await writeFile$1(transcriptPath, serializeSession$1(result.transcript));
|
|
3753
4048
|
logInfo(ctx, `Transcript: ${transcriptPath}`);
|
|
3754
4049
|
}
|
|
@@ -3894,10 +4189,11 @@ function withColoredHelp(cmd) {
|
|
|
3894
4189
|
*/
|
|
3895
4190
|
function createProgram() {
|
|
3896
4191
|
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})`);
|
|
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})`);
|
|
3898
4193
|
registerReadmeCommand(program);
|
|
3899
4194
|
registerDocsCommand(program);
|
|
3900
4195
|
registerSpecCommand(program);
|
|
4196
|
+
registerApisCommand(program);
|
|
3901
4197
|
registerApplyCommand(program);
|
|
3902
4198
|
registerDumpCommand(program);
|
|
3903
4199
|
registerExamplesCommand(program);
|