# santhorchestrator
Discovery orchestrator v0.1 (wires **loginflow**, **appmap**, **authjar**, and **scanclient** into a single cold-URL pipeline with scanner fan-out).
## CLI
```bash
cargo install --path crates/orchestrator-cli
santhorchestrator scan https://target.example/ \
--creds creds.toml \
--scanners scald,bolascan,gossan
# Optional: captchaforge-backed browser login (compile with --features captchaforge)
santhorchestrator scan https://target.example/ --creds creds.toml --captchaforge
```
### `creds.toml`
```toml
[[role]]
name = "alice"
username = "alice@example.com"
password = "secret"
[[role]]
name = "bob"
username = "bob@example.com"
password = "secret2"
```
## Pipeline (v0.1)
1. **Probe**: `scanclient` GET of cold URL (stealth `ProfileBundle` user-agent)
2. **Login**: `loginflow` HTTP fast-path with stealth profile → `authjar` (multi-role loop)
3. **Crawl**: minimal same-origin link harvest → `CrawlArtifacts`
4. **Model**: `appmap::AppMapBuilder::from_crawl`
5. **Fan-out**: registered [`ScannerRunner`](crates/orchestrator-core/src/runners/mod.rs) implementations:
- **scald**: `scald scan` subprocess when on PATH; else authenticated GET probe
- **bolascan**: `bolascan` CLI when on PATH; else `appmap::replay_across` role probe
- **gossan**: `gossan scan` subprocess when on PATH; else origin-header probe
## Library
```rust
use orchestrator_core::{Orchestrator, ScanRequest, ScannerId, ScanMode};
use url::Url;
let orchestrator = Orchestrator::builder().build()?;
let result = orchestrator.scan(ScanRequest {
target: Url::parse("https://example.com/")?,
credentials: None,
scanners: vec![ScannerId::Scald],
mode: ScanMode::RealTarget,
use_captchaforge: false,
}).await?;
```
## Tests
```bash
cd software/santhorchestrator
cargo test -p orchestrator-core
```