markform 0.0.1 → 0.1.1

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.
@@ -1,5 +1,5 @@
1
- import { $ as PatchSchema, S as parseRolesFlag, b as USER_ROLE, d as serializeRawMarkdown, f as AGENT_ROLE, g as DEFAULT_PORT, h as DEFAULT_MAX_TURNS, m as DEFAULT_MAX_PATCHES_PER_TURN, p as DEFAULT_MAX_ISSUES, r as inspect, t as applyPatches, u as serialize, x as formatSuggestedLlms, y as SUGGESTED_LLMS } from "./apply-C0vjijlP.mjs";
2
- import { _ as parseForm, a as resolveModel, c as createMockAgent, h as serializeSession, i as getProviderNames, o as createLiveAgent, r as getProviderInfo, t as VERSION, u as createHarness } from "./src-DBD3Dt4f.mjs";
1
+ import { C as parseRolesFlag, b as USER_ROLE, d as serializeRawMarkdown, et as PatchSchema, f as AGENT_ROLE, g as DEFAULT_PORT, h as DEFAULT_MAX_TURNS, m as DEFAULT_MAX_PATCHES_PER_TURN, p as DEFAULT_MAX_ISSUES, r as inspect, t as applyPatches, u as serialize, x as formatSuggestedLlms, y as SUGGESTED_LLMS } from "./apply-BQdd-fdx.mjs";
2
+ import { _ as parseForm, a as resolveModel, c as createMockAgent, h as serializeSession, i as getProviderNames, o as createLiveAgent, r as getProviderInfo, t as VERSION, u as createHarness } from "./src-Cs4_9lWP.mjs";
3
3
  import YAML from "yaml";
4
4
  import { Command } from "commander";
5
5
  import pc from "picocolors";
@@ -453,6 +453,52 @@ function exportMultiFormat(form, basePath) {
453
453
  return paths;
454
454
  }
455
455
 
456
+ //#endregion
457
+ //#region src/cli/lib/patchFormat.ts
458
+ /** Maximum characters for a patch value display before truncation */
459
+ const PATCH_VALUE_MAX_LENGTH = 1e3;
460
+ /**
461
+ * Truncate a string to max length with ellipsis if needed.
462
+ */
463
+ function truncate(value, maxLength = PATCH_VALUE_MAX_LENGTH) {
464
+ if (value.length <= maxLength) return value;
465
+ return value.slice(0, maxLength) + "…";
466
+ }
467
+ /**
468
+ * Format a patch value for display with truncation.
469
+ */
470
+ function formatPatchValue(patch) {
471
+ switch (patch.op) {
472
+ case "set_string": return patch.value ? truncate(`"${patch.value}"`) : "(empty)";
473
+ case "set_number": return patch.value !== null ? String(patch.value) : "(empty)";
474
+ case "set_string_list": return patch.items.length > 0 ? truncate(`[${patch.items.join(", ")}]`) : "(empty)";
475
+ case "set_single_select": return patch.selected ?? "(none)";
476
+ case "set_multi_select": return patch.selected.length > 0 ? truncate(`[${patch.selected.join(", ")}]`) : "(none)";
477
+ case "set_checkboxes": return truncate(Object.entries(patch.values).map(([k, v]) => `${k}:${v}`).join(", "));
478
+ case "clear_field": return "(cleared)";
479
+ case "skip_field": return patch.reason ? truncate(`(skipped: ${patch.reason})`) : "(skipped)";
480
+ case "set_url": return patch.value ? truncate(`"${patch.value}"`) : "(empty)";
481
+ case "set_url_list": return patch.items.length > 0 ? truncate(`[${patch.items.join(", ")}]`) : "(empty)";
482
+ }
483
+ }
484
+ /**
485
+ * Get a short display name for the patch operation type.
486
+ */
487
+ function formatPatchType(patch) {
488
+ switch (patch.op) {
489
+ case "set_string": return "string";
490
+ case "set_number": return "number";
491
+ case "set_string_list": return "string_list";
492
+ case "set_single_select": return "select";
493
+ case "set_multi_select": return "multi_select";
494
+ case "set_checkboxes": return "checkboxes";
495
+ case "clear_field": return "clear";
496
+ case "skip_field": return "skip";
497
+ case "set_url": return "url";
498
+ case "set_url_list": return "url_list";
499
+ }
500
+ }
501
+
456
502
  //#endregion
457
503
  //#region src/cli/examples/exampleRegistry.ts
458
504
  /**
@@ -481,6 +527,13 @@ const EXAMPLE_DEFINITIONS = [
481
527
  description: "Financial analysis with one user field (company) and agent-filled quarterly analysis sections.",
482
528
  filename: "earnings-analysis.form.md",
483
529
  path: "earnings-analysis/earnings-analysis.form.md"
530
+ },
531
+ {
532
+ id: "startup-deep-research",
533
+ title: "Startup Deep Research",
534
+ description: "Comprehensive startup intelligence gathering with company info, founders, funding, competitors, social media, and community presence.",
535
+ filename: "startup-deep-research.form.md",
536
+ path: "startup-deep-research/startup-deep-research.form.md"
484
537
  }
485
538
  ];
486
539
  /**
@@ -514,6 +567,17 @@ function loadExampleContent(exampleId) {
514
567
  function getExampleById(id) {
515
568
  return EXAMPLE_DEFINITIONS.find((e) => e.id === id);
516
569
  }
570
+ /**
571
+ * Get the absolute path to an example's source file.
572
+ * @param exampleId - The example ID (e.g., 'simple', 'political-research')
573
+ * @returns The absolute path to the example form file
574
+ * @throws Error if the example is not found
575
+ */
576
+ function getExamplePath(exampleId) {
577
+ const example = EXAMPLE_DEFINITIONS.find((e) => e.id === exampleId);
578
+ if (!example) throw new Error(`Unknown example: ${exampleId}`);
579
+ return join(getExamplesDir(), example.path);
580
+ }
517
581
 
518
582
  //#endregion
519
583
  //#region src/cli/lib/versioning.ts
@@ -623,6 +687,38 @@ function formatFieldLabel(ctx) {
623
687
  return `${ctx.field.label}${required} ${progress}`;
624
688
  }
625
689
  /**
690
+ * Create a skip_field patch for the given field.
691
+ */
692
+ function createSkipPatch(field) {
693
+ return {
694
+ op: "skip_field",
695
+ fieldId: field.id,
696
+ reason: "User skipped in console"
697
+ };
698
+ }
699
+ /**
700
+ * For optional fields, prompt user to choose between filling or skipping.
701
+ * Returns "fill" if user wants to enter a value, or a skip_field patch if skipping.
702
+ * Returns null if user cancelled.
703
+ */
704
+ async function promptSkipOrFill(ctx) {
705
+ const field = ctx.field;
706
+ if (field.required) return "fill";
707
+ const result = await p.select({
708
+ message: `${formatFieldLabel(ctx)} ${pc.dim("(optional)")}`,
709
+ options: [{
710
+ value: "fill",
711
+ label: "Enter value"
712
+ }, {
713
+ value: "skip",
714
+ label: "Skip this field"
715
+ }]
716
+ });
717
+ if (p.isCancel(result)) return null;
718
+ if (result === "skip") return createSkipPatch(field);
719
+ return "fill";
720
+ }
721
+ /**
626
722
  * Prompt for a string field value.
627
723
  */
628
724
  async function promptForString(ctx) {
@@ -850,11 +946,76 @@ async function promptForCheckboxes(ctx) {
850
946
  };
851
947
  }
852
948
  /**
949
+ * Prompt for a URL field value.
950
+ */
951
+ async function promptForUrl(ctx) {
952
+ const field = ctx.field;
953
+ const currentVal = ctx.currentValue?.kind === "url" ? ctx.currentValue.value : null;
954
+ const result = await p.text({
955
+ message: formatFieldLabel(ctx),
956
+ placeholder: currentVal ?? "https://example.com",
957
+ initialValue: currentVal ?? "",
958
+ validate: (value) => {
959
+ if (field.required && !value.trim()) return "This field is required";
960
+ if (!value.trim()) return;
961
+ try {
962
+ new URL(value);
963
+ } catch {
964
+ return "Please enter a valid URL (e.g., https://example.com)";
965
+ }
966
+ }
967
+ });
968
+ if (p.isCancel(result)) return null;
969
+ if (!result && !field.required) return null;
970
+ return {
971
+ op: "set_url",
972
+ fieldId: field.id,
973
+ value: result || null
974
+ };
975
+ }
976
+ /**
977
+ * Prompt for a URL list field value.
978
+ */
979
+ async function promptForUrlList(ctx) {
980
+ const field = ctx.field;
981
+ const currentItems = ctx.currentValue?.kind === "url_list" ? ctx.currentValue.items : [];
982
+ const hint = ctx.description ? `${ctx.description.slice(0, 50)}... (one URL per line)` : "Enter URLs, one per line. Press Enter twice when done.";
983
+ const result = await p.text({
984
+ message: formatFieldLabel(ctx),
985
+ placeholder: hint,
986
+ initialValue: currentItems.join("\n"),
987
+ validate: (value) => {
988
+ const items$1 = value.split("\n").map((s) => s.trim()).filter(Boolean);
989
+ if (field.required && items$1.length === 0) return "At least one URL is required";
990
+ if (field.minItems && items$1.length < field.minItems) return `Minimum ${field.minItems} URLs required`;
991
+ if (field.maxItems && items$1.length > field.maxItems) return `Maximum ${field.maxItems} URLs allowed`;
992
+ for (const item of items$1) try {
993
+ new URL(item);
994
+ } catch {
995
+ return `Invalid URL: ${item}`;
996
+ }
997
+ }
998
+ });
999
+ if (p.isCancel(result)) return null;
1000
+ const items = result.split("\n").map((s) => s.trim()).filter(Boolean);
1001
+ if (items.length === 0 && !field.required) return null;
1002
+ return {
1003
+ op: "set_url_list",
1004
+ fieldId: field.id,
1005
+ items
1006
+ };
1007
+ }
1008
+ /**
853
1009
  * Prompt user for a single field value based on field type.
854
1010
  * Returns a Patch to set the value, or null if skipped/cancelled.
1011
+ *
1012
+ * For optional fields, first offers a choice to skip or fill.
855
1013
  */
856
1014
  async function promptForField(ctx) {
857
1015
  if (ctx.description) p.note(ctx.description, pc.dim("Instructions"));
1016
+ const skipOrFillResult = await promptSkipOrFill(ctx);
1017
+ if (skipOrFillResult === null) return null;
1018
+ if (typeof skipOrFillResult !== "string") return skipOrFillResult;
858
1019
  switch (ctx.field.kind) {
859
1020
  case "string": return promptForString(ctx);
860
1021
  case "number": return promptForNumber(ctx);
@@ -862,6 +1023,8 @@ async function promptForField(ctx) {
862
1023
  case "single_select": return promptForSingleSelect(ctx);
863
1024
  case "multi_select": return promptForMultiSelect(ctx);
864
1025
  case "checkboxes": return promptForCheckboxes(ctx);
1026
+ case "url": return promptForUrl(ctx);
1027
+ case "url_list": return promptForUrlList(ctx);
865
1028
  default: return null;
866
1029
  }
867
1030
  }
@@ -935,7 +1098,7 @@ function showInteractiveIntro(formTitle, role, fieldCount) {
935
1098
  /**
936
1099
  * Show outro message after interactive fill.
937
1100
  */
938
- function showInteractiveOutro(patchCount, outputPath, cancelled) {
1101
+ function showInteractiveOutro(patchCount, cancelled) {
939
1102
  if (cancelled) {
940
1103
  p.cancel("Interactive fill cancelled.");
941
1104
  return;
@@ -944,7 +1107,7 @@ function showInteractiveOutro(patchCount, outputPath, cancelled) {
944
1107
  p.outro(pc.yellow("No changes made."));
945
1108
  return;
946
1109
  }
947
- p.outro(`✓ ${patchCount} field(s) updated. Saved to ${formatPath(outputPath)}`);
1110
+ p.outro(`✓ ${patchCount} field(s) updated.`);
948
1111
  }
949
1112
 
950
1113
  //#endregion
@@ -958,7 +1121,7 @@ function printExamplesList() {
958
1121
  console.log(` ${pc.cyan(example.id)}`);
959
1122
  console.log(` ${pc.bold(example.title)}`);
960
1123
  console.log(` ${pc.dim(example.description)}`);
961
- console.log(` Default filename: ${example.filename}`);
1124
+ console.log(` Source: ${formatPath(getExamplePath(example.id))}`);
962
1125
  console.log("");
963
1126
  }
964
1127
  }
@@ -984,7 +1147,7 @@ function buildModelOptions() {
984
1147
  for (const [provider, models] of Object.entries(SUGGESTED_LLMS)) {
985
1148
  const info = getProviderInfo(provider);
986
1149
  const keyStatus = !!process.env[info.envVar] ? pc.green("✓") : pc.dim("○");
987
- for (const model of models.slice(0, 2)) options.push({
1150
+ for (const model of models) options.push({
988
1151
  value: `${provider}/${model}`,
989
1152
  label: `${provider}/${model}`,
990
1153
  hint: `${keyStatus} ${info.envVar}`
@@ -1046,14 +1209,15 @@ async function runAgentFill(form, modelId, _outputPath) {
1046
1209
  let stepResult = harness.step();
1047
1210
  while (!stepResult.isComplete && !harness.hasReachedMaxTurns()) {
1048
1211
  console.log(pc.dim(` Turn ${stepResult.turnNumber}: ${stepResult.issues.length} issue(s) to address`));
1049
- const patches = await agent.generatePatches(stepResult.issues, harness.getForm(), harnessConfig.maxPatchesPerTurn);
1212
+ const { patches } = await agent.generatePatches(stepResult.issues, harness.getForm(), harnessConfig.maxPatchesPerTurn);
1050
1213
  for (const patch of patches) {
1051
- const fieldId = patch.fieldId;
1052
- console.log(pc.dim(` → ${patch.op} ${fieldId}`));
1214
+ const typeName = formatPatchType(patch);
1215
+ const value = formatPatchValue(patch);
1216
+ console.log(pc.dim(` ${pc.cyan(patch.fieldId)} (${typeName}) = ${pc.green(value)}`));
1053
1217
  }
1054
1218
  stepResult = harness.apply(patches, stepResult.issues);
1055
1219
  console.log(pc.dim(` ${patches.length} patch(es) applied, ${stepResult.issues.length} remaining`));
1056
- if (!stepResult.isComplete) stepResult = harness.step();
1220
+ if (!stepResult.isComplete && !harness.hasReachedMaxTurns()) stepResult = harness.step();
1057
1221
  }
1058
1222
  if (stepResult.isComplete) p.log.success(pc.green(`Form completed in ${harness.getTurnNumber()} turn(s)`));
1059
1223
  else p.log.warn(pc.yellow(`Max turns reached (${harnessConfig.maxTurns})`));
@@ -1151,12 +1315,12 @@ async function runInteractiveFlow(preselectedId) {
1151
1315
  showInteractiveIntro(form.schema.title ?? form.schema.id, targetRoles.join(", "), uniqueFieldIds.size);
1152
1316
  const { patches, cancelled } = await runInteractiveFill(form, inspectResult.issues);
1153
1317
  if (cancelled) {
1154
- showInteractiveOutro(0, "", true);
1318
+ showInteractiveOutro(0, true);
1155
1319
  process.exit(1);
1156
1320
  }
1157
1321
  if (patches.length > 0) applyPatches(form, patches);
1158
1322
  const { formPath, rawPath, yamlPath } = exportMultiFormat(form, outputPath);
1159
- showInteractiveOutro(patches.length, outputPath, false);
1323
+ showInteractiveOutro(patches.length, false);
1160
1324
  console.log("");
1161
1325
  p.log.success("Outputs:");
1162
1326
  console.log(` ${formatPath(formPath)} ${pc.dim("(markform)")}`);
@@ -1180,7 +1344,7 @@ async function runInteractiveFlow(preselectedId) {
1180
1344
  if (p.isCancel(runAgent) || !runAgent) {
1181
1345
  console.log("");
1182
1346
  console.log(pc.dim("You can run agent fill later with:"));
1183
- console.log(pc.dim(` markform fill ${formatPath(outputPath)} --agent=live --model=<provider/model>`));
1347
+ console.log(pc.dim(` markform fill ${formatPath(outputPath)} --model=<provider/model>`));
1184
1348
  p.outro(pc.dim("Happy form filling!"));
1185
1349
  return;
1186
1350
  }
@@ -1223,7 +1387,7 @@ async function runInteractiveFlow(preselectedId) {
1223
1387
  p.log.error(`Agent fill failed: ${message}`);
1224
1388
  console.log("");
1225
1389
  console.log(pc.dim("You can try again with:"));
1226
- console.log(pc.dim(` markform fill ${formatPath(outputPath)} --agent=live --model=${modelId}`));
1390
+ console.log(pc.dim(` markform fill ${formatPath(outputPath)} --model=${modelId}`));
1227
1391
  }
1228
1392
  }
1229
1393
  p.outro(pc.dim("Happy form filling!"));
@@ -1315,22 +1479,6 @@ function registerExportCommand(program) {
1315
1479
 
1316
1480
  //#endregion
1317
1481
  //#region src/cli/commands/fill.ts
1318
- /** Supported agent types */
1319
- const AGENT_TYPES = ["mock", "live"];
1320
- /**
1321
- * Format a patch value for display.
1322
- */
1323
- function formatPatchValue(patch) {
1324
- switch (patch.op) {
1325
- case "set_string": return patch.value ? `"${patch.value}"` : "(empty)";
1326
- case "set_number": return patch.value !== null ? String(patch.value) : "(empty)";
1327
- case "set_string_list": return patch.items.length > 0 ? `[${patch.items.join(", ")}]` : "(empty)";
1328
- case "set_single_select": return patch.selected ?? "(none)";
1329
- case "set_multi_select": return patch.selected.length > 0 ? `[${patch.selected.join(", ")}]` : "(none)";
1330
- case "set_checkboxes": return Object.entries(patch.values).map(([k, v]) => `${k}:${v}`).join(", ");
1331
- case "clear_field": return "(cleared)";
1332
- }
1333
- }
1334
1482
  /**
1335
1483
  * Format session transcript for console output.
1336
1484
  */
@@ -1369,7 +1517,7 @@ function formatConsoleSession(transcript, useColors) {
1369
1517
  * Register the fill command.
1370
1518
  */
1371
1519
  function registerFillCommand(program) {
1372
- program.command("fill <file>").description("Run an agent to autonomously fill a form").option("--agent <type>", `Agent type: ${AGENT_TYPES.join(", ")} (default: live)`, "live").option("--model <id>", "Model ID for live agent (format: provider/model-id, e.g. openai/gpt-4o)").option("--mock-source <file>", "Path to completed form for mock agent").option("--record <file>", "Record session transcript to file").option("--max-turns <n>", `Maximum turns (default: ${DEFAULT_MAX_TURNS})`, String(DEFAULT_MAX_TURNS)).option("--max-patches <n>", `Maximum patches per turn (default: ${DEFAULT_MAX_PATCHES_PER_TURN})`, String(DEFAULT_MAX_PATCHES_PER_TURN)).option("--max-issues <n>", `Maximum issues shown per turn (default: ${DEFAULT_MAX_ISSUES})`, String(DEFAULT_MAX_ISSUES)).option("--max-fields <n>", "Maximum unique fields per turn (applied before --max-issues)").option("--max-groups <n>", "Maximum unique groups per turn (applied before --max-issues)").option("--roles <roles>", "Target roles to fill (comma-separated, or '*' for all; default: 'agent' in agent mode, 'user' in --interactive mode)").option("--mode <mode>", "Fill mode: continue (skip filled fields) or overwrite (re-fill; default: continue)").option("-o, --output <file>", "Write final form to file").option("--prompt <file>", "Path to custom system prompt file (appends to default)").option("--instructions <text>", "Inline system prompt (appends to default; takes precedence over --prompt)").option("-i, --interactive", "Interactive mode: prompt user for field values (defaults to user role)").action(async (file, options, cmd) => {
1520
+ program.command("fill <file>").description("Run an agent to autonomously fill a form").option("--mock", "Use mock agent (requires --mock-source)").option("--model <id>", "Model ID for live agent (format: provider/model-id, e.g. openai/gpt-4o)").option("--mock-source <file>", "Path to completed form for mock agent").option("--record <file>", "Record session transcript to file").option("--max-turns <n>", `Maximum turns (default: ${DEFAULT_MAX_TURNS})`, String(DEFAULT_MAX_TURNS)).option("--max-patches <n>", `Maximum patches per turn (default: ${DEFAULT_MAX_PATCHES_PER_TURN})`, String(DEFAULT_MAX_PATCHES_PER_TURN)).option("--max-issues <n>", `Maximum issues shown per turn (default: ${DEFAULT_MAX_ISSUES})`, String(DEFAULT_MAX_ISSUES)).option("--max-fields <n>", "Maximum unique fields per turn (applied before --max-issues)").option("--max-groups <n>", "Maximum unique groups per turn (applied before --max-issues)").option("--roles <roles>", "Target roles to fill (comma-separated, or '*' for all; default: 'agent', or 'user' in --interactive mode)").option("--mode <mode>", "Fill mode: continue (skip filled fields) or overwrite (re-fill; default: continue)").option("-o, --output <file>", "Write final form to file").option("--prompt <file>", "Path to custom system prompt file (appends to default)").option("--instructions <text>", "Inline system prompt (appends to default; takes precedence over --prompt)").option("-i, --interactive", "Interactive mode: prompt user for field values (defaults to user role)").action(async (file, options, cmd) => {
1373
1521
  const ctx = getCommandContext(cmd);
1374
1522
  const filePath = resolve(file);
1375
1523
  try {
@@ -1395,8 +1543,8 @@ function registerFillCommand(program) {
1395
1543
  logVerbose(ctx, "Parsing form...");
1396
1544
  const form = parseForm(formContent);
1397
1545
  if (options.interactive) {
1398
- if (options.agent && options.agent !== "live") {
1399
- logError("--interactive cannot be used with --agent");
1546
+ if (options.mock) {
1547
+ logError("--interactive cannot be used with --mock");
1400
1548
  process.exit(1);
1401
1549
  }
1402
1550
  if (options.model) {
@@ -1414,35 +1562,38 @@ function registerFillCommand(program) {
1414
1562
  showInteractiveIntro(formTitle, targetRoles.join(", "), uniqueFieldIds.size);
1415
1563
  const { patches, cancelled } = await runInteractiveFill(form, inspectResult.issues);
1416
1564
  if (cancelled) {
1417
- showInteractiveOutro(0, "", true);
1565
+ showInteractiveOutro(0, true);
1418
1566
  process.exit(1);
1419
1567
  }
1420
1568
  if (patches.length > 0) applyPatches(form, patches);
1421
1569
  const durationMs$1 = Date.now() - startTime;
1422
1570
  const outputPath$1 = options.output ? resolve(options.output) : generateVersionedPath(filePath);
1423
- const formMarkdown$1 = serialize(form);
1424
- if (ctx.dryRun) logInfo(ctx, `[DRY RUN] Would write form to: ${outputPath$1}`);
1425
- else await writeFile(outputPath$1, formMarkdown$1);
1426
- showInteractiveOutro(patches.length, outputPath$1, false);
1571
+ if (ctx.dryRun) {
1572
+ logInfo(ctx, `[DRY RUN] Would write form to: ${outputPath$1}`);
1573
+ showInteractiveOutro(patches.length, false);
1574
+ } else {
1575
+ const { formPath, rawPath, yamlPath } = exportMultiFormat(form, outputPath$1);
1576
+ showInteractiveOutro(patches.length, false);
1577
+ console.log("");
1578
+ p.log.success("Outputs:");
1579
+ console.log(` ${formatPath(formPath)} ${pc.dim("(markform)")}`);
1580
+ console.log(` ${formatPath(rawPath)} ${pc.dim("(plain markdown)")}`);
1581
+ console.log(` ${formatPath(yamlPath)} ${pc.dim("(values as YAML)")}`);
1582
+ }
1427
1583
  logTiming(ctx, "Fill time", durationMs$1);
1428
1584
  if (patches.length > 0) {
1429
1585
  console.log("");
1430
1586
  console.log(pc.dim("Next step: fill remaining fields with agent"));
1431
- console.log(pc.dim(` markform fill ${outputPath$1} --agent=live --model=<provider/model>`));
1587
+ console.log(pc.dim(` markform fill ${formatPath(outputPath$1)} --model=<provider/model>`));
1432
1588
  }
1433
1589
  process.exit(0);
1434
1590
  }
1435
- const agentType = options.agent ?? "live";
1436
- if (!AGENT_TYPES.includes(agentType)) {
1437
- logError(`Invalid agent type '${options.agent}'. Valid types: ${AGENT_TYPES.join(", ")}`);
1591
+ if (options.mock && !options.mockSource) {
1592
+ logError("--mock requires --mock-source <file>");
1438
1593
  process.exit(1);
1439
1594
  }
1440
- if (agentType === "mock" && !options.mockSource) {
1441
- logError("--agent=mock requires --mock-source <file>");
1442
- process.exit(1);
1443
- }
1444
- if (agentType === "live" && !options.model) {
1445
- logError("--agent=live requires --model <provider/model-id>");
1595
+ if (!options.mock && !options.model) {
1596
+ logError("Live agent requires --model <provider/model-id>");
1446
1597
  console.log("");
1447
1598
  console.log(formatSuggestedLlms());
1448
1599
  process.exit(1);
@@ -1460,14 +1611,14 @@ function registerFillCommand(program) {
1460
1611
  const harness = createHarness(form, harnessConfig);
1461
1612
  let agent;
1462
1613
  let mockPath;
1463
- if (agentType === "mock") {
1614
+ if (options.mock) {
1464
1615
  mockPath = resolve(options.mockSource);
1465
1616
  logVerbose(ctx, `Reading mock source: ${mockPath}`);
1466
1617
  agent = createMockAgent(parseForm(await readFile(mockPath)));
1467
1618
  } else {
1468
1619
  const modelId = options.model;
1469
1620
  logVerbose(ctx, `Resolving model: ${modelId}`);
1470
- const { model } = await resolveModel(modelId);
1621
+ const { model, provider } = await resolveModel(modelId);
1471
1622
  let systemPrompt;
1472
1623
  if (options.instructions) {
1473
1624
  systemPrompt = options.instructions;
@@ -1478,15 +1629,18 @@ function registerFillCommand(program) {
1478
1629
  systemPrompt = await readFile(promptPath);
1479
1630
  }
1480
1631
  const primaryRole = targetRoles[0] === "*" ? AGENT_ROLE : targetRoles[0];
1481
- agent = createLiveAgent({
1632
+ const liveAgent = createLiveAgent({
1482
1633
  model,
1634
+ provider,
1483
1635
  systemPromptAddition: systemPrompt,
1484
1636
  targetRole: primaryRole
1485
1637
  });
1638
+ agent = liveAgent;
1639
+ logInfo(ctx, `Available tools: ${liveAgent.getAvailableToolNames().join(", ")}`);
1486
1640
  logVerbose(ctx, `Using live agent with model: ${modelId}`);
1487
1641
  }
1488
1642
  logInfo(ctx, pc.cyan(`Filling form: ${filePath}`));
1489
- logInfo(ctx, `Agent: ${agentType}${options.model ? ` (${options.model})` : ""}`);
1643
+ logInfo(ctx, `Agent: ${options.mock ? "mock" : "live"}${options.model ? ` (${options.model})` : ""}`);
1490
1644
  logVerbose(ctx, `Max turns: ${harnessConfig.maxTurns}`);
1491
1645
  logVerbose(ctx, `Max patches per turn: ${harnessConfig.maxPatchesPerTurn}`);
1492
1646
  logVerbose(ctx, `Max issues per step: ${harnessConfig.maxIssues}`);
@@ -1495,15 +1649,35 @@ function registerFillCommand(program) {
1495
1649
  let stepResult = harness.step();
1496
1650
  logInfo(ctx, `Turn ${pc.bold(String(stepResult.turnNumber))}: ${pc.yellow(String(stepResult.issues.length))} issues`);
1497
1651
  while (!stepResult.isComplete && !harness.hasReachedMaxTurns()) {
1498
- const patches = await agent.generatePatches(stepResult.issues, harness.getForm(), harnessConfig.maxPatchesPerTurn);
1499
- logInfo(ctx, ` → ${pc.yellow(String(patches.length))} patches`);
1652
+ const { patches, stats } = await agent.generatePatches(stepResult.issues, harness.getForm(), harnessConfig.maxPatchesPerTurn);
1653
+ logInfo(ctx, ` → ${pc.yellow(String(patches.length))} patches:`);
1500
1654
  for (const patch of patches) {
1655
+ const typeName = formatPatchType(patch);
1501
1656
  const value = formatPatchValue(patch);
1502
- logVerbose(ctx, ` ${pc.cyan(patch.fieldId)} ${pc.dim("=")} ${pc.green(value)}`);
1657
+ logInfo(ctx, ` ${pc.cyan(patch.fieldId)} ${pc.dim(`(${typeName})`)} ${pc.dim("=")} ${pc.green(value)}`);
1658
+ }
1659
+ if (stats) {
1660
+ logVerbose(ctx, ` Stats: ${stats.inputTokens ?? 0} in / ${stats.outputTokens ?? 0} out tokens`);
1661
+ if (stats.toolCalls.length > 0) logVerbose(ctx, ` Tools: ${stats.toolCalls.map((t) => `${t.name}(${t.count})`).join(", ")}`);
1662
+ if (stats.prompts) {
1663
+ logVerbose(ctx, ``);
1664
+ logVerbose(ctx, pc.dim(` ─── System Prompt ───`));
1665
+ for (const line of stats.prompts.system.split("\n")) logVerbose(ctx, pc.dim(` ${line}`));
1666
+ logVerbose(ctx, ``);
1667
+ logVerbose(ctx, pc.dim(` ─── Context Prompt ───`));
1668
+ for (const line of stats.prompts.context.split("\n")) logVerbose(ctx, pc.dim(` ${line}`));
1669
+ logVerbose(ctx, ``);
1670
+ }
1503
1671
  }
1504
- stepResult = harness.apply(patches, stepResult.issues);
1672
+ let llmStats;
1673
+ if (stats) llmStats = {
1674
+ inputTokens: stats.inputTokens,
1675
+ outputTokens: stats.outputTokens,
1676
+ toolCalls: stats.toolCalls.length > 0 ? stats.toolCalls : void 0
1677
+ };
1678
+ stepResult = harness.apply(patches, stepResult.issues, llmStats);
1505
1679
  if (stepResult.isComplete) logInfo(ctx, pc.green(` ✓ Complete`));
1506
- else {
1680
+ else if (!harness.hasReachedMaxTurns()) {
1507
1681
  stepResult = harness.step();
1508
1682
  logInfo(ctx, `Turn ${pc.bold(String(stepResult.turnNumber))}: ${pc.yellow(String(stepResult.issues.length))} issues`);
1509
1683
  }
@@ -1519,7 +1693,7 @@ function registerFillCommand(program) {
1519
1693
  await writeFile(outputPath, formMarkdown);
1520
1694
  logSuccess(ctx, `Form written to: ${outputPath}`);
1521
1695
  }
1522
- const transcript = buildSessionTranscript(filePath, agentType, mockPath, options.model, harnessConfig, harness.getTurns(), stepResult.isComplete, outputPath);
1696
+ const transcript = buildSessionTranscript(filePath, options.mock ? "mock" : "live", mockPath, options.model, harnessConfig, harness.getTurns(), stepResult.isComplete, outputPath);
1523
1697
  if (options.record) {
1524
1698
  const recordPath = resolve(options.record);
1525
1699
  const yaml = serializeSession(transcript);
@@ -1544,20 +1718,20 @@ function registerFillCommand(program) {
1544
1718
  /**
1545
1719
  * Build a session transcript from harness execution.
1546
1720
  */
1547
- function buildSessionTranscript(formPath, agentType, mockPath, modelId, harnessConfig, turns, expectComplete, outputPath) {
1721
+ function buildSessionTranscript(formPath, mockMode, mockPath, modelId, harnessConfig, turns, expectComplete, outputPath) {
1548
1722
  const transcript = {
1549
1723
  sessionVersion: "0.1.0",
1550
- mode: agentType,
1724
+ mode: mockMode,
1551
1725
  form: { path: formPath },
1552
1726
  harness: harnessConfig,
1553
1727
  turns,
1554
1728
  final: {
1555
1729
  expectComplete,
1556
- expectedCompletedForm: agentType === "mock" ? mockPath ?? outputPath : outputPath
1730
+ expectedCompletedForm: mockMode === "mock" ? mockPath ?? outputPath : outputPath
1557
1731
  }
1558
1732
  };
1559
- if (agentType === "mock" && mockPath) transcript.mock = { completedMock: mockPath };
1560
- else if (agentType === "live" && modelId) transcript.live = { modelId };
1733
+ if (mockMode === "mock" && mockPath) transcript.mock = { completedMock: mockPath };
1734
+ else if (mockMode === "live" && modelId) transcript.live = { modelId };
1561
1735
  return transcript;
1562
1736
  }
1563
1737
 
@@ -1649,7 +1823,8 @@ function formatConsoleReport$1(report, useColors) {
1649
1823
  lines.push(bold("Progress:"));
1650
1824
  lines.push(` Total fields: ${progress.counts.totalFields}`);
1651
1825
  lines.push(` Required: ${progress.counts.requiredFields}`);
1652
- lines.push(` Submitted: ${progress.counts.submittedFields}`);
1826
+ lines.push(` Answered: ${progress.counts.answeredFields}`);
1827
+ lines.push(` Skipped: ${progress.counts.skippedFields}`);
1653
1828
  lines.push(` Complete: ${progress.counts.completeFields}`);
1654
1829
  lines.push(` Incomplete: ${progress.counts.incompleteFields}`);
1655
1830
  lines.push(` Invalid: ${progress.counts.invalidFields}`);
@@ -1736,6 +1911,100 @@ function registerInspectCommand(program) {
1736
1911
  });
1737
1912
  }
1738
1913
 
1914
+ //#endregion
1915
+ //#region src/cli/commands/instructions.ts
1916
+ /**
1917
+ * Get the path to the README.md file.
1918
+ * Works both during development and when installed as a package.
1919
+ */
1920
+ function getReadmePath() {
1921
+ const thisDir = dirname(fileURLToPath(import.meta.url));
1922
+ if (thisDir.split(/[/\\]/).pop() === "dist") return join(dirname(thisDir), "README.md");
1923
+ return join(dirname(dirname(dirname(thisDir))), "README.md");
1924
+ }
1925
+ /**
1926
+ * Load the README content.
1927
+ */
1928
+ function loadReadme() {
1929
+ const readmePath = getReadmePath();
1930
+ try {
1931
+ return readFileSync(readmePath, "utf-8");
1932
+ } catch (error) {
1933
+ const message = error instanceof Error ? error.message : String(error);
1934
+ throw new Error(`Failed to load README from ${readmePath}: ${message}`);
1935
+ }
1936
+ }
1937
+ /**
1938
+ * Apply basic terminal formatting to markdown content.
1939
+ * Colorizes headers, code blocks, and other elements for better readability.
1940
+ */
1941
+ function formatMarkdown(content, useColors) {
1942
+ if (!useColors) return content;
1943
+ const lines = content.split("\n");
1944
+ const formatted = [];
1945
+ let inCodeBlock = false;
1946
+ for (const line of lines) {
1947
+ if (line.startsWith("```")) {
1948
+ inCodeBlock = !inCodeBlock;
1949
+ formatted.push(pc.dim(line));
1950
+ continue;
1951
+ }
1952
+ if (inCodeBlock) {
1953
+ formatted.push(pc.dim(line));
1954
+ continue;
1955
+ }
1956
+ if (line.startsWith("# ")) {
1957
+ formatted.push(pc.bold(pc.cyan(line)));
1958
+ continue;
1959
+ }
1960
+ if (line.startsWith("## ")) {
1961
+ formatted.push(pc.bold(pc.blue(line)));
1962
+ continue;
1963
+ }
1964
+ if (line.startsWith("### ")) {
1965
+ formatted.push(pc.bold(line));
1966
+ continue;
1967
+ }
1968
+ let formattedLine = line.replace(/`([^`]+)`/g, (_match, code) => {
1969
+ return pc.yellow(code);
1970
+ });
1971
+ formattedLine = formattedLine.replace(/\*\*([^*]+)\*\*/g, (_match, text) => {
1972
+ return pc.bold(text);
1973
+ });
1974
+ formattedLine = formattedLine.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_match, text, url) => {
1975
+ return `${pc.cyan(text)} ${pc.dim(`(${url})`)}`;
1976
+ });
1977
+ formatted.push(formattedLine);
1978
+ }
1979
+ return formatted.join("\n");
1980
+ }
1981
+ /**
1982
+ * Check if stdout is an interactive terminal.
1983
+ */
1984
+ function isInteractive() {
1985
+ return process.stdout.isTTY === true;
1986
+ }
1987
+ /**
1988
+ * Display content. In a future enhancement, could pipe to a pager for long output.
1989
+ */
1990
+ function displayContent(content) {
1991
+ console.log(content);
1992
+ }
1993
+ /**
1994
+ * Register the instructions command.
1995
+ */
1996
+ function registerInstructionsCommand(program) {
1997
+ program.command("instructions").alias("readme").alias("docs").description("Display usage instructions and documentation").option("--raw", "Output raw markdown without formatting").action((options, cmd) => {
1998
+ const ctx = getCommandContext(cmd);
1999
+ try {
2000
+ displayContent(formatMarkdown(loadReadme(), !options.raw && isInteractive() && !ctx.quiet));
2001
+ } catch (error) {
2002
+ logError(error instanceof Error ? error.message : String(error));
2003
+ process.exit(1);
2004
+ }
2005
+ });
2006
+ }
2007
+
1739
2008
  //#endregion
1740
2009
  //#region src/cli/commands/models.ts
1741
2010
  /**
@@ -1881,6 +2150,13 @@ function formDataToPatches(formData, form) {
1881
2150
  const fields = form.schema.groups.flatMap((g) => g.children);
1882
2151
  for (const field of fields) {
1883
2152
  const fieldId = field.id;
2153
+ if (formData[`__skip__${fieldId}`] === "1" && !field.required) {
2154
+ patches.push({
2155
+ op: "skip_field",
2156
+ fieldId
2157
+ });
2158
+ continue;
2159
+ }
1884
2160
  switch (field.kind) {
1885
2161
  case "string": {
1886
2162
  const value = formData[fieldId];
@@ -1980,6 +2256,38 @@ function formDataToPatches(formData, form) {
1980
2256
  });
1981
2257
  }
1982
2258
  break;
2259
+ case "url": {
2260
+ const value = formData[fieldId];
2261
+ if (typeof value === "string" && value.trim() !== "") patches.push({
2262
+ op: "set_url",
2263
+ fieldId,
2264
+ value: value.trim()
2265
+ });
2266
+ else patches.push({
2267
+ op: "clear_field",
2268
+ fieldId
2269
+ });
2270
+ break;
2271
+ }
2272
+ case "url_list": {
2273
+ const value = formData[fieldId];
2274
+ if (typeof value === "string" && value.trim() !== "") {
2275
+ const items = value.split("\n").map((s) => s.trim()).filter((s) => s !== "");
2276
+ if (items.length > 0) patches.push({
2277
+ op: "set_url_list",
2278
+ fieldId,
2279
+ items
2280
+ });
2281
+ else patches.push({
2282
+ op: "clear_field",
2283
+ fieldId
2284
+ });
2285
+ } else patches.push({
2286
+ op: "clear_field",
2287
+ fieldId
2288
+ });
2289
+ break;
2290
+ }
1983
2291
  }
1984
2292
  }
1985
2293
  return patches;
@@ -2026,9 +2334,9 @@ async function handleSave(req, res, form, filePath, ctx, updateForm) {
2026
2334
  * @public Exported for testing.
2027
2335
  */
2028
2336
  function renderFormHtml(form) {
2029
- const { schema, valuesByFieldId } = form;
2337
+ const { schema, valuesByFieldId, skipsByFieldId } = form;
2030
2338
  const formTitle = schema.title ?? schema.id;
2031
- const groupsHtml = schema.groups.map((group) => renderGroup(group, valuesByFieldId)).join("\n");
2339
+ const groupsHtml = schema.groups.map((group) => renderGroup(group, valuesByFieldId, skipsByFieldId ?? {})).join("\n");
2032
2340
  return `<!DOCTYPE html>
2033
2341
  <html lang="en">
2034
2342
  <head>
@@ -2047,7 +2355,7 @@ function renderFormHtml(form) {
2047
2355
  color: #212529;
2048
2356
  }
2049
2357
  h1 { color: #495057; border-bottom: 2px solid #dee2e6; padding-bottom: 0.5rem; }
2050
- h2 { color: #6c757d; margin-top: 2rem; font-size: 1.25rem; }
2358
+ h2 { color: #6c757d; font-size: 1.25rem; }
2051
2359
  .group {
2052
2360
  background: white;
2053
2361
  border-radius: 8px;
@@ -2159,6 +2467,40 @@ function renderFormHtml(form) {
2159
2467
  color: #6c757d;
2160
2468
  margin-top: 0.25rem;
2161
2469
  }
2470
+ .field-actions {
2471
+ display: flex;
2472
+ gap: 0.5rem;
2473
+ margin-top: 0.5rem;
2474
+ }
2475
+ .btn-skip {
2476
+ padding: 0.25rem 0.75rem;
2477
+ font-size: 0.85rem;
2478
+ background: #f8f9fa;
2479
+ border: 1px solid #ced4da;
2480
+ border-radius: 4px;
2481
+ color: #6c757d;
2482
+ cursor: pointer;
2483
+ }
2484
+ .btn-skip:hover {
2485
+ background: #e9ecef;
2486
+ color: #495057;
2487
+ }
2488
+ .field-skipped {
2489
+ opacity: 0.6;
2490
+ }
2491
+ .field-skipped input,
2492
+ .field-skipped textarea,
2493
+ .field-skipped select {
2494
+ background: #f8f9fa;
2495
+ }
2496
+ .skipped-badge {
2497
+ font-size: 0.75rem;
2498
+ padding: 0.15rem 0.4rem;
2499
+ background: #6c757d;
2500
+ color: white;
2501
+ border-radius: 3px;
2502
+ margin-left: 0.5rem;
2503
+ }
2162
2504
  </style>
2163
2505
  </head>
2164
2506
  <body>
@@ -2170,10 +2512,50 @@ function renderFormHtml(form) {
2170
2512
  </div>
2171
2513
  </form>
2172
2514
  <script>
2515
+ // Track fields marked for skip
2516
+ const skippedFields = new Set();
2517
+
2518
+ // Handle skip button clicks
2519
+ document.querySelectorAll('.btn-skip').forEach(btn => {
2520
+ btn.addEventListener('click', (e) => {
2521
+ const fieldId = e.target.dataset.skipField;
2522
+ if (!fieldId) return;
2523
+
2524
+ // Toggle skip state
2525
+ if (skippedFields.has(fieldId)) {
2526
+ skippedFields.delete(fieldId);
2527
+ e.target.textContent = 'Skip';
2528
+ e.target.classList.remove('btn-skip-active');
2529
+ // Re-enable the field input
2530
+ const fieldDiv = e.target.closest('.field');
2531
+ fieldDiv.classList.remove('field-skipped');
2532
+ fieldDiv.querySelectorAll('input, select, textarea').forEach(input => {
2533
+ input.disabled = false;
2534
+ });
2535
+ } else {
2536
+ skippedFields.add(fieldId);
2537
+ e.target.textContent = 'Unskip';
2538
+ e.target.classList.add('btn-skip-active');
2539
+ // Disable the field input to show it's skipped
2540
+ const fieldDiv = e.target.closest('.field');
2541
+ fieldDiv.classList.add('field-skipped');
2542
+ fieldDiv.querySelectorAll('input, select, textarea').forEach(input => {
2543
+ input.disabled = true;
2544
+ });
2545
+ }
2546
+ });
2547
+ });
2548
+
2173
2549
  document.getElementById('markform').addEventListener('submit', async (e) => {
2174
2550
  e.preventDefault();
2175
2551
  const formData = new FormData(e.target);
2176
2552
  const params = new URLSearchParams();
2553
+
2554
+ // Add skip markers for skipped fields
2555
+ for (const fieldId of skippedFields) {
2556
+ params.append('__skip__' + fieldId, '1');
2557
+ }
2558
+
2177
2559
  for (const [key, value] of formData) {
2178
2560
  params.append(key, value);
2179
2561
  }
@@ -2201,9 +2583,9 @@ function renderFormHtml(form) {
2201
2583
  /**
2202
2584
  * Render a field group as HTML.
2203
2585
  */
2204
- function renderGroup(group, values) {
2586
+ function renderGroup(group, values, skips) {
2205
2587
  const groupTitle = group.title ?? group.id;
2206
- const fieldsHtml = group.children.map((field) => renderFieldHtml(field, values[field.id])).join("\n");
2588
+ const fieldsHtml = group.children.map((field) => renderFieldHtml(field, values[field.id], skips[field.id])).join("\n");
2207
2589
  return `
2208
2590
  <div class="group">
2209
2591
  <h2>${escapeHtml(groupTitle)}</h2>
@@ -2214,79 +2596,109 @@ function renderGroup(group, values) {
2214
2596
  * Render a field as HTML.
2215
2597
  * @public Exported for testing.
2216
2598
  */
2217
- function renderFieldHtml(field, value) {
2599
+ function renderFieldHtml(field, value, skipInfo) {
2600
+ const isSkipped = skipInfo?.skipped === true;
2218
2601
  const requiredMark = field.required ? "<span class=\"required\">*</span>" : "";
2219
2602
  const typeLabel = `<span class="type-badge">${field.kind}</span>`;
2603
+ const skippedBadge = isSkipped ? "<span class=\"skipped-badge\">Skipped</span>" : "";
2604
+ const fieldClass = isSkipped ? "field field-skipped" : "field";
2605
+ const disabledAttr = isSkipped ? " disabled" : "";
2220
2606
  let inputHtml;
2221
2607
  switch (field.kind) {
2222
2608
  case "string":
2223
- inputHtml = renderStringInput(field, value);
2609
+ inputHtml = renderStringInput(field, value, disabledAttr);
2224
2610
  break;
2225
2611
  case "number":
2226
- inputHtml = renderNumberInput(field, value);
2612
+ inputHtml = renderNumberInput(field, value, disabledAttr);
2227
2613
  break;
2228
2614
  case "string_list":
2229
- inputHtml = renderStringListInput(field, value);
2615
+ inputHtml = renderStringListInput(field, value, disabledAttr);
2230
2616
  break;
2231
2617
  case "single_select":
2232
- inputHtml = renderSingleSelectInput(field, value);
2618
+ inputHtml = renderSingleSelectInput(field, value, disabledAttr);
2233
2619
  break;
2234
2620
  case "multi_select":
2235
- inputHtml = renderMultiSelectInput(field, value);
2621
+ inputHtml = renderMultiSelectInput(field, value, disabledAttr);
2236
2622
  break;
2237
2623
  case "checkboxes":
2238
- inputHtml = renderCheckboxesInput(field, value);
2624
+ inputHtml = renderCheckboxesInput(field, value, disabledAttr);
2625
+ break;
2626
+ case "url":
2627
+ inputHtml = renderUrlInput(field, value, disabledAttr);
2628
+ break;
2629
+ case "url_list":
2630
+ inputHtml = renderUrlListInput(field, value, disabledAttr);
2239
2631
  break;
2240
2632
  default: inputHtml = "<div class=\"field-help\">(unknown field type)</div>";
2241
2633
  }
2634
+ const skipButton = !field.required && !isSkipped ? `<div class="field-actions">
2635
+ <button type="button" class="btn-skip" data-skip-field="${field.id}">Skip</button>
2636
+ </div>` : "";
2242
2637
  return `
2243
- <div class="field">
2638
+ <div class="${fieldClass}">
2244
2639
  <label class="field-label" for="field-${field.id}">
2245
- ${escapeHtml(field.label)} ${requiredMark} ${typeLabel}
2640
+ ${escapeHtml(field.label)} ${requiredMark} ${typeLabel} ${skippedBadge}
2246
2641
  </label>
2247
2642
  ${inputHtml}
2643
+ ${skipButton}
2248
2644
  </div>`;
2249
2645
  }
2250
2646
  /**
2251
2647
  * Render a string field as text input.
2252
2648
  */
2253
- function renderStringInput(field, value) {
2649
+ function renderStringInput(field, value, disabledAttr) {
2254
2650
  const currentValue = value?.kind === "string" && value.value !== null ? value.value : "";
2255
2651
  const requiredAttr = field.required ? " required" : "";
2256
2652
  const minLengthAttr = field.minLength !== void 0 ? ` minlength="${field.minLength}"` : "";
2257
2653
  const maxLengthAttr = field.maxLength !== void 0 ? ` maxlength="${field.maxLength}"` : "";
2258
- return `<input type="text" id="field-${field.id}" name="${field.id}" value="${escapeHtml(currentValue)}"${requiredAttr}${minLengthAttr}${maxLengthAttr}>`;
2654
+ return `<input type="text" id="field-${field.id}" name="${field.id}" value="${escapeHtml(currentValue)}"${requiredAttr}${minLengthAttr}${maxLengthAttr}${disabledAttr}>`;
2259
2655
  }
2260
2656
  /**
2261
2657
  * Render a number field as number input.
2262
2658
  */
2263
- function renderNumberInput(field, value) {
2659
+ function renderNumberInput(field, value, disabledAttr) {
2264
2660
  const currentValue = value?.kind === "number" && value.value !== null ? String(value.value) : "";
2265
2661
  const requiredAttr = field.required ? " required" : "";
2266
2662
  const minAttr = field.min !== void 0 ? ` min="${field.min}"` : "";
2267
2663
  const maxAttr = field.max !== void 0 ? ` max="${field.max}"` : "";
2268
2664
  const stepAttr = field.integer ? " step=\"1\"" : "";
2269
- return `<input type="number" id="field-${field.id}" name="${field.id}" value="${escapeHtml(currentValue)}"${requiredAttr}${minAttr}${maxAttr}${stepAttr}>`;
2665
+ return `<input type="number" id="field-${field.id}" name="${field.id}" value="${escapeHtml(currentValue)}"${requiredAttr}${minAttr}${maxAttr}${stepAttr}${disabledAttr}>`;
2270
2666
  }
2271
2667
  /**
2272
2668
  * Render a string list field as textarea.
2273
2669
  */
2274
- function renderStringListInput(field, value) {
2670
+ function renderStringListInput(field, value, disabledAttr) {
2275
2671
  const currentValue = (value?.kind === "string_list" ? value.items : []).join("\n");
2276
2672
  const requiredAttr = field.required ? " required" : "";
2277
- return `<textarea id="field-${field.id}" name="${field.id}" placeholder="Enter one item per line"${requiredAttr}>${escapeHtml(currentValue)}</textarea>`;
2673
+ return `<textarea id="field-${field.id}" name="${field.id}" placeholder="Enter one item per line"${requiredAttr}${disabledAttr}>${escapeHtml(currentValue)}</textarea>`;
2674
+ }
2675
+ /**
2676
+ * Render a URL field as url input.
2677
+ */
2678
+ function renderUrlInput(field, value, disabledAttr) {
2679
+ const currentValue = value?.kind === "url" && value.value !== null ? value.value : "";
2680
+ const requiredAttr = field.required ? " required" : "";
2681
+ return `<input type="url" id="field-${field.id}" name="${field.id}" value="${escapeHtml(currentValue)}" placeholder="https://example.com"${requiredAttr}${disabledAttr}>`;
2682
+ }
2683
+ /**
2684
+ * Render a URL list field as textarea.
2685
+ */
2686
+ function renderUrlListInput(field, value, disabledAttr) {
2687
+ const currentValue = (value?.kind === "url_list" ? value.items : []).join("\n");
2688
+ const requiredAttr = field.required ? " required" : "";
2689
+ return `<textarea id="field-${field.id}" name="${field.id}" placeholder="Enter one URL per line"${requiredAttr}${disabledAttr}>${escapeHtml(currentValue)}</textarea>`;
2278
2690
  }
2279
2691
  /**
2280
2692
  * Render a single-select field as select element.
2281
2693
  */
2282
- function renderSingleSelectInput(field, value) {
2694
+ function renderSingleSelectInput(field, value, disabledAttr) {
2283
2695
  const selected = value?.selected ?? null;
2284
2696
  const requiredAttr = field.required ? " required" : "";
2285
2697
  const options = field.options.map((opt) => {
2286
2698
  const selectedAttr = selected === opt.id ? " selected" : "";
2287
2699
  return `<option value="${escapeHtml(opt.id)}"${selectedAttr}>${escapeHtml(opt.label)}</option>`;
2288
2700
  }).join("\n ");
2289
- return `<select id="field-${field.id}" name="${field.id}"${requiredAttr}>
2701
+ return `<select id="field-${field.id}" name="${field.id}"${requiredAttr}${disabledAttr}>
2290
2702
  <option value="">-- Select --</option>
2291
2703
  ${options}
2292
2704
  </select>`;
@@ -2294,14 +2706,14 @@ function renderSingleSelectInput(field, value) {
2294
2706
  /**
2295
2707
  * Render a multi-select field as checkboxes.
2296
2708
  */
2297
- function renderMultiSelectInput(field, value) {
2709
+ function renderMultiSelectInput(field, value, disabledAttr) {
2298
2710
  const selected = value?.selected ?? [];
2299
2711
  return `<div class="checkbox-group">
2300
2712
  ${field.options.map((opt) => {
2301
2713
  const checkedAttr = selected.includes(opt.id) ? " checked" : "";
2302
2714
  const checkboxId = `field-${field.id}-${opt.id}`;
2303
2715
  return `<div class="checkbox-item">
2304
- <input type="checkbox" id="${checkboxId}" name="${field.id}" value="${escapeHtml(opt.id)}"${checkedAttr}>
2716
+ <input type="checkbox" id="${checkboxId}" name="${field.id}" value="${escapeHtml(opt.id)}"${checkedAttr}${disabledAttr}>
2305
2717
  <label for="${checkboxId}">${escapeHtml(opt.label)}</label>
2306
2718
  </div>`;
2307
2719
  }).join("\n ")}
@@ -2310,7 +2722,7 @@ function renderMultiSelectInput(field, value) {
2310
2722
  /**
2311
2723
  * Render checkboxes field based on mode.
2312
2724
  */
2313
- function renderCheckboxesInput(field, value) {
2725
+ function renderCheckboxesInput(field, value, disabledAttr) {
2314
2726
  const checkboxValues = value?.values ?? {};
2315
2727
  const mode = field.checkboxMode ?? "multi";
2316
2728
  if (mode === "simple") return `<div class="checkbox-group">
@@ -2318,7 +2730,7 @@ function renderCheckboxesInput(field, value) {
2318
2730
  const checkedAttr = checkboxValues[opt.id] === "done" ? " checked" : "";
2319
2731
  const checkboxId = `field-${field.id}-${opt.id}`;
2320
2732
  return `<div class="checkbox-item">
2321
- <input type="checkbox" id="${checkboxId}" name="${field.id}" value="${escapeHtml(opt.id)}"${checkedAttr}>
2733
+ <input type="checkbox" id="${checkboxId}" name="${field.id}" value="${escapeHtml(opt.id)}"${checkedAttr}${disabledAttr}>
2322
2734
  <label for="${checkboxId}">${escapeHtml(opt.label)}</label>
2323
2735
  </div>`;
2324
2736
  }).join("\n ")}
@@ -2330,7 +2742,7 @@ function renderCheckboxesInput(field, value) {
2330
2742
  const selectName = `${field.id}.${opt.id}`;
2331
2743
  return `<div class="option-row">
2332
2744
  <span class="option-label">${escapeHtml(opt.label)}</span>
2333
- <select id="${selectId}" name="${selectName}">
2745
+ <select id="${selectId}" name="${selectName}"${disabledAttr}>
2334
2746
  <option value="unfilled"${state === "unfilled" ? " selected" : ""}>-- Select --</option>
2335
2747
  <option value="yes"${state === "yes" ? " selected" : ""}>Yes</option>
2336
2748
  <option value="no"${state === "no" ? " selected" : ""}>No</option>
@@ -2345,7 +2757,7 @@ function renderCheckboxesInput(field, value) {
2345
2757
  const selectName = `${field.id}.${opt.id}`;
2346
2758
  return `<div class="option-row">
2347
2759
  <span class="option-label">${escapeHtml(opt.label)}</span>
2348
- <select id="${selectId}" name="${selectName}">
2760
+ <select id="${selectId}" name="${selectName}"${disabledAttr}>
2349
2761
  <option value="todo"${state === "todo" ? " selected" : ""}>To Do</option>
2350
2762
  <option value="active"${state === "active" ? " selected" : ""}>Active</option>
2351
2763
  <option value="done"${state === "done" ? " selected" : ""}>Done</option>
@@ -2551,6 +2963,7 @@ function createProgram() {
2551
2963
  registerFillCommand(program);
2552
2964
  registerModelsCommand(program);
2553
2965
  registerExamplesCommand(program);
2966
+ registerInstructionsCommand(program);
2554
2967
  return program;
2555
2968
  }
2556
2969
  /**
@@ -2561,5 +2974,4 @@ async function runCli() {
2561
2974
  }
2562
2975
 
2563
2976
  //#endregion
2564
- export { runCli as t };
2565
- //# sourceMappingURL=cli-9fvFySww.mjs.map
2977
+ export { runCli as t };