From 8cec8d6546bffe7ae10cb58d351e99f36fa01381 Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Tue, 10 Mar 2026 13:00:36 -0400 Subject: [PATCH] Add `arc init` command to initialize a new arc project Co-Authored-By: Claude Opus 4.6 (1M context) --- AGENTS.md | 2 +- lib/crates/arc-cli/src/init.rs | 70 ++++++++++++++++++++++++++++++++++ lib/crates/arc-cli/src/main.rs | 7 ++++ 3 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 lib/crates/arc-cli/src/init.rs diff --git a/AGENTS.md b/AGENTS.md index b705634b0..0c1ab5418 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -37,7 +37,7 @@ The OpenAPI spec at `docs/api-reference/arc-api.yaml` is the source of truth for Arc is an AI-powered workflow orchestration platform. Workflows are defined as DOT graphs, where each node is a stage (agent, prompt, command, conditional, human, parallel, etc.) executed by the workflow engine. ### Rust crates (`lib/crates/`) -- **arc-cli** — CLI entry point. Commands: `run`, `exec`, `serve`, `validate`, `parse`, `cp`, `model`, `doctor`, `install`, `ps`, `system prune`, `llm` +- **arc-cli** — CLI entry point. Commands: `run`, `exec`, `serve`, `validate`, `parse`, `cp`, `model`, `doctor`, `init`, `install`, `ps`, `system prune`, `llm` - **arc-workflows** — Core workflow engine. Parses DOT graphs, runs stages, manages checkpoints/resume, hooks, retros, and human-in-the-loop interactions - **arc-agent** — AI coding agent with tool use (Bash, Read, Write, Edit, Glob, Grep, WebFetch). `Sandbox` trait abstracts execution environments - **arc-api** — Axum HTTP server. Routes for runs, sessions, models, completions, usage. SSE event streaming. Demo mode via header diff --git a/lib/crates/arc-cli/src/init.rs b/lib/crates/arc-cli/src/init.rs new file mode 100644 index 000000000..7d68ab671 --- /dev/null +++ b/lib/crates/arc-cli/src/init.rs @@ -0,0 +1,70 @@ +use anyhow::{bail, Context, Result}; +use std::path::PathBuf; + +pub async fn run_init() -> Result<()> { + let output = std::process::Command::new("git") + .args(["rev-parse", "--show-toplevel"]) + .output() + .context("failed to run git")?; + + if !output.status.success() { + bail!("not a git repository — run `git init` first"); + } + + let repo_root = PathBuf::from( + String::from_utf8(output.stdout) + .context("git output was not valid UTF-8")? + .trim(), + ); + + let arc_toml = repo_root.join("arc.toml"); + if arc_toml.exists() { + bail!( + "already initialized — arc.toml exists at {}", + arc_toml.display() + ); + } + + // Create arc.toml + std::fs::write(&arc_toml, "version = 1\n\n[arc]\nroot = \"arc/\"\n") + .with_context(|| format!("failed to write {}", arc_toml.display()))?; + eprintln!("Created {}", arc_toml.display()); + + // Create hello workflow directory + let workflow_dir = repo_root.join("arc/workflows/hello"); + std::fs::create_dir_all(&workflow_dir) + .with_context(|| format!("failed to create {}", workflow_dir.display()))?; + + // Create workflow.dot + let dot_path = workflow_dir.join("workflow.dot"); + std::fs::write( + &dot_path, + r#"digraph Hello { + graph [goal="Say hello and demonstrate a basic arc workflow"] + rankdir=LR + + start [shape=Mdiamond, label="Start"] + exit [shape=Msquare, label="Exit"] + + greet [label="Greet", prompt="Say hello! Introduce yourself and explain that this is a test of the arc workflow engine."] + + start -> greet -> exit +} +"#, + ) + .with_context(|| format!("failed to write {}", dot_path.display()))?; + eprintln!("Created {}", dot_path.display()); + + // Create workflow.toml + let toml_path = workflow_dir.join("workflow.toml"); + std::fs::write( + &toml_path, + "version = 1\ngraph = \"workflow.dot\"\n\n[sandbox]\nprovider = \"local\"\n", + ) + .with_context(|| format!("failed to write {}", toml_path.display()))?; + eprintln!("Created {}", toml_path.display()); + + eprintln!("\nProject initialized! Run a workflow with:\n arc run arc/workflows/hello/workflow.toml --no-retro"); + + Ok(()) +} diff --git a/lib/crates/arc-cli/src/main.rs b/lib/crates/arc-cli/src/main.rs index 4936b0c31..03ba5fde0 100644 --- a/lib/crates/arc-cli/src/main.rs +++ b/lib/crates/arc-cli/src/main.rs @@ -1,5 +1,6 @@ mod cli_config; mod doctor; +mod init; mod install; mod logging; @@ -85,6 +86,8 @@ enum Command { #[arg(short, long)] live: bool, }, + /// Initialize a new arc project + Init, /// Set up the Arc environment (LLMs, certs, GitHub) Install, /// List workflow runs @@ -191,6 +194,7 @@ async fn main_inner() -> Result<()> { #[cfg(feature = "server")] Command::Serve(_) => "serve", Command::Doctor { .. } => "doctor", + Command::Init => "init", Command::Install => "install", Command::Ps(_) => "ps", Command::Pr { .. } => "pr", @@ -396,6 +400,9 @@ async fn main_inner() -> Result<()> { let exit_code = doctor::run_doctor(verbose, live).await; std::process::exit(exit_code); } + Command::Init => { + init::run_init().await?; + } Command::Install => { install::run_install().await?; }