skald/vendor/clawdforge/examples/basic.rs
Kayos f71b533e52 v0.2 scaffold: vendor clawdforge SDK + forge module + Whisper plan
The Rust SDK already existed at Sulkta-Coop/clawdforge clients/rust/ — async,
reqwest-based, bearer-auth, exposes Client::run() + Session for multi-turn.
Vendoring it into vendor/clawdforge so skald is self-contained: no
git-submodule + no needing the clawdforge repo cloned next to skald.
Trade-off accepted: updates require manual re-copy until both sides
stabilize and we publish to a private cargo registry.

What landed:

- vendor/clawdforge/ — full SDK source from Sulkta-Coop/clawdforge HEAD.
  Pinned in skald-core/Cargo.toml as a path dep.
- skald-core/src/forge.rs — three-pass orchestration shell. Forge wraps
  clawdforge::Client; generate() / cleanup() / audit() each build a
  RunRequest with the right system prompt + model alias (always opus),
  call client.run(), return a PassOutput.
  Prompt templates are TODO stubs (SYSTEM_GEN_TODO etc) — filling in the
  actual prose-craft prompts is its own deep session.
- skald-core/src/config.rs — ForgeConfig { base_url, app_token, model }.
  Resolved by the binary from env (CLAWDFORGE_URL + CLAWDFORGE_TOKEN);
  lib stays env-agnostic.
- skald-core::AuditFinding + AuditResponse — parse shape for what the
  third-Opus canon audit returns, ready to map onto audit_findings rows.
- docs/tts-pipeline.md — full plan for v0.2 narration + post-TTS audit
  chain. Whisper-large-v3 STT does text-to-text verification on every
  render; an optional Gemini Flash audio pass catches subjective issues
  (prosody, tone) Whisper can't see. Reroll loop on crit findings.

What's still stubbed:

- Prompt templates in forge.rs (gen / cleanup / audit) — placeholders
  that describe the role but don't constrain output shape yet.
- context.rs (assemble the LLM context blob from DB rows) — entire module
  TBD.
- No CLI subcommand yet for invoking forge — that comes after context.rs.

Naming note: in Rust 2024 'gen' is a reserved keyword (for generators),
so the method is Forge::generate(), not Forge::gen().
2026-05-13 10:18:56 -07:00

77 lines
2.2 KiB
Rust

//! End-to-end usage example.
//!
//! Run against a live clawdforge:
//!
//! ```sh
//! CLAWDFORGE_URL=http://localhost:8800 \
//! CLAWDFORGE_TOKEN=cf_xxxxxxxxxxxxxxxx \
//! cargo run --example basic
//! ```
use clawdforge::{Client, RunRequest};
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct Hello {
hello: String,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let url =
std::env::var("CLAWDFORGE_URL").unwrap_or_else(|_| "http://localhost:8800".to_string());
let token = std::env::var("CLAWDFORGE_TOKEN")
.map_err(|_| "set CLAWDFORGE_TOKEN to a cf_... bearer minted via /admin/tokens")?;
let client = Client::builder().base_url(url).token(token).build()?;
// 1) liveness
let h = client.healthz().await?;
println!(
"healthz: ok={} claude_present={} version={:?}",
h.ok, h.claude_present, h.claude_version
);
// 2) JSON-shaped run
let r = client
.run(RunRequest {
prompt: r#"Reply with JSON: {"hello": "world"}"#.into(),
model: Some("sonnet".into()),
timeout_secs: Some(30),
..Default::default()
})
.await?;
println!(
"duration_ms={} stop_reason={:?}",
r.duration_ms, r.stop_reason
);
match r.as_json::<Hello>() {
Ok(v) => println!("parsed: hello = {}", v.hello),
Err(_) => match r.as_text() {
Some(t) => println!("text reply: {t}"),
None => println!("unparseable reply: {:?}", r.result),
},
}
// 3) optional file upload — only if a path is given.
if let Ok(path) = std::env::var("CLAWDFORGE_DEMO_FILE") {
let ft = client.upload_file(&path, Some(3600)).await?;
println!(
"uploaded {} bytes -> {} (ttl {}s)",
ft.size, ft.file_token, ft.ttl_secs
);
let r2 = client
.run(RunRequest {
prompt: "Describe the attached file in one sentence.".into(),
files: Some(vec![ft.file_token]),
..Default::default()
})
.await?;
println!("file-run reply: {:?}", r2.result);
}
Ok(())
}