imprint-mcp 0.6.1 → 0.6.3

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/src/cli.ts CHANGED
@@ -25,7 +25,6 @@ function loadDotenv(): void {
25
25
  if (!(key in process.env)) process.env[key] = value;
26
26
  }
27
27
  }
28
-
29
28
  loadDotenv();
30
29
 
31
30
  /** Parse a duration string: "5m" → 300000, "1h" → 3600000, "30s" → 30000, "5000" → 5000.
@@ -113,7 +112,7 @@ export const VERB_HELP: Record<string, VerbHelp> = {
113
112
  summary:
114
113
  'Record a workflow, compile both artifacts, emit the tool, and connect to your AI platform — all in one interactive flow. Supports resuming incomplete runs and multiple workflows per site.',
115
114
  usage: [
116
- 'imprint teach <site> [--url <url>] [--from-session <path>] [--persist-profile] [--no-interactive] [--all-tools] [--provider <name>] [--model <name>] [--timeout <duration>] [--keep-test] [--skip-replay] [--from-step <step>] [--to-step <step>] [--only <step>]',
115
+ 'imprint teach <site> [--url <url>] [--from-session <path>] [--persist-profile] [--no-interactive] [--all-tools] [--tool <toolName>] [--provider <name>] [--model <name>] [--timeout <duration>] [--keep-test] [--skip-replay] [--from-step <step>] [--to-step <step>] [--only <step>]',
117
116
  ],
118
117
  flags: [
119
118
  { name: '--url <url>', description: 'Starting URL (else about:blank).' },
@@ -132,6 +131,11 @@ export const VERB_HELP: Record<string, VerbHelp> = {
132
131
  description:
133
132
  'With --no-interactive, compile every detected candidate tool instead of only the primary.',
134
133
  },
134
+ {
135
+ name: '--tool <toolName>',
136
+ description:
137
+ 'With --from-step/--only, resume exactly one persisted tool from the retained recording.',
138
+ },
135
139
  {
136
140
  name: '--provider <name>',
137
141
  description:
@@ -406,10 +410,14 @@ export const VERB_HELP: Record<string, VerbHelp> = {
406
410
  },
407
411
  'mcp-server': {
408
412
  summary: "Serve one site's generated tools as MCP (stdio default).",
409
- usage: ['imprint mcp-server <site> [--http] [--port <num>]'],
413
+ usage: ['imprint mcp-server <site> [--http] [--port <num>] [--include-tools <names>]'],
410
414
  flags: [
411
415
  { name: '--http', description: 'Use Streamable HTTP transport instead of stdio.' },
412
416
  { name: '--port <num>', description: 'Port for HTTP transport (default 8765).' },
417
+ {
418
+ name: '--include-tools <names>',
419
+ description: 'Expose only these comma-separated generated tool names.',
420
+ },
413
421
  ],
414
422
  example: 'imprint mcp-server southwest',
415
423
  },
@@ -1118,6 +1126,7 @@ async function main(argv: string[]): Promise<number> {
1118
1126
  options: {
1119
1127
  http: { type: 'boolean' },
1120
1128
  port: { type: 'string' },
1129
+ 'include-tools': { type: 'string' },
1121
1130
  },
1122
1131
  allowPositionals: false,
1123
1132
  });
@@ -1126,6 +1135,10 @@ async function main(argv: string[]): Promise<number> {
1126
1135
  site,
1127
1136
  http: values.http,
1128
1137
  port: values.port ? Number(values.port) : undefined,
1138
+ includeToolNames: values['include-tools']
1139
+ ?.split(',')
1140
+ .map((name) => name.trim())
1141
+ .filter(Boolean),
1129
1142
  });
1130
1143
  return 0;
1131
1144
  }
@@ -1410,6 +1423,7 @@ async function main(argv: string[]): Promise<number> {
1410
1423
  'persist-profile': { type: 'boolean' },
1411
1424
  'no-interactive': { type: 'boolean' },
1412
1425
  'all-tools': { type: 'boolean' },
1426
+ tool: { type: 'string' },
1413
1427
  provider: { type: 'string' },
1414
1428
  model: { type: 'string' },
1415
1429
  timeout: { type: 'string' },
@@ -1428,7 +1442,9 @@ async function main(argv: string[]): Promise<number> {
1428
1442
  // mutual exclusion with --from-session, and --to-step ≥ redact when
1429
1443
  // --from-session enters the chain at redact), returning the resolved window
1430
1444
  // or the exact error message to print. Extracted for unit-testing.
1431
- const { resolveTeachPhaseWindow } = await import('./imprint/teach-state.ts');
1445
+ const { resolveTeachPhaseWindow, validateToolScopedResumeStep } = await import(
1446
+ './imprint/teach-state.ts'
1447
+ );
1432
1448
  const phaseWindow = resolveTeachPhaseWindow(values);
1433
1449
  if ('error' in phaseWindow) {
1434
1450
  console.error(phaseWindow.error);
@@ -1437,6 +1453,22 @@ async function main(argv: string[]): Promise<number> {
1437
1453
  const fromStepArg = phaseWindow.fromStep;
1438
1454
  const toStepArg = phaseWindow.toStep;
1439
1455
 
1456
+ if (values.tool && !fromStepArg) {
1457
+ console.error('error: --tool requires --from-step or --only');
1458
+ return 2;
1459
+ }
1460
+ if (values.tool) {
1461
+ const toolWindowError = validateToolScopedResumeStep(fromStepArg);
1462
+ if (toolWindowError) {
1463
+ console.error(toolWindowError);
1464
+ return 2;
1465
+ }
1466
+ }
1467
+ if (values.tool && values['all-tools']) {
1468
+ console.error('error: --tool cannot be combined with --all-tools');
1469
+ return 2;
1470
+ }
1471
+
1440
1472
  if (!site && values['no-interactive']) {
1441
1473
  console.error(
1442
1474
  'error: `imprint teach` requires a <site> argument in --no-interactive mode.\n <site> is a label you choose — it names the output folder under ~/.imprint/.\n\n example: imprint teach google-flights --url https://flights.google.com\n→ run `imprint teach --help` for usage.',
@@ -1482,6 +1514,7 @@ async function main(argv: string[]): Promise<number> {
1482
1514
  'imprint.model': values.model ?? 'auto',
1483
1515
  'imprint.timeout_ms': teachTimeoutMs ?? 'default',
1484
1516
  'imprint.all_tools': values['all-tools'] ?? false,
1517
+ 'imprint.tool': values.tool,
1485
1518
  'imprint.no_interactive': values['no-interactive'] ?? false,
1486
1519
  'imprint.skip_replay': values['skip-replay'] ?? false,
1487
1520
  },
@@ -1498,6 +1531,7 @@ async function main(argv: string[]): Promise<number> {
1498
1531
  maxDurationMs: teachTimeoutMs,
1499
1532
  keepTest: values['keep-test'] || process.env.IMPRINT_KEEP_TEST === '1',
1500
1533
  allTools: values['all-tools'],
1534
+ tool: values.tool,
1501
1535
  skipReplay: values['skip-replay'],
1502
1536
  fromStep: fromStepArg,
1503
1537
  toStep: toStepArg,
@@ -1533,6 +1567,8 @@ async function main(argv: string[]): Promise<number> {
1533
1567
  'build-plan-path': { type: 'string' },
1534
1568
  'shared-modules-json': { type: 'string' },
1535
1569
  'auth-plan-json': { type: 'string' },
1570
+ 'revision-mode': { type: 'boolean' },
1571
+ provider: { type: 'string' },
1536
1572
  site: { type: 'string' },
1537
1573
  },
1538
1574
  allowPositionals: false,
@@ -1571,11 +1607,92 @@ async function main(argv: string[]): Promise<number> {
1571
1607
  buildPlanPath: values['build-plan-path'],
1572
1608
  sharedModules,
1573
1609
  authToolPlan: authToolPlan ?? undefined,
1610
+ revisionMode: values['revision-mode'],
1611
+ provider: values.provider as
1612
+ | 'anthropic-api'
1613
+ | 'claude-cli'
1614
+ | 'codex-cli'
1615
+ | 'cursor-cli'
1616
+ | undefined,
1574
1617
  site: values.site,
1575
1618
  });
1576
1619
  return 0;
1577
1620
  }
1578
1621
 
1622
+ // Hidden semantic-verifier MCP server. It exposes backend preparation,
1623
+ // verifier-owned suite/targeted calls, and structured report submission.
1624
+ case '__mcp-live-verifier-server': {
1625
+ const { values } = parseArgs({
1626
+ args: argv.slice(1),
1627
+ options: {
1628
+ 'workflow-path': { type: 'string' },
1629
+ 'report-path': { type: 'string' },
1630
+ 'log-path': { type: 'string' },
1631
+ attempt: { type: 'string' },
1632
+ 'session-label': { type: 'string' },
1633
+ },
1634
+ allowPositionals: false,
1635
+ });
1636
+ if (!values['workflow-path'] || !values['report-path'] || !values['session-label']) {
1637
+ console.error(
1638
+ 'error: __mcp-live-verifier-server requires --workflow-path <path>, --report-path <path>, and --session-label <label>',
1639
+ );
1640
+ return 2;
1641
+ }
1642
+ const { runLiveVerifierMcpServer } = await import('./imprint/mcp-live-verifier-server.ts');
1643
+ await runLiveVerifierMcpServer({
1644
+ workflowPath: values['workflow-path'],
1645
+ reportPath: values['report-path'],
1646
+ sessionLabel: values['session-label'],
1647
+ logPath: values['log-path'],
1648
+ attempt: values.attempt ? Number.parseInt(values.attempt, 10) : undefined,
1649
+ });
1650
+ return 0;
1651
+ }
1652
+
1653
+ // Hidden one-shot worker for compile-time live verification. The MCP
1654
+ // verifier owns this worker as a detached process group, so a wedged
1655
+ // browser-backed probe can be terminated without killing the verifier that
1656
+ // must still write an inconclusive/changes-required report.
1657
+ case '__probe-live-verification-backends': {
1658
+ const { values } = parseArgs({
1659
+ args: argv.slice(1),
1660
+ options: {
1661
+ 'workflow-path': { type: 'string' },
1662
+ 'out-path': { type: 'string' },
1663
+ },
1664
+ allowPositionals: false,
1665
+ });
1666
+ if (!values['workflow-path'] || !values['out-path']) {
1667
+ console.error(
1668
+ 'error: __probe-live-verification-backends requires --workflow-path <path> and --out-path <path>',
1669
+ );
1670
+ return 2;
1671
+ }
1672
+ const rawParams = await Bun.stdin.text();
1673
+ const params = rawParams.trim()
1674
+ ? (JSON.parse(rawParams) as Record<string, string | number | boolean>)
1675
+ : {};
1676
+ const { resolveWorkflowTool } = await import('./imprint/backend-ladder.ts');
1677
+ const { probeResolvedTool } = await import('./imprint/probe-backends.ts');
1678
+ const workflowPath = values['workflow-path'];
1679
+ const tool = resolveWorkflowTool(workflowPath);
1680
+ const assetRoot = resolve(dirname(workflowPath), '..', '..');
1681
+ const result = await probeResolvedTool(
1682
+ { site: tool.site, paramOverrides: params },
1683
+ assetRoot,
1684
+ tool,
1685
+ values['out-path'],
1686
+ );
1687
+ console.log(
1688
+ JSON.stringify({
1689
+ outPath: result.outPath,
1690
+ preferredBackend: result.cache.preferredOrder[0],
1691
+ }),
1692
+ );
1693
+ return 0;
1694
+ }
1695
+
1579
1696
  case 'update': {
1580
1697
  const { checkForUpdate, performUpdate } = await import('./imprint/update.ts');
1581
1698
  const checkOnly = argv.slice(1).includes('--check');
@@ -49,6 +49,10 @@ export type OnDeadlineReached = () => Promise<number | null>;
49
49
  export interface AgentResult {
50
50
  outcome: 'done' | 'give_up' | 'timeout' | 'soft_cap' | 'error';
51
51
  doneSummary?: string;
52
+ /** Raw arguments supplied to the reserved done tool. Callers with a richer
53
+ * done contract can make a post-verification decision without teaching the
54
+ * generic agent loop about that contract. */
55
+ doneInput?: Record<string, unknown>;
52
56
  giveUpReason?: string;
53
57
  giveUpDetail?: string;
54
58
  errorMessage?: string;
@@ -294,12 +298,13 @@ export async function runAgentLoop(opts: AgentLoopOptions): Promise<AgentResult>
294
298
  // Check for done() or give_up() first
295
299
  for (const block of toolUseBlocks) {
296
300
  if (block.name === 'done') {
297
- const input = block.input as { summary?: string };
301
+ const input = block.input as Record<string, unknown>;
298
302
  return {
299
303
  action: 'return',
300
304
  result: {
301
305
  outcome: 'done',
302
- doneSummary: input.summary ?? 'Task completed',
306
+ doneSummary: typeof input.summary === 'string' ? input.summary : 'Task completed',
307
+ doneInput: input,
303
308
  turns: turn,
304
309
  durationMs: Date.now() - startTime,
305
310
  inputTokens,