Skip to main content

txcript/harness/
campfire.rs

1//! Campfire embeds pi and writes the identical on-disk format under
2//! `~/.campfire/agent/sessions/` (honoring `CAMPFIRE_CODING_AGENT_*`). It is a
3//! thin delegate: the body type, codec, and store logic are pi's, reused
4//! verbatim; only the harness identity and the sessions root differ.
5
6use std::path::PathBuf;
7
8use crate::error::Result;
9use crate::harness::jsonl;
10use crate::harness::pi::{self, Record};
11use crate::transcript::{Codec, Common, Discovered, Harness, Saved, Store, TextCodec, Transcript};
12
13/// The Campfire harness marker. Shares pi's native [`Record`] body.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub struct Campfire;
16
17impl Harness for Campfire {
18    const NAME: &'static str = "campfire";
19    type Body = Vec<Record>;
20}
21
22impl Codec for Campfire {
23    fn to_common(transcript: &Transcript<Self>) -> Result<Transcript<Common>> {
24        Ok(Transcript::new(
25            transcript.meta.clone(),
26            pi::records_to_messages(&transcript.body, transcript.meta.timestamp),
27        ))
28    }
29
30    fn from_common(transcript: &Transcript<Common>) -> Result<Transcript<Self>> {
31        Ok(Transcript::new(
32            transcript.meta.clone(),
33            pi::messages_to_records(&transcript.meta, &transcript.body),
34        ))
35    }
36}
37
38impl TextCodec for Campfire {
39    fn from_text(text: &str) -> Result<Transcript<Self>> {
40        // Identical format to pi — reuse its parser and metadata extraction.
41        let records = pi::records_from_text(text);
42        Ok(Transcript::new(pi::meta_from_records(&records), records))
43    }
44
45    fn to_text(transcript: &Transcript<Self>) -> Result<String> {
46        jsonl::render(&transcript.body)
47    }
48}
49
50/// Reads and writes Campfire sessions under a sessions root.
51#[derive(Debug, Clone)]
52pub struct CampfireStore {
53    pub sessions_dir: PathBuf,
54}
55
56impl CampfireStore {
57    pub fn new(sessions_dir: impl Into<PathBuf>) -> Self {
58        Self {
59            sessions_dir: sessions_dir.into(),
60        }
61    }
62
63    /// Campfire's default sessions root, honoring `CAMPFIRE_CODING_AGENT_*`.
64    pub fn default_root() -> Option<Self> {
65        pi::resolve_sessions_dir(".campfire", "CAMPFIRE").map(Self::new)
66    }
67}
68
69impl Store for CampfireStore {
70    type H = Campfire;
71    type Ref = PathBuf;
72
73    fn discover(&self) -> Result<Vec<Discovered<PathBuf>>> {
74        Ok(pi::discover_format(&self.sessions_dir))
75    }
76
77    fn load(&self, reference: &PathBuf) -> Result<Transcript<Campfire>> {
78        pi::load_session(reference, Campfire::from_text)
79    }
80
81    fn save(&self, transcript: &Transcript<Campfire>) -> Result<Saved<PathBuf>> {
82        pi::write_session(&self.sessions_dir, &transcript.meta, &transcript.body)
83    }
84
85    fn delete(&self, reference: &PathBuf) -> Result<()> {
86        Ok(std::fs::remove_file(reference)?)
87    }
88}