mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-06 02:48:25 +00:00
- 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>
147 lines
4.3 KiB
Rust
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);
|
|
}
|
|
}
|