use clap::Subcommand;
use std::fs;
use std::path::{Path, PathBuf};
#[derive(Subcommand)]
pub enum InitAction {
Run {
#[arg(default_value = ".")]
dir: String,
#[arg(long)]
yes: bool,
},
Dry {
#[arg(default_value = ".")]
dir: String,
},
}
pub fn dispatch(action: InitAction) {
match action {
InitAction::Run { dir, yes } => run(&dir, yes, false),
InitAction::Dry { dir } => run(&dir, true, true),
}
}
const SETTINGS_JSON: &str = r#"{
"permissions": {
"allow": [
"Bash(git status:*)",
"Bash(git log:*)",
"Bash(git diff:*)",
"Bash(cargo build:*)",
"Bash(cargo test:*)",
"Bash(npm test:*)",
"Bash(yana-rt *)"
],
"deny": []
},
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [{ "type": "command", "command": "bash .claude/hooks/pre-tool-use.sh" }]
}
]
}
}
"#;
const PRE_TOOL_USE_SH: &str = r#"#!/usr/bin/env bash
# Yana AI pre-tool-use gate — generated by yana-rt init
# Blocks: rm -rf, force push, pipe-to-shell, eval injection
set -uo pipefail
INPUT=$(cat)
TOOL=$(echo "$INPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('tool_name',''))" 2>/dev/null || echo "")
CMD=$(echo "$INPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('tool_input',{}).get('command',''))" 2>/dev/null || echo "")
BLOCKED=(
"rm -rf"
"rm -fr"
"git push --force"
"git push -f"
"git reset --hard"
"| bash"
"| sh"
"| python"
"base64 -d.*|"
"eval \$("
"curl.*| bash"
"wget.*| bash"
)
for pattern in "${BLOCKED[@]}"; do
if echo "$CMD" | grep -qE "$pattern" 2>/dev/null; then
echo '{"decision":"block","reason":"[yana-ai] Blocked pattern: '"$pattern"'"}'
exit 0
fi
done
echo '{"decision":"allow"}'
"#;
const YANA_CONFIG: &str = r#"# .yana-ai/config.toml — generated by yana-rt init
version = "1.0"
[sandbox]
mode = "auto" # docker | nsjail | ulimit | auto
[gates]
level = 2 # 1=basic | 2=standard | 3=strict
[scan]
fail_on = "high" # info | low | medium | high | critical
[budget]
daily_token_limit = 50000
session_token_cap = 10000
"#;
const GITIGNORE_APPEND: &str = r#"
# Yana AI
.yana-ai/cache/
releases/logs/
"#;
struct FileSpec {
path: &'static str,
content: &'static str,
exec: bool,
}
fn file_specs() -> Vec<FileSpec> {
vec![
FileSpec { path: ".claude/settings.json", content: SETTINGS_JSON, exec: false },
FileSpec { path: ".claude/hooks/pre-tool-use.sh", content: PRE_TOOL_USE_SH, exec: true },
FileSpec { path: ".yana-ai/config.toml", content: YANA_CONFIG, exec: false },
]
}
fn run(dir: &str, _yes: bool, dry: bool) {
let root = PathBuf::from(dir);
println!(" yana-rt init{}", if dry { " (dry run)" } else { "" });
println!(" target: {}", root.display());
println!("{}", "─".repeat(52));
let specs = file_specs();
let mut created = 0usize;
let mut skipped = 0usize;
for spec in &specs {
let dest = root.join(spec.path);
let exists = dest.exists();
if exists {
println!(" skip {}", spec.path);
skipped += 1;
continue;
}
println!(" create {}", spec.path);
created += 1;
if dry { continue; }
if let Some(parent) = dest.parent() {
fs::create_dir_all(parent)
.unwrap_or_else(|e| eprintln!("[init] mkdir failed: {e}"));
}
fs::write(&dest, spec.content)
.unwrap_or_else(|e| eprintln!("[init] write failed {}: {e}", spec.path));
#[cfg(unix)]
if spec.exec {
use std::os::unix::fs::PermissionsExt;
if let Ok(meta) = fs::metadata(&dest) {
let mut perms = meta.permissions();
perms.set_mode(0o755);
let _ = fs::set_permissions(&dest, perms);
}
}
}
let gi = root.join(".gitignore");
if gi.exists() {
let existing = fs::read_to_string(&gi).unwrap_or_default();
if !existing.contains("Yana AI") {
println!(" append .gitignore");
if !dry {
let _ = fs::write(&gi, format!("{}{}", existing, GITIGNORE_APPEND));
}
}
}
println!("{}", "─".repeat(52));
println!(" {} created · {} skipped", created, skipped);
if !dry {
println!();
println!(" Next steps:");
println!(" 1. npm install yana-ai # wire hooks");
println!(" 2. npx yana-ai-install # activate");
println!(" 3. yana-rt scan . # first scan");
println!(" 4. yana-rt doctor run . # health check");
}
}