use crate::backends::{build_embedder, ConfiguredEmbedder};
use crate::startup::{apply_config_file, default_store_path};
pub(crate) fn run_export(
argv: &[String],
flags: &[String],
) -> Result<(), Box<dyn std::error::Error>> {
apply_config_file(argv)?;
let options = ExportOptions::parse(flags)?;
let store = options
.store_path
.or_else(|| std::env::var("VELESDB_MEMORY_PATH").ok())
.unwrap_or_else(default_store_path);
let store = std::path::Path::new(&store);
let written = write_export(store, options.output.as_deref(), options.include_internal)?;
eprintln!("[velesdb-memory] exported {written} memories");
Ok(())
}
pub(crate) fn write_export(
store: &std::path::Path,
output: Option<&str>,
include_internal: bool,
) -> Result<u64, Box<dyn std::error::Error>> {
if let Some(path) = output {
let mut file = std::io::BufWriter::new(std::fs::File::create(path)?);
let written = velesdb_memory::export::export_jsonl(store, &mut file, include_internal)?;
std::io::Write::flush(&mut file)?;
Ok(written)
} else {
let stdout = std::io::stdout();
let mut lock = stdout.lock();
Ok(velesdb_memory::export::export_jsonl(
store,
&mut lock,
include_internal,
)?)
}
}
pub(crate) struct ExportOptions {
store_path: Option<String>,
output: Option<String>,
include_internal: bool,
}
impl ExportOptions {
fn parse(flags: &[String]) -> Result<Self, Box<dyn std::error::Error>> {
let mut options = Self {
store_path: None,
output: None,
include_internal: false,
};
let mut it = flags.iter();
while let Some(flag) = it.next() {
match flag.as_str() {
"--include-internal" => options.include_internal = true,
"--store" => options.store_path = Some(Self::value_of(&mut it, "--store")?),
"--output" => options.output = Some(Self::value_of(&mut it, "--output")?),
other => return Err(format!("unknown export flag '{other}'").into()),
}
}
Ok(options)
}
fn value_of(
it: &mut std::slice::Iter<'_, String>,
flag: &str,
) -> Result<String, Box<dyn std::error::Error>> {
Ok(it
.next()
.ok_or_else(|| format!("{flag} requires a path argument"))?
.clone())
}
}
pub(crate) fn run_migrate_embeddings(
argv: &[String],
flags: &[String],
) -> Result<(), Box<dyn std::error::Error>> {
use velesdb_memory::migration;
let options = migration::parse_migrate_args(flags)?;
apply_config_file(argv)?;
let store_path = migrate_store_path(&options);
let ConfiguredEmbedder { embedder, model } = build_embedder()?;
let target = migration::TargetContract {
model,
dimension: embedder.dimension(),
strategy: options.strategy,
};
let scratch = migrate_scratch_parent(&options, &store_path)?;
if options.dry_run {
let report = migration::dry_run(
&store_path,
&scratch,
&target,
options.destination.as_deref(),
)?;
print!("{}", migration::render(&report));
if migration::refuses(&report) {
std::process::exit(2);
}
return Ok(());
}
run_migrate_rebuild(&options, &store_path, &scratch, &target, embedder.as_ref())
}
pub(crate) fn run_migrate_rebuild(
options: &velesdb_memory::migration::MigrateOptions,
store_path: &std::path::Path,
scratch: &std::path::Path,
target: &velesdb_memory::migration::TargetContract,
embedder: &dyn velesdb_memory::Embedder,
) -> Result<(), Box<dyn std::error::Error>> {
use velesdb_memory::migration;
let destination = migration::require_destination(options)?;
let outcome = migration::migrate(
store_path,
scratch,
target,
&destination,
embedder,
MIGRATE_BATCH,
)?;
if let Some(executed) = &outcome.executed {
print!("{}", migration::render(&executed.report));
println!(
"rebuild: {} facts written, {} already present, {} edges, journal at {}",
executed.rebuild.facts,
executed.rebuild.collisions,
executed.rebuild.edges,
executed.workspace.display(),
);
}
if let Some(validated) = &outcome.validated {
println!(
"validated: {} facts and {} edges compared, {} divergence(s) explained by expiry",
validated.facts, validated.edges, validated.explained_by_expiry,
);
}
println!("activated: {}", outcome.switched.activated.display());
println!("{}", migration::migration_complete_notice());
Ok(())
}
pub(crate) const MIGRATE_BATCH: usize = 1024;
pub(crate) fn migrate_store_path(
options: &velesdb_memory::migration::MigrateOptions,
) -> std::path::PathBuf {
options.store.clone().unwrap_or_else(|| {
std::path::PathBuf::from(
std::env::var("VELESDB_MEMORY_PATH").unwrap_or_else(|_| default_store_path()),
)
})
}
pub(crate) fn migrate_scratch_parent(
options: &velesdb_memory::migration::MigrateOptions,
store_path: &std::path::Path,
) -> Result<std::path::PathBuf, String> {
if let Some(dir) = options.scratch.clone() {
return Ok(dir);
}
let resolved = std::fs::canonicalize(store_path).unwrap_or_else(|_| store_path.to_path_buf());
velesdb_memory::migration::default_scratch_parent(&resolved)
}
#[cfg(feature = "context")]
pub(crate) const DEFAULT_COMPILE_STDIN_BUDGET: u64 = 2_000;
#[cfg(feature = "context")]
#[derive(Debug, PartialEq, Eq)]
pub(crate) struct CompileStdinOptions {
token_budget: u64,
query: String,
}
#[cfg(feature = "context")]
impl Default for CompileStdinOptions {
fn default() -> Self {
Self {
token_budget: DEFAULT_COMPILE_STDIN_BUDGET,
query: String::new(),
}
}
}
#[cfg(feature = "context")]
#[derive(serde::Serialize)]
pub(crate) struct CompileStdinOutput {
content: String,
tokens_in: u64,
tokens_out: u64,
tokens_saved: u64,
risk: String,
}
#[cfg(feature = "context")]
pub(crate) fn parse_compile_stdin_budget(value: Option<&String>) -> Result<u64, String> {
let raw = value.ok_or_else(|| "--budget requires a value".to_owned())?;
let parsed: u64 = raw
.parse()
.map_err(|_| format!("--budget expects a positive integer, got {raw:?}"))?;
if parsed == 0 {
return Err("--budget must be greater than 0".to_owned());
}
Ok(parsed)
}
#[cfg(feature = "context")]
pub(crate) fn parse_compile_stdin_args(args: &[String]) -> Result<CompileStdinOptions, String> {
let mut options = CompileStdinOptions::default();
let mut index = 0;
while index < args.len() {
let flag = args[index].as_str();
let value = args.get(index + 1);
match flag {
"--budget" => {
options.token_budget = parse_compile_stdin_budget(value)?;
index += 2;
}
"--query" => {
options
.query
.clone_from(value.ok_or_else(|| "--query requires a value".to_owned())?);
index += 2;
}
other => return Err(format!("unknown compile-stdin flag {other:?}")),
}
}
Ok(options)
}
#[cfg(feature = "context")]
pub(crate) fn compile_stdin_json(
text: &str,
options: &CompileStdinOptions,
) -> Result<String, Box<dyn std::error::Error>> {
use velesdb_memory::context::{
segment_transcript, CompilePolicy, CompileRequest, ContextCompiler, SegmentationPolicy,
};
if text.trim().is_empty() {
return Err("compile-stdin received empty input on stdin".into());
}
let outcome = segment_transcript(text, &SegmentationPolicy::default())?;
let request = CompileRequest {
query: options.query.clone(),
fragments: outcome
.segments
.into_iter()
.map(|segment| segment.fragment)
.collect(),
project: None,
target_model: None,
token_budget: options.token_budget,
memory_scope: None,
policy: None,
};
let compiled = ContextCompiler::new(CompilePolicy::default()).compile(&request)?;
if compiled.content.is_empty() {
return Err(format!(
"a budget of {} tokens fits none of the {} input tokens — every fragment was \
externalized and the compiled context is empty; raise --budget",
options.token_budget, compiled.insights.tokens_in
)
.into());
}
let output = CompileStdinOutput {
content: compiled.content,
tokens_in: compiled.insights.tokens_in,
tokens_out: compiled.insights.tokens_out,
tokens_saved: compiled.insights.tokens_saved,
risk: format!("{:?}", compiled.risk).to_lowercase(),
};
Ok(serde_json::to_string(&output)?)
}
#[cfg(feature = "context")]
pub(crate) fn run_compile_stdin(args: &[String]) -> Result<(), Box<dyn std::error::Error>> {
use std::io::Read as _;
let options = parse_compile_stdin_args(args)?;
let mut text = String::new();
std::io::stdin().read_to_string(&mut text)?;
println!("{}", compile_stdin_json(&text, &options)?);
Ok(())
}
#[cfg(not(feature = "context"))]
pub(crate) fn run_compile_stdin(_args: &[String]) -> Result<(), Box<dyn std::error::Error>> {
Err("`compile-stdin` requires building with `--features context`".into())
}
#[cfg(all(test, feature = "context"))]
#[path = "daemon_commands_tests.rs"]
mod compile_stdin_tests;