The human-input provider pauses workflow execution to request input from a human user. This enables interactive workflows, approval gates, and dynamic parameter collection.
The human-input provider supports four input methods, prioritized as follows:
- CLI with
--messageargument - Inline text or file path - Piped stdin - Input from pipe or file redirect
- SDK hook - Custom
onHumanInputcallback - Interactive prompt - Beautiful terminal UI (when TTY available)
checks:
approval:
type: human-input
prompt: "Do you approve this deployment? (yes/no)"checks:
approval:
type: human-input
prompt: "Enter approval decision" # Required: prompt text
placeholder: "Type yes or no..." # Optional: placeholder text
allow_empty: false # Optional: allow empty input (default: false)
multiline: false # Optional: enable multiline input (default: false)
timeout: 300 # Optional: timeout in seconds
default: "no" # Optional: default valueThe user's input is available to dependent checks via the outputs variable. By default (no schema), human-input returns an object { text: string, ts: number } where ts is the timestamp (milliseconds since epoch):
checks:
get-version:
type: human-input
prompt: "Enter version number"
tag-release:
type: command
depends_on: [get-version]
exec: |
# Prefer .text to read the string payload
git tag v{{ outputs['get-version'].text | default: outputs['get-version'] }}
git push origin v{{ outputs['get-version'].text | default: outputs['get-version'] }}See also: Default Output Schema
Provide input directly via command line:
# Inline message
visor --check approval --message "yes"
# From file
visor --check approval --message ./approval.txtPipe input from another command or file:
# From echo
echo "yes" | visor --check approval
# From file
visor --check approval < approval.txt
# From command
curl https://api.example.com/approval | visor --check approvalUse a custom hook for programmatic input:
import { runChecks } from '@probelabs/visor/sdk';
// Option 1: Using deprecated static method (backward compatible)
// Note: HumanInputCheckProvider is not exported from the SDK;
// use executionContext instead (recommended)
// Option 2: Using ExecutionContext (recommended)
await runChecks({
config,
executionContext: {
hooks: {
onHumanInput: async (request) => {
console.log(`Prompt: ${request.prompt}`);
return 'yes';
}
}
}
});The HumanInputRequest interface:
interface HumanInputRequest {
checkId: string; // Check name/ID
prompt: string; // Prompt text
placeholder?: string; // Placeholder text
allowEmpty: boolean; // Allow empty input
multiline: boolean; // Multiline mode
timeout?: number; // Timeout in milliseconds
default?: string; // Default value
}When running in a TTY (interactive terminal), a beautiful prompt appears:
┌─────────────────────────────────────────────┐
│ Enter approval decision │
├─────────────────────────────────────────────┤
│ Type yes or no... │
│ │
│ Press Ctrl+D when done (or Ctrl+C to exit) │
└─────────────────────────────────────────────┘
checks:
manual-approval:
type: human-input
prompt: "Approve deployment to production? (yes/no)"
allow_empty: false
deploy:
type: command
depends_on: [manual-approval]
fail_if: |
outputs['manual-approval'].toLowerCase() !== 'yes'
exec: ./deploy.sh productionchecks:
get-version:
type: human-input
prompt: "Enter version number (e.g., 1.2.3)"
validate-version:
type: javascript
depends_on: [get-version]
exec: |
const version = outputs['get-version'];
const valid = /^\d+\.\d+\.\d+$/.test(version);
if (!valid) throw new Error('Invalid version format');checks:
get-feature:
type: human-input
prompt: "Enter feature name"
get-description:
type: human-input
prompt: "Enter feature description"
multiline: true
depends_on: [get-feature]
create-issue:
type: command
depends_on: [get-feature, get-description]
exec: |
gh issue create \
--title "{{ outputs['get-feature'] }}" \
--body "{{ outputs['get-description'] }}"See examples/calculator-sdk-real.ts for a complete working example with:
- Multiple human-input checks in sequence
- Output passing between checks
- JavaScript execution with user input
- Memory storage
import { runChecks } from '@probelabs/visor/sdk';
const config = {
version: '1.0',
checks: {
approval: {
type: 'human-input',
prompt: 'Approve? (yes/no)'
}
}
};
// Run with execution context (recommended)
const result = await runChecks({
config,
executionContext: {
hooks: {
onHumanInput: async (request) => {
// Your custom input logic
return await getUserInput(request.prompt);
}
}
}
});import { runChecks } from '@probelabs/visor/sdk';
const result = await runChecks({
config,
checks: ['approval'],
executionContext: {
cliMessage: 'yes' // Simulates --message flag
}
});import { runChecks } from '@probelabs/visor/sdk';
const testInputs: Record<string, string> = {
'get-number1': '42',
'get-number2': '7',
'get-operation': '+'
};
await runChecks({
config,
executionContext: {
hooks: {
onHumanInput: async (request) => {
return testInputs[request.checkId] || '';
}
}
}
});The human-input provider includes several security features:
All user input is automatically sanitized before being passed to dependent checks:
- Null bytes removed - Prevents C-string injection
- Control characters removed - Except newlines and tabs
- Size limit - Maximum 100KB per input
When using --message with file paths:
- Path normalization - Resolves
..components - Directory restriction - Only reads files within current working directory
- File type validation - Only reads regular files
- Async operations - Non-blocking file I/O
Stdin input has protection against denial-of-service:
- Size limit - Default 1MB maximum
- Timeout support - Configurable timeout
- Resource cleanup - Proper cleanup with
stdin.pause()
The new ExecutionContext pattern eliminates global state:
- Thread-safe - No global mutable state
- Test isolation - Each execution has isolated context
- Concurrent safe - Safe for parallel execution
Old way (deprecated, internal use only):
The static HumanInputCheckProvider.setHooks() method is deprecated and not exported from the public SDK. If you were using it directly by importing from internal paths, migrate to the new pattern.
New way (recommended):
import { runChecks } from '@probelabs/visor/sdk';
await runChecks({
config,
executionContext: {
hooks: {
onHumanInput: async (request) => 'yes'
}
}
});Benefits of the new approach:
- Thread-safe (no global state)
- Better for concurrent executions
- Easier to test
- More flexible
- Calculator SDK Example - Complete working example
- Debugging Guide - Debugging techniques
- Command Provider - Executing shell commands
- MCP Provider - MCP integration