fabro/lib/crates/fabro-cli/src/skill.rs
Bryan Helmkamp 28884ae093 rename Arc to Fabro in all Rust crates, symbols, env vars, and supporting files
- Rename 20 crate directories lib/crates/arc-* → fabro-*
- Update all Cargo.toml: crate names, dep paths, feature flags, bin name
- Rename arc_server module → fabro_server in fabro-llm
- ArcError → FabroError across 30+ files
- ARC_VERSION/ARC_GIT_SHA/ARC_BUILD_DATE → FABRO_* constants
- All use/qualified paths: arc_agent:: → fabro_agent::, etc. (~1500 occurrences)
- Env vars ARC_* → FABRO_* in string literals and shell scripts
- String literals: X-Arc-Demo, arc-bot, arc@local, arc-web, arc-mcp, etc.
- Path strings: .arc/ → .fabro/, arc.toml → fabro.toml, refs/arc/ → refs/fabro/
- arc-api.yaml → fabro-api.yaml (OpenAPI spec)
- skills/arc-create-workflow → fabro-create-workflow
- trycmd fixtures: $ arc → $ fabro
- Inline snapshots (insta) updated
- CI, Docker, install.sh, scripts, CLAUDE.md, AGENTS.md
- TypeScript app: env vars, headers, JWT issuer
- Docs: page slugs, git refs, config paths, sandbox names, repo URLs
- Repo references: brynary/arc → fabro-sh/fabro

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 12:25:58 -04:00

147 lines
4.3 KiB
Rust

use std::path::Path;
use anyhow::{bail, Result};
use clap::{Args, ValueEnum};
use tracing::{debug, info};
const SKILL_MD: &str = include_str!("../../../../skills/fabro-create-workflow/SKILL.md");
const REF_DOT_LANGUAGE: &str =
include_str!("../../../../skills/fabro-create-workflow/references/dot-language.md");
const REF_EXAMPLE_WORKFLOWS: &str =
include_str!("../../../../skills/fabro-create-workflow/references/example-workflows.md");
const REF_RUN_CONFIGURATION: &str =
include_str!("../../../../skills/fabro-create-workflow/references/run-configuration.md");
const SKILL_FILES: &[(&str, &str)] = &[
("SKILL.md", SKILL_MD),
("references/dot-language.md", REF_DOT_LANGUAGE),
("references/example-workflows.md", REF_EXAMPLE_WORKFLOWS),
("references/run-configuration.md", REF_RUN_CONFIGURATION),
];
#[derive(Clone, ValueEnum)]
pub enum SkillDir {
Claude,
Agents,
}
#[derive(Args)]
pub struct SkillInstallArgs {
/// Where to install: user-level or project-level
#[arg(long = "for", default_value = "user")]
scope: SkillScope,
/// Target directory convention
#[arg(long)]
dir: SkillDir,
/// Overwrite existing skill without prompting
#[arg(long)]
force: bool,
}
#[derive(Clone, ValueEnum)]
pub enum SkillScope {
User,
Project,
}
/// Install all skill files under `base_dir/fabro-create-workflow/`.
pub fn install_skill_to(base_dir: &Path) -> Result<()> {
let skill_dir = base_dir.join("fabro-create-workflow");
for (rel_path, content) in SKILL_FILES {
let dest = skill_dir.join(rel_path);
if let Some(parent) = dest.parent() {
std::fs::create_dir_all(parent)?;
}
debug!(file = %rel_path, "Writing skill file");
std::fs::write(&dest, content)?;
}
info!(path = %skill_dir.display(), "Skill installed");
Ok(())
}
pub fn run_skill_install(args: &SkillInstallArgs) -> Result<()> {
let base_dir = resolve_base_dir(&args.scope, &args.dir)?;
let skill_dir = base_dir.join("fabro-create-workflow");
if skill_dir.exists() && !args.force {
let confirm = dialoguer::Confirm::new()
.with_prompt(format!(
"Skill directory already exists at {}. Overwrite?",
skill_dir.display()
))
.default(false)
.interact()?;
if !confirm {
bail!("Aborted: skill directory already exists");
}
}
install_skill_to(&base_dir)
}
fn resolve_base_dir(scope: &SkillScope, dir: &SkillDir) -> Result<std::path::PathBuf> {
let dir_name = match dir {
SkillDir::Claude => ".claude",
SkillDir::Agents => ".agents",
};
let root = match scope {
SkillScope::User => {
dirs::home_dir().ok_or_else(|| anyhow::anyhow!("Could not determine home directory"))?
}
SkillScope::Project => std::env::current_dir()?,
};
Ok(root.join(dir_name).join("skills"))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn embedded_files_are_non_empty() {
assert!(!SKILL_MD.is_empty());
assert!(!REF_DOT_LANGUAGE.is_empty());
assert!(!REF_EXAMPLE_WORKFLOWS.is_empty());
assert!(!REF_RUN_CONFIGURATION.is_empty());
}
#[test]
fn install_writes_all_files() {
let tmp = tempfile::tempdir().unwrap();
let base = tmp.path().join("skills");
install_skill_to(&base).unwrap();
for (rel_path, content) in SKILL_FILES {
let path = base.join("fabro-create-workflow").join(rel_path);
assert!(path.exists(), "Missing file: {rel_path}");
let written = std::fs::read_to_string(&path).unwrap();
assert_eq!(written, *content, "Content mismatch: {rel_path}");
}
}
#[test]
fn install_overwrites_existing_files() {
let tmp = tempfile::tempdir().unwrap();
let base = tmp.path().join("skills");
install_skill_to(&base).unwrap();
// Write a sentinel to one file
let sentinel_path = base.join("fabro-create-workflow/SKILL.md");
std::fs::write(&sentinel_path, "old content").unwrap();
// Re-install should overwrite
install_skill_to(&base).unwrap();
let content = std::fs::read_to_string(&sentinel_path).unwrap();
assert_eq!(content, SKILL_MD);
}
}