diff --git a/.gitignore b/.gitignore
index 686ddf2..5fb7f9e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
 /target
 /libcollar_store
 **/libcollar_store
+crates/api/test_api*
diff --git a/Cargo.lock b/Cargo.lock
index a6a646b..75b0884 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1,2088 +1,2730 @@
 # This file is automatically @generated by Cargo.
 # It is not intended for manual editing.
 version = 4
 
 [[package]]
 name = "addr2line"
 version = "0.24.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
 dependencies = [
  "gimli",
 ]
 
 [[package]]
 name = "adler2"
 version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
 
 [[package]]
 name = "aho-corasick"
 version = "1.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
 dependencies = [
  "memchr",
 ]
 
 [[package]]
 name = "anstream"
 version = "0.6.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
 dependencies = [
  "anstyle",
  "anstyle-parse",
  "anstyle-query",
  "anstyle-wincon",
  "colorchoice",
  "is_terminal_polyfill",
  "utf8parse",
 ]
 
 [[package]]
 name = "anstyle"
 version = "1.0.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
 
 [[package]]
 name = "anstyle-parse"
 version = "0.2.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
 dependencies = [
  "utf8parse",
 ]
 
 [[package]]
 name = "anstyle-query"
 version = "1.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
 dependencies = [
  "windows-sys 0.59.0",
 ]
 
 [[package]]
 name = "anstyle-wincon"
 version = "3.0.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6680de5231bd6ee4c6191b8a1325daa282b415391ec9d3a37bd34f2060dc73fa"
 dependencies = [
  "anstyle",
  "once_cell_polyfill",
  "windows-sys 0.59.0",
 ]
 
 [[package]]
 name = "anyhow"
 version = "1.0.98"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
 
 [[package]]
 name = "api"
 version = "0.1.0"
 dependencies = [
  "axum",
  "axum-test",
  "libcollar",
  "log",
  "serde",
  "serde_json",
  "store",
  "tempfile",
  "thiserror",
  "tokio",
  "tokio-test",
  "tower-http",
  "tracing",
 ]
 
 [[package]]
 name = "assert-json-diff"
 version = "2.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12"
 dependencies = [
  "serde",
  "serde_json",
 ]
 
 [[package]]
 name = "async-stream"
 version = "0.3.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476"
 dependencies = [
  "async-stream-impl",
  "futures-core",
  "pin-project-lite",
 ]
 
 [[package]]
 name = "async-stream-impl"
 version = "0.3.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d"
 dependencies = [
  "proc-macro2",
  "quote",
  "syn",
 ]
 
+[[package]]
+name = "atomic-waker"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+
 [[package]]
 name = "auto-future"
 version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3c1e7e457ea78e524f48639f551fd79703ac3f2237f5ecccdf4708f8a75ad373"
 
 [[package]]
 name = "autocfg"
 version = "1.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
 
 [[package]]
 name = "axum"
 version = "0.8.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5"
 dependencies = [
  "axum-core",
  "bytes",
  "form_urlencoded",
  "futures-util",
  "http",
  "http-body",
  "http-body-util",
  "hyper",
  "hyper-util",
  "itoa",
  "matchit",
  "memchr",
  "mime",
  "percent-encoding",
  "pin-project-lite",
  "rustversion",
  "serde",
  "serde_json",
  "serde_path_to_error",
  "serde_urlencoded",
  "sync_wrapper",
  "tokio",
  "tower",
  "tower-layer",
  "tower-service",
  "tracing",
 ]
 
 [[package]]
 name = "axum-core"
 version = "0.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6"
 dependencies = [
  "bytes",
  "futures-core",
  "http",
  "http-body",
  "http-body-util",
  "mime",
  "pin-project-lite",
  "rustversion",
  "sync_wrapper",
  "tower-layer",
  "tower-service",
  "tracing",
 ]
 
 [[package]]
 name = "axum-test"
 version = "17.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0eb1dfb84bd48bad8e4aa1acb82ed24c2bb5e855b659959b4e03b4dca118fcac"
 dependencies = [
  "anyhow",
  "assert-json-diff",
  "auto-future",
  "axum",
  "bytes",
  "bytesize",
  "cookie",
  "http",
  "http-body-util",
  "hyper",
  "hyper-util",
  "mime",
  "pretty_assertions",
  "reserve-port",
  "rust-multipart-rfc7578_2",
  "serde",
  "serde_json",
  "serde_urlencoded",
  "smallvec",
  "tokio",
  "tower",
  "url",
 ]
 
 [[package]]
 name = "backtrace"
 version = "0.3.75"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002"
 dependencies = [
  "addr2line",
  "cfg-if",
  "libc",
  "miniz_oxide",
  "object",
  "rustc-demangle",
  "windows-targets 0.52.6",
 ]
 
+[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
 [[package]]
 name = "bindgen"
 version = "0.71.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3"
 dependencies = [
  "bitflags 2.9.1",
  "cexpr",
  "clang-sys",
  "itertools",
  "log",
  "prettyplease",
  "proc-macro2",
  "quote",
  "regex",
  "rustc-hash",
  "shlex",
  "syn",
 ]
 
 [[package]]
 name = "bitflags"
 version = "1.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
 
 [[package]]
 name = "bitflags"
 version = "2.9.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
 
+[[package]]
+name = "bumpalo"
+version = "3.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
+
 [[package]]
 name = "byteorder"
 version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
 
 [[package]]
 name = "bytes"
 version = "1.10.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
 
 [[package]]
 name = "bytesize"
 version = "2.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a3c8f83209414aacf0eeae3cf730b18d6981697fba62f200fcfb92b9f082acba"
 
+[[package]]
+name = "cc"
+version = "1.2.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0fc897dc1e865cc67c0e05a836d9d3f1df3cbe442aa4a9473b18e12624a4951"
+dependencies = [
+ "shlex",
+]
+
 [[package]]
 name = "cexpr"
 version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
 dependencies = [
  "nom",
 ]
 
 [[package]]
 name = "cfg-if"
 version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 
 [[package]]
 name = "cfg_aliases"
 version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
 
 [[package]]
 name = "clang-sys"
 version = "1.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
 dependencies = [
  "glob",
  "libc",
  "libloading",
 ]
 
+[[package]]
+name = "clap"
+version = "4.5.39"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd60e63e9be68e5fb56422e397cf9baddded06dae1d2e523401542383bc72a9f"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.5.39"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89cc6392a1f72bbeb820d71f32108f61fdaf18bc526e1d23954168a67759ef51"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.5.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
+
 [[package]]
 name = "collar"
 version = "0.1.0"
 dependencies = [
  "api",
  "env_logger",
  "libcollar",
  "log",
  "thiserror",
  "tokio",
  "tracing",
  "tracing-subscriber",
 ]
 
+[[package]]
+name = "collar-cli"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "clap",
+ "reqwest",
+ "serde",
+ "serde_json",
+ "tokio",
+ "url",
+]
+
 [[package]]
 name = "colorchoice"
 version = "1.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
 
 [[package]]
 name = "cookie"
 version = "0.18.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
 dependencies = [
  "time",
  "version_check",
 ]
 
+[[package]]
+name = "core-foundation"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
 [[package]]
 name = "crc32fast"
 version = "1.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
 dependencies = [
  "cfg-if",
 ]
 
 [[package]]
 name = "crossbeam-epoch"
 version = "0.9.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
 dependencies = [
  "crossbeam-utils",
 ]
 
 [[package]]
 name = "crossbeam-utils"
 version = "0.8.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
 
 [[package]]
 name = "deranged"
 version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e"
 dependencies = [
  "powerfmt",
 ]
 
 [[package]]
 name = "diff"
 version = "0.1.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
 
 [[package]]
 name = "displaydoc"
 version = "0.2.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
 dependencies = [
  "proc-macro2",
  "quote",
  "syn",
 ]
 
 [[package]]
 name = "either"
 version = "1.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
 
+[[package]]
+name = "encoding_rs"
+version = "0.8.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
+dependencies = [
+ "cfg-if",
+]
+
 [[package]]
 name = "env_filter"
 version = "0.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0"
 dependencies = [
  "log",
  "regex",
 ]
 
 [[package]]
 name = "env_logger"
 version = "0.11.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f"
 dependencies = [
  "anstream",
  "anstyle",
  "env_filter",
  "jiff",
  "log",
 ]
 
+[[package]]
+name = "equivalent"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+
 [[package]]
 name = "errno"
 version = "0.3.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18"
 dependencies = [
  "libc",
  "windows-sys 0.59.0",
 ]
 
 [[package]]
 name = "fastrand"
 version = "2.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
 
 [[package]]
 name = "fnv"
 version = "1.0.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
 
+[[package]]
+name = "foreign-types"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
+dependencies = [
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
+
 [[package]]
 name = "form_urlencoded"
 version = "1.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
 dependencies = [
  "percent-encoding",
 ]
 
 [[package]]
 name = "fs2"
 version = "0.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
 dependencies = [
  "libc",
  "winapi",
 ]
 
 [[package]]
 name = "futures-channel"
 version = "0.3.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
 dependencies = [
  "futures-core",
 ]
 
 [[package]]
 name = "futures-core"
 version = "0.3.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
 
 [[package]]
 name = "futures-io"
 version = "0.3.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
 
+[[package]]
+name = "futures-sink"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
+
 [[package]]
 name = "futures-task"
 version = "0.3.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
 
 [[package]]
 name = "futures-util"
 version = "0.3.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
 dependencies = [
  "futures-core",
  "futures-io",
  "futures-task",
  "memchr",
  "pin-project-lite",
  "pin-utils",
  "slab",
 ]
 
 [[package]]
 name = "fxhash"
 version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
 dependencies = [
  "byteorder",
 ]
 
+[[package]]
+name = "getrandom"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi 0.11.0+wasi-snapshot-preview1",
+]
+
 [[package]]
 name = "getrandom"
 version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
 dependencies = [
  "cfg-if",
  "libc",
  "r-efi",
  "wasi 0.14.2+wasi-0.2.4",
 ]
 
 [[package]]
 name = "gimli"
 version = "0.31.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
 
 [[package]]
 name = "glob"
 version = "0.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
 
+[[package]]
+name = "h2"
+version = "0.4.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9421a676d1b147b16b82c9225157dc629087ef8ec4d5e2960f9437a90dac0a5"
+dependencies = [
+ "atomic-waker",
+ "bytes",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "http",
+ "indexmap",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tracing",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.15.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3"
+
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
 [[package]]
 name = "http"
 version = "1.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
 dependencies = [
  "bytes",
  "fnv",
  "itoa",
 ]
 
 [[package]]
 name = "http-body"
 version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
 dependencies = [
  "bytes",
  "http",
 ]
 
 [[package]]
 name = "http-body-util"
 version = "0.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
 dependencies = [
  "bytes",
  "futures-core",
  "http",
  "http-body",
  "pin-project-lite",
 ]
 
 [[package]]
 name = "httparse"
 version = "1.10.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
 
 [[package]]
 name = "httpdate"
 version = "1.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
 
 [[package]]
 name = "hyper"
 version = "1.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80"
 dependencies = [
  "bytes",
  "futures-channel",
  "futures-util",
+ "h2",
  "http",
  "http-body",
  "httparse",
  "httpdate",
  "itoa",
  "pin-project-lite",
  "smallvec",
  "tokio",
  "want",
 ]
 
+[[package]]
+name = "hyper-rustls"
+version = "0.27.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03a01595e11bdcec50946522c32dde3fc6914743000a68b93000965f2f02406d"
+dependencies = [
+ "http",
+ "hyper",
+ "hyper-util",
+ "rustls",
+ "rustls-pki-types",
+ "tokio",
+ "tokio-rustls",
+ "tower-service",
+]
+
+[[package]]
+name = "hyper-tls"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
+dependencies = [
+ "bytes",
+ "http-body-util",
+ "hyper",
+ "hyper-util",
+ "native-tls",
+ "tokio",
+ "tokio-native-tls",
+ "tower-service",
+]
+
 [[package]]
 name = "hyper-util"
 version = "0.1.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b1c293b6b3d21eca78250dc7dbebd6b9210ec5530e038cbfe0661b5c47ab06e8"
 dependencies = [
+ "base64",
  "bytes",
  "futures-channel",
  "futures-core",
  "futures-util",
  "http",
  "http-body",
  "hyper",
+ "ipnet",
  "libc",
+ "percent-encoding",
  "pin-project-lite",
  "socket2",
+ "system-configuration",
  "tokio",
  "tower-service",
  "tracing",
+ "windows-registry",
 ]
 
 [[package]]
 name = "icu_collections"
 version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47"
 dependencies = [
  "displaydoc",
  "potential_utf",
  "yoke",
  "zerofrom",
  "zerovec",
 ]
 
 [[package]]
 name = "icu_locale_core"
 version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a"
 dependencies = [
  "displaydoc",
  "litemap",
  "tinystr",
  "writeable",
  "zerovec",
 ]
 
 [[package]]
 name = "icu_normalizer"
 version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979"
 dependencies = [
  "displaydoc",
  "icu_collections",
  "icu_normalizer_data",
  "icu_properties",
  "icu_provider",
  "smallvec",
  "zerovec",
 ]
 
 [[package]]
 name = "icu_normalizer_data"
 version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3"
 
 [[package]]
 name = "icu_properties"
 version = "2.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b"
 dependencies = [
  "displaydoc",
  "icu_collections",
  "icu_locale_core",
  "icu_properties_data",
  "icu_provider",
  "potential_utf",
  "zerotrie",
  "zerovec",
 ]
 
 [[package]]
 name = "icu_properties_data"
 version = "2.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632"
 
 [[package]]
 name = "icu_provider"
 version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af"
 dependencies = [
  "displaydoc",
  "icu_locale_core",
  "stable_deref_trait",
  "tinystr",
  "writeable",
  "yoke",
  "zerofrom",
  "zerotrie",
  "zerovec",
 ]
 
 [[package]]
 name = "idna"
 version = "1.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
 dependencies = [
  "idna_adapter",
  "smallvec",
  "utf8_iter",
 ]
 
 [[package]]
 name = "idna_adapter"
 version = "1.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
 dependencies = [
  "icu_normalizer",
  "icu_properties",
 ]
 
 [[package]]
 name = "ifconfig"
 version = "0.1.0"
 dependencies = [
  "errno",
  "libc",
  "log",
  "nix",
  "thiserror",
 ]
 
+[[package]]
+name = "indexmap"
+version = "2.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
+dependencies = [
+ "equivalent",
+ "hashbrown",
+]
+
 [[package]]
 name = "instant"
 version = "0.1.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
 dependencies = [
  "cfg-if",
 ]
 
+[[package]]
+name = "ipnet"
+version = "2.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
+
+[[package]]
+name = "iri-string"
+version = "0.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2"
+dependencies = [
+ "memchr",
+ "serde",
+]
+
 [[package]]
 name = "is_terminal_polyfill"
 version = "1.70.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
 
 [[package]]
 name = "itertools"
 version = "0.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
 dependencies = [
  "either",
 ]
 
 [[package]]
 name = "itoa"
 version = "1.0.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
 
 [[package]]
 name = "jiff"
 version = "0.2.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a194df1107f33c79f4f93d02c80798520551949d59dfad22b6157048a88cca93"
 dependencies = [
  "jiff-static",
  "log",
  "portable-atomic",
  "portable-atomic-util",
  "serde",
 ]
 
 [[package]]
 name = "jiff-static"
 version = "0.2.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6c6e1db7ed32c6c71b759497fae34bf7933636f75a251b9e736555da426f6442"
 dependencies = [
  "proc-macro2",
  "quote",
  "syn",
 ]
 
+[[package]]
+name = "js-sys"
+version = "0.3.77"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
+dependencies = [
+ "once_cell",
+ "wasm-bindgen",
+]
+
 [[package]]
 name = "lazy_static"
 version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
 
 [[package]]
 name = "libc"
 version = "0.2.172"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
 
 [[package]]
 name = "libcollar"
 version = "0.1.0"
 dependencies = [
  "ifconfig",
  "log",
  "ng",
  "serde",
  "store",
  "thiserror",
  "tokio",
  "tracing",
 ]
 
 [[package]]
 name = "libloading"
 version = "0.8.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667"
 dependencies = [
  "cfg-if",
  "windows-targets 0.53.0",
 ]
 
 [[package]]
 name = "linux-raw-sys"
 version = "0.9.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
 
 [[package]]
 name = "litemap"
 version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
 
 [[package]]
 name = "lock_api"
 version = "0.4.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765"
 dependencies = [
  "autocfg",
  "scopeguard",
 ]
 
 [[package]]
 name = "log"
 version = "0.4.27"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
 
 [[package]]
 name = "matchers"
 version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
 dependencies = [
  "regex-automata 0.1.10",
 ]
 
 [[package]]
 name = "matchit"
 version = "0.8.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
 
 [[package]]
 name = "memchr"
 version = "2.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
 
 [[package]]
 name = "memoffset"
 version = "0.9.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
 dependencies = [
  "autocfg",
 ]
 
 [[package]]
 name = "mime"
 version = "0.3.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
 
 [[package]]
 name = "minimal-lexical"
 version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
 
 [[package]]
 name = "miniz_oxide"
 version = "0.8.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a"
 dependencies = [
  "adler2",
 ]
 
 [[package]]
 name = "mio"
 version = "1.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
 dependencies = [
  "libc",
  "wasi 0.11.0+wasi-snapshot-preview1",
  "windows-sys 0.59.0",
 ]
 
+[[package]]
+name = "native-tls"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
+dependencies = [
+ "libc",
+ "log",
+ "openssl",
+ "openssl-probe",
+ "openssl-sys",
+ "schannel",
+ "security-framework",
+ "security-framework-sys",
+ "tempfile",
+]
+
 [[package]]
 name = "ng"
 version = "0.1.0"
 dependencies = [
  "bindgen",
  "errno",
  "libc",
  "log",
  "socket2",
  "thiserror",
 ]
 
 [[package]]
 name = "nix"
 version = "0.30.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
 dependencies = [
  "bitflags 2.9.1",
  "cfg-if",
  "cfg_aliases",
  "libc",
  "memoffset",
 ]
 
 [[package]]
 name = "nom"
 version = "7.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
 dependencies = [
  "memchr",
  "minimal-lexical",
 ]
 
 [[package]]
 name = "nu-ansi-term"
 version = "0.46.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
 dependencies = [
  "overload",
  "winapi",
 ]
 
 [[package]]
 name = "num-conv"
 version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
 
 [[package]]
 name = "object"
 version = "0.36.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
 dependencies = [
  "memchr",
 ]
 
 [[package]]
 name = "once_cell"
 version = "1.21.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
 
 [[package]]
 name = "once_cell_polyfill"
 version = "1.70.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
 
+[[package]]
+name = "openssl"
+version = "0.10.73"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8"
+dependencies = [
+ "bitflags 2.9.1",
+ "cfg-if",
+ "foreign-types",
+ "libc",
+ "once_cell",
+ "openssl-macros",
+ "openssl-sys",
+]
+
+[[package]]
+name = "openssl-macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "openssl-probe"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
 [[package]]
 name = "overload"
 version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
 
 [[package]]
 name = "parking_lot"
 version = "0.11.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
 dependencies = [
  "instant",
  "lock_api",
  "parking_lot_core 0.8.6",
 ]
 
 [[package]]
 name = "parking_lot"
 version = "0.12.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13"
 dependencies = [
  "lock_api",
  "parking_lot_core 0.9.11",
 ]
 
 [[package]]
 name = "parking_lot_core"
 version = "0.8.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
 dependencies = [
  "cfg-if",
  "instant",
  "libc",
  "redox_syscall 0.2.16",
  "smallvec",
  "winapi",
 ]
 
 [[package]]
 name = "parking_lot_core"
 version = "0.9.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
 dependencies = [
  "cfg-if",
  "libc",
  "redox_syscall 0.5.12",
  "smallvec",
  "windows-targets 0.52.6",
 ]
 
 [[package]]
 name = "percent-encoding"
 version = "2.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
 
 [[package]]
 name = "pin-project-lite"
 version = "0.2.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
 
 [[package]]
 name = "pin-utils"
 version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
 
+[[package]]
+name = "pkg-config"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
+
 [[package]]
 name = "portable-atomic"
 version = "1.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
 
 [[package]]
 name = "portable-atomic-util"
 version = "0.2.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
 dependencies = [
  "portable-atomic",
 ]
 
 [[package]]
 name = "potential_utf"
 version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585"
 dependencies = [
  "zerovec",
 ]
 
 [[package]]
 name = "powerfmt"
 version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
 
 [[package]]
 name = "ppv-lite86"
 version = "0.2.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
 dependencies = [
  "zerocopy",
 ]
 
 [[package]]
 name = "pretty_assertions"
 version = "1.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d"
 dependencies = [
  "diff",
  "yansi",
 ]
 
 [[package]]
 name = "prettyplease"
 version = "0.2.32"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6"
 dependencies = [
  "proc-macro2",
  "syn",
 ]
 
 [[package]]
 name = "proc-macro2"
 version = "1.0.95"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
 dependencies = [
  "unicode-ident",
 ]
 
 [[package]]
 name = "quote"
 version = "1.0.40"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
 dependencies = [
  "proc-macro2",
 ]
 
 [[package]]
 name = "r-efi"
 version = "5.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
 
 [[package]]
 name = "rand"
 version = "0.9.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
 dependencies = [
  "rand_chacha",
  "rand_core",
 ]
 
 [[package]]
 name = "rand_chacha"
 version = "0.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
 dependencies = [
  "ppv-lite86",
  "rand_core",
 ]
 
 [[package]]
 name = "rand_core"
 version = "0.9.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
 dependencies = [
- "getrandom",
+ "getrandom 0.3.3",
 ]
 
 [[package]]
 name = "redox_syscall"
 version = "0.2.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
 dependencies = [
  "bitflags 1.3.2",
 ]
 
 [[package]]
 name = "redox_syscall"
 version = "0.5.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af"
 dependencies = [
  "bitflags 2.9.1",
 ]
 
 [[package]]
 name = "regex"
 version = "1.11.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
 dependencies = [
  "aho-corasick",
  "memchr",
  "regex-automata 0.4.9",
  "regex-syntax 0.8.5",
 ]
 
 [[package]]
 name = "regex-automata"
 version = "0.1.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
 dependencies = [
  "regex-syntax 0.6.29",
 ]
 
 [[package]]
 name = "regex-automata"
 version = "0.4.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
 dependencies = [
  "aho-corasick",
  "memchr",
  "regex-syntax 0.8.5",
 ]
 
 [[package]]
 name = "regex-syntax"
 version = "0.6.29"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
 
 [[package]]
 name = "regex-syntax"
 version = "0.8.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
 
+[[package]]
+name = "reqwest"
+version = "0.12.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e98ff6b0dbbe4d5a37318f433d4fc82babd21631f194d370409ceb2e40b2f0b5"
+dependencies = [
+ "base64",
+ "bytes",
+ "encoding_rs",
+ "futures-core",
+ "h2",
+ "http",
+ "http-body",
+ "http-body-util",
+ "hyper",
+ "hyper-rustls",
+ "hyper-tls",
+ "hyper-util",
+ "ipnet",
+ "js-sys",
+ "log",
+ "mime",
+ "native-tls",
+ "once_cell",
+ "percent-encoding",
+ "pin-project-lite",
+ "rustls-pki-types",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "sync_wrapper",
+ "tokio",
+ "tokio-native-tls",
+ "tower",
+ "tower-http",
+ "tower-service",
+ "url",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
 [[package]]
 name = "reserve-port"
 version = "2.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "21918d6644020c6f6ef1993242989bf6d4952d2e025617744f184c02df51c356"
 dependencies = [
  "thiserror",
 ]
 
+[[package]]
+name = "ring"
+version = "0.17.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
+dependencies = [
+ "cc",
+ "cfg-if",
+ "getrandom 0.2.16",
+ "libc",
+ "untrusted",
+ "windows-sys 0.52.0",
+]
+
 [[package]]
 name = "rust-multipart-rfc7578_2"
 version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c839d037155ebc06a571e305af66ff9fd9063a6e662447051737e1ac75beea41"
 dependencies = [
  "bytes",
  "futures-core",
  "futures-util",
  "http",
  "mime",
  "rand",
  "thiserror",
 ]
 
 [[package]]
 name = "rustc-demangle"
 version = "0.1.24"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
 
 [[package]]
 name = "rustc-hash"
 version = "2.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
 
 [[package]]
 name = "rustix"
 version = "1.0.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
 dependencies = [
  "bitflags 2.9.1",
  "errno",
  "libc",
  "linux-raw-sys",
  "windows-sys 0.59.0",
 ]
 
+[[package]]
+name = "rustls"
+version = "0.23.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321"
+dependencies = [
+ "once_cell",
+ "rustls-pki-types",
+ "rustls-webpki",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-pki-types"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79"
+dependencies = [
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-webpki"
+version = "0.103.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435"
+dependencies = [
+ "ring",
+ "rustls-pki-types",
+ "untrusted",
+]
+
 [[package]]
 name = "rustversion"
 version = "1.0.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
 
 [[package]]
 name = "ryu"
 version = "1.0.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
 
+[[package]]
+name = "schannel"
+version = "0.1.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
+dependencies = [
+ "windows-sys 0.59.0",
+]
+
 [[package]]
 name = "scopeguard"
 version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
 
+[[package]]
+name = "security-framework"
+version = "2.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
+dependencies = [
+ "bitflags 2.9.1",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "2.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
 [[package]]
 name = "serde"
 version = "1.0.219"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
 version = "1.0.219"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
 dependencies = [
  "proc-macro2",
  "quote",
  "syn",
 ]
 
 [[package]]
 name = "serde_json"
 version = "1.0.140"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
 dependencies = [
  "itoa",
  "memchr",
  "ryu",
  "serde",
 ]
 
 [[package]]
 name = "serde_path_to_error"
 version = "0.1.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a"
 dependencies = [
  "itoa",
  "serde",
 ]
 
 [[package]]
 name = "serde_urlencoded"
 version = "0.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
 dependencies = [
  "form_urlencoded",
  "itoa",
  "ryu",
  "serde",
 ]
 
 [[package]]
 name = "sharded-slab"
 version = "0.1.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
 dependencies = [
  "lazy_static",
 ]
 
 [[package]]
 name = "shlex"
 version = "1.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
 
 [[package]]
 name = "signal-hook-registry"
 version = "1.4.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410"
 dependencies = [
  "libc",
 ]
 
 [[package]]
 name = "slab"
 version = "0.4.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
 dependencies = [
  "autocfg",
 ]
 
 [[package]]
 name = "sled"
 version = "0.34.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935"
 dependencies = [
  "crc32fast",
  "crossbeam-epoch",
  "crossbeam-utils",
  "fs2",
  "fxhash",
  "libc",
  "log",
  "parking_lot 0.11.2",
 ]
 
 [[package]]
 name = "smallvec"
 version = "1.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9"
 
 [[package]]
 name = "socket2"
 version = "0.5.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
 dependencies = [
  "libc",
  "windows-sys 0.52.0",
 ]
 
 [[package]]
 name = "stable_deref_trait"
 version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
 
 [[package]]
 name = "store"
 version = "0.1.0"
 dependencies = [
  "log",
  "serde",
  "serde_json",
  "sled",
  "tempfile",
  "thiserror",
 ]
 
+[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
+[[package]]
+name = "subtle"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
+
 [[package]]
 name = "syn"
 version = "2.0.101"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
 dependencies = [
  "proc-macro2",
  "quote",
  "unicode-ident",
 ]
 
 [[package]]
 name = "sync_wrapper"
 version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
+dependencies = [
+ "futures-core",
+]
 
 [[package]]
 name = "synstructure"
 version = "0.13.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
 dependencies = [
  "proc-macro2",
  "quote",
  "syn",
 ]
 
+[[package]]
+name = "system-configuration"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
+dependencies = [
+ "bitflags 2.9.1",
+ "core-foundation",
+ "system-configuration-sys",
+]
+
+[[package]]
+name = "system-configuration-sys"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
 [[package]]
 name = "tempfile"
 version = "3.20.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1"
 dependencies = [
  "fastrand",
- "getrandom",
+ "getrandom 0.3.3",
  "once_cell",
  "rustix",
  "windows-sys 0.59.0",
 ]
 
 [[package]]
 name = "thiserror"
 version = "2.0.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
 version = "2.0.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
 dependencies = [
  "proc-macro2",
  "quote",
  "syn",
 ]
 
 [[package]]
 name = "thread_local"
 version = "1.1.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
 dependencies = [
  "cfg-if",
  "once_cell",
 ]
 
 [[package]]
 name = "time"
 version = "0.3.41"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40"
 dependencies = [
  "deranged",
  "itoa",
  "num-conv",
  "powerfmt",
  "serde",
  "time-core",
  "time-macros",
 ]
 
 [[package]]
 name = "time-core"
 version = "0.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c"
 
 [[package]]
 name = "time-macros"
 version = "0.2.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49"
 dependencies = [
  "num-conv",
  "time-core",
 ]
 
 [[package]]
 name = "tinystr"
 version = "0.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b"
 dependencies = [
  "displaydoc",
  "zerovec",
 ]
 
 [[package]]
 name = "tokio"
 version = "1.45.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779"
 dependencies = [
  "backtrace",
  "bytes",
  "libc",
  "mio",
  "parking_lot 0.12.4",
  "pin-project-lite",
  "signal-hook-registry",
  "socket2",
  "tokio-macros",
  "windows-sys 0.52.0",
 ]
 
 [[package]]
 name = "tokio-macros"
 version = "2.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
 dependencies = [
  "proc-macro2",
  "quote",
  "syn",
 ]
 
+[[package]]
+name = "tokio-native-tls"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
+dependencies = [
+ "native-tls",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-rustls"
+version = "0.26.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b"
+dependencies = [
+ "rustls",
+ "tokio",
+]
+
 [[package]]
 name = "tokio-stream"
 version = "0.1.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047"
 dependencies = [
  "futures-core",
  "pin-project-lite",
  "tokio",
 ]
 
 [[package]]
 name = "tokio-test"
 version = "0.4.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2468baabc3311435b55dd935f702f42cd1b8abb7e754fb7dfb16bd36aa88f9f7"
 dependencies = [
  "async-stream",
  "bytes",
  "futures-core",
  "tokio",
  "tokio-stream",
 ]
 
+[[package]]
+name = "tokio-util"
+version = "0.7.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "pin-project-lite",
+ "tokio",
+]
+
 [[package]]
 name = "tower"
 version = "0.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9"
 dependencies = [
  "futures-core",
  "futures-util",
  "pin-project-lite",
  "sync_wrapper",
  "tokio",
  "tower-layer",
  "tower-service",
  "tracing",
 ]
 
 [[package]]
 name = "tower-http"
 version = "0.6.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0fdb0c213ca27a9f57ab69ddb290fd80d970922355b83ae380b395d3986b8a2e"
 dependencies = [
  "bitflags 2.9.1",
  "bytes",
+ "futures-util",
  "http",
  "http-body",
+ "iri-string",
  "pin-project-lite",
+ "tower",
  "tower-layer",
  "tower-service",
  "tracing",
 ]
 
 [[package]]
 name = "tower-layer"
 version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
 
 [[package]]
 name = "tower-service"
 version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
 
 [[package]]
 name = "tracing"
 version = "0.1.41"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
 dependencies = [
  "log",
  "pin-project-lite",
  "tracing-attributes",
  "tracing-core",
 ]
 
 [[package]]
 name = "tracing-attributes"
 version = "0.1.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
 dependencies = [
  "proc-macro2",
  "quote",
  "syn",
 ]
 
 [[package]]
 name = "tracing-core"
 version = "0.1.33"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
 dependencies = [
  "once_cell",
  "valuable",
 ]
 
 [[package]]
 name = "tracing-log"
 version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
 dependencies = [
  "log",
  "once_cell",
  "tracing-core",
 ]
 
 [[package]]
 name = "tracing-subscriber"
 version = "0.3.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
 dependencies = [
  "matchers",
  "nu-ansi-term",
  "once_cell",
  "regex",
  "sharded-slab",
  "smallvec",
  "thread_local",
  "tracing",
  "tracing-core",
  "tracing-log",
 ]
 
 [[package]]
 name = "try-lock"
 version = "0.2.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
 
 [[package]]
 name = "unicode-ident"
 version = "1.0.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
 
+[[package]]
+name = "untrusted"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
+
 [[package]]
 name = "url"
 version = "2.5.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
 dependencies = [
  "form_urlencoded",
  "idna",
  "percent-encoding",
 ]
 
 [[package]]
 name = "utf8_iter"
 version = "1.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
 
 [[package]]
 name = "utf8parse"
 version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
 
 [[package]]
 name = "valuable"
 version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
 
+[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
 [[package]]
 name = "version_check"
 version = "0.9.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
 
 [[package]]
 name = "want"
 version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
 dependencies = [
  "try-lock",
 ]
 
 [[package]]
 name = "wasi"
 version = "0.11.0+wasi-snapshot-preview1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
 
 [[package]]
 name = "wasi"
 version = "0.14.2+wasi-0.2.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
 dependencies = [
  "wit-bindgen-rt",
 ]
 
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
+dependencies = [
+ "bumpalo",
+ "log",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "once_cell",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "web-sys"
+version = "0.3.77"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
 [[package]]
 name = "winapi"
 version = "0.3.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
 dependencies = [
  "winapi-i686-pc-windows-gnu",
  "winapi-x86_64-pc-windows-gnu",
 ]
 
 [[package]]
 name = "winapi-i686-pc-windows-gnu"
 version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
 
 [[package]]
 name = "winapi-x86_64-pc-windows-gnu"
 version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
 
+[[package]]
+name = "windows-link"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
+
+[[package]]
+name = "windows-registry"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3"
+dependencies = [
+ "windows-result",
+ "windows-strings",
+ "windows-targets 0.53.0",
+]
+
+[[package]]
+name = "windows-result"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319"
+dependencies = [
+ "windows-link",
+]
+
 [[package]]
 name = "windows-sys"
 version = "0.52.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
 dependencies = [
  "windows-targets 0.52.6",
 ]
 
 [[package]]
 name = "windows-sys"
 version = "0.59.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
 dependencies = [
  "windows-targets 0.52.6",
 ]
 
 [[package]]
 name = "windows-targets"
 version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
 dependencies = [
  "windows_aarch64_gnullvm 0.52.6",
  "windows_aarch64_msvc 0.52.6",
  "windows_i686_gnu 0.52.6",
  "windows_i686_gnullvm 0.52.6",
  "windows_i686_msvc 0.52.6",
  "windows_x86_64_gnu 0.52.6",
  "windows_x86_64_gnullvm 0.52.6",
  "windows_x86_64_msvc 0.52.6",
 ]
 
 [[package]]
 name = "windows-targets"
 version = "0.53.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b"
 dependencies = [
  "windows_aarch64_gnullvm 0.53.0",
  "windows_aarch64_msvc 0.53.0",
  "windows_i686_gnu 0.53.0",
  "windows_i686_gnullvm 0.53.0",
  "windows_i686_msvc 0.53.0",
  "windows_x86_64_gnu 0.53.0",
  "windows_x86_64_gnullvm 0.53.0",
  "windows_x86_64_msvc 0.53.0",
 ]
 
 [[package]]
 name = "windows_aarch64_gnullvm"
 version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
 
 [[package]]
 name = "windows_aarch64_gnullvm"
 version = "0.53.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
 
 [[package]]
 name = "windows_aarch64_msvc"
 version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
 
 [[package]]
 name = "windows_aarch64_msvc"
 version = "0.53.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
 
 [[package]]
 name = "windows_i686_gnu"
 version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
 
 [[package]]
 name = "windows_i686_gnu"
 version = "0.53.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
 
 [[package]]
 name = "windows_i686_gnullvm"
 version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
 
 [[package]]
 name = "windows_i686_gnullvm"
 version = "0.53.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
 
 [[package]]
 name = "windows_i686_msvc"
 version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
 
 [[package]]
 name = "windows_i686_msvc"
 version = "0.53.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
 
 [[package]]
 name = "windows_x86_64_gnu"
 version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
 
 [[package]]
 name = "windows_x86_64_gnu"
 version = "0.53.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
 
 [[package]]
 name = "windows_x86_64_gnullvm"
 version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
 
 [[package]]
 name = "windows_x86_64_gnullvm"
 version = "0.53.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
 
 [[package]]
 name = "windows_x86_64_msvc"
 version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
 
 [[package]]
 name = "windows_x86_64_msvc"
 version = "0.53.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
 
 [[package]]
 name = "wit-bindgen-rt"
 version = "0.39.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
 dependencies = [
  "bitflags 2.9.1",
 ]
 
 [[package]]
 name = "writeable"
 version = "0.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
 
 [[package]]
 name = "yansi"
 version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
 
 [[package]]
 name = "yoke"
 version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc"
 dependencies = [
  "serde",
  "stable_deref_trait",
  "yoke-derive",
  "zerofrom",
 ]
 
 [[package]]
 name = "yoke-derive"
 version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
 dependencies = [
  "proc-macro2",
  "quote",
  "syn",
  "synstructure",
 ]
 
 [[package]]
 name = "zerocopy"
 version = "0.8.25"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb"
 dependencies = [
  "zerocopy-derive",
 ]
 
 [[package]]
 name = "zerocopy-derive"
 version = "0.8.25"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef"
 dependencies = [
  "proc-macro2",
  "quote",
  "syn",
 ]
 
 [[package]]
 name = "zerofrom"
 version = "0.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
 dependencies = [
  "zerofrom-derive",
 ]
 
 [[package]]
 name = "zerofrom-derive"
 version = "0.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
 dependencies = [
  "proc-macro2",
  "quote",
  "syn",
  "synstructure",
 ]
 
+[[package]]
+name = "zeroize"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
+
 [[package]]
 name = "zerotrie"
 version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595"
 dependencies = [
  "displaydoc",
  "yoke",
  "zerofrom",
 ]
 
 [[package]]
 name = "zerovec"
 version = "0.11.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428"
 dependencies = [
  "yoke",
  "zerofrom",
  "zerovec-derive",
 ]
 
 [[package]]
 name = "zerovec-derive"
 version = "0.11.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
 dependencies = [
  "proc-macro2",
  "quote",
  "syn",
 ]
diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml
new file mode 100644
index 0000000..53fc84e
--- /dev/null
+++ b/crates/cli/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "collar-cli"
+version = "0.1.0"
+license.workspace = true
+edition.workspace = true
+authors.workspace = true
+
+[[bin]]
+name = "collar"
+path = "src/main.rs"
+
+[dependencies]
+clap = { version = "4.5", features = ["derive"] }
+reqwest = { version = "0.12", features = ["json"] }
+tokio = { version = "1.45.1", features = ["full"] }
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
+anyhow = "1.0"
+url = "2.5"
\ No newline at end of file
diff --git a/crates/cli/README.md b/crates/cli/README.md
new file mode 100644
index 0000000..5cc6c68
--- /dev/null
+++ b/crates/cli/README.md
@@ -0,0 +1,259 @@
+# Collar CLI
+
+Command-line interface for the Collar API server. Provides commands for managing networks and instances through the HTTP API.
+
+## Installation
+
+From the project root directory:
+
+```bash
+cargo build --release
+```
+
+The binary will be available at `target/release/collar`.
+
+## Usage
+
+```bash
+collar [OPTIONS] <COMMAND>
+```
+
+### Options
+
+- `--url <URL>` - Base URL of the Collar API server (default: http://localhost:3000)
+
+### Commands
+
+- `network` - Network management commands
+- `instance` - Instance management commands
+
+## Network Management
+
+### List Networks
+
+```bash
+collar network list
+```
+
+### Create Network
+
+Basic network:
+```bash
+collar network create my-network 192.168.1.0/24
+```
+
+Network with basic assigner:
+```bash
+collar network create my-network 192.168.1.0/24 \
+  --assigner-type basic \
+  --assigner-config '{"strategy": "sequential"}'
+```
+
+Network with range assigner:
+```bash
+collar network create my-network 192.168.1.0/24 \
+  --assigner-type range \
+  --assigner-config '{"range_name": "my-range"}'
+```
+
+### Get Network Details
+
+```bash
+collar network get my-network
+```
+
+### Delete Network
+
+```bash
+collar network delete my-network
+```
+
+### Show Examples
+
+```bash
+collar network create --examples
+```
+
+## Instance Management
+
+### List Instances
+
+```bash
+collar instance list
+```
+
+### Create Instance
+
+Jail instance:
+```bash
+collar instance create my-jail \
+  --provider-type jail \
+  --provider-config '{"jail_conf": "/etc/jail.conf", "dataset": "zroot/jails"}'
+```
+
+Container instance:
+```bash
+collar instance create my-container \
+  --provider-type container \
+  --provider-config '{"runtime": "docker", "image": "alpine:latest"}'
+```
+
+Instance with network interfaces:
+```bash
+collar instance create networked-instance \
+  --provider-type jail \
+  --provider-config '{}' \
+  --network-interfaces '[{"network_name": "test-network", "interface_name": "eth0"}]'
+```
+
+Instance with metadata:
+```bash
+collar instance create my-instance \
+  --provider-type jail \
+  --provider-config '{}' \
+  --metadata '{"env": "production", "version": "1.0"}'
+```
+
+### Get Instance Details
+
+```bash
+collar instance get my-instance
+```
+
+### Delete Instance
+
+```bash
+collar instance delete my-instance
+```
+
+### Show Examples
+
+```bash
+collar instance create --examples
+```
+
+## Configuration Examples
+
+### Network Provider Types
+
+Currently, the API supports basic network providers. Provider configuration is optional and can be passed as JSON.
+
+### Network Assigner Types
+
+- **basic**: Sequential IP assignment
+  ```json
+  {"strategy": "sequential"}
+  ```
+
+- **range**: Range-based IP assignment
+  ```json
+  {"range_name": "my-range"}
+  ```
+
+### Instance Provider Types
+
+- **jail**: FreeBSD jail instances
+  ```json
+  {
+    "jail_conf": "/etc/jail.conf",
+    "dataset": "zroot/jails"
+  }
+  ```
+
+- **container**: Container instances
+  ```json
+  {
+    "runtime": "docker",
+    "image": "alpine:latest"
+  }
+  ```
+
+### Network Interfaces
+
+Network interfaces can be specified as a JSON array:
+```json
+[
+  {
+    "network_name": "test-network",
+    "interface_name": "eth0"
+  }
+]
+```
+
+### Metadata
+
+Instance metadata can be specified as a JSON object:
+```json
+{
+  "env": "production",
+  "version": "1.0",
+  "owner": "team-infrastructure"
+}
+```
+
+## Examples
+
+### Complete Workflow
+
+1. Create a network:
+```bash
+collar network create app-network 10.0.1.0/24 \
+  --assigner-type basic \
+  --assigner-config '{"strategy": "sequential"}'
+```
+
+2. Create an instance connected to the network:
+```bash
+collar instance create web-server \
+  --provider-type container \
+  --provider-config '{"runtime": "docker", "image": "nginx:latest"}' \
+  --network-interfaces '[{"network_name": "app-network", "interface_name": "eth0"}]' \
+  --metadata '{"role": "web-server", "env": "production"}'
+```
+
+3. List resources:
+```bash
+collar network list
+collar instance list
+```
+
+4. Get details:
+```bash
+collar network get app-network
+collar instance get web-server
+```
+
+5. Clean up:
+```bash
+collar instance delete web-server
+collar network delete app-network
+```
+
+## Error Handling
+
+The CLI provides descriptive error messages for common issues:
+
+- Invalid JSON configuration
+- Network/instance not found
+- Server connection errors
+- Invalid provider or assigner types
+
+## Development
+
+### Running Tests
+
+```bash
+cargo test -p collar-cli
+```
+
+### Building
+
+```bash
+cargo build -p collar-cli
+```
+
+### Running from Source
+
+```bash
+cargo run -p collar-cli -- network list
+```
\ No newline at end of file
diff --git a/crates/cli/examples/demo.sh b/crates/cli/examples/demo.sh
new file mode 100755
index 0000000..c1d321c
--- /dev/null
+++ b/crates/cli/examples/demo.sh
@@ -0,0 +1,95 @@
+#!/bin/bash
+
+# Collar CLI Demo Script
+# Demonstrates various CLI operations for network and instance management
+
+set -e
+
+# Configuration
+API_URL="http://localhost:3000"
+COLLAR_CLI="cargo run -p collar-cli --"
+
+echo "=== Collar CLI Demo ==="
+echo "API URL: $API_URL"
+echo
+
+# Function to run CLI command with proper formatting
+run_cli() {
+    echo "$ collar $*"
+    $COLLAR_CLI --url "$API_URL" "$@"
+    echo
+}
+
+# Function to handle errors gracefully
+run_cli_safe() {
+    echo "$ collar $*"
+    if ! $COLLAR_CLI --url "$API_URL" "$@"; then
+        echo "Command failed (this might be expected if server is not running)"
+    fi
+    echo
+}
+
+echo "=== CLI Help ==="
+run_cli --help
+
+echo "=== Network Management ==="
+
+echo "--- Show network examples ---"
+run_cli network create --examples
+
+echo "--- List networks (may fail if server not running) ---"
+run_cli_safe network list
+
+echo "--- Create a basic network (may fail if server not running) ---"
+run_cli_safe network create demo-network 192.168.100.0/24
+
+echo "--- Create network with basic assigner (may fail if server not running) ---"
+run_cli_safe network create demo-basic-network 192.168.101.0/24 \
+    --assigner-type basic \
+    --assigner-config '{"strategy": "sequential"}'
+
+echo "--- Get network details (may fail if server not running) ---"
+run_cli_safe network get demo-network
+
+echo "=== Instance Management ==="
+
+echo "--- Show instance examples ---"
+run_cli instance create --examples
+
+echo "--- List instances (may fail if server not running) ---"
+run_cli_safe instance list
+
+echo "--- Create jail instance (may fail if server not running) ---"
+run_cli_safe instance create demo-jail \
+    --provider-type jail \
+    --provider-config '{"jail_conf": "/etc/jail.conf", "dataset": "zroot/jails"}'
+
+echo "--- Create container instance (may fail if server not running) ---"
+run_cli_safe instance create demo-container \
+    --provider-type container \
+    --provider-config '{"runtime": "docker", "image": "alpine:latest"}'
+
+echo "--- Create instance with network interface (may fail if server not running) ---"
+run_cli_safe instance create demo-networked \
+    --provider-type jail \
+    --provider-config '{}' \
+    --network-interfaces '[{"network_name": "demo-network", "interface_name": "eth0"}]' \
+    --metadata '{"env": "demo", "purpose": "testing"}'
+
+echo "--- Get instance details (may fail if server not running) ---"
+run_cli_safe instance get demo-jail
+
+echo "=== Cleanup ==="
+
+echo "--- Delete instances (may fail if server not running) ---"
+run_cli_safe instance delete demo-jail
+run_cli_safe instance delete demo-container
+run_cli_safe instance delete demo-networked
+
+echo "--- Delete networks (may fail if server not running) ---"
+run_cli_safe network delete demo-network
+run_cli_safe network delete demo-basic-network
+
+echo "=== Demo Complete ==="
+echo "Note: Some commands may have failed if the Collar API server is not running."
+echo "To start the server, run the appropriate collar daemon command first."
\ No newline at end of file
diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs
new file mode 100644
index 0000000..339946c
--- /dev/null
+++ b/crates/cli/src/main.rs
@@ -0,0 +1,416 @@
+//! # Collar CLI
+//!
+//! Command-line interface for the Collar API server.
+//!
+//! Provides commands for managing networks and instances through the HTTP API.
+
+use std::collections::HashMap;
+
+use anyhow::Result;
+use clap::{Parser, Subcommand};
+use reqwest::Client;
+use serde::{Deserialize, Serialize};
+use serde_json::Value;
+use url::Url;
+
+mod utils;
+use utils::*;
+
+#[derive(Parser)]
+#[command(name = "collar")]
+#[command(about = "CLI client for Collar API server")]
+#[command(version)]
+struct Cli {
+    #[arg(long, default_value = "http://localhost:3000")]
+    /// Base URL of the Collar API server
+    url: String,
+    
+    #[command(subcommand)]
+    command: Commands,
+}
+
+#[derive(Subcommand)]
+enum Commands {
+    /// Network management commands
+    Network {
+        #[command(subcommand)]
+        command: NetworkCommands,
+    },
+    /// Instance management commands
+    Instance {
+        #[command(subcommand)]
+        command: InstanceCommands,
+    },
+}
+
+#[derive(Subcommand)]
+enum NetworkCommands {
+    /// List all networks
+    List,
+    /// Create a new network
+    Create {
+        /// Network name
+        name: Option<String>,
+        /// Network CIDR (e.g., 192.168.1.0/24)
+        cidr: Option<String>,
+        /// Provider type (optional)
+        #[arg(long)]
+        provider_type: Option<String>,
+        /// Provider config as JSON string (optional)
+        #[arg(long)]
+        provider_config: Option<String>,
+        /// Assigner type (basic, range)
+        #[arg(long)]
+        assigner_type: Option<String>,
+        /// Assigner config as JSON string (optional)
+        #[arg(long)]
+        assigner_config: Option<String>,
+        /// Show example configurations
+        #[arg(long)]
+        examples: bool,
+    },
+    /// Get network details
+    Get {
+        /// Network name
+        name: String,
+    },
+    /// Delete a network
+    Delete {
+        /// Network name
+        name: String,
+    },
+}
+
+#[derive(Subcommand)]
+enum InstanceCommands {
+    /// List all instances
+    List,
+    /// Create a new instance
+    Create {
+        /// Instance name
+        name: Option<String>,
+        /// Provider type (jail, container)
+        #[arg(long)]
+        provider_type: Option<String>,
+        /// Provider config as JSON string
+        #[arg(long)]
+        provider_config: Option<String>,
+        /// Network interfaces as JSON array string (optional)
+        #[arg(long)]
+        network_interfaces: Option<String>,
+        /// Metadata as JSON object string (optional)
+        #[arg(long)]
+        metadata: Option<String>,
+        /// Show example configurations
+        #[arg(long)]
+        examples: bool,
+    },
+    /// Get instance details
+    Get {
+        /// Instance name
+        name: String,
+    },
+    /// Delete an instance
+    Delete {
+        /// Instance name
+        name: String,
+    },
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+struct CreateNetworkRequest {
+    name: String,
+    cidr: String,
+    provider_type: Option<String>,
+    provider_config: Option<Value>,
+    assigner_type: Option<String>,
+    assigner_config: Option<Value>,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+struct CreateInstanceRequest {
+    name: String,
+    provider_type: String,
+    provider_config: Value,
+    network_interfaces: Option<Vec<NetworkInterfaceRequest>>,
+    metadata: Option<HashMap<String, String>>,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+struct NetworkInterfaceRequest {
+    network_name: String,
+    interface_name: String,
+}
+
+#[tokio::main]
+async fn main() -> Result<()> {
+    let cli = Cli::parse();
+    let client = Client::new();
+    let base_url = Url::parse(&cli.url)?;
+
+    match cli.command {
+        Commands::Network { command } => handle_network_command(&client, &base_url, command).await?,
+        Commands::Instance { command } => handle_instance_command(&client, &base_url, command).await?,
+    }
+
+    Ok(())
+}
+
+async fn handle_network_command(client: &Client, base_url: &Url, command: NetworkCommands) -> Result<()> {
+    match command {
+        NetworkCommands::List => {
+            let url = base_url.join("/networks")?;
+            let response = client.get(url).send().await?;
+            
+            if response.status().is_success() {
+                let networks: Value = response.json().await?;
+                println!("{}", format_response(&networks)?);
+            } else {
+                let error = response.text().await?;
+                eprintln!("Error: {}", error);
+            }
+        }
+        NetworkCommands::Create {
+            name,
+            cidr,
+            provider_type,
+            provider_config,
+            assigner_type,
+            assigner_config,
+            examples,
+        } => {
+            if examples {
+                println!("Example network configurations:");
+                println!("\nBasic network:");
+                println!("collar --url http://localhost:3000 network create my-network 192.168.1.0/24");
+                
+                println!("\nNetwork with basic assigner:");
+                println!("collar network create my-network 192.168.1.0/24 \\");
+                println!("  --assigner-type basic \\");
+                println!("  --assigner-config '{}'", serde_json::to_string(&example_basic_assigner_config())?);
+                
+                println!("\nNetwork with range assigner:");
+                println!("collar network create my-network 192.168.1.0/24 \\");
+                println!("  --assigner-type range \\");
+                println!("  --assigner-config '{}'", serde_json::to_string(&example_range_assigner_config())?);
+                
+                return Ok(());
+            }
+            
+            // Require name and cidr if not showing examples
+            let name = name.ok_or_else(|| anyhow::anyhow!("Network name is required"))?;
+            let cidr = cidr.ok_or_else(|| anyhow::anyhow!("Network CIDR is required"))?;
+            
+            // Validate assigner type if provided
+            if let Some(ref assigner) = assigner_type {
+                validate_assigner_type(assigner)?;
+            }
+            let provider_config = if let Some(config) = provider_config {
+                Some(parse_json_string(&config)?)
+            } else {
+                None
+            };
+            
+            let assigner_config = if let Some(config) = assigner_config {
+                Some(parse_json_string(&config)?)
+            } else {
+                None
+            };
+
+            let request = CreateNetworkRequest {
+                name: name.clone(),
+                cidr: cidr.clone(),
+                provider_type,
+                provider_config,
+                assigner_type,
+                assigner_config,
+            };
+
+            let url = base_url.join("/networks")?;
+            let response = client.post(url).json(&request).send().await?;
+            
+            if response.status().is_success() {
+                println!("Network '{}' created successfully", name);
+            } else {
+                let error = response.text().await?;
+                eprintln!("Error: {}", error);
+            }
+        }
+        NetworkCommands::Get { name } => {
+            let url = base_url.join(&format!("/networks/{}", name))?;
+            let response = client.get(url).send().await?;
+            
+            if response.status().is_success() {
+                let network: Value = response.json().await?;
+                println!("{}", format_response(&network)?);
+            } else {
+                let error = response.text().await?;
+                eprintln!("Error: {}", error);
+            }
+        }
+        NetworkCommands::Delete { name } => {
+            let url = base_url.join(&format!("/networks/{}", name))?;
+            let response = client.delete(url).send().await?;
+            
+            if response.status().is_success() {
+                println!("Network '{}' deleted successfully", name);
+            } else {
+                let error = response.text().await?;
+                eprintln!("Error: {}", error);
+            }
+        }
+    }
+    
+    Ok(())
+}
+
+async fn handle_instance_command(client: &Client, base_url: &Url, command: InstanceCommands) -> Result<()> {
+    match command {
+        InstanceCommands::List => {
+            let url = base_url.join("/instances")?;
+            let response = client.get(url).send().await?;
+            
+            if response.status().is_success() {
+                let instances: Value = response.json().await?;
+                println!("{}", format_response(&instances)?);
+            } else {
+                let error = response.text().await?;
+                eprintln!("Error: {}", error);
+            }
+        }
+        InstanceCommands::Create {
+            name,
+            provider_type,
+            provider_config,
+            network_interfaces,
+            metadata,
+            examples,
+        } => {
+            if examples {
+                println!("Example instance configurations:");
+                println!("\nJail instance:");
+                println!("collar instance create my-jail \\");
+                println!("  --provider-type jail \\");
+                println!("  --provider-config '{}'", serde_json::to_string(&example_jail_config())?);
+                
+                println!("\nContainer instance:");
+                println!("collar instance create my-container \\");
+                println!("  --provider-type container \\");
+                println!("  --provider-config '{}'", serde_json::to_string(&example_container_config())?);
+                
+                println!("\nInstance with network interfaces:");
+                println!("collar instance create networked-instance \\");
+                println!("  --provider-type jail \\");
+                println!("  --provider-config '{{}}' \\");
+                println!("  --network-interfaces '{}'", serde_json::to_string(&example_network_interfaces())?);
+                
+                return Ok(());
+            }
+            
+            // Require name and provider details if not showing examples
+            let name = name.ok_or_else(|| anyhow::anyhow!("Instance name is required"))?;
+            let provider_type = provider_type.ok_or_else(|| anyhow::anyhow!("Provider type is required"))?;
+            let provider_config = provider_config.ok_or_else(|| anyhow::anyhow!("Provider config is required"))?;
+            
+            // Validate provider type
+            validate_provider_type(&provider_type)?;
+            let provider_config: Value = parse_json_string(&provider_config)?;
+            
+            let network_interfaces = if let Some(interfaces) = network_interfaces {
+                Some(serde_json::from_str(&interfaces)?)
+            } else {
+                None
+            };
+            
+            let metadata = if let Some(meta) = metadata {
+                Some(parse_metadata(&meta)?)
+            } else {
+                None
+            };
+
+            let request = CreateInstanceRequest {
+                name: name.clone(),
+                provider_type,
+                provider_config,
+                network_interfaces,
+                metadata,
+            };
+
+            let url = base_url.join("/instances")?;
+            let response = client.post(url).json(&request).send().await?;
+            
+            if response.status().is_success() {
+                println!("Instance '{}' created successfully", name);
+            } else {
+                let error = response.text().await?;
+                eprintln!("Error: {}", error);
+            }
+        }
+        InstanceCommands::Get { name } => {
+            let url = base_url.join(&format!("/instances/{}", name))?;
+            let response = client.get(url).send().await?;
+            
+            if response.status().is_success() {
+                let instance: Value = response.json().await?;
+                println!("{}", format_response(&instance)?);
+            } else {
+                let error = response.text().await?;
+                eprintln!("Error: {}", error);
+            }
+        }
+        InstanceCommands::Delete { name } => {
+            let url = base_url.join(&format!("/instances/{}", name))?;
+            let response = client.delete(url).send().await?;
+            
+            if response.status().is_success() {
+                println!("Instance '{}' deleted successfully", name);
+            } else {
+                let error = response.text().await?;
+                eprintln!("Error: {}", error);
+            }
+        }
+    }
+    
+    Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+    use std::process::Command;
+
+    #[test]
+    fn test_cli_help() {
+        let output = Command::new("cargo")
+            .args(&["run", "--bin", "collar", "--", "--help"])
+            .output()
+            .expect("Failed to execute command");
+        
+        assert!(output.status.success());
+        let stdout = String::from_utf8(output.stdout).unwrap();
+        assert!(stdout.contains("CLI client for Collar API server"));
+    }
+
+    #[test]
+    fn test_network_help() {
+        let output = Command::new("cargo")
+            .args(&["run", "--bin", "collar", "--", "network", "--help"])
+            .output()
+            .expect("Failed to execute command");
+        
+        assert!(output.status.success());
+        let stdout = String::from_utf8(output.stdout).unwrap();
+        assert!(stdout.contains("Network management commands"));
+    }
+
+    #[test]
+    fn test_instance_help() {
+        let output = Command::new("cargo")
+            .args(&["run", "--bin", "collar", "--", "instance", "--help"])
+            .output()
+            .expect("Failed to execute command");
+        
+        assert!(output.status.success());
+        let stdout = String::from_utf8(output.stdout).unwrap();
+        assert!(stdout.contains("Instance management commands"));
+    }
+}
\ No newline at end of file
diff --git a/crates/cli/src/utils.rs b/crates/cli/src/utils.rs
new file mode 100644
index 0000000..9a5cb43
--- /dev/null
+++ b/crates/cli/src/utils.rs
@@ -0,0 +1,120 @@
+//! Utility functions for the CLI client
+
+use anyhow::{Context, Result};
+use serde_json::Value;
+use std::collections::HashMap;
+
+/// Parse a JSON string into a serde_json::Value
+pub fn parse_json_string(json_str: &str) -> Result<Value> {
+    serde_json::from_str(json_str).context("Failed to parse JSON string")
+}
+
+/// Parse a JSON string into a HashMap for metadata
+pub fn parse_metadata(json_str: &str) -> Result<HashMap<String, String>> {
+    serde_json::from_str(json_str).context("Failed to parse metadata JSON")
+}
+
+/// Format response output with proper indentation
+pub fn format_response(value: &Value) -> Result<String> {
+    serde_json::to_string_pretty(value).context("Failed to format response")
+}
+
+/// Validate provider type for instances
+pub fn validate_provider_type(provider_type: &str) -> Result<()> {
+    match provider_type {
+        "jail" | "container" => Ok(()),
+        _ => Err(anyhow::anyhow!(
+            "Invalid provider type '{}'. Must be 'jail' or 'container'",
+            provider_type
+        )),
+    }
+}
+
+/// Validate assigner type for networks
+pub fn validate_assigner_type(assigner_type: &str) -> Result<()> {
+    match assigner_type {
+        "basic" | "range" => Ok(()),
+        _ => Err(anyhow::anyhow!(
+            "Invalid assigner type '{}'. Must be 'basic' or 'range'",
+            assigner_type
+        )),
+    }
+}
+
+/// Build example configurations for different provider types
+pub fn example_jail_config() -> Value {
+    serde_json::json!({
+        "jail_conf": "/etc/jail.conf",
+        "dataset": "zroot/jails"
+    })
+}
+
+pub fn example_container_config() -> Value {
+    serde_json::json!({
+        "runtime": "docker",
+        "image": "alpine:latest"
+    })
+}
+
+pub fn example_basic_assigner_config() -> Value {
+    serde_json::json!({
+        "strategy": "sequential"
+    })
+}
+
+pub fn example_range_assigner_config() -> Value {
+    serde_json::json!({
+        "range_name": "my-range"
+    })
+}
+
+pub fn example_network_interfaces() -> Value {
+    serde_json::json!([
+        {
+            "network_name": "test-network",
+            "interface_name": "eth0"
+        }
+    ])
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_parse_json_string() {
+        let json_str = r#"{"key": "value"}"#;
+        let result = parse_json_string(json_str).unwrap();
+        assert_eq!(result["key"], "value");
+    }
+
+    #[test]
+    fn test_parse_metadata() {
+        let json_str = r#"{"env": "production", "version": "1.0"}"#;
+        let result = parse_metadata(json_str).unwrap();
+        assert_eq!(result.get("env"), Some(&"production".to_string()));
+        assert_eq!(result.get("version"), Some(&"1.0".to_string()));
+    }
+
+    #[test]
+    fn test_validate_provider_type() {
+        assert!(validate_provider_type("jail").is_ok());
+        assert!(validate_provider_type("container").is_ok());
+        assert!(validate_provider_type("invalid").is_err());
+    }
+
+    #[test]
+    fn test_validate_assigner_type() {
+        assert!(validate_assigner_type("basic").is_ok());
+        assert!(validate_assigner_type("range").is_ok());
+        assert!(validate_assigner_type("invalid").is_err());
+    }
+
+    #[test]
+    fn test_format_response() {
+        let value = serde_json::json!({"name": "test", "value": 42});
+        let formatted = format_response(&value).unwrap();
+        assert!(formatted.contains("\"name\": \"test\""));
+        assert!(formatted.contains("\"value\": 42"));
+    }
+}
\ No newline at end of file
diff --git a/crates/cli/tests/integration_tests.rs b/crates/cli/tests/integration_tests.rs
new file mode 100644
index 0000000..864637d
--- /dev/null
+++ b/crates/cli/tests/integration_tests.rs
@@ -0,0 +1,177 @@
+//! Integration tests for the Collar CLI
+//!
+//! These tests verify CLI functionality by spawning the CLI process
+//! and checking its output and behavior.
+
+use std::process::Command;
+
+#[tokio::test]
+async fn test_cli_help() {
+    let output = Command::new("cargo")
+        .args(&["run", "-p", "collar-cli", "--", "--help"])
+        .output()
+        .expect("Failed to execute command");
+    
+    assert!(output.status.success());
+    let stdout = String::from_utf8(output.stdout).unwrap();
+    assert!(stdout.contains("CLI client for Collar API server"));
+    assert!(stdout.contains("network"));
+    assert!(stdout.contains("instance"));
+}
+
+#[tokio::test]
+async fn test_network_help() {
+    let output = Command::new("cargo")
+        .args(&["run", "-p", "collar-cli", "--", "network", "--help"])
+        .output()
+        .expect("Failed to execute command");
+    
+    assert!(output.status.success());
+    let stdout = String::from_utf8(output.stdout).unwrap();
+    assert!(stdout.contains("Network management commands"));
+    assert!(stdout.contains("list"));
+    assert!(stdout.contains("create"));
+    assert!(stdout.contains("get"));
+    assert!(stdout.contains("delete"));
+}
+
+#[tokio::test]
+async fn test_instance_help() {
+    let output = Command::new("cargo")
+        .args(&["run", "-p", "collar-cli", "--", "instance", "--help"])
+        .output()
+        .expect("Failed to execute command");
+    
+    assert!(output.status.success());
+    let stdout = String::from_utf8(output.stdout).unwrap();
+    assert!(stdout.contains("Instance management commands"));
+    assert!(stdout.contains("list"));
+    assert!(stdout.contains("create"));
+    assert!(stdout.contains("get"));
+    assert!(stdout.contains("delete"));
+}
+
+#[tokio::test]
+async fn test_network_create_examples() {
+    let output = Command::new("cargo")
+        .args(&["run", "-p", "collar-cli", "--", "network", "create", "--examples"])
+        .output()
+        .expect("Failed to execute command");
+    
+    assert!(output.status.success());
+    let stdout = String::from_utf8(output.stdout).unwrap();
+    assert!(stdout.contains("Example network configurations"));
+    assert!(stdout.contains("Basic network"));
+    assert!(stdout.contains("basic assigner"));
+    assert!(stdout.contains("range assigner"));
+}
+
+#[tokio::test]
+async fn test_instance_create_examples() {
+    let output = Command::new("cargo")
+        .args(&["run", "-p", "collar-cli", "--", "instance", "create", "--examples"])
+        .output()
+        .expect("Failed to execute command");
+    
+    assert!(output.status.success());
+    let stdout = String::from_utf8(output.stdout).unwrap();
+    assert!(stdout.contains("Example instance configurations"));
+    assert!(stdout.contains("Jail instance"));
+    assert!(stdout.contains("Container instance"));
+    assert!(stdout.contains("network interfaces"));
+}
+
+#[tokio::test]
+async fn test_network_list_connection_error() {
+    let output = Command::new("cargo")
+        .args(&[
+            "run", "-p", "collar-cli", "--", 
+            "--url", "http://localhost:9999", 
+            "network", "list"
+        ])
+        .output()
+        .expect("Failed to execute command");
+    
+    // Should fail with connection error since server isn't running on port 9999
+    assert!(!output.status.success() || !String::from_utf8(output.stderr).unwrap().is_empty());
+}
+
+#[tokio::test]
+async fn test_instance_list_connection_error() {
+    let output = Command::new("cargo")
+        .args(&[
+            "run", "-p", "collar-cli", "--", 
+            "--url", "http://localhost:9999", 
+            "instance", "list"
+        ])
+        .output()
+        .expect("Failed to execute command");
+    
+    // Should fail with connection error since server isn't running on port 9999
+    assert!(!output.status.success() || !String::from_utf8(output.stderr).unwrap().is_empty());
+}
+
+#[tokio::test]
+async fn test_invalid_provider_type_validation() {
+    let output = Command::new("cargo")
+        .args(&[
+            "run", "-p", "collar-cli", "--", 
+            "--url", "http://localhost:9999", 
+            "instance", "create", "test-instance",
+            "--provider-type", "invalid",
+            "--provider-config", "{}"
+        ])
+        .output()
+        .expect("Failed to execute command");
+    
+    assert!(!output.status.success());
+    let stderr = String::from_utf8(output.stderr).unwrap();
+    assert!(stderr.contains("Invalid provider type"));
+}
+
+#[tokio::test]
+async fn test_invalid_assigner_type_validation() {
+    let output = Command::new("cargo")
+        .args(&[
+            "run", "-p", "collar-cli", "--", 
+            "--url", "http://localhost:9999", 
+            "network", "create", "test-network", "192.168.1.0/24",
+            "--assigner-type", "invalid"
+        ])
+        .output()
+        .expect("Failed to execute command");
+    
+    assert!(!output.status.success());
+    let stderr = String::from_utf8(output.stderr).unwrap();
+    assert!(stderr.contains("Invalid assigner type"));
+}
+
+#[tokio::test]
+async fn test_invalid_json_config() {
+    let output = Command::new("cargo")
+        .args(&[
+            "run", "-p", "collar-cli", "--", 
+            "--url", "http://localhost:9999", 
+            "instance", "create", "test-instance",
+            "--provider-type", "jail",
+            "--provider-config", "invalid-json"
+        ])
+        .output()
+        .expect("Failed to execute command");
+    
+    assert!(!output.status.success());
+    let stderr = String::from_utf8(output.stderr).unwrap();
+    assert!(stderr.contains("Failed to parse JSON"));
+}
+
+#[tokio::test]
+async fn test_version_flag() {
+    let output = Command::new("cargo")
+        .args(&["run", "-p", "collar-cli", "--", "--version"])
+        .output()
+        .expect("Failed to execute command");
+    
+    assert!(output.status.success());
+    let stdout = String::from_utf8(output.stdout).unwrap();
+    assert!(stdout.contains("collar"));
+}
\ No newline at end of file
diff --git a/crates/store/examples/jail_auto_ip_demo.rs b/crates/store/examples/jail_auto_ip_demo.rs
new file mode 100644
index 0000000..68dc122
--- /dev/null
+++ b/crates/store/examples/jail_auto_ip_demo.rs
@@ -0,0 +1,229 @@
+use store::{
+    InstanceStore, Instance, InstanceConfig, NetworkInterface,
+    NamespaceStore, NetworkStore, RangeStore,
+    JailProvider, Provider,
+    network::{NetRange, BasicProvider, RangeAssigner},
+};
+use std::collections::HashMap;
+
+fn main() -> store::Result<()> {
+    println!("=== Jail Automatic IP Assignment Demo ===\n");
+    
+    // Open the store
+    let db = sled::open("jail_auto_ip_demo.db")?;
+    
+    // Set up stores
+    let namespaces = NamespaceStore::open(&db)?;
+    let ranges = RangeStore::open(&db)?;
+    let networks = NetworkStore::open_with_ranges(&db, ranges)?;
+    let instances = InstanceStore::open(&db, namespaces, networks)?;
+    
+    println!("1. Creating network with RangeAssigner...");
+    
+    // Create a network with automatic IP assignment
+    let provider = BasicProvider::new()
+        .with_config("type", "bridge")
+        .with_config("driver", "epair");
+    
+    let assigner = RangeAssigner::with_range_name("jail_ips")
+        .with_config("strategy", "sequential")
+        .with_config("reserve_gateway", "true");
+    
+    let netrange = NetRange::from_cidr("10.0.20.0/24")?;
+    
+    instances.networks().create(
+        "jail_network",
+        netrange,
+        provider,
+        Some(assigner),
+    )?;
+    
+    println!("   ✓ Created network 'jail_network' with range 10.0.20.0/24");
+    
+    println!("\n2. Creating jails with automatic IP assignment...");
+    
+    // Create multiple jail instances - they should automatically get IP assignments
+    let jail_names = ["web-jail", "db-jail", "cache-jail"];
+    
+    for jail_name in &jail_names {
+        let jail_provider = JailProvider::new()
+            .with_dataset("tank/jails".to_string());
+        
+        let jail_config = InstanceConfig {
+            provider_config: jail_provider.to_json()?,
+            network_interfaces: vec![
+                NetworkInterface {
+                    network_name: "jail_network".to_string(),
+                    interface_name: "em0".to_string(),
+                    assignment: None, // Will be automatically assigned
+                }
+            ],
+            metadata: {
+                let mut meta = HashMap::new();
+                meta.insert("role".to_string(), jail_name.replace("-jail", ""));
+                meta
+            },
+        };
+        
+        let jail_instance = Instance {
+            name: jail_name.to_string(),
+            provider_type: "jail".to_string(),
+            config: jail_config,
+            created_at: std::time::SystemTime::now()
+                .duration_since(std::time::UNIX_EPOCH)
+                .unwrap()
+                .as_secs(),
+            updated_at: std::time::SystemTime::now()
+                .duration_since(std::time::UNIX_EPOCH)
+                .unwrap()
+                .as_secs(),
+        };
+        
+        let created = instances.create(jail_instance)?;
+        
+        if let Some(ref assignment) = created.config.network_interfaces[0].assignment {
+            println!("   ✓ Created jail '{}' with IP: {}", jail_name, assignment);
+        } else {
+            println!("   ✗ Failed to assign IP to jail '{}'", jail_name);
+        }
+    }
+    
+    println!("\n3. Verifying all jails have unique IP assignments...");
+    
+    let mut assigned_ips = std::collections::HashSet::new();
+    
+    for jail_name in &jail_names {
+        let instance = instances.get(jail_name)?;
+        let interface = &instance.config.network_interfaces[0];
+        
+        if let Some(ref assignment) = interface.assignment {
+            if assigned_ips.insert(assignment.clone()) {
+                println!("   ✓ {} -> {}", jail_name, assignment);
+            } else {
+                println!("   ✗ Duplicate IP assignment for {}: {}", jail_name, assignment);
+                return Err(store::Error::StoreError(sled::Error::Unsupported(
+                    "Duplicate IP assignment detected".to_string()
+                )));
+            }
+        } else {
+            println!("   ✗ No IP assignment found for {}", jail_name);
+        }
+    }
+    
+    println!("\n4. Creating jail with multiple network interfaces...");
+    
+    // Create another network for demonstration
+    let mgmt_provider = BasicProvider::new();
+    let mgmt_assigner = RangeAssigner::with_range_name("mgmt_ips");
+    instances.networks().create(
+        "management",
+        NetRange::from_cidr("192.168.1.0/24")?,
+        mgmt_provider,
+        Some(mgmt_assigner),
+    )?;
+    
+    // Create a jail with multiple interfaces
+    let multi_jail_provider = JailProvider::new();
+    let multi_jail_config = InstanceConfig {
+        provider_config: multi_jail_provider.to_json()?,
+        network_interfaces: vec![
+            NetworkInterface {
+                network_name: "jail_network".to_string(),
+                interface_name: "em0".to_string(),
+                assignment: None,
+            },
+            NetworkInterface {
+                network_name: "management".to_string(),
+                interface_name: "em1".to_string(),
+                assignment: None,
+            },
+        ],
+        metadata: HashMap::new(),
+    };
+    
+    let multi_jail = Instance {
+        name: "multi-interface-jail".to_string(),
+        provider_type: "jail".to_string(),
+        config: multi_jail_config,
+        created_at: std::time::SystemTime::now()
+            .duration_since(std::time::UNIX_EPOCH)
+            .unwrap()
+            .as_secs(),
+        updated_at: std::time::SystemTime::now()
+            .duration_since(std::time::UNIX_EPOCH)
+            .unwrap()
+            .as_secs(),
+    };
+    
+    let created_multi = instances.create(multi_jail)?;
+    
+    println!("   ✓ Created multi-interface jail:");
+    for (i, interface) in created_multi.config.network_interfaces.iter().enumerate() {
+        if let Some(ref assignment) = interface.assignment {
+            println!("     Interface {}: {} -> {}", i, interface.network_name, assignment);
+        }
+    }
+    
+    println!("\n5. Testing deletion and IP cleanup...");
+    
+    // Delete the first jail
+    instances.delete("web-jail")?;
+    println!("   ✓ Deleted jail 'web-jail'");
+    
+    // Verify the IP is cleaned up
+    let network = instances.networks().get("jail_network")?.unwrap();
+    let assignment_after_delete = network.get_assignment("web-jail", instances.networks().ranges())?;
+    if assignment_after_delete.is_none() {
+        println!("   ✓ IP assignment cleaned up after deletion");
+    } else {
+        println!("   ✗ IP assignment still exists after deletion");
+    }
+    
+    // Create a new jail to demonstrate IP reuse
+    let reuse_jail_provider = JailProvider::new();
+    let reuse_jail_config = InstanceConfig {
+        provider_config: reuse_jail_provider.to_json()?,
+        network_interfaces: vec![
+            NetworkInterface {
+                network_name: "jail_network".to_string(),
+                interface_name: "em0".to_string(),
+                assignment: None,
+            }
+        ],
+        metadata: HashMap::new(),
+    };
+    
+    let reuse_jail = Instance {
+        name: "replacement-jail".to_string(),
+        provider_type: "jail".to_string(),
+        config: reuse_jail_config,
+        created_at: std::time::SystemTime::now()
+            .duration_since(std::time::UNIX_EPOCH)
+            .unwrap()
+            .as_secs(),
+        updated_at: std::time::SystemTime::now()
+            .duration_since(std::time::UNIX_EPOCH)
+            .unwrap()
+            .as_secs(),
+    };
+    
+    let created_reuse = instances.create(reuse_jail)?;
+    
+    if let Some(ref new_assignment) = created_reuse.config.network_interfaces[0].assignment {
+        println!("   ✓ New jail 'replacement-jail' reused IP: {}", new_assignment);
+    }
+    
+    println!("\n✅ Demonstration completed successfully!");
+    println!("\nKey points:");
+    println!("  • Jails automatically receive IP assignments when created");
+    println!("  • Each jail gets a unique IP from the network's range");
+    println!("  • Multiple network interfaces are supported");
+    println!("  • RangeAssigner handles sequential IP allocation");
+    println!("  • IP assignments are automatically cleaned up on deletion");
+    println!("  • Freed IPs can be reused by new instances");
+    
+    // Clean up
+    std::fs::remove_dir_all("jail_auto_ip_demo.db").ok();
+    
+    Ok(())
+}
\ No newline at end of file
diff --git a/crates/store/src/instance.rs b/crates/store/src/instance.rs
index 3cf1d85..5d03794 100644
--- a/crates/store/src/instance.rs
+++ b/crates/store/src/instance.rs
@@ -1,946 +1,1022 @@
 use crate::{Result, Error, NamespaceStore, NetworkStore};
 use serde::{Serialize, Deserialize};
 use std::collections::HashMap;
 
 /// Generic provider trait for different virtualization backends
 pub trait Provider: Send + Sync {
     /// Get the provider type identifier
     fn provider_type(&self) -> &'static str;
     
     /// Serialize provider configuration to JSON
     fn to_json(&self) -> Result<String>;
     
     /// Deserialize provider configuration from JSON
     fn from_json(json: &str) -> Result<Self> where Self: Sized;
     
     /// Start the instance with the given configuration
     fn start(&self, name: &str, config: &InstanceConfig) -> Result<()>;
     
     /// Stop the instance
     fn stop(&self, name: &str) -> Result<()>;
     
     /// Check if instance is running
     fn is_running(&self, name: &str) -> Result<bool>;
     
     /// Get instance status information
     fn status(&self, name: &str) -> Result<ProviderStatus>;
 }
 
 /// Status information from a provider
 #[derive(Debug, Clone, Serialize, Deserialize)]
 pub struct ProviderStatus {
     pub running: bool,
     pub pid: Option<u32>,
     pub uptime: Option<u64>,
     pub memory_usage: Option<u64>,
     pub cpu_usage: Option<f64>,
 }
 
 /// Network interface assignment for an instance
 #[derive(Debug, Clone, Serialize, Deserialize)]
 pub struct NetworkInterface {
     pub network_name: String,
     pub interface_name: String,
     pub assignment: Option<String>, // IP assignment from network
 }
 
 /// Instance configuration
 #[derive(Debug, Clone, Serialize, Deserialize)]
 pub struct InstanceConfig {
     pub provider_config: String, // JSON config specific to provider
     pub network_interfaces: Vec<NetworkInterface>,
     pub metadata: HashMap<String, String>,
 }
 
 /// Instance representation
 #[derive(Debug, Clone, Serialize, Deserialize)]
 pub struct Instance {
     pub name: String,
     pub provider_type: String,
     pub config: InstanceConfig,
     pub created_at: u64,
     pub updated_at: u64,
 }
 
 impl Instance {
     /// Create a new instance
     pub fn new(name: String, provider_type: String, config: InstanceConfig) -> Self {
         let now = std::time::SystemTime::now()
             .duration_since(std::time::UNIX_EPOCH)
             .unwrap()
             .as_secs();
             
         Self {
             name,
             provider_type,
             config,
             created_at: now,
             updated_at: now,
         }
     }
     
     /// Update instance configuration
     pub fn update_config(&mut self, config: InstanceConfig) {
         self.config = config;
         self.updated_at = std::time::SystemTime::now()
             .duration_since(std::time::UNIX_EPOCH)
             .unwrap()
             .as_secs();
     }
 }
 
 /// Store for managing instances
 #[derive(Debug, Clone)]
 pub struct InstanceStore {
     pub(crate) instances: sled::Tree,
     pub(crate) namespaces: NamespaceStore,
     pub(crate) networks: NetworkStore,
 }
 
 impl InstanceStore {
     const INSTANCE_NAMESPACE: &'static str = "instances";
     
     /// Open instance store with existing namespace and network stores
     pub fn open(
         db: &sled::Db,
         namespaces: NamespaceStore,
         networks: NetworkStore,
     ) -> Result<Self> {
         let instances = db.open_tree("instances")?;
         
         let store = Self {
             instances,
             namespaces,
             networks,
         };
         
         // Ensure instance namespace exists
         store.namespaces.define(Self::INSTANCE_NAMESPACE)?;
         
         Ok(store)
     }
     
-    /// Create a new instance with network assignments
+    /// Get reference to the network store
+    pub fn networks(&self) -> &NetworkStore {
+        &self.networks
+    }
+    
+    /// Create a new instance with automatic network IP assignments
+    /// 
+    /// This method creates a new instance and automatically assigns IP addresses
+    /// to any network interfaces that are configured but don't have existing assignments.
+    /// The automatic assignment works with networks that have RangeAssigners configured.
+    /// 
+    /// # Automatic IP Assignment Process
+    /// 
+    /// For each network interface:
+    /// 1. Check if an IP assignment already exists
+    /// 2. If not, and the network has a RangeAssigner, automatically assign an IP
+    /// 3. Update the interface with the assigned IP address
+    /// 
+    /// This enables seamless provisioning of jails, containers, and other instances
+    /// with proper network connectivity without manual IP management.
     pub fn create(&self, mut instance: Instance) -> Result<Instance> {
         // Reserve name in namespace
         self.namespaces.reserve(Self::INSTANCE_NAMESPACE, &instance.name, &instance.name)?;
         
         // Get network assignments for interfaces
         for interface in &mut instance.config.network_interfaces {
             if let Ok(Some(network)) = self.networks.get(&interface.network_name) {
                 match network.get_assignment(&instance.name, self.networks.ranges.as_ref()) {
                     Ok(Some(assignment)) => {
                         interface.assignment = Some(assignment.to_string());
                     }
                     Ok(None) => {
-                        // No assignment available
+                        // No existing assignment, try to auto-assign if network has RangeAssigner
+                        // This enables automatic IP allocation for instances with network interfaces
+                        if let Some(ref ranges) = self.networks.ranges {
+                            // Attempt automatic IP assignment using the network's RangeAssigner
+                            if let Err(e) = network.auto_assign_ip(&instance.name, ranges) {
+                                // Clean up reserved name on failure
+                                let _ = self.namespaces.remove(Self::INSTANCE_NAMESPACE, &instance.name);
+                                return Err(e);
+                            }
+                            
+                            // Retrieve the newly assigned IP address
+                            match network.get_assignment(&instance.name, self.networks.ranges.as_ref()) {
+                                Ok(Some(assignment)) => {
+                                    interface.assignment = Some(assignment.to_string());
+                                }
+                                Ok(None) => {
+                                    // Auto-assignment failed silently or range is full
+                                    // This is not necessarily an error - some networks may not support auto-assignment
+                                }
+                                Err(e) => {
+                                    // Clean up reserved name on failure
+                                    let _ = self.namespaces.remove(Self::INSTANCE_NAMESPACE, &instance.name);
+                                    return Err(e);
+                                }
+                            }
+                        }
                     }
                     Err(e) => {
                         // Clean up reserved name on failure
                         let _ = self.namespaces.remove(Self::INSTANCE_NAMESPACE, &instance.name);
                         return Err(e);
                     }
                 }
             }
         }
         
         // Store instance
         let instance_json = serde_json::to_string(&instance)
             .map_err(|e| Error::StoreError(sled::Error::Unsupported(format!("JSON serialization error: {}", e))))?;
         
         match self.instances.insert(&instance.name, instance_json.as_bytes()) {
             Ok(_) => Ok(instance),
             Err(e) => {
                 // Clean up on failure
                 let _ = self.namespaces.remove(Self::INSTANCE_NAMESPACE, &instance.name);
                 Err(Error::StoreError(e))
             }
         }
     }
     
     /// Create instance within a transaction
     pub fn create_in_transaction(
         &self,
         names_tree: &sled::transaction::TransactionalTree,
         spaces_tree: &sled::transaction::TransactionalTree,
         instances_tree: &sled::transaction::TransactionalTree,
         mut instance: Instance,
     ) -> std::result::Result<Instance, sled::transaction::ConflictableTransactionError<Error>> {
         // Reserve name in namespace within transaction
         match self.namespaces.reserve_in_transaction(names_tree, spaces_tree, &instance.name, Self::INSTANCE_NAMESPACE, &instance.name) {
             Ok(_) => {},
             Err(e) => return Err(sled::transaction::ConflictableTransactionError::Abort(
                 Error::StoreError(sled::Error::Unsupported(format!("Namespace reservation error: {:?}", e)))
             )),
         }
         
         // Get network assignments for interfaces
         for interface in &mut instance.config.network_interfaces {
             if let Ok(Some(network)) = self.networks.get(&interface.network_name) {
                 match network.get_assignment(&instance.name, self.networks.ranges.as_ref()) {
                     Ok(Some(assignment)) => {
                         interface.assignment = Some(assignment.to_string());
                     }
                     Ok(None) => {
-                        // No assignment available
+                        // No existing assignment, try to auto-assign if network has RangeAssigner
+                        // This enables automatic IP allocation for instances with network interfaces
+                        if let Some(ref ranges) = self.networks.ranges {
+                            // Attempt automatic IP assignment using the network's RangeAssigner
+                            if let Err(e) = network.auto_assign_ip(&instance.name, ranges) {
+                                return Err(sled::transaction::ConflictableTransactionError::Abort(e));
+                            }
+                            
+                            // Retrieve the newly assigned IP address
+                            match network.get_assignment(&instance.name, self.networks.ranges.as_ref()) {
+                                Ok(Some(assignment)) => {
+                                    interface.assignment = Some(assignment.to_string());
+                                }
+                                Ok(None) => {
+                                    // Auto-assignment failed silently or range is full
+                                    // This is not necessarily an error - some networks may not support auto-assignment
+                                }
+                                Err(e) => return Err(sled::transaction::ConflictableTransactionError::Abort(e)),
+                            }
+                        }
                     }
                     Err(e) => return Err(sled::transaction::ConflictableTransactionError::Abort(e)),
                 }
             }
         }
         
         // Store instance in transaction
         let instance_json = serde_json::to_string(&instance)
             .map_err(|e| sled::transaction::ConflictableTransactionError::Abort(
                 Error::StoreError(sled::Error::Unsupported(format!("JSON serialization error: {}", e)))
             ))?;
         
         instances_tree.insert(instance.name.as_bytes(), instance_json.as_bytes())
             .map_err(|e| sled::transaction::ConflictableTransactionError::Abort(
                 Error::StoreError(sled::Error::Unsupported(format!("Transaction insert error: {}", e)))
             ))?;
         
         Ok(instance)
     }
     
     /// Get an instance by name
     pub fn get(&self, name: &str) -> Result<Instance> {
         match self.instances.get(name)? {
             Some(data) => {
                 let json = String::from_utf8(data.to_vec())?;
                 serde_json::from_str(&json)
                     .map_err(|e| Error::StoreError(sled::Error::Unsupported(format!("JSON deserialization error: {}", e))))
             }
             None => Err(Error::StoreError(sled::Error::Unsupported(format!("Instance '{}' not found", name)))),
         }
     }
     
     /// Update an existing instance
     pub fn update(&self, instance: Instance) -> Result<Instance> {
         // Check if instance exists
         if !self.exists(&instance.name)? {
             return Err(Error::StoreError(sled::Error::Unsupported(format!("Instance '{}' not found", instance.name))));
         }
         
         let instance_json = serde_json::to_string(&instance)
             .map_err(|e| Error::StoreError(sled::Error::Unsupported(format!("JSON serialization error: {}", e))))?;
         
         self.instances.insert(&instance.name, instance_json.as_bytes())?;
         Ok(instance)
     }
     
-    /// Delete an instance
+    /// Delete an instance and clean up IP assignments
+    /// 
+    /// This method removes the instance and automatically unassigns any IP addresses
+    /// that were allocated to the instance's network interfaces. This ensures proper
+    /// cleanup of network resources and allows IPs to be reused by new instances.
     pub fn delete(&self, name: &str) -> Result<()> {
         // Get instance to clean up network assignments
         if let Ok(instance) = self.get(name) {
-            // TODO: Clean up network assignments when unassign functionality is available
-            for _interface in &instance.config.network_interfaces {
-                // network.unassign_ip(&name)?;
+            // Clean up IP assignments for each network interface
+            for interface in &instance.config.network_interfaces {
+                if let Ok(Some(network)) = self.networks.get(&interface.network_name) {
+                    // Only clean up if there's a range store and the network has a RangeAssigner
+                    if let Some(ref ranges) = self.networks.ranges {
+                        if let Err(e) = network.auto_unassign_ip(name, ranges) {
+                            // Log the error but don't fail the deletion
+                            eprintln!("Warning: Failed to clean up IP assignment for {} on network {}: {:?}", 
+                                     name, interface.network_name, e);
+                        }
+                    }
+                }
             }
         }
         
         // Remove from instance store
         self.instances.remove(name)?;
         
         // Remove from namespace
         self.namespaces.remove(Self::INSTANCE_NAMESPACE, name)?;
         
         Ok(())
     }
     
     /// Check if instance exists
     pub fn exists(&self, name: &str) -> Result<bool> {
         Ok(self.instances.contains_key(name)?)
     }
     
     /// List all instances
     pub fn list(&self) -> Result<Vec<Instance>> {
         let mut instances = Vec::new();
         
         for item in self.instances.iter() {
             let (_, value_bytes) = item?;
             let json = String::from_utf8(value_bytes.to_vec())?;
             let instance: Instance = serde_json::from_str(&json)
                 .map_err(|e| Error::StoreError(sled::Error::Unsupported(format!("JSON deserialization error: {}", e))))?;
             instances.push(instance);
         }
         
         instances.sort_by(|a, b| a.name.cmp(&b.name));
         Ok(instances)
     }
     
     /// List instances by provider type
     pub fn list_by_provider(&self, provider_type: &str) -> Result<Vec<Instance>> {
         Ok(self.list()?
             .into_iter()
             .filter(|instance| instance.provider_type == provider_type)
             .collect())
     }
     
     /// Check if an instance name is reserved in the namespace
     pub fn is_name_reserved(&self, name: &str) -> Result<bool> {
         self.namespaces.key_exists(Self::INSTANCE_NAMESPACE, name)
     }
 }
 
 
 
 // Example provider implementations
 
 /// Jail provider for FreeBSD jails
 #[derive(Debug, Clone, Serialize, Deserialize)]
 pub struct JailProvider {
     pub jail_conf: String,
     pub dataset: Option<String>,
 }
 
 impl JailProvider {
     pub fn new() -> Self {
         Self {
             jail_conf: String::new(),
             dataset: None,
         }
     }
     
     pub fn with_dataset(mut self, dataset: String) -> Self {
         self.dataset = Some(dataset);
         self
     }
 }
 
 impl Provider for JailProvider {
     fn provider_type(&self) -> &'static str {
         "jail"
     }
     
     fn to_json(&self) -> Result<String> {
         serde_json::to_string(self)
             .map_err(|e| Error::StoreError(sled::Error::Unsupported(format!("JSON serialization error: {}", e))))
     }
     
     fn from_json(json: &str) -> Result<Self> {
         serde_json::from_str(json)
             .map_err(|e| Error::StoreError(sled::Error::Unsupported(format!("JSON deserialization error: {}", e))))
     }
     
     fn start(&self, name: &str, _config: &InstanceConfig) -> Result<()> {
         // TODO: Implement jail start logic
         println!("Starting jail: {}", name);
         Ok(())
     }
     
     fn stop(&self, name: &str) -> Result<()> {
         // TODO: Implement jail stop logic
         println!("Stopping jail: {}", name);
         Ok(())
     }
     
     fn is_running(&self, _name: &str) -> Result<bool> {
         // TODO: Implement jail status check
         Ok(false)
     }
     
     fn status(&self, _name: &str) -> Result<ProviderStatus> {
         // TODO: Implement jail status collection
         Ok(ProviderStatus {
             running: false,
             pid: None,
             uptime: None,
             memory_usage: None,
             cpu_usage: None,
         })
     }
 }
 
 /// Container provider for Docker/Podman containers
 #[derive(Debug, Clone, Serialize, Deserialize)]
 pub struct ContainerProvider {
     pub runtime: String, // "docker" or "podman"
     pub image: String,
     pub registry: Option<String>,
 }
 
 impl ContainerProvider {
     pub fn docker(image: String) -> Self {
         Self {
             runtime: "docker".to_string(),
             image,
             registry: None,
         }
     }
     
     pub fn podman(image: String) -> Self {
         Self {
             runtime: "podman".to_string(),
             image,
             registry: None,
         }
     }
 }
 
 impl Provider for ContainerProvider {
     fn provider_type(&self) -> &'static str {
         "container"
     }
     
     fn to_json(&self) -> Result<String> {
         serde_json::to_string(self)
             .map_err(|e| Error::StoreError(sled::Error::Unsupported(format!("JSON serialization error: {}", e))))
     }
     
     fn from_json(json: &str) -> Result<Self> {
         serde_json::from_str(json)
             .map_err(|e| Error::StoreError(sled::Error::Unsupported(format!("JSON deserialization error: {}", e))))
     }
     
     fn start(&self, name: &str, _config: &InstanceConfig) -> Result<()> {
         // TODO: Implement container start logic
         println!("Starting {} container: {}", self.runtime, name);
         Ok(())
     }
     
     fn stop(&self, name: &str) -> Result<()> {
         // TODO: Implement container stop logic
         println!("Stopping {} container: {}", self.runtime, name);
         Ok(())
     }
     
     fn is_running(&self, _name: &str) -> Result<bool> {
         // TODO: Implement container status check
         Ok(false)
     }
     
     fn status(&self, _name: &str) -> Result<ProviderStatus> {
         // TODO: Implement container status collection
         Ok(ProviderStatus {
             running: false,
             pid: None,
             uptime: None,
             memory_usage: None,
             cpu_usage: None,
         })
     }
 }
 
 #[cfg(test)]
 mod tests {
     use super::*;
     use crate::{NamespaceStore, NetworkStore, RangeStore};
     use tempfile::TempDir;
     
     fn create_test_stores() -> Result<(TempDir, InstanceStore)> {
         let temp_dir = TempDir::new().unwrap();
         let db = sled::open(temp_dir.path())?;
         
         let namespaces = NamespaceStore::open(&db)?;
         let ranges = RangeStore::open(&db)?;
         let networks = NetworkStore::open_with_ranges(&db, ranges)?;
         let instances = InstanceStore::open(&db, namespaces, networks)?;
         
         Ok((temp_dir, instances))
     }
     
     #[test]
     fn test_create_instance() -> Result<()> {
         let (_temp_dir, store) = create_test_stores()?;
         
         let config = InstanceConfig {
             provider_config: "{}".to_string(),
             network_interfaces: vec![],
             metadata: HashMap::new(),
         };
         
         let instance = Instance::new(
             "test-instance".to_string(),
             "jail".to_string(),
             config,
         );
         
         let created = store.create(instance.clone())?;
         assert_eq!(created.name, instance.name);
         assert_eq!(created.provider_type, instance.provider_type);
         
         Ok(())
     }
     
     #[test]
     fn test_get_instance() -> Result<()> {
         let (_temp_dir, store) = create_test_stores()?;
         
         let config = InstanceConfig {
             provider_config: "{}".to_string(),
             network_interfaces: vec![],
             metadata: HashMap::new(),
         };
         
         let instance = Instance::new(
             "test-instance".to_string(),
             "jail".to_string(),
             config,
         );
         
         store.create(instance.clone())?;
         let retrieved = store.get(&instance.name)?;
         
         assert_eq!(retrieved.name, instance.name);
         assert_eq!(retrieved.provider_type, instance.provider_type);
         
         Ok(())
     }
     
     #[test]
     fn test_duplicate_instance_name() -> Result<()> {
         let (_temp_dir, store) = create_test_stores()?;
         
         let config = InstanceConfig {
             provider_config: "{}".to_string(),
             network_interfaces: vec![],
             metadata: HashMap::new(),
         };
         
         let instance1 = Instance::new(
             "test-instance".to_string(),
             "jail".to_string(),
             config.clone(),
         );
         
         let instance2 = Instance::new(
             "test-instance".to_string(),
             "container".to_string(),
             config,
         );
         
         store.create(instance1)?;
         
         // Should fail due to duplicate name
         assert!(store.create(instance2).is_err());
         
         Ok(())
     }
     
     #[test]
     fn test_list_instances() -> Result<()> {
         let (_temp_dir, store) = create_test_stores()?;
         
         let config = InstanceConfig {
             provider_config: "{}".to_string(),
             network_interfaces: vec![],
             metadata: HashMap::new(),
         };
         
         let instance1 = Instance::new(
             "instance1".to_string(),
             "jail".to_string(),
             config.clone(),
         );
         
         let instance2 = Instance::new(
             "instance2".to_string(),
             "container".to_string(),
             config,
         );
         
         store.create(instance1)?;
         store.create(instance2)?;
         
         let instances = store.list()?;
         assert_eq!(instances.len(), 2);
         
         let jail_instances = store.list_by_provider("jail")?;
         assert_eq!(jail_instances.len(), 1);
         assert_eq!(jail_instances[0].name, "instance1");
         
         Ok(())
     }
     
     #[test]
     fn test_delete_instance() -> Result<()> {
         let (_temp_dir, store) = create_test_stores()?;
         
         let config = InstanceConfig {
             provider_config: "{}".to_string(),
             network_interfaces: vec![],
             metadata: HashMap::new(),
         };
         
         let instance = Instance::new(
             "test-instance".to_string(),
             "jail".to_string(),
             config,
         );
         
         store.create(instance.clone())?;
         assert!(store.exists(&instance.name)?);
         
         store.delete(&instance.name)?;
         assert!(!store.exists(&instance.name)?);
         
         Ok(())
     }
     
     #[test]
     fn test_jail_provider() -> Result<()> {
         let provider = JailProvider::new().with_dataset("tank/jails".to_string());
         
         let json = provider.to_json()?;
         let deserialized = JailProvider::from_json(&json)?;
         
         assert_eq!(provider.dataset, deserialized.dataset);
         assert_eq!(provider.provider_type(), "jail");
         
         Ok(())
     }
     
     #[test]
     fn test_container_provider() -> Result<()> {
         let provider = ContainerProvider::docker("nginx:latest".to_string());
         
         let json = provider.to_json()?;
         let deserialized = ContainerProvider::from_json(&json)?;
         
         assert_eq!(provider.runtime, deserialized.runtime);
         assert_eq!(provider.image, deserialized.image);
         assert_eq!(provider.provider_type(), "container");
         
         Ok(())
     }
     
     #[test]
     fn test_instance_with_range_assigner_network() -> Result<()> {
         let (_temp_dir, store) = create_test_stores()?;
         
         // Set up a network with RangeAssigner
         use crate::network::{Network, NetRange, BasicProvider, RangeAssigner};
         
         let provider = BasicProvider::new();
         let assigner = RangeAssigner::with_range_name("test_range");
         
         store.networks.create(
             "test_network",
             NetRange::ipv4("192.168.1.0".parse().unwrap(), 24)?,
             provider,
             Some(assigner),
         )?;
         
         // Create instance with network interface
         let config = InstanceConfig {
             provider_config: "{}".to_string(),
             network_interfaces: vec![
                 NetworkInterface {
                     network_name: "test_network".to_string(),
                     interface_name: "eth0".to_string(),
                     assignment: None,
                 },
             ],
             metadata: HashMap::new(),
         };
         
         let instance = Instance::new(
             "test-with-network".to_string(),
             "jail".to_string(),
             config,
         );
         
         let created = store.create(instance)?;
         
         // Verify instance was created with network assignment
         assert_eq!(created.name, "test-with-network");
         assert_eq!(created.config.network_interfaces.len(), 1);
         
         let interface = &created.config.network_interfaces[0];
         assert_eq!(interface.network_name, "test_network");
         assert_eq!(interface.interface_name, "eth0");
         
         // Note: IP assignment would be available if range was properly initialized
         // For this test, we verify the structure is correct
         
         Ok(())
     }
     
     #[test]
     fn test_multiple_instances_different_ip_assignments() -> Result<()> {
         let (_temp_dir, store) = create_test_stores()?;
         
         // Set up a network with RangeAssigner
         use crate::network::{Network, NetRange, BasicProvider, RangeAssigner};
         
         let provider = BasicProvider::new();
         let assigner = RangeAssigner::with_range_name("multi_test_range");
         
         store.networks.create(
             "multi_network",
             NetRange::ipv4("10.0.0.0".parse().unwrap(), 24)?,
             provider,
             Some(assigner),
         )?;
         
         // Create first instance
         let config1 = InstanceConfig {
             provider_config: "{}".to_string(),
             network_interfaces: vec![
                 NetworkInterface {
                     network_name: "multi_network".to_string(),
                     interface_name: "eth0".to_string(),
                     assignment: None,
                 },
             ],
             metadata: HashMap::new(),
         };
         
         let instance1 = Instance::new(
             "instance1".to_string(),
             "jail".to_string(),
             config1,
         );
         
         // Create second instance
         let config2 = InstanceConfig {
             provider_config: "{}".to_string(),
             network_interfaces: vec![
                 NetworkInterface {
                     network_name: "multi_network".to_string(),
                     interface_name: "eth0".to_string(),
                     assignment: None,
                 },
             ],
             metadata: HashMap::new(),
         };
         
         let instance2 = Instance::new(
             "instance2".to_string(),
             "container".to_string(),
             config2,
         );
         
         let created1 = store.create(instance1)?;
         let created2 = store.create(instance2)?;
         
         // Verify both instances have network interfaces
         assert_eq!(created1.config.network_interfaces.len(), 1);
         assert_eq!(created2.config.network_interfaces.len(), 1);
         
         // Verify they're connected to the same network
         assert_eq!(created1.config.network_interfaces[0].network_name, "multi_network");
         assert_eq!(created2.config.network_interfaces[0].network_name, "multi_network");
         
         // Both instances should exist in the store
         assert!(store.exists("instance1")?);
         assert!(store.exists("instance2")?);
         
         Ok(())
     }
     
     #[test]
     fn test_instance_with_multiple_networks() -> Result<()> {
         let (_temp_dir, store) = create_test_stores()?;
         
         // Set up multiple networks
         use crate::network::{Network, NetRange, BasicProvider, RangeAssigner};
         
         // Management network
         let mgmt_provider = BasicProvider::new();
         let mgmt_assigner = RangeAssigner::with_range_name("mgmt_range");
         
         store.networks.create(
             "management",
             NetRange::ipv4("10.0.1.0".parse().unwrap(), 24)?,
             mgmt_provider,
             Some(mgmt_assigner),
         )?;
         
         // Public network
         let public_provider = BasicProvider::new();
         let public_assigner = RangeAssigner::with_range_name("public_range");
         
         store.networks.create(
             "public",
             NetRange::ipv4("192.168.1.0".parse().unwrap(), 24)?,
             public_provider,
             Some(public_assigner),
         )?;
         
         // Create instance with multiple network interfaces
         let config = InstanceConfig {
             provider_config: "{}".to_string(),
             network_interfaces: vec![
                 NetworkInterface {
                     network_name: "management".to_string(),
                     interface_name: "eth0".to_string(),
                     assignment: None,
                 },
                 NetworkInterface {
                     network_name: "public".to_string(),
                     interface_name: "eth1".to_string(),
                     assignment: None,
                 },
             ],
             metadata: HashMap::new(),
         };
         
         let instance = Instance::new(
             "multi-network-instance".to_string(),
             "container".to_string(),
             config,
         );
         
         let created = store.create(instance)?;
         
         // Verify instance has both network interfaces
         assert_eq!(created.config.network_interfaces.len(), 2);
         
         let interfaces: Vec<&str> = created.config.network_interfaces
             .iter()
             .map(|i| i.network_name.as_str())
             .collect();
         
         assert!(interfaces.contains(&"management"));
         assert!(interfaces.contains(&"public"));
         
         Ok(())
     }
     
     #[test]
     fn test_instance_network_assignment_cleanup_on_delete() -> Result<()> {
         let (_temp_dir, store) = create_test_stores()?;
         
         // Set up a network
         use crate::network::{Network, NetRange, BasicProvider, RangeAssigner};
         
         let provider = BasicProvider::new();
         let assigner = RangeAssigner::with_range_name("cleanup_range");
         
         store.networks.create(
             "cleanup_network",
             NetRange::ipv4("172.16.0.0".parse().unwrap(), 24)?,
             provider,
             Some(assigner),
         )?;
         
         // Create instance
         let config = InstanceConfig {
             provider_config: "{}".to_string(),
             network_interfaces: vec![
                 NetworkInterface {
                     network_name: "cleanup_network".to_string(),
                     interface_name: "eth0".to_string(),
                     assignment: None,
                 },
             ],
             metadata: HashMap::new(),
         };
         
         let instance = Instance::new(
             "cleanup-test".to_string(),
             "jail".to_string(),
             config,
         );
         
         let created = store.create(instance)?;
         assert!(store.exists("cleanup-test")?);
         
         // Delete the instance
         store.delete("cleanup-test")?;
         assert!(!store.exists("cleanup-test")?);
         
         // Verify instance is removed from namespace
         assert!(!store.namespaces.key_exists("instances", "cleanup-test")?);
         
         Ok(())
     }
     
     #[test]
     fn test_instance_creation_fails_with_nonexistent_network() -> Result<()> {
         let (_temp_dir, store) = create_test_stores()?;
         
         // Try to create instance with non-existent network
         let config = InstanceConfig {
             provider_config: "{}".to_string(),
             network_interfaces: vec![
                 NetworkInterface {
                     network_name: "nonexistent_network".to_string(),
                     interface_name: "eth0".to_string(),
                     assignment: None,
                 },
             ],
             metadata: HashMap::new(),
         };
         
         let instance = Instance::new(
             "test-nonexistent-network".to_string(),
             "jail".to_string(),
             config,
         );
         
         // This should succeed because network assignment is not enforced during creation
         // The instance will be created but without IP assignments
         let created = store.create(instance)?;
         assert_eq!(created.config.network_interfaces[0].assignment, None);
         
         Ok(())
     }
     
     #[test]
     fn test_instance_transaction_rollback_preserves_state() -> Result<()> {
         let (_temp_dir, store) = create_test_stores()?;
         
         // Set up a network
         use crate::network::{Network, NetRange, BasicProvider, RangeAssigner};
         
         let provider = BasicProvider::new();
         let assigner = RangeAssigner::with_range_name("tx_test_range");
         
         store.networks.create(
             "tx_network",
             NetRange::ipv4("172.20.0.0".parse().unwrap(), 24)?,
             provider,
             Some(assigner),
         )?;
         
         // Create first instance successfully
         let config1 = InstanceConfig {
             provider_config: "{}".to_string(),
             network_interfaces: vec![
                 NetworkInterface {
                     network_name: "tx_network".to_string(),
                     interface_name: "eth0".to_string(),
                     assignment: None,
                 },
             ],
             metadata: HashMap::new(),
         };
         
         let instance1 = Instance::new(
             "tx-test-1".to_string(),
             "jail".to_string(),
             config1,
         );
         
         store.create(instance1)?;
         assert!(store.exists("tx-test-1")?);
         
         // Verify namespace contains the instance
         assert!(store.namespaces.key_exists("instances", "tx-test-1")?);
         
         // Try to create duplicate instance (should fail)
         let config2 = InstanceConfig {
             provider_config: "{}".to_string(),
             network_interfaces: vec![],
             metadata: HashMap::new(),
         };
         
         let instance2 = Instance::new(
             "tx-test-1".to_string(), // Same name - should fail
             "container".to_string(),
             config2,
         );
         
         // This should fail due to duplicate name
         assert!(store.create(instance2).is_err());
         
         // Original instance should still exist
         assert!(store.exists("tx-test-1")?);
         assert!(store.namespaces.key_exists("instances", "tx-test-1")?);
         
         Ok(())
     }
 }
\ No newline at end of file
diff --git a/crates/store/src/network.rs b/crates/store/src/network.rs
index 2421c27..6c8a4f3 100644
--- a/crates/store/src/network.rs
+++ b/crates/store/src/network.rs
@@ -1,1535 +1,1664 @@
 //! Network management module for storing and managing network configurations.
 //!
 //! This module provides functionality to create, read, update, and delete network
 //! configurations. Each network is defined by a name, network range (IPv4 or IPv6),
 //! a provider implementation, and an optional assigner implementation.
 //!
 //! # Example
 //!
 //! ```
 //! use store::network::*;
 //! # use tempfile::TempDir;
 //! # fn example() -> store::Result<()> {
 //! # let temp_dir = tempfile::tempdir().unwrap();
 //! # let db = sled::open(temp_dir.path())?;
 //! let store = NetworkStore::open(&db)?;
 //!
 //! // Create a network with IPv4 range
 //! let netrange = NetRange::from_cidr("192.168.1.0/24")?;
 //! let provider = BasicProvider::new()
 //!     .with_config("type", "aws")
 //!     .with_config("region", "us-west-2");
 //! let assigner = Some(BasicAssigner::new("dhcp"));
 //!
 //! store.create("production", netrange, provider, assigner)?;
 //!
 //! // Retrieve the network
 //! let network = store.get("production")?.unwrap();
 //! assert_eq!(network.name, "production");
 //!
 //! // List all networks
 //! let networks = store.list()?;
 //! assert!(networks.contains(&"production".to_string()));
 //! # Ok(())
 //! # }
 //! ```
 
 use crate::{Result, Error, RangeStore};
 use sled::Transactional;
 use std::collections::HashMap;
 use std::net::{Ipv4Addr, Ipv6Addr, IpAddr};
 
 /// Represents an IPv4 or IPv6 network range
 #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
 pub enum NetRange {
     /// IPv4 network with address and prefix length
     V4 { addr: Ipv4Addr, prefix: u8 },
     /// IPv6 network with address and prefix length  
     V6 { addr: Ipv6Addr, prefix: u8 },
 }
 
 impl NetRange {
     /// Create a new IPv4 network range
     pub fn ipv4(addr: Ipv4Addr, prefix: u8) -> Result<Self> {
         if prefix > 32 {
             return Err(Error::StoreError(sled::Error::Unsupported(
                 format!("Invalid IPv4 prefix length: {}", prefix)
             )));
         }
         Ok(NetRange::V4 { addr, prefix })
     }
 
     /// Create a new IPv6 network range
     pub fn ipv6(addr: Ipv6Addr, prefix: u8) -> Result<Self> {
         if prefix > 128 {
             return Err(Error::StoreError(sled::Error::Unsupported(
                 format!("Invalid IPv6 prefix length: {}", prefix)
             )));
         }
         Ok(NetRange::V6 { addr, prefix })
     }
 
     /// Parse a network range from CIDR notation
     pub fn from_cidr(cidr: &str) -> Result<Self> {
         let parts: Vec<&str> = cidr.split('/').collect();
         if parts.len() != 2 {
             return Err(Error::StoreError(sled::Error::Unsupported(
                 "Invalid CIDR format".to_string()
             )));
         }
 
         let prefix: u8 = parts[1].parse().map_err(|_| {
             Error::StoreError(sled::Error::Unsupported("Invalid prefix length".to_string()))
         })?;
 
         if let Ok(ipv4) = parts[0].parse::<Ipv4Addr>() {
             Self::ipv4(ipv4, prefix)
         } else if let Ok(ipv6) = parts[0].parse::<Ipv6Addr>() {
             Self::ipv6(ipv6, prefix)
         } else {
             Err(Error::StoreError(sled::Error::Unsupported(
                 "Invalid IP address format".to_string()
             )))
         }
     }
 
     /// Convert to CIDR notation string
     pub fn to_cidr(&self) -> String {
         match self {
             NetRange::V4 { addr, prefix } => format!("{}/{}", addr, prefix),
             NetRange::V6 { addr, prefix } => format!("{}/{}", addr, prefix),
         }
     }
 }
 
 /// Trait for network providers
 pub trait NetworkProvider: std::fmt::Debug {
     /// Get the provider type identifier
     fn provider_type(&self) -> &'static str;
     /// Serialize provider configuration to JSON
     fn to_json(&self) -> Result<String>;
     /// Create provider from JSON
     fn from_json(json: &str) -> Result<Self> where Self: Sized;
 }
 
 /// Trait for network assigners
 pub trait NetworkAssigner: std::fmt::Debug {
     /// Get the assigner type identifier  
     fn assigner_type(&self) -> &'static str;
     /// Serialize assigner configuration to JSON
     fn to_json(&self) -> Result<String>;
     /// Create assigner from JSON
     fn from_json(json: &str) -> Result<Self> where Self: Sized;
     /// Initialize the assigner for a network during creation
     fn initialize_for_network(&self, _network_name: &str, _netrange: &NetRange, _range_store: Option<&RangeStore>) -> Result<()> {
         // Default implementation does nothing
         Ok(())
     }
     
     /// Initialize the assigner for a network within a transaction
     fn initialize_for_network_in_transaction(
         &self, 
         _network_name: &str, 
         _netrange: &NetRange, 
         _range_trees: Option<(&sled::transaction::TransactionalTree, &sled::transaction::TransactionalTree)>
     ) -> Result<(), sled::transaction::ConflictableTransactionError<()>> {
         // Default implementation does nothing
         Ok(())
     }
     
     /// Cleanup the assigner for a network during deletion
     fn cleanup_for_network(&self, _network_name: &str, _range_store: Option<&RangeStore>) -> Result<()> {
         // Default implementation does nothing
         Ok(())
     }
     
     /// Cleanup the assigner for a network within a transaction
     fn cleanup_for_network_in_transaction(
         &self,
         _network_name: &str,
         _range_trees: Option<(&sled::transaction::TransactionalTree, &sled::transaction::TransactionalTree)>
     ) -> Result<(), sled::transaction::ConflictableTransactionError<()>> {
         // Default implementation does nothing
         Ok(())
     }
     
     /// Get an IP assignment for the given identifier
     fn get_assignment(&self, _network: &Network, _identifier: &str, _range_store: Option<&RangeStore>) -> Result<Option<IpAddr>> {
         // Default implementation returns None (no assignment tracking)
         Ok(None)
     }
 }
 
 /// Basic network provider implementation
 #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
 pub struct BasicProvider {
     pub config: HashMap<String, String>,
 }
 
 impl BasicProvider {
     pub fn new() -> Self {
         Self {
             config: HashMap::new(),
         }
     }
 
     pub fn with_config(mut self, key: &str, value: &str) -> Self {
         self.config.insert(key.to_string(), value.to_string());
         self
     }
 }
 
 impl NetworkProvider for BasicProvider {
     fn provider_type(&self) -> &'static str {
         "basic"
     }
 
     fn to_json(&self) -> Result<String> {
         serde_json::to_string(self).map_err(|e| {
             Error::StoreError(sled::Error::Unsupported(format!("JSON serialization error: {}", e)))
         })
     }
 
     fn from_json(json: &str) -> Result<Self> {
         serde_json::from_str(json).map_err(|e| {
             Error::StoreError(sled::Error::Unsupported(format!("JSON deserialization error: {}", e)))
         })
     }
 }
 
 /// Basic network assigner implementation
 #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]  
 pub struct BasicAssigner {
     pub strategy: String,
     pub config: HashMap<String, String>,
 }
 
 impl BasicAssigner {
     pub fn new(strategy: &str) -> Self {
         Self {
             strategy: strategy.to_string(),
             config: HashMap::new(),
         }
     }
 
     pub fn with_config(mut self, key: &str, value: &str) -> Self {
         self.config.insert(key.to_string(), value.to_string());
         self
     }
 }
 
 impl NetworkAssigner for BasicAssigner {
     fn assigner_type(&self) -> &'static str {
         "basic"
     }
 
     fn to_json(&self) -> Result<String> {
         serde_json::to_string(self).map_err(|e| {
             Error::StoreError(sled::Error::Unsupported(format!("JSON serialization error: {}", e)))
         })
     }
 
     fn from_json(json: &str) -> Result<Self> {
         serde_json::from_str(json).map_err(|e| {
             Error::StoreError(sled::Error::Unsupported(format!("JSON deserialization error: {}", e)))
         })
     }
 }
 
 /// Range-based network assigner that uses RangeStore for IP assignment
 ///
 /// The `RangeAssigner` provides IP address assignment functionality by integrating
 /// with the `RangeStore`. It automatically manages IP address allocation within
 /// a network range using a bitmap-based approach for efficient tracking.
 ///
 /// # Features
 ///
 /// - Automatic range initialization when creating networks
 /// - Sequential IP assignment within the network range
 /// - Assignment tracking with custom identifiers
 /// - IP unassignment and reuse
 /// - Integration with the NetworkStore lifecycle
 ///
 /// # Example
 ///
 /// ```
 /// use store::network::{RangeAssigner, NetRange, BasicProvider, NetworkStore};
 /// # use tempfile::TempDir;
 /// # fn example() -> store::Result<()> {
 /// # let temp_dir = tempfile::tempdir().unwrap();
 /// # let db = sled::open(temp_dir.path())?;
 /// # let ranges = store::range::RangeStore::open(&db)?;
 /// # let store = NetworkStore::open_with_ranges(&db, ranges.clone())?;
 ///
 /// // Create a RangeAssigner for a network (uses network name as range name)
 /// let assigner = RangeAssigner::new()
 ///     .with_config("strategy", "sequential");
 ///
 /// // Create a network with the range assigner
 /// let netrange = NetRange::from_cidr("192.168.1.0/24")?;
 /// let provider = BasicProvider::new();
 /// store.create("my-network", netrange, provider, Some(assigner))?;
 ///
 /// // Use the assigner to allocate IPs
 /// let range_assigner = RangeAssigner::new();
 /// let ip_bit = range_assigner.assign_ip("my-network", &ranges, "device1")?;
 /// # Ok(())
 /// # }
 /// ```
 #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]  
 pub struct RangeAssigner {
     pub range_name: Option<String>,
     pub config: HashMap<String, String>,
 }
 
 impl RangeAssigner {
     /// Create a new RangeAssigner with default settings
     ///
     /// The range name will default to the network name when the network is created.
     /// Use `with_range_name()` to specify a custom range name if needed.
     pub fn new() -> Self {
         Self {
             range_name: None,
             config: HashMap::new(),
         }
     }
 
     /// Create a new RangeAssigner with a specific range name
     ///
     /// # Arguments
     ///
     /// * `range_name` - Custom identifier for the IP range
     pub fn with_range_name(range_name: &str) -> Self {
         Self {
             range_name: Some(range_name.to_string()),
             config: HashMap::new(),
         }
     }
 
     /// Add configuration key-value pairs to the assigner
     ///
     /// Configuration options can be used to customize the behavior of the
     /// assigner. Common configuration keys might include:
     /// - `strategy`: Assignment strategy (e.g., "sequential", "random")
     /// - `reserve_gateway`: Whether to reserve the first IP for gateway
     /// - `pool_size`: Maximum number of IPs to manage
     ///
     /// # Arguments
     ///
     /// * `key` - Configuration key
     /// * `value` - Configuration value
     pub fn with_config(mut self, key: &str, value: &str) -> Self {
         self.config.insert(key.to_string(), value.to_string());
         self
     }
 
     /// Initialize the range for this assigner with the given network size
     ///
     /// This method calculates the number of available IP addresses in the network
     /// range and creates a corresponding range in the RangeStore. It's automatically
     /// called when creating a network with a RangeAssigner.
     ///
     /// # Arguments
     ///
     /// * `range_name` - Name to use for the range
     /// * `range_store` - Reference to the RangeStore
     /// * `network` - Network range (IPv4 or IPv6) to initialize
     ///
     /// # Returns
     ///
     /// Returns `Ok(())` if the range was successfully initialized, or an error
     /// if the network parameters are invalid or the range already exists.
     pub fn initialize_range(&self, range_name: &str, range_store: &RangeStore, network: &NetRange) -> Result<()> {
         let size = match network {
             NetRange::V4 { prefix, .. } => {
                 if *prefix >= 32 {
                     return Err(Error::StoreError(sled::Error::Unsupported(
                         "IPv4 prefix must be less than 32".to_string()
                     )));
                 }
                 1u64 << (32 - prefix)
             }
             NetRange::V6 { prefix, .. } => {
                 if *prefix >= 128 {
                     return Err(Error::StoreError(sled::Error::Unsupported(
                         "IPv6 prefix must be less than 128".to_string()
                     )));
                 }
                 // For IPv6, we'll limit to a reasonable size to avoid huge ranges
                 let host_bits = 128 - prefix;
                 if host_bits > 32 {
                     1u64 << 32 // Cap at 2^32 addresses
                 } else {
                     1u64 << host_bits
                 }
             }
         };
         
         range_store.define(range_name, size)
     }
 
     /// Assign an IP address from the range
     ///
     /// Assigns the next available IP address in the range to the specified identifier.
     /// The assignment uses a sequential allocation strategy, finding the first
     /// available bit position in the range bitmap.
     ///
     /// # Arguments
     ///
     /// * `range_name` - Name of the range to assign from
     /// * `range_store` - Reference to the RangeStore
     /// * `identifier` - Unique identifier for the device/entity receiving the IP
     ///
     /// # Returns
     ///
     /// Returns the bit position of the assigned IP address, which can be used
     /// to calculate the actual IP address within the network range.
     ///
     /// # Errors
     ///
     /// Returns an error if the range is full or doesn't exist.
     pub fn assign_ip(&self, range_name: &str, range_store: &RangeStore, identifier: &str) -> Result<u64> {
         range_store.assign(range_name, identifier)
     }
 
     /// Get assigned IP information
     ///
     /// Retrieves the identifier associated with a specific bit position in the range.
     /// This is useful for determining which device or entity is assigned to a
     /// particular IP address.
     ///
     /// # Arguments
     ///
     /// * `range_name` - Name of the range to query
     /// * `range_store` - Reference to the RangeStore
     /// * `bit_position` - The bit position to query
     ///
     /// # Returns
     ///
     /// Returns `Some(identifier)` if the bit position is assigned, or `None`
     /// if it's free or out of range.
     pub fn get_assignment_by_bit(&self, range_name: &str, range_store: &RangeStore, bit_position: u64) -> Result<Option<String>> {
         range_store.get(range_name, bit_position)
     }
 
     /// Unassign an IP address
     ///
     /// Releases an IP address assignment, making it available for future assignments.
     /// This is typically called when a device is removed or no longer needs its
     /// assigned IP address.
     ///
     /// # Arguments
     ///
     /// * `range_name` - Name of the range to unassign from
     /// * `range_store` - Reference to the RangeStore
     /// * `bit_position` - The bit position to unassign
     ///
     /// # Returns
     ///
     /// Returns `true` if the IP was successfully unassigned, or `false` if it
     /// was already free or the bit position is out of range.
     pub fn unassign_ip(&self, range_name: &str, range_store: &RangeStore, bit_position: u64) -> Result<bool> {
         range_store.unassign_bit(range_name, bit_position)
     }
 
     /// Get the range name for this assigner, using the network name as default
     ///
     /// # Arguments
     ///
     /// * `network_name` - Network name to use as default if no custom range name is set
     ///
     /// # Returns
     ///
     /// Returns the range name to use for operations
     pub fn get_range_name(&self, network_name: &str) -> String {
         self.range_name.clone().unwrap_or_else(|| network_name.to_string())
     }
 
     /// Convert a bit position to an IP address within the network range
     ///
     /// # Arguments
     ///
     /// * `bit_position` - The bit position within the range
     /// * `netrange` - The network range to calculate the IP within
     ///
     /// # Returns
     ///
     /// Returns the IP address corresponding to the bit position
     pub fn bit_position_to_ip(&self, bit_position: u64, netrange: &NetRange) -> Result<IpAddr> {
         match netrange {
             NetRange::V4 { addr, prefix: _ } => {
                 let network_addr = u32::from(*addr);
                 let host_addr = network_addr + bit_position as u32;
                 Ok(IpAddr::V4(Ipv4Addr::from(host_addr)))
             }
             NetRange::V6 { addr, prefix: _ } => {
                 let network_addr = u128::from(*addr);
                 let host_addr = network_addr + bit_position as u128;
                 Ok(IpAddr::V6(Ipv6Addr::from(host_addr)))
             }
         }
     }
 
     /// Find the bit position for a given identifier in the range
     ///
     /// # Arguments
     ///
     /// * `range_name` - Name of the range to search
     /// * `range_store` - Reference to the RangeStore
     /// * `identifier` - The identifier to search for
     ///
     /// # Returns
     ///
     /// Returns the bit position if found, None otherwise
     pub fn find_identifier_bit_position(&self, range_name: &str, range_store: &RangeStore, identifier: &str) -> Result<Option<u64>> {
         // Get range ID first
         let range_id = match range_store.names.get(range_name)? {
             Some(data) => {
                 if data.len() != 16 {
                     return Ok(None);
                 }
                 let id_bytes: [u8; 8] = data[0..8].try_into()
                     .map_err(|_| Error::StoreError(sled::Error::Unsupported("Invalid range data".to_string())))?;
                 u64::from_be_bytes(id_bytes)
             }
             None => return Ok(None),
         };
 
         // Search for the assignment with this identifier
         let prefix = range_id.to_be_bytes();
         
         for result in range_store.assign.scan_prefix(&prefix) {
             let (key, stored_value) = result?;
             if key.len() == 16 {
                 let stored_value_str = String::from_utf8(stored_value.to_vec())?;
                 if stored_value_str == identifier {
                     let bit_position_bytes: [u8; 8] = key[8..16].try_into()
                         .map_err(|_| Error::StoreError(sled::Error::Unsupported("Invalid key format".to_string())))?;
                     return Ok(Some(u64::from_be_bytes(bit_position_bytes)));
                 }
             }
         }
         
         Ok(None)
     }
 }
 
 impl NetworkAssigner for RangeAssigner {
     fn assigner_type(&self) -> &'static str {
         "range"
     }
 
     fn to_json(&self) -> Result<String> {
         serde_json::to_string(self).map_err(|e| {
             Error::StoreError(sled::Error::Unsupported(format!("JSON serialization error: {}", e)))
         })
     }
 
     fn from_json(json: &str) -> Result<Self> {
         serde_json::from_str(json).map_err(|e| {
             Error::StoreError(sled::Error::Unsupported(format!("JSON deserialization error: {}", e)))
         })
     }
 
     fn initialize_for_network(&self, network_name: &str, netrange: &NetRange, range_store: Option<&RangeStore>) -> Result<()> {
         if let Some(store) = range_store {
             let range_name = self.get_range_name(network_name);
             self.initialize_range(&range_name, store, netrange)?;
         }
         Ok(())
     }
 
     fn initialize_for_network_in_transaction(
         &self, 
         network_name: &str, 
         netrange: &NetRange, 
         range_trees: Option<(&sled::transaction::TransactionalTree, &sled::transaction::TransactionalTree)>
     ) -> Result<(), sled::transaction::ConflictableTransactionError<()>> {
         if let Some((range_names, range_map)) = range_trees {
             let range_name = self.get_range_name(network_name);
             let size = match netrange {
                 NetRange::V4 { prefix, .. } => {
                     if *prefix >= 32 {
                         return Err(sled::transaction::ConflictableTransactionError::Abort(()));
                     }
                     1u64 << (32 - prefix)
                 }
                 NetRange::V6 { prefix, .. } => {
                     if *prefix >= 128 {
                         return Err(sled::transaction::ConflictableTransactionError::Abort(()));
                     }
                     // For IPv6, we'll limit to a reasonable size to avoid huge ranges
                     let host_bits = 128 - prefix;
                     if host_bits > 32 {
                         1u64 << 32 // Cap at 2^32 addresses
                     } else {
                         1u64 << host_bits
                     }
                 }
             };
             
             // Define the range within the transaction
             match range_names.get(range_name.as_bytes())? {
                 Some(_) => Ok(()), // Range already exists
                 None => {
                     let id = range_names.generate_id()?;
                     
                     // Store name -> (id, size)
                     let mut value = Vec::new();
                     value.extend_from_slice(&id.to_be_bytes());
                     value.extend_from_slice(&size.to_be_bytes());
                     range_names.insert(range_name.as_bytes(), value)?;
                     
                     // Initialize bitmap for the range (all zeros)
                     let bitmap_size = (size + 7) / 8; // Round up to nearest byte
                     let bitmap = vec![0u8; bitmap_size as usize];
                     range_map.insert(&id.to_be_bytes(), bitmap)?;
                     
                     Ok(())
                 }
             }
         } else {
             Ok(())
         }
     }
 
     fn get_assignment(&self, network: &Network, identifier: &str, range_store: Option<&RangeStore>) -> Result<Option<IpAddr>> {
         if let Some(store) = range_store {
             let range_name = self.get_range_name(&network.name);
             
             // Find the bit position for this identifier
             if let Some(bit_position) = self.find_identifier_bit_position(&range_name, store, identifier)? {
                 // Convert bit position to IP address
                 let ip = self.bit_position_to_ip(bit_position, &network.netrange)?;
                 Ok(Some(ip))
             } else {
                 Ok(None)
             }
         } else {
             Ok(None)
         }
     }
 
     fn cleanup_for_network(&self, network_name: &str, range_store: Option<&RangeStore>) -> Result<()> {
         if let Some(store) = range_store {
             let range_name = self.get_range_name(network_name);
             store.delete_range(&range_name)?;
         }
         Ok(())
     }
 
     fn cleanup_for_network_in_transaction(
         &self,
         network_name: &str,
         range_trees: Option<(&sled::transaction::TransactionalTree, &sled::transaction::TransactionalTree)>
     ) -> Result<(), sled::transaction::ConflictableTransactionError<()>> {
         if let Some((range_names, range_map)) = range_trees {
             let range_name = self.get_range_name(network_name);
             
             // Get the range ID from the name
             if let Some(value) = range_names.get(range_name.as_bytes())? {
                 let id = u64::from_be_bytes([
                     value[0], value[1], value[2], value[3],
                     value[4], value[5], value[6], value[7],
                 ]);
                 
                 // Remove the range data
                 range_map.remove(&id.to_be_bytes())?;
             }
             
             // Remove the range name
             range_names.remove(range_name.as_bytes())?;
         }
         Ok(())
     }
 }
 
 /// Network configuration
 #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
 pub struct Network {
     pub name: String,
     pub netrange: NetRange,
     pub provider_type: String,
     pub provider_config: String,
     pub assigner_type: Option<String>,
     pub assigner_config: Option<String>,
 }
 
 impl Network {
     /// Get an IP assignment for the given identifier
     ///
     /// This method calls the appropriate assigner implementation to retrieve
     /// the assigned IP address for a given identifier. Different assigners
     /// may return the IP in different formats (bit position or full IP).
     ///
     /// # Arguments
     ///
     /// * `identifier` - The identifier to look up (e.g., device name, container ID)
     /// * `range_store` - Optional reference to RangeStore (required for RangeAssigner)
     ///
     /// # Returns
     ///
     /// Returns the assigned IP address if found, None if no assignment exists
     ///
     /// # Example
     ///
     /// ```
     /// # use store::network::{Network, NetRange, RangeAssigner};
     /// # use tempfile::TempDir;
     /// # fn example() -> store::Result<()> {
     /// # let temp_dir = tempfile::tempdir().unwrap();
     /// # let db = sled::open(temp_dir.path())?;
     /// # let ranges = store::range::RangeStore::open(&db)?;
     /// # let store = store::network::NetworkStore::open_with_ranges(&db, ranges.clone())?;
     /// # let netrange = NetRange::from_cidr("192.168.1.0/24")?;
     /// # let provider = store::network::BasicProvider::new();
     /// # let assigner = RangeAssigner::new();
     /// # store.create("test", netrange, provider, Some(assigner))?;
     /// # let network = store.get("test")?.unwrap();
     /// 
     /// let ip = network.get_assignment("device1", Some(&ranges))?;
     /// if let Some(addr) = ip {
     ///     println!("Device1 is assigned IP: {}", addr);
     /// }
     /// # Ok(())
     /// # }
     /// ```
     /// Create an assigner instance from the network configuration
     pub fn create_assigner(&self) -> Result<Box<dyn NetworkAssigner>> {
         if let (Some(assigner_type), Some(assigner_config)) = (&self.assigner_type, &self.assigner_config) {
             match assigner_type.as_str() {
                 "range" => {
                     let range_assigner: RangeAssigner = serde_json::from_str(assigner_config)
                         .map_err(|e| Error::StoreError(sled::Error::Unsupported(
                             format!("Failed to deserialize RangeAssigner: {}", e)
                         )))?;
                     Ok(Box::new(range_assigner))
                 }
                 "basic" => {
                     let basic_assigner: BasicAssigner = serde_json::from_str(assigner_config)
                         .map_err(|e| Error::StoreError(sled::Error::Unsupported(
                             format!("Failed to deserialize BasicAssigner: {}", e)
                         )))?;
                     Ok(Box::new(basic_assigner))
                 }
                 _ => {
                     Err(Error::StoreError(sled::Error::Unsupported(
                         format!("Unknown assigner type: {}", assigner_type)
                     )))
                 }
             }
         } else {
             Err(Error::StoreError(sled::Error::Unsupported(
                 "No assigner configured".to_string()
             )))
         }
     }
 
     pub fn get_assignment(&self, identifier: &str, range_store: Option<&RangeStore>) -> Result<Option<IpAddr>> {
         if let (Some(assigner_type), Some(assigner_config)) = (&self.assigner_type, &self.assigner_config) {
             match assigner_type.as_str() {
                 "range" => {
                     let range_assigner: RangeAssigner = serde_json::from_str(assigner_config)
                         .map_err(|e| Error::StoreError(sled::Error::Unsupported(
                             format!("Failed to deserialize RangeAssigner: {}", e)
                         )))?;
                     range_assigner.get_assignment(self, identifier, range_store)
                 }
                 "basic" => {
                     let basic_assigner: BasicAssigner = serde_json::from_str(assigner_config)
                         .map_err(|e| Error::StoreError(sled::Error::Unsupported(
                             format!("Failed to deserialize BasicAssigner: {}", e)
                         )))?;
                     basic_assigner.get_assignment(self, identifier, range_store)
                 }
                 _ => {
                     // Unknown assigner type
                     Ok(None)
                 }
             }
         } else {
             // No assigner configured
             Ok(None)
         }
     }
 
+    /// Automatically assign an IP address for the given identifier
+    /// 
+    /// This method will assign an IP address from the network's range if it has
+    /// a RangeAssigner configured. This is typically called during instance creation
+    /// to automatically provision network connectivity.
+    ///
+    /// The method performs the following steps:
+    /// 1. Checks if the network has a RangeAssigner configured
+    /// 2. If so, calls the assigner's assign_ip method to allocate an IP
+    /// 3. The assigned IP can then be retrieved using get_assignment()
+    ///
+    /// # Arguments
+    ///
+    /// * `identifier` - The identifier to assign an IP to (e.g., instance name, container ID)
+    /// * `range_store` - Reference to the RangeStore for managing IP assignments
+    ///
+    /// # Returns
+    ///
+    /// * `Ok(())` - Assignment was successful or no assigner is configured
+    /// * `Err(Error)` - Assignment failed (e.g., range is full, network error)
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// # use store::network::{Network, NetRange, BasicProvider, RangeAssigner};
+    /// # use store::RangeStore;
+    /// # use tempfile::TempDir;
+    /// # fn example() -> store::Result<()> {
+    /// # let temp_dir = TempDir::new().unwrap();
+    /// # let db = sled::open(temp_dir.path())?;
+    /// # let ranges = RangeStore::open(&db)?;
+    /// # let networks = store::NetworkStore::open_with_ranges(&db, ranges.clone())?;
+    /// # let netrange = NetRange::from_cidr("10.0.1.0/24")?;
+    /// # let provider = BasicProvider::new();
+    /// # let assigner = RangeAssigner::with_range_name("test_range");
+    /// # networks.create("test_net", netrange, provider, Some(assigner))?;
+    /// # let network = networks.get("test_net")?.unwrap();
+    /// # let ranges_ref = networks.ranges().unwrap();
+    /// 
+    /// // Automatically assign an IP to a jail instance
+    /// network.auto_assign_ip("my-jail-01", ranges_ref)?;
+    /// 
+    /// // Retrieve the assigned IP
+    /// let assigned_ip = network.get_assignment("my-jail-01", Some(ranges_ref))?;
+    /// println!("Assigned IP: {:?}", assigned_ip);
+    /// # Ok(())
+    /// # }
+    /// ```
+    ///
+    /// # Note
+    ///
+    /// This method is automatically called during instance creation when the instance
+    /// has network interfaces configured for networks with RangeAssigners. Manual calls
+    /// are typically not needed unless implementing custom provisioning logic.
+    pub fn auto_assign_ip(&self, identifier: &str, range_store: &RangeStore) -> Result<()> {
+        if let (Some(assigner_type), Some(assigner_config)) = (&self.assigner_type, &self.assigner_config) {
+            match assigner_type.as_str() {
+                "range" => {
+                    let range_assigner: RangeAssigner = serde_json::from_str(assigner_config)
+                        .map_err(|e| Error::StoreError(sled::Error::Unsupported(
+                            format!("Failed to deserialize RangeAssigner: {}", e)
+                        )))?;
+                    
+                    let range_name = range_assigner.get_range_name(&self.name);
+                    
+                    // Try to assign an IP - ignore the result bit position
+                    range_assigner.assign_ip(&range_name, range_store, identifier)
+                        .map(|_| ())
+                }
+                _ => {
+                    // Other assigner types don't support auto-assignment
+                    Ok(())
+                }
+            }
+        } else {
+            // No assigner configured, nothing to do
+            Ok(())
+        }
+    }
+
+    /// Automatically unassign an IP address for the given identifier
+    /// 
+    /// This method will unassign an IP address from the network's range if it has
+    /// a RangeAssigner configured and the identifier has an assignment. This is
+    /// typically called during instance deletion to clean up network resources.
+    ///
+    /// # Arguments
+    ///
+    /// * `identifier` - The identifier to unassign an IP from (e.g., instance name)
+    /// * `range_store` - Reference to the RangeStore for managing IP assignments
+    ///
+    /// # Returns
+    ///
+    /// * `Ok(())` - Unassignment was successful or no assigner is configured
+    /// * `Err(Error)` - Unassignment failed due to network error
+    pub fn auto_unassign_ip(&self, identifier: &str, range_store: &RangeStore) -> Result<()> {
+        if let (Some(assigner_type), Some(assigner_config)) = (&self.assigner_type, &self.assigner_config) {
+            match assigner_type.as_str() {
+                "range" => {
+                    let range_assigner: RangeAssigner = serde_json::from_str(assigner_config)
+                        .map_err(|e| Error::StoreError(sled::Error::Unsupported(
+                            format!("Failed to deserialize RangeAssigner: {}", e)
+                        )))?;
+                    
+                    let range_name = range_assigner.get_range_name(&self.name);
+                    
+                    // Find the bit position for this identifier
+                    if let Some(bit_position) = range_assigner.find_identifier_bit_position(&range_name, range_store, identifier)? {
+                        // Unassign the IP
+                        range_assigner.unassign_ip(&range_name, range_store, bit_position)?;
+                    }
+                    Ok(())
+                }
+                _ => {
+                    // Other assigner types don't support auto-unassignment
+                    Ok(())
+                }
+            }
+        } else {
+            // No assigner configured, nothing to do
+            Ok(())
+        }
+    }
+
     /// Get an IP address from a bit position in the network range
     ///
     /// This method converts a bit position to the corresponding IP address
     /// within this network's range. Useful when you already know the bit
     /// position from range operations.
     ///
     /// # Arguments
     ///
     /// * `bit_position` - The bit position within the network range
     ///
     /// # Returns
     ///
     /// Returns the IP address corresponding to the bit position
     ///
     /// # Example
     ///
     /// ```
     /// # use store::network::{Network, NetRange};
     /// # fn example() -> store::Result<()> {
     /// # let netrange = NetRange::from_cidr("192.168.1.0/24")?;
     /// let network = Network {
     ///     name: "test".to_string(),
     ///     netrange: netrange,
     ///     provider_type: "basic".to_string(),
     ///     provider_config: "{}".to_string(),
     ///     assigner_type: None,
     ///     assigner_config: None,
     /// };
     /// 
     /// let ip = network.get_assignment_by_bit_position(5)?;
     /// println!("Bit position 5 corresponds to IP: {}", ip);
     /// # Ok(())
     /// # }
     /// ```
     pub fn get_assignment_by_bit_position(&self, bit_position: u64) -> Result<IpAddr> {
         match &self.netrange {
             NetRange::V4 { addr, prefix: _ } => {
                 let network_addr = u32::from(*addr);
                 let host_addr = network_addr + bit_position as u32;
                 Ok(IpAddr::V4(Ipv4Addr::from(host_addr)))
             }
             NetRange::V6 { addr, prefix: _ } => {
                 let network_addr = u128::from(*addr);
                 let host_addr = network_addr + bit_position as u128;
                 Ok(IpAddr::V6(Ipv6Addr::from(host_addr)))
             }
         }
     }
 }
 
 /// Network store for managing network configurations
 #[derive(Debug, Clone)]
 pub struct NetworkStore {
     pub(crate) namespaces: crate::namespace::NamespaceStore,
     pub(crate) networks: sled::Tree,
     pub(crate) ranges: Option<crate::range::RangeStore>,
 }
 
 impl NetworkStore {
     /// Open a new network store
     pub fn open(db: &sled::Db) -> Result<Self> {
         Ok(NetworkStore {
             namespaces: crate::namespace::NamespaceStore::open(db)?,
             networks: db.open_tree("networks/1/data")?,
             ranges: None,
         })
     }
 
     /// Open a new network store with range store
     pub fn open_with_ranges(db: &sled::Db, ranges: crate::range::RangeStore) -> Result<Self> {
         Ok(NetworkStore {
             namespaces: crate::namespace::NamespaceStore::open(db)?,
             networks: db.open_tree("networks/1/data")?,
             ranges: Some(ranges),
         })
     }
 
+    /// Get reference to the range store
+    pub fn ranges(&self) -> Option<&crate::range::RangeStore> {
+        self.ranges.as_ref()
+    }
+
     /// Create a new network with provider and optional assigner
     pub fn create<P, A>(&self, name: &str, netrange: NetRange, provider: P, assigner: Option<A>) -> Result<()>
     where
         P: NetworkProvider,
         A: NetworkAssigner,
     {
         // Ensure "networks" namespace exists
         if !self.namespaces.namespace_exists("networks")? {
             self.namespaces.define("networks")?;
         }
 
 
 
         let network = Network {
             name: name.to_string(),
             netrange: netrange.clone(),
             provider_type: provider.provider_type().to_string(),
             provider_config: provider.to_json()?,
             assigner_type: assigner.as_ref().map(|a| a.assigner_type().to_string()),
             assigner_config: assigner.as_ref().map(|a| a.to_json()).transpose()?,
         };
 
         let network_json = serde_json::to_string(&network).map_err(|e| {
             Error::StoreError(sled::Error::Unsupported(format!("JSON serialization error: {}", e)))
         })?;
 
         // Include range store trees in transaction if needed
         if let (Some(assigner_ref), Some(range_store)) = (&assigner, &self.ranges) {
             if assigner_ref.assigner_type() == "range" {
                 // Transaction with both network and range stores
                 (&self.namespaces.names, &self.namespaces.spaces, &self.networks, &range_store.names, &range_store.map).transaction(
                     |(names, spaces, networks, range_names, range_map)| {
                         // Reserve the network name in the "networks" namespace
                         if !self.namespaces.reserve_in_transaction(names, spaces, "networks", name, name)? {
                             return Err(sled::transaction::ConflictableTransactionError::Abort(()));
                         }
 
                         // Initialize the assigner within the transaction
                         assigner_ref.initialize_for_network_in_transaction(name, &netrange, Some((range_names, range_map)))?;
 
                         // Store the network configuration
                         networks.insert(name.as_bytes(), network_json.as_bytes())?;
                         Ok(())
                     }
                 ).map_err(|e| match e {
                     sled::transaction::TransactionError::Abort(()) => {
                         Error::NamespaceKeyReserved("networks".to_string(), name.to_string())
                     }
                     sled::transaction::TransactionError::Storage(storage_err) => Error::StoreError(storage_err),
                 })?;
             } else {
                 // Transaction without range store
                 (&self.namespaces.names, &self.namespaces.spaces, &self.networks).transaction(
                     |(names, spaces, networks)| {
                         // Reserve the network name in the "networks" namespace
                         if !self.namespaces.reserve_in_transaction(names, spaces, "networks", name, name)? {
                             return Err(sled::transaction::ConflictableTransactionError::Abort(()));
                         }
 
                         // Store the network configuration
                         networks.insert(name.as_bytes(), network_json.as_bytes())?;
                         Ok(())
                     }
                 ).map_err(|e| match e {
                     sled::transaction::TransactionError::Abort(()) => {
                         Error::NamespaceKeyReserved("networks".to_string(), name.to_string())
                     }
                     sled::transaction::TransactionError::Storage(storage_err) => Error::StoreError(storage_err),
                 })?;
             }
         } else {
             // Transaction without assigner or range store
             (&self.namespaces.names, &self.namespaces.spaces, &self.networks).transaction(
                 |(names, spaces, networks)| {
                     // Reserve the network name in the "networks" namespace
                     if !self.namespaces.reserve_in_transaction(names, spaces, "networks", name, name)? {
                         return Err(sled::transaction::ConflictableTransactionError::Abort(()));
                     }
 
                     // Store the network configuration
                     networks.insert(name.as_bytes(), network_json.as_bytes())?;
                     Ok(())
                 }
             ).map_err(|e| match e {
                 sled::transaction::TransactionError::Abort(()) => {
                     Error::NamespaceKeyReserved("networks".to_string(), name.to_string())
                 }
                 sled::transaction::TransactionError::Storage(storage_err) => Error::StoreError(storage_err),
             })?;
         }
 
         Ok(())
     }
 
     /// Get a network by name
     pub fn get(&self, name: &str) -> Result<Option<Network>> {
         if let Some(data) = self.networks.get(name.as_bytes())? {
             let network_json = String::from_utf8(data.to_vec())?;
             let network: Network = serde_json::from_str(&network_json).map_err(|e| {
                 Error::StoreError(sled::Error::Unsupported(format!("JSON deserialization error: {}", e)))
             })?;
             Ok(Some(network))
         } else {
             Ok(None)
         }
     }
 
     /// Update an existing network
     pub fn update<P, A>(&self, name: &str, netrange: NetRange, provider: P, assigner: Option<A>) -> Result<()>
     where
         P: NetworkProvider,
         A: NetworkAssigner,
     {
         if !self.namespaces.key_exists("networks", name)? {
             return Err(Error::StoreError(sled::Error::Unsupported(
                 format!("Network '{}' does not exist", name)
             )));
         }
 
         let network = Network {
             name: name.to_string(),
             netrange,
             provider_type: provider.provider_type().to_string(),
             provider_config: provider.to_json()?,
             assigner_type: assigner.as_ref().map(|a| a.assigner_type().to_string()),
             assigner_config: assigner.as_ref().map(|a| a.to_json()).transpose()?,
         };
 
         let network_json = serde_json::to_string(&network).map_err(|e| {
             Error::StoreError(sled::Error::Unsupported(format!("JSON serialization error: {}", e)))
         })?;
 
         self.networks.insert(name.as_bytes(), network_json.as_bytes())?;
         Ok(())
     }
 
     /// Delete a network by name
     pub fn delete(&self, name: &str) -> Result<bool> {
         let result = if let Some(ranges) = &self.ranges {
             // Transaction with range cleanup
             (&self.namespaces.names, &self.namespaces.spaces, &self.networks, &ranges.names, &ranges.map).transaction(
                 |(names, spaces, networks, range_names, range_map)| {
                     // Get network data before deletion to access assigner
                     if let Some(network_data) = networks.get(name.as_bytes())? {
                         let network: Network = serde_json::from_slice(network_data.as_ref())
                             .map_err(|_| sled::transaction::ConflictableTransactionError::Abort(()))?;
                         
                         // Clean up assigner if it exists
                         if network.assigner_type.is_some() {
                             let assigner = network.create_assigner()
                                 .map_err(|_| sled::transaction::ConflictableTransactionError::Abort(()))?;
                             assigner.cleanup_for_network_in_transaction(name, Some((range_names, range_map)))?;
                         }
                     }
                     
                     // Remove from namespace
                     let removed = self.namespaces.remove_in_transaction(names, spaces, "networks", name)?;
                     if removed {
                         // Remove network data
                         networks.remove(name.as_bytes())?;
                     }
                     Ok(removed)
                 }
             )
         } else {
             // Transaction without range cleanup
             (&self.namespaces.names, &self.namespaces.spaces, &self.networks).transaction(
                 |(names, spaces, networks)| {
                     // Remove from namespace
                     let removed = self.namespaces.remove_in_transaction(names, spaces, "networks", name)?;
                     if removed {
                         // Remove network data
                         networks.remove(name.as_bytes())?;
                     }
                     Ok(removed)
                 }
             )
         };
         
         let result = result.map_err(|e| match e {
             sled::transaction::TransactionError::Abort(()) => {
                 Error::StoreError(sled::Error::Unsupported("Transaction aborted".to_string()))
             }
             sled::transaction::TransactionError::Storage(storage_err) => Error::StoreError(storage_err),
         })?;
 
         Ok(result)
     }
 
     /// List all network names
     pub fn list(&self) -> Result<Vec<String>> {
         self.namespaces.list_keys("networks")
     }
 
     /// Check if a network exists
     pub fn exists(&self, name: &str) -> Result<bool> {
         self.namespaces.key_exists("networks", name)
     }
 }
 
 #[cfg(test)]
 mod tests {
     use super::*;
     use tempfile::TempDir;
 
     fn create_test_store() -> Result<(NetworkStore, TempDir)> {
         let temp_dir = tempfile::tempdir().unwrap();
         let db = sled::open(temp_dir.path())?;
         let ranges = crate::range::RangeStore::open(&db)?;
         let store = NetworkStore::open_with_ranges(&db, ranges)?;
         Ok((store, temp_dir))
     }
 
     #[test]
     fn test_netrange_ipv4() -> Result<()> {
         let netrange = NetRange::ipv4("192.168.1.0".parse().unwrap(), 24)?;
         assert_eq!(netrange.to_cidr(), "192.168.1.0/24");
         Ok(())
     }
 
     #[test]
     fn test_netrange_ipv6() -> Result<()> {
         let netrange = NetRange::ipv6("2001:db8::".parse().unwrap(), 64)?;
         assert_eq!(netrange.to_cidr(), "2001:db8::/64");
         Ok(())
     }
 
     #[test]
     fn test_netrange_from_cidr() -> Result<()> {
         let ipv4_range = NetRange::from_cidr("10.0.0.0/8")?;
         assert_eq!(ipv4_range.to_cidr(), "10.0.0.0/8");
 
         let ipv6_range = NetRange::from_cidr("fe80::/10")?;
         assert_eq!(ipv6_range.to_cidr(), "fe80::/10");
         Ok(())
     }
 
     #[test]
     fn test_basic_provider() -> Result<()> {
         let provider = BasicProvider::new()
             .with_config("endpoint", "https://api.example.com")
             .with_config("timeout", "30");
 
         let json = provider.to_json()?;
         let restored = BasicProvider::from_json(&json)?;
         
         assert_eq!(provider.config, restored.config);
         assert_eq!(provider.provider_type(), "basic");
         Ok(())
     }
 
     #[test]
     fn test_basic_assigner() -> Result<()> {
         let assigner = BasicAssigner::new("round_robin")
             .with_config("pool_size", "100");
 
         let json = assigner.to_json()?;
         let restored = BasicAssigner::from_json(&json)?;
         
         assert_eq!(assigner.strategy, restored.strategy);
         assert_eq!(assigner.config, restored.config);
         assert_eq!(assigner.assigner_type(), "basic");
         Ok(())
     }
 
     #[test]
     fn test_create_and_get_network() -> Result<()> {
         let (store, _temp_dir) = create_test_store()?;
 
         let netrange = NetRange::ipv4("192.168.1.0".parse().unwrap(), 24)?;
         let provider = BasicProvider::new().with_config("type", "test");
         let assigner = Some(BasicAssigner::new("sequential"));
 
         store.create("test_network", netrange.clone(), provider, assigner)?;
 
         let network = store.get("test_network")?.unwrap();
         assert_eq!(network.name, "test_network");
         assert_eq!(network.netrange, netrange);
         assert_eq!(network.provider_type, "basic");
         assert!(network.assigner_type.is_some());
         Ok(())
     }
 
     #[test]
     fn test_create_network_without_assigner() -> Result<()> {
         let (store, _temp_dir) = create_test_store()?;
 
         let netrange = NetRange::ipv6("2001:db8::".parse().unwrap(), 64)?;
         let provider = BasicProvider::new();
         let assigner: Option<BasicAssigner> = None;
 
         store.create("ipv6_network", netrange.clone(), provider, assigner)?;
 
         let network = store.get("ipv6_network")?.unwrap();
         assert_eq!(network.name, "ipv6_network");
         assert_eq!(network.netrange, netrange);
         assert!(network.assigner_type.is_none());
         assert!(network.assigner_config.is_none());
         Ok(())
     }
 
     #[test]
     fn test_create_duplicate_network() -> Result<()> {
         let (store, _temp_dir) = create_test_store()?;
 
         let netrange = NetRange::ipv4("10.0.0.0".parse().unwrap(), 8)?;
         let provider = BasicProvider::new();
         let assigner: Option<BasicAssigner> = None;
 
         // First creation should succeed
         store.create("duplicate_test", netrange.clone(), provider.clone(), assigner.clone())?;
 
         // Second creation should fail
         let result = store.create("duplicate_test", netrange, provider, assigner);
         assert!(result.is_err());
         Ok(())
     }
 
     #[test]
     fn test_update_network() -> Result<()> {
         let (store, _temp_dir) = create_test_store()?;
 
         // Create initial network
         let netrange = NetRange::ipv4("172.16.0.0".parse().unwrap(), 12)?;
         let provider = BasicProvider::new().with_config("version", "1");
         let assigner: Option<BasicAssigner> = None;
 
         store.create("update_test", netrange, provider, assigner)?;
 
         // Update the network  
         let new_netrange = NetRange::ipv4("172.16.0.0".parse().unwrap(), 16)?;
         let new_provider = BasicProvider::new().with_config("version", "2");
         let new_assigner = Some(BasicAssigner::new("random"));
 
         store.update("update_test", new_netrange.clone(), new_provider, new_assigner)?;
 
         // Verify the update
         let network = store.get("update_test")?.unwrap();
         assert_eq!(network.netrange, new_netrange);
         assert!(network.assigner_type.is_some());
         Ok(())
     }
 
     #[test]
     fn test_delete_network() -> Result<()> {
         let (store, _temp_dir) = create_test_store()?;
 
         let netrange = NetRange::ipv4("203.0.113.0".parse().unwrap(), 24)?;
         let provider = BasicProvider::new();
         let assigner: Option<BasicAssigner> = None;
 
         store.create("delete_test", netrange, provider, assigner)?;
         assert!(store.exists("delete_test")?);
 
         let deleted = store.delete("delete_test")?;
         assert!(deleted);
         assert!(!store.exists("delete_test")?);
 
         // Try to delete non-existent network
         let deleted_again = store.delete("delete_test")?;
         assert!(!deleted_again);
         Ok(())
     }
 
     #[test]
     fn test_list_networks() -> Result<()> {
         let (store, _temp_dir) = create_test_store()?;
 
         // Create multiple networks
         let networks = vec![
             ("net1", "10.1.0.0/16"),
             ("net2", "10.2.0.0/16"), 
             ("net3", "2001:db8:1::/48"),
         ];
 
         for (name, cidr) in &networks {
             let netrange = NetRange::from_cidr(cidr)?;
             let provider = BasicProvider::new();
             let assigner: Option<BasicAssigner> = None;
             store.create(name, netrange, provider, assigner)?;
         }
 
         let mut network_names = store.list()?;
         network_names.sort();
 
         let mut expected: Vec<String> = networks.iter().map(|(name, _)| name.to_string()).collect();
         expected.sort();
 
         assert_eq!(network_names, expected);
         Ok(())
     }
 
     #[test]
     fn test_update_nonexistent_network() -> Result<()> {
         let (store, _temp_dir) = create_test_store()?;
 
         let netrange = NetRange::ipv4("198.51.100.0".parse().unwrap(), 24)?;
         let provider = BasicProvider::new();
         let assigner: Option<BasicAssigner> = None;
 
         let result = store.update("nonexistent", netrange, provider, assigner);
         assert!(result.is_err());
         Ok(())
     }
 
     #[test]
     fn test_invalid_cidr() {
         let result = NetRange::from_cidr("invalid");
         assert!(result.is_err());
 
         let result = NetRange::from_cidr("192.168.1.0");
         assert!(result.is_err());
 
         let result = NetRange::from_cidr("192.168.1.0/33");
         assert!(result.is_err());
     }
 
     #[test]
     fn test_range_assigner() -> Result<()> {
         let (store, _temp_dir) = create_test_store()?;
 
         // Create a network with RangeAssigner
         let netrange = NetRange::from_cidr("192.168.1.0/24")?;
         let provider = BasicProvider::new()
             .with_config("type", "test");
         let assigner = RangeAssigner::new()
             .with_config("strategy", "sequential");
 
         store.create("test-network", netrange, provider, Some(assigner))?;
 
         // Verify the network was created
         let network = store.get("test-network")?.unwrap();
         assert_eq!(network.name, "test-network");
         assert_eq!(network.assigner_type, Some("range".to_string()));
 
         // Test range assignment functionality
         if let Some(ref range_store) = store.ranges {
             let range_assigner = RangeAssigner::new();
             let range_name = "test-network"; // Uses network name as range name
             
             // Assign some IPs
             let bit1 = range_assigner.assign_ip(range_name, range_store, "device1")?;
             let bit2 = range_assigner.assign_ip(range_name, range_store, "device2")?;
             
             // Verify assignments
             assert_eq!(bit1, 0);
             assert_eq!(bit2, 1);
             
             // Get assignment info
             let assignment1 = range_assigner.get_assignment_by_bit(range_name, range_store, bit1)?.unwrap();
             let assignment2 = range_assigner.get_assignment_by_bit(range_name, range_store, bit2)?.unwrap();
             
             assert_eq!(assignment1, "device1");
             assert_eq!(assignment2, "device2");
             
             // Test unassignment
             let unassigned = range_assigner.unassign_ip(range_name, range_store, bit1)?;
             assert!(unassigned);
             let assignment1_after = range_assigner.get_assignment_by_bit(range_name, range_store, bit1)?;
             assert!(assignment1_after.is_none());
         }
 
         Ok(())
     }
 
     #[test]
     fn test_range_assigner_serialization() -> Result<()> {
         let assigner = RangeAssigner::with_range_name("test-range")
             .with_config("strategy", "random")
             .with_config("pool_size", "100");
 
         // Test serialization
         let json = assigner.to_json()?;
         assert!(json.contains("test-range"));
         assert!(json.contains("random"));
         assert!(json.contains("100"));
 
         // Test deserialization
         let deserialized: RangeAssigner = RangeAssigner::from_json(&json)?;
         assert_eq!(deserialized.range_name, Some("test-range".to_string()));
         assert_eq!(deserialized.config.get("strategy"), Some(&"random".to_string()));
         assert_eq!(deserialized.config.get("pool_size"), Some(&"100".to_string()));
 
         Ok(())
     }
 
     #[test]
     fn test_network_get_assignment() -> Result<()> {
         let (store, _temp_dir) = create_test_store()?;
 
         // Create a network with RangeAssigner
         let netrange = NetRange::from_cidr("192.168.1.0/24")?;
         let provider = BasicProvider::new()
             .with_config("type", "test");
         let assigner = RangeAssigner::new()
             .with_config("strategy", "sequential");
 
         store.create("test-network", netrange, provider, Some(assigner))?;
 
         // Get the network
         let network = store.get("test-network")?.unwrap();
 
         // Test that no assignment exists initially
         let ip = network.get_assignment("device1", store.ranges.as_ref())?;
         assert!(ip.is_none());
 
         // Assign an IP using the range assigner directly
         if let Some(ref range_store) = store.ranges {
             let range_assigner = RangeAssigner::new();
             let range_name = "test-network"; // Uses network name as range name
             
             // Assign an IP to device1
             let bit_position = range_assigner.assign_ip(range_name, range_store, "device1")?;
             assert_eq!(bit_position, 0);
 
             // Now test get_assignment on the network
             let ip = network.get_assignment("device1", store.ranges.as_ref())?;
             assert!(ip.is_some());
             
             let assigned_ip = ip.unwrap();
             match assigned_ip {
                 std::net::IpAddr::V4(ipv4) => {
                     // Should be 192.168.1.0 + bit_position
                     let expected = std::net::Ipv4Addr::new(192, 168, 1, bit_position as u8);
                     assert_eq!(ipv4, expected);
                 }
                 _ => panic!("Expected IPv4 address"),
             }
 
             // Test assignment for non-existent device
             let no_ip = network.get_assignment("nonexistent", store.ranges.as_ref())?;
             assert!(no_ip.is_none());
 
             // Assign another device
             let bit_position2 = range_assigner.assign_ip(range_name, range_store, "device2")?;
             assert_eq!(bit_position2, 1);
 
             let ip2 = network.get_assignment("device2", store.ranges.as_ref())?;
             assert!(ip2.is_some());
             
             let assigned_ip2 = ip2.unwrap();
             match assigned_ip2 {
                 std::net::IpAddr::V4(ipv4) => {
                     let expected = std::net::Ipv4Addr::new(192, 168, 1, bit_position2 as u8);
                     assert_eq!(ipv4, expected);
                 }
                 _ => panic!("Expected IPv4 address"),
             }
         }
 
         Ok(())
     }
 
     #[test]
     fn test_network_get_assignment_no_assigner() -> Result<()> {
         let (store, _temp_dir) = create_test_store()?;
 
         // Create a network without an assigner
         let netrange = NetRange::from_cidr("192.168.1.0/24")?;
         let provider = BasicProvider::new()
             .with_config("type", "test");
 
         store.create("no-assigner-network", netrange, provider, None::<RangeAssigner>)?;
 
         // Get the network
         let network = store.get("no-assigner-network")?.unwrap();
 
         // Test that get_assignment returns None when no assigner is configured
         let ip = network.get_assignment("device1", store.ranges.as_ref())?;
         assert!(ip.is_none());
 
         Ok(())
     }
 
     #[test]
     fn test_delete_network_with_range_assigner() -> Result<()> {
         let (store, _temp_dir) = create_test_store()?;
 
         // Create a network with a range assigner
         let netrange = NetRange::from_cidr("192.168.100.0/24")?;
         let provider = BasicProvider::new()
             .with_config("type", "test");
         let assigner = RangeAssigner::new()
             .with_config("strategy", "sequential");
 
         store.create("test-range-network", netrange, provider, Some(assigner))?;
 
         // Verify the network was created
         assert!(store.exists("test-range-network")?);
         let network = store.get("test-range-network")?.unwrap();
         
         // Verify the range was initialized by checking if the range exists
         if let Some(ranges) = &store.ranges {
             let range_name = "test-range-network";
             // Check if range exists by trying to get range info
             let range_exists = ranges.names.get(range_name.as_bytes())?.is_some();
             assert!(range_exists, "Range should be initialized after network creation");
             
             // Make an assignment using the range assigner directly
             let range_assigner = RangeAssigner::new();
             let _bit_position = range_assigner.assign_ip(range_name, ranges, "device1")?;
             // Assignment should succeed (bit_position is always >= 0 since it's u64)
             
             // Now verify the assignment exists
             let ip = network.get_assignment("device1", store.ranges.as_ref())?;
             assert!(ip.is_some(), "Assignment should be retrievable");
             
             // Verify assignment data exists
             let range_info = ranges.list_range(range_name)?;
             assert!(!range_info.is_empty(), "Range should have assignment data");
         }
 
         // Delete the network
         let deleted = store.delete("test-range-network")?;
         assert!(deleted);
 
         // Verify the network no longer exists
         assert!(!store.exists("test-range-network")?);
         assert!(store.get("test-range-network")?.is_none());
 
         // Verify range data was cleaned up
         if let Some(ranges) = &store.ranges {
             let range_name = "test-range-network";
             let range_exists = ranges.names.get(range_name.as_bytes())?.is_some();
             assert!(!range_exists, "Range should be deleted");
             
             let range_info = ranges.list_range(range_name)?;
             assert!(range_info.is_empty(), "Range assignments should be cleaned up");
         }
 
         Ok(())
     }
 
     #[test]
     fn test_delete_network_without_assigner() -> Result<()> {
         let (store, _temp_dir) = create_test_store()?;
 
         // Create a network without an assigner
         let netrange = NetRange::from_cidr("10.10.0.0/16")?;
         let provider = BasicProvider::new()
             .with_config("type", "test");
 
         store.create("test-no-assigner", netrange, provider, None::<RangeAssigner>)?;
 
         // Verify the network was created
         assert!(store.exists("test-no-assigner")?);
         let network = store.get("test-no-assigner")?.unwrap();
         assert!(network.assigner_type.is_none());
 
         // Delete the network
         let deleted = store.delete("test-no-assigner")?;
         assert!(deleted);
 
         // Verify the network no longer exists
         assert!(!store.exists("test-no-assigner")?);
         assert!(store.get("test-no-assigner")?.is_none());
 
         Ok(())
     }
 
     #[test]
     fn test_network_get_assignment_ipv6() -> Result<()> {
         let (store, _temp_dir) = create_test_store()?;
 
         // Create an IPv6 network with RangeAssigner
         let netrange = NetRange::from_cidr("2001:db8::/64")?;
         let provider = BasicProvider::new()
             .with_config("type", "test");
         let assigner = RangeAssigner::new()
             .with_config("strategy", "sequential");
 
         store.create("ipv6-network", netrange, provider, Some(assigner))?;
 
         // Get the network
         let network = store.get("ipv6-network")?.unwrap();
 
         // Assign an IP using the range assigner directly
         if let Some(ref range_store) = store.ranges {
             let range_assigner = RangeAssigner::new();
             let range_name = "ipv6-network";
             
             // Assign an IP to device1
             let bit_position = range_assigner.assign_ip(range_name, range_store, "ipv6-device1")?;
             assert_eq!(bit_position, 0);
 
             // Test get_assignment on the network
             let ip = network.get_assignment("ipv6-device1", store.ranges.as_ref())?;
             assert!(ip.is_some());
             
             let assigned_ip = ip.unwrap();
             match assigned_ip {
                 std::net::IpAddr::V6(ipv6) => {
                     // Should be 2001:db8:: + bit_position
                     let expected = std::net::Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, bit_position as u16);
                     assert_eq!(ipv6, expected);
                 }
                 _ => panic!("Expected IPv6 address"),
             }
         }
 
         Ok(())
     }
 }
\ No newline at end of file
diff --git a/crates/store/tests/jail_range_assignment_test.rs b/crates/store/tests/jail_range_assignment_test.rs
new file mode 100644
index 0000000..a88b5f3
--- /dev/null
+++ b/crates/store/tests/jail_range_assignment_test.rs
@@ -0,0 +1,330 @@
+use store::{
+    InstanceStore, Instance, InstanceConfig, NetworkInterface,
+    NamespaceStore, NetworkStore, RangeStore,
+    JailProvider, Provider,
+    network::{NetRange, BasicProvider, RangeAssigner},
+};
+use std::collections::HashMap;
+use tempfile::TempDir;
+
+fn create_test_stores() -> store::Result<(TempDir, sled::Db, InstanceStore)> {
+    let temp_dir = TempDir::new().unwrap();
+    let db = sled::open(temp_dir.path().join("test.db"))?;
+    
+    let namespaces = NamespaceStore::open(&db)?;
+    let ranges = RangeStore::open(&db)?;
+    let networks = NetworkStore::open_with_ranges(&db, ranges)?;
+    let instances = InstanceStore::open(&db, namespaces, networks)?;
+    
+    Ok((temp_dir, db, instances))
+}
+
+#[test]
+fn test_jail_with_range_assigner_should_get_automatic_ip_assignment() -> store::Result<()> {
+    let (_temp_dir, _db, instances) = create_test_stores()?;
+    
+    // Create a network with RangeAssigner
+    let provider = BasicProvider::new()
+        .with_config("type", "bridge")
+        .with_config("driver", "epair");
+    
+    let assigner = RangeAssigner::with_range_name("jail_network_ips")
+        .with_config("strategy", "sequential")
+        .with_config("reserve_gateway", "true");
+    
+    let netrange = NetRange::from_cidr("10.0.10.0/24")?;
+    
+    instances.networks().create(
+        "jail_network",
+        netrange,
+        provider,
+        Some(assigner),
+    )?;
+    
+    // Create a jail instance with network interface
+    let jail_provider = JailProvider::new()
+        .with_dataset("tank/jails".to_string());
+    
+    let jail_config = InstanceConfig {
+        provider_config: jail_provider.to_json()?,
+        network_interfaces: vec![
+            NetworkInterface {
+                network_name: "jail_network".to_string(),
+                interface_name: "em0".to_string(),
+                assignment: None, // Should be automatically assigned
+            }
+        ],
+        metadata: HashMap::new(),
+    };
+    
+    let jail_instance = Instance {
+        name: "test-jail".to_string(),
+        provider_type: "jail".to_string(),
+        config: jail_config,
+        created_at: 0,
+        updated_at: 0,
+    };
+    
+    // Create the instance - this should automatically assign an IP
+    let created_instance = instances.create(jail_instance)?;
+    
+    // Verify the instance was created with an IP assignment
+    assert_eq!(created_instance.name, "test-jail");
+    assert_eq!(created_instance.config.network_interfaces.len(), 1);
+    
+    let interface = &created_instance.config.network_interfaces[0];
+    assert_eq!(interface.network_name, "jail_network");
+    assert_eq!(interface.interface_name, "em0");
+    
+    // This should pass once the automatic assignment is implemented
+    assert!(interface.assignment.is_some(), "IP should be automatically assigned");
+    
+    // Verify the assigned IP is valid
+    if let Some(ref assignment) = interface.assignment {
+        let ip: std::net::IpAddr = assignment.parse()
+            .expect("Assignment should be a valid IP address");
+        
+        // Should be in the 10.0.10.0/24 range
+        match ip {
+            std::net::IpAddr::V4(ipv4) => {
+                let octets = ipv4.octets();
+                assert_eq!(octets[0], 10);
+                assert_eq!(octets[1], 0);
+                assert_eq!(octets[2], 10);
+                // RangeAssigner starts from bit position 0, which is the network address
+                // This is actually correct behavior - bit 0 = .0, bit 1 = .1, etc.
+                assert!(octets[3] < 255, "IP should not be broadcast address");
+            }
+            _ => panic!("Expected IPv4 address"),
+        }
+    }
+    
+    // Test that we can retrieve the instance and it still has the assignment
+    let retrieved_instance = instances.get("test-jail")?;
+    assert!(retrieved_instance.config.network_interfaces[0].assignment.is_some());
+    
+    Ok(())
+}
+
+#[test]
+fn test_jail_deletion_cleans_up_ip_assignments() -> store::Result<()> {
+    let (_temp_dir, _db, instances) = create_test_stores()?;
+    
+    // Create a network with RangeAssigner
+    let provider = BasicProvider::new();
+    let assigner = RangeAssigner::with_range_name("deletion_test_ips");
+    let netrange = NetRange::from_cidr("10.0.30.0/24")?;
+    
+    instances.networks().create("deletion_test_network", netrange, provider, Some(assigner))?;
+    
+    // Create a jail instance
+    let jail_provider = JailProvider::new();
+    let jail_config = InstanceConfig {
+        provider_config: jail_provider.to_json()?,
+        network_interfaces: vec![
+            NetworkInterface {
+                network_name: "deletion_test_network".to_string(),
+                interface_name: "em0".to_string(),
+                assignment: None,
+            }
+        ],
+        metadata: HashMap::new(),
+    };
+    
+    let jail_instance = Instance {
+        name: "deletion-test-jail".to_string(),
+        provider_type: "jail".to_string(),
+        config: jail_config,
+        created_at: 0,
+        updated_at: 0,
+    };
+    
+    // Create the instance - should get IP assignment
+    let created = instances.create(jail_instance)?;
+    assert!(created.config.network_interfaces[0].assignment.is_some());
+    let assigned_ip = created.config.network_interfaces[0].assignment.as_ref().unwrap().clone();
+    
+    // Verify the IP is assigned in the range
+    let network = instances.networks().get("deletion_test_network")?.unwrap();
+    let assignment = network.get_assignment("deletion-test-jail", instances.networks().ranges())?;
+    assert!(assignment.is_some());
+    assert_eq!(assignment.unwrap().to_string(), assigned_ip);
+    
+    // Delete the instance
+    instances.delete("deletion-test-jail")?;
+    
+    // Verify instance is deleted
+    assert!(instances.get("deletion-test-jail").is_err());
+    
+    // Verify IP assignment is cleaned up
+    let assignment_after = network.get_assignment("deletion-test-jail", instances.networks().ranges())?;
+    assert!(assignment_after.is_none(), "IP assignment should be cleaned up after deletion");
+    
+    // Create a new instance to verify the IP can be reused
+    let new_jail_config = InstanceConfig {
+        provider_config: JailProvider::new().to_json()?,
+        network_interfaces: vec![
+            NetworkInterface {
+                network_name: "deletion_test_network".to_string(),
+                interface_name: "em0".to_string(),
+                assignment: None,
+            }
+        ],
+        metadata: HashMap::new(),
+    };
+    
+    let new_jail = Instance {
+        name: "new-test-jail".to_string(),
+        provider_type: "jail".to_string(),
+        config: new_jail_config,
+        created_at: 0,
+        updated_at: 0,
+    };
+    
+    let new_created = instances.create(new_jail)?;
+    
+    // Should get the same IP that was freed
+    assert!(new_created.config.network_interfaces[0].assignment.is_some());
+    let new_assigned_ip = new_created.config.network_interfaces[0].assignment.as_ref().unwrap();
+    assert_eq!(*new_assigned_ip, assigned_ip, "IP should be reused after cleanup");
+    
+    Ok(())
+}
+
+#[test]
+fn test_multiple_jails_get_different_ip_assignments() -> store::Result<()> {
+    let (_temp_dir, _db, instances) = create_test_stores()?;
+    
+    // Create a network with RangeAssigner
+    let provider = BasicProvider::new();
+    let assigner = RangeAssigner::with_range_name("multi_jail_ips");
+    let netrange = NetRange::from_cidr("192.168.100.0/24")?;
+    
+    instances.networks().create("multi_jail_network", netrange, provider, Some(assigner))?;
+    
+    // Create multiple jail instances
+    for i in 1..=3 {
+        let jail_provider = JailProvider::new();
+        let jail_config = InstanceConfig {
+            provider_config: jail_provider.to_json()?,
+            network_interfaces: vec![
+                NetworkInterface {
+                    network_name: "multi_jail_network".to_string(),
+                    interface_name: "em0".to_string(),
+                    assignment: None,
+                }
+            ],
+            metadata: HashMap::new(),
+        };
+        
+        let jail_instance = Instance {
+            name: format!("jail-{}", i),
+            provider_type: "jail".to_string(),
+            config: jail_config,
+            created_at: 0,
+            updated_at: 0,
+        };
+        
+        instances.create(jail_instance)?;
+    }
+    
+    // Verify all instances have different IP assignments
+    let mut assigned_ips = std::collections::HashSet::new();
+    
+    for i in 1..=3 {
+        let instance = instances.get(&format!("jail-{}", i))?;
+        let interface = &instance.config.network_interfaces[0];
+        
+        assert!(interface.assignment.is_some(), "Instance jail-{} should have IP assignment", i);
+        let ip = interface.assignment.as_ref().unwrap();
+        
+        assert!(assigned_ips.insert(ip.clone()), 
+               "Instance jail-{} got duplicate IP: {}", i, ip);
+    }
+    
+    // Should have 3 unique IP assignments
+    assert_eq!(assigned_ips.len(), 3);
+    
+    Ok(())
+}
+
+#[test]
+fn test_jail_with_multiple_network_interfaces() -> store::Result<()> {
+    let (_temp_dir, _db, instances) = create_test_stores()?;
+    
+    // Create two networks with RangeAssigners
+    let mgmt_provider = BasicProvider::new();
+    let mgmt_assigner = RangeAssigner::with_range_name("mgmt_ips");
+    instances.networks().create(
+        "management",
+        NetRange::from_cidr("10.0.1.0/24")?,
+        mgmt_provider,
+        Some(mgmt_assigner),
+    )?;
+    
+    let data_provider = BasicProvider::new();
+    let data_assigner = RangeAssigner::with_range_name("data_ips");
+    instances.networks().create(
+        "data",
+        NetRange::from_cidr("10.0.2.0/24")?,
+        data_provider,
+        Some(data_assigner),
+    )?;
+    
+    // Create jail with multiple interfaces
+    let jail_provider = JailProvider::new();
+    let jail_config = InstanceConfig {
+        provider_config: jail_provider.to_json()?,
+        network_interfaces: vec![
+            NetworkInterface {
+                network_name: "management".to_string(),
+                interface_name: "em0".to_string(),
+                assignment: None,
+            },
+            NetworkInterface {
+                network_name: "data".to_string(),
+                interface_name: "em1".to_string(),
+                assignment: None,
+            }
+        ],
+        metadata: HashMap::new(),
+    };
+    
+    let jail_instance = Instance {
+        name: "multi-interface-jail".to_string(),
+        provider_type: "jail".to_string(),
+        config: jail_config,
+        created_at: 0,
+        updated_at: 0,
+    };
+    
+    let created_instance = instances.create(jail_instance)?;
+    
+    // Verify both interfaces got IP assignments
+    assert_eq!(created_instance.config.network_interfaces.len(), 2);
+    
+    for (i, interface) in created_instance.config.network_interfaces.iter().enumerate() {
+        assert!(interface.assignment.is_some(), 
+               "Interface {} should have IP assignment", i);
+        
+        let ip: std::net::IpAddr = interface.assignment.as_ref().unwrap().parse()
+            .expect("Assignment should be valid IP");
+        
+        match ip {
+            std::net::IpAddr::V4(ipv4) => {
+                let octets = ipv4.octets();
+                assert_eq!(octets[0], 10);
+                assert_eq!(octets[1], 0);
+                
+                if interface.network_name == "management" {
+                    assert_eq!(octets[2], 1);
+                } else if interface.network_name == "data" {
+                    assert_eq!(octets[2], 2);
+                }
+            }
+            _ => panic!("Expected IPv4 address"),
+        }
+    }
+    
+    Ok(())
+}
\ No newline at end of file