ggen_cli_lib/cmds/
capability.rs1use clap_noun_verb::{NounVerbError, Result};
12use clap_noun_verb_macros::verb;
13use serde_json::{json, Value};
14use std::path::PathBuf;
15
16use ggen_core::domain::packs::capability_registry::{
17 list_capabilities, resolve_capability_to_packs,
18};
19use ggen_core::packs::lockfile::{LockedPack, PackLockfile, PackSource};
20
21fn project_root() -> Result<PathBuf> {
24 std::env::current_dir()
25 .map_err(|e| NounVerbError::execution_error(format!("cannot resolve project dir: {}", e)))
26}
27
28fn atomic_packs_for(surface: &str, projection: Option<&str>, runtime: Option<&str>) -> Vec<String> {
31 let mut packs = resolve_capability_to_packs(surface, projection, runtime).unwrap_or_default();
32 if let Some(p) = projection {
33 let projection_pack = format!("projection-{}", p);
34 if !packs.contains(&projection_pack) {
35 packs.push(projection_pack);
36 }
37 }
38 packs
39}
40
41fn require_surface(surface: &str) -> Result<()> {
42 if surface.trim().is_empty() {
43 return Err(NounVerbError::argument_error(
44 "capability surface must not be empty",
45 ));
46 }
47 Ok(())
48}
49
50#[verb]
55pub fn enable(
56 #[arg(index = 1)] surface: String, projection: Option<String>, runtime: Option<String>,
57) -> Result<Value> {
58 require_surface(&surface)?;
59 let packs = atomic_packs_for(&surface, projection.as_deref(), runtime.as_deref());
60
61 let root = project_root()?;
63 let lock_path = root.join(".ggen").join("packs.lock");
64 let mut lockfile = if lock_path.exists() {
65 PackLockfile::from_file(&lock_path)
66 .map_err(|e| NounVerbError::execution_error(format!("cannot read lockfile: {}", e)))?
67 } else {
68 PackLockfile::new(env!("CARGO_PKG_VERSION"))
69 };
70 for pid in &packs {
71 let digest = ggen_core::calculate_sha256(format!("{}@0.0.0", pid).as_bytes());
72 lockfile.add_pack(
73 pid,
74 LockedPack {
75 version: "0.0.0".to_string(),
76 source: PackSource::Registry {
77 url: "https://registry.ggen.io".to_string(),
78 },
79 integrity: Some(format!("sha256-{}", digest)),
80 installed_at: chrono::Utc::now(),
81 dependencies: Vec::new(),
82 },
83 );
84 }
85 lockfile
86 .save(&lock_path)
87 .map_err(|e| NounVerbError::execution_error(format!("cannot write lockfile: {}", e)))?;
88
89 Ok(json!({
90 "capability": surface,
91 "projection": projection,
92 "runtime": runtime,
93 "atomic_packs": packs,
94 "lockfile": lock_path.display().to_string(),
95 }))
96}
97
98#[verb]
100pub fn list() -> Result<Value> {
101 let caps: Vec<Value> = list_capabilities()
102 .into_iter()
103 .map(|c| {
104 json!({
105 "id": c.id,
106 "name": c.name,
107 "description": c.description,
108 "category": c.category,
109 "atomic_packs": c.atomic_packs,
110 })
111 })
112 .collect();
113 Ok(json!({ "total": caps.len(), "capabilities": caps }))
114}
115
116#[verb]
118pub fn inspect(#[arg(index = 1)] surface: String) -> Result<Value> {
119 require_surface(&surface)?;
120 let packs = atomic_packs_for(&surface, None, None);
121 Ok(json!({
122 "capability": surface,
123 "atomic_packs": packs,
124 }))
125}