Rename the project to FieldCraft

Crates fieldcraft-*, binaries fieldcraft and fieldcraft-cli, the .fieldcraft file (format tag
fieldcraft/1, manifest fieldcraft.json), FIELDCRAFT_* environment variables, the MCP server
name, egui widget ids (fc_*), the app icon document FieldCraft.icon and its generated family,
docs and licences. No compatibility shim: no files of the earlier name exist.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
This commit is contained in:
jyvikoandClaude Opus 5.5 authored and Irakli Kourtis committed 2026-10-13 23:51:40 -04:00
1 parent 9bc4e1b182
commit d5bd74570f
268 files changed
+1666 -1666

No files matched your search

+15
View File
@@ -0,0 +1,15 @@
[package]
name = "fieldcraft-cli"
description = "FieldCraft from the command line: list commands, run scripts, serve MCP"
version.workspace = true
edition.workspace = true
license.workspace = true
[lints]
workspace = true
[dependencies]
fieldcraft-engine = { workspace = true }
fieldcraft-io = { workspace = true }
fieldcraft-mcp = { workspace = true }
serde_json = { workspace = true }
@@ -1,27 +1,27 @@
//! modelcraft-cli commands
//! modelcraft-cli run <script.json>
//! modelcraft-cli features
//! modelcraft-cli mcp [--connect ADDR | --headless] [--automation-read-root DIR] [--automation-write-root DIR]
//! fieldcraft-cli commands
//! fieldcraft-cli run <script.json>
//! fieldcraft-cli features
//! fieldcraft-cli mcp [--connect ADDR | --headless] [--automation-read-root DIR] [--automation-write-root DIR]
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
#![forbid(unsafe_code)]
use std::path::Path;
use std::process::ExitCode;
use modelcraft_engine::Session;
use fieldcraft_engine::Session;
use serde_json::json;
const USAGE: &str = "\
usage:
modelcraft-cli commands list every command with its parameters
modelcraft-cli run <script.json> run a script (a JSON array of {\"command\", \"params\"})
fieldcraft-cli commands list every command with its parameters
fieldcraft-cli run <script.json> run a script (a JSON array of {\"command\", \"params\"})
and print the model summary
modelcraft-cli features print docs/features.md (every capability, ready or
fieldcraft-cli features print docs/features.md (every capability, ready or
coming soon) from the capability catalogue
modelcraft-cli mcp [options] serve the Model Context Protocol on stdin/stdout
fieldcraft-cli mcp [options] serve the Model Context Protocol on stdin/stdout
(docs/mcp.md):
--headless an in-process session (the default)
--connect ADDR drive the app at ADDR (`modelcraft --control PORT`);
--connect ADDR drive the app at ADDR (`fieldcraft --control PORT`);
headless when it cannot be reached
--automation-read-root DIR files may be read only inside DIR
--automation-write-root DIR files may be written only inside DIR (with either root,
@@ -35,13 +35,13 @@ fn main() -> ExitCode {
let args: Vec<String> = std::env::args().skip(1).collect();
let result = match args.first().map(String::as_str) {
Some("commands") => {
for c in modelcraft_engine::commands() {
for c in fieldcraft_engine::commands() {
println!("{:<24} {:<18} {}", c.id, c.label, c.params);
}
Ok(())
}
Some("features") => {
print!("{}", modelcraft_engine::catalogue::features_page());
print!("{}", fieldcraft_engine::catalogue::features_page());
Ok(())
}
Some("mcp") => mcp(args.get(1..).unwrap_or(&[])),
@@ -62,7 +62,7 @@ fn main() -> ExitCode {
}
/// The `mcp` options: each given once, as `--flag value` or `--flag=value`.
fn mcp_options(args: &[String]) -> Result<modelcraft_mcp::Options, String> {
fn mcp_options(args: &[String]) -> Result<fieldcraft_mcp::Options, String> {
let mut connect = None;
let mut headless = false;
let mut read = None;
@@ -94,18 +94,18 @@ fn mcp_options(args: &[String]) -> Result<modelcraft_mcp::Options, String> {
return Err("--connect and --headless exclude each other".into());
}
let roots = match (&read, &write) {
(None, None) => modelcraft_io::roots::Roots::default(),
_ => modelcraft_io::roots::Roots::new(read.as_deref().map(Path::new), write.as_deref().map(Path::new)).map_err(|e| e.to_string())?,
(None, None) => fieldcraft_io::roots::Roots::default(),
_ => fieldcraft_io::roots::Roots::new(read.as_deref().map(Path::new), write.as_deref().map(Path::new)).map_err(|e| e.to_string())?,
};
Ok(modelcraft_mcp::Options { connect, roots })
Ok(fieldcraft_mcp::Options { connect, roots })
}
fn mcp(args: &[String]) -> Result<(), String> {
let (backend, note) = modelcraft_mcp::backend(mcp_options(args)?)?;
let (backend, note) = fieldcraft_mcp::backend(mcp_options(args)?)?;
if let Some(note) = note {
eprintln!("modelcraft-cli mcp: {note}");
eprintln!("fieldcraft-cli mcp: {note}");
}
let mut server = modelcraft_mcp::Server::new(backend);
let mut server = fieldcraft_mcp::Server::new(backend);
server.serve(std::io::stdin().lock(), std::io::stdout().lock()).map_err(|e| format!("mcp: {e}"))
}
@@ -127,7 +127,7 @@ fn run(path: &str) -> Result<(), String> {
mod tests {
use super::*;
fn opts(args: &[&str]) -> Result<modelcraft_mcp::Options, String> {
fn opts(args: &[&str]) -> Result<fieldcraft_mcp::Options, String> {
mcp_options(&args.iter().map(|s| s.to_string()).collect::<Vec<_>>())
}
@@ -1,6 +1,6 @@
[package]
name = "modelcraft"
description = "ModelCraft desktop app"
name = "fieldcraft"
description = "FieldCraft desktop app"
version.workspace = true
edition.workspace = true
license.workspace = true
@@ -10,9 +10,9 @@ publish = false
workspace = true
[dependencies]
modelcraft-engine = { workspace = true }
modelcraft-ui-egui = { workspace = true }
modelcraft-io = { workspace = true }
fieldcraft-engine = { workspace = true }
fieldcraft-ui-egui = { workspace = true }
fieldcraft-io = { workspace = true }
rfd = { workspace = true }
eframe = { workspace = true, features = ["default_fonts", "wgpu", "accesskit", "wayland", "x11"] }
egui = { workspace = true }
@@ -1,6 +1,6 @@
//! Loopback JSON-lines control server: one request per line, one reply per line. Agents and
//! tests drive the app through it; the UI thread answers each request between frames
//! (`modelcraft_ui_egui::control`).
//! (`fieldcraft_ui_egui::control`).
//!
//! Adapted from SolidCraft's `apps/solidcraft/src/control_server.rs` (MIT OR Apache-2.0), see
//! ATTRIBUTION.md.
@@ -11,7 +11,7 @@ use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::mpsc::{Receiver, Sender, channel};
use std::time::Duration;
use modelcraft_ui_egui::ControlRequest;
use fieldcraft_ui_egui::ControlRequest;
use serde_json::{Value, json};
/// Longest request line (bytes).
@@ -31,11 +31,11 @@ pub fn start(port: u16, ctx: egui::Context) -> Receiver<ControlRequest> {
let listener = match TcpListener::bind(("127.0.0.1", port)) {
Ok(l) => l,
Err(e) => {
eprintln!("modelcraft: control server failed to bind 127.0.0.1:{port}: {e}");
eprintln!("fieldcraft: control server failed to bind 127.0.0.1:{port}: {e}");
return rx;
}
};
eprintln!("modelcraft: control server listening on 127.0.0.1:{port}");
eprintln!("fieldcraft: control server listening on 127.0.0.1:{port}");
std::thread::spawn(move || {
for stream in listener.incoming().flatten() {
if OPEN.fetch_add(1, Ordering::Relaxed) >= MAX_CONNECTIONS {
@@ -1,23 +1,23 @@
//! ModelCraft desktop app.
//! FieldCraft desktop app.
//!
//! Usage: `modelcraft [--sample [NAME]] [--control PORT] [FILE…]` (files open as if dropped on the
//! window; NAME is a sample of `modelcraft_engine::sample`, `parts` by default)
//! Usage: `fieldcraft [--sample [NAME]] [--control PORT] [FILE…]` (files open as if dropped on the
//! window; NAME is a sample of `fieldcraft_engine::sample`, `parts` by default)
//!
//! `--sample` opens a sample model (`modelcraft_engine::sample`). `--control PORT` (or
//! `MODELCRAFT_CONTROL_PORT`) starts a localhost JSON-lines control server:
//! `--sample` opens a sample model (`fieldcraft_engine::sample`). `--control PORT` (or
//! `FIELDCRAFT_CONTROL_PORT`) starts a localhost JSON-lines control server:
//! `{"id":1,"method":"engine.execute","params":{"command":"model.info","params":{}}}`. See
//! `modelcraft_ui_egui::control` and docs/control-protocol.md.
//! `fieldcraft_ui_egui::control` and docs/control-protocol.md.
#![cfg_attr(all(target_os = "windows", not(debug_assertions)), windows_subsystem = "windows")]
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
#![forbid(unsafe_code)]
mod control_server;
use modelcraft_engine::Session;
use modelcraft_engine::autosave::{self, Autosaver};
use modelcraft_ui_egui::{Filter, ModelApp, Services};
use fieldcraft_engine::Session;
use fieldcraft_engine::autosave::{self, Autosaver};
use fieldcraft_ui_egui::{Filter, ModelApp, Services};
const USAGE: &str = "usage: modelcraft [--sample [parts|cantilever|impact]] [--control PORT] [FILE…] (FILE: .modelcraft, .solidcraft or .3mf)";
const USAGE: &str = "usage: fieldcraft [--sample [parts|cantilever|impact]] [--control PORT] [FILE…] (FILE: .fieldcraft, .solidcraft or .3mf)";
const DEPTH_BITS: u8 = 24;
/// The window draws its own caption buttons where the OS ones would sit apart from the app bar.
const CUSTOM_TITLEBAR: bool = !cfg!(target_os = "macos");
@@ -90,7 +90,7 @@ fn services() -> Services {
dialog(filters).set_file_name(name).save_file().map(|p| p.to_string_lossy().to_string())
})),
// Beside the recovery folder in the per-user data folder.
recent_path: modelcraft_io::recovery::default_dir().and_then(|d| d.parent().map(|p| p.join("recent.json"))),
recent_path: fieldcraft_io::recovery::default_dir().and_then(|d| d.parent().map(|p| p.join("recent.json"))),
}
}
@@ -111,7 +111,7 @@ fn parse_args(args: impl IntoIterator<Item = String>, env_port: Option<String>)
match a.as_str() {
"--sample" => {
// An optional sample name follows.
let name = args.peek().filter(|a| modelcraft_engine::sample::find(a).is_some()).cloned();
let name = args.peek().filter(|a| fieldcraft_engine::sample::find(a).is_some()).cloned();
if name.is_some() {
args.next();
}
@@ -122,7 +122,7 @@ fn parse_args(args: impl IntoIterator<Item = String>, env_port: Option<String>)
out.control = Some(port.parse().map_err(|_| format!("--control: `{port}` is not a port number"))?);
}
"--version" | "-V" => {
println!("modelcraft {}", modelcraft_ui_egui::VERSION);
println!("fieldcraft {}", fieldcraft_ui_egui::VERSION);
return Ok(None);
}
"--help" | "-h" => {
@@ -137,17 +137,17 @@ fn parse_args(args: impl IntoIterator<Item = String>, env_port: Option<String>)
}
fn main() -> eframe::Result {
let args = match parse_args(std::env::args().skip(1), std::env::var("MODELCRAFT_CONTROL_PORT").ok()) {
let args = match parse_args(std::env::args().skip(1), std::env::var("FIELDCRAFT_CONTROL_PORT").ok()) {
Ok(Some(a)) => a,
Ok(None) => return Ok(()),
Err(e) => {
eprintln!("modelcraft: {e}");
eprintln!("fieldcraft: {e}");
std::process::exit(2);
}
};
let options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default()
.with_title("ModelCraft")
.with_title("FieldCraft")
.with_icon(app_icon())
.with_inner_size([1400.0, 900.0])
.with_min_inner_size([960.0, 600.0])
@@ -163,7 +163,7 @@ fn main() -> eframe::Result {
..Default::default()
};
eframe::run_native(
"ModelCraft",
"FieldCraft",
options,
Box::new(move |cc| {
let mut app = ModelApp::new(Session::default()).with_services(services());
@@ -171,7 +171,7 @@ fn main() -> eframe::Result {
app.integrated_titlebar = cfg!(target_os = "macos");
if let Some(rs) = &cc.wgpu_render_state {
let info = rs.adapter.get_info();
eprintln!("modelcraft: graphics {:?} {}, MSAA {}x (requested {MSAA}x)", info.backend, info.name, rs.msaa_samples);
eprintln!("fieldcraft: graphics {:?} {}, MSAA {}x (requested {MSAA}x)", info.backend, info.name, rs.msaa_samples);
app.set_wgpu(rs, DEPTH_BITS, rs.msaa_samples, &cc.egui_ctx);
}
if let Some(port) = args.control {
@@ -224,9 +224,9 @@ mod tests {
assert!(parse(&["--control", "99999"], None).is_err());
assert!(parse(&["--frobnicate"], None).is_err());
assert_eq!(parse(&["--sample", "impact"], None).map(|a| a.and_then(|a| a.sample)), Ok(Some("impact".into())));
let a = parse(&["--sample", "x.modelcraft"], None).unwrap().unwrap();
assert_eq!((a.sample.as_deref(), a.files), (Some("parts"), vec!["x.modelcraft".to_string()]));
assert_eq!(parse(&["a.modelcraft", "--sample"], None).map(|a| a.map(|a| a.files)), Ok(Some(vec!["a.modelcraft".to_string()])));
let a = parse(&["--sample", "x.fieldcraft"], None).unwrap().unwrap();
assert_eq!((a.sample.as_deref(), a.files), (Some("parts"), vec!["x.fieldcraft".to_string()]));
assert_eq!(parse(&["a.fieldcraft", "--sample"], None).map(|a| a.map(|a| a.files)), Ok(Some(vec!["a.fieldcraft".to_string()])));
assert_eq!(parse(&["--help"], None), Ok(None));
}
}
-15
View File
@@ -1,15 +0,0 @@
[package]
name = "modelcraft-cli"
description = "ModelCraft from the command line: list commands, run scripts, serve MCP"
version.workspace = true
edition.workspace = true
license.workspace = true
[lints]
workspace = true
[dependencies]
modelcraft-engine = { workspace = true }
modelcraft-io = { workspace = true }
modelcraft-mcp = { workspace = true }
serde_json = { workspace = true }