- 1
use std::path::{Path, PathBuf}; - 2
- 3
use vak_plugin::{InstallOptions, InstallScope, PluginStore}; - 4
- 5
use crate::cli::{MarketplaceTrustArg, PluginAction, PluginScopeArg}; - 6
- 7
pub(crate) fn run_plugins(cwd: PathBuf, action: PluginAction) -> i32 { - 8
match action { - 9
PluginAction::CatalogInspect { path, json } => match vak_plugin::inspect_catalog(&path) { - 10
Ok(catalog) => print_value(&catalog, json, |value| { - 11
println!( - 12
"{} {:?} {} entries\ndigest: {}\ntrace: {}", - 13
value.name, - 14
value.format, - 15
value.entries.len(), - 16
value.digest, - 17
value.trace_id - 18
); - 19
for warning in &value.warnings { - 20
println!("warning: {warning}"); - 21
} - 22
}), - 23
Err(error) => fail(error), - 24
}, - 25
PluginAction::CatalogRegister { - 26
path, - 27
label, - 28
trust, - 29
scope, - 30
json, - 31
} => { - 32
let trust = match trust { - 33
MarketplaceTrustArg::ManualReview => vak_plugin::MarketplaceTrust::ManualReview, - 34
MarketplaceTrustArg::PinnedCommit => vak_plugin::MarketplaceTrust::PinnedCommit, - 35
MarketplaceTrustArg::LocalOnly => vak_plugin::MarketplaceTrust::LocalOnly, - 36
}; - 37
match store(&cwd, scope).register_catalog_source(&path, &label, trust) { - 38
Ok(source) => print_value(&source, json, |value| { - 39
println!( - 40
"registered {} {:?} disabled\ntrace: {}", - 41
value.label, value.format, value.trace_id - 42
) - 43
}), - 44
Err(error) => fail(error), - 45
} - 46
} - 47
PluginAction::CatalogSources { scope, json } => match store(&cwd, scope).list_sources() { - 48
Ok(sources) => print_value(&sources, json, |values| { - 49
for source in values { - 50
println!( - 51
"{} {} {}", - 52
source.label, - 53
if source.enabled { - 54
"enabled" - 55
} else { - 56
"disabled" - 57
}, - 58
source.trace_id - 59
); - 60
} - 61
}), - 62
Err(error) => fail(error), - 63
}, - 64
PluginAction::CatalogSearch { query, scope, json } => { - 65
let needle = query.trim().to_lowercase(); - 66
if needle.is_empty() { - 67
eprintln!("error: catalog search query must not be empty"); - 68
return 2; - 69
} - 70
let store = store(&cwd, scope); - 71
let sources = match store.list_sources() { - 72
Ok(sources) => sources, - 73
Err(error) => return fail(error), - 74
}; - 75
let mut matches = Vec::new(); - 76
for source in sources { - 77
let inspection = match vak_plugin::inspect_catalog(&source.root) { - 78
Ok(inspection) => inspection, - 79
Err(error) => return fail(error), - 80
}; - 81
if inspection.digest != source.catalog_digest { - 82
return fail(vak_plugin::PluginError::UnsafePackage(format!( - 83
"marketplace catalog changed since registration: {}", - 84
source.root.display() - 85
))); - 86
} - 87
for entry in inspection.entries { - 88
let haystack = format!( - 89
"{} {}", - 90
entry.name, - 91
entry.description.as_deref().unwrap_or_default() - 92
) - 93
.to_lowercase(); - 94
if haystack.contains(&needle) { - 95
matches.push(serde_json::json!({ - 96
"source_id": source.id, - 97
"source_label": source.label, - 98
"source_enabled": source.enabled, - 99
"name": entry.name, - 100
"version": entry.version, - 101
"description": entry.description, - 102
"license": entry.license, - 103
"catalog_digest": source.catalog_digest, - 104
})); - 105
} - 106
} - 107
} - 108
if json { - 109
println!("{}", serde_json::Value::Array(matches)); - 110
} else if matches.is_empty() { - 111
println!("no marketplace entries matched '{query}'"); - 112
} else { - 113
for entry in matches { - 114
println!( - 115
"{} — {} [{}; {}]", - 116
entry["name"].as_str().unwrap_or("unknown"), - 117
entry["description"].as_str().unwrap_or("no description"), - 118
entry["source_label"].as_str().unwrap_or("unknown source"), - 119
if entry["source_enabled"] == true { - 120
"enabled" - 121
} else { - 122
"disabled" - 123
} - 124
); - 125
} - 126
} - 127
0 - 128
} - 129
PluginAction::CatalogInstall { - 130
catalog, - 131
name, - 132
scope, - 133
allow_unlicensed, - 134
json, - 135
} => { - 136
let store = store(&cwd, scope); - 137
let inspection = match vak_plugin::inspect_catalog(&catalog) { - 138
Ok(value) => value, - 139
Err(error) => return fail(error), - 140
}; - 141
let entry = match inspection.entries.iter().find(|entry| entry.name == name) { - 142
Some(entry) => entry, - 143
None => return fail(vak_plugin::PluginError::NotInstalled(name)), - 144
}; - 145
let staging = store - 146
.packages_root() - 147
.join("catalog-staging") - 148
.join(&entry.name); - 149
let package = match vak_plugin::materialize_catalog_entry(&catalog, entry, &staging) { - 150
Ok(path) => path, - 151
Err(error) => return fail(error), - 152
}; - 153
let result = store.install_local( - 154
&package, - 155
InstallOptions { - 156
scope: install_scope(scope), - 157
allow_unlicensed, - 158
}, - 159
); - 160
let _ = std::fs::remove_dir_all(&staging); - 161
match result { - 162
Ok(plugin) => print_value(&plugin, json, |value| { - 163
println!( - 164
"installed catalog entry {} {} disabled\ntrace: {}", - 165
value.name, value.version, value.trace_id - 166
); - 167
}), - 168
Err(error) => fail(error), - 169
} - 170
} - 171
PluginAction::Inspect { path, json } => match vak_plugin::inspect_package(&path) { - 172
Ok(inspection) => print_value(&inspection, json, |value| { - 173
println!( - 174
"{} {} {} {} files / {} bytes", - 175
value.manifest.name, - 176
value.manifest.version, - 177
value.digest, - 178
value.file_count, - 179
value.total_bytes - 180
); - 181
print_capabilities(&value.capabilities); - 182
for warning in &value.warnings { - 183
println!("warning: {warning}"); - 184
} - 185
}), - 186
Err(error) => fail(error), - 187
}, - 188
PluginAction::Install { - 189
path, - 190
scope, - 191
allow_unlicensed, - 192
json, - 193
} => { - 194
let store = store(&cwd, scope); - 195
let options = InstallOptions { - 196
scope: install_scope(scope), - 197
allow_unlicensed, - 198
}; - 199
match store.install_local(&path, options) { - 200
Ok(plugin) => print_value(&plugin, json, |value| { - 201
println!( - 202
"installed {} {} disabled\ndigest: {}\ntrace: {}", - 203
value.name, value.version, value.digest, value.trace_id - 204
); - 205
print_capabilities(&value.capabilities); - 206
}), - 207
Err(error) => fail(error), - 208
} - 209
} - 210
PluginAction::Update { - 211
path, - 212
scope, - 213
allow_unlicensed, - 214
json, - 215
} => match store(&cwd, scope).update_local( - 216
&path, - 217
InstallOptions { - 218
scope: install_scope(scope), - 219
allow_unlicensed, - 220
}, - 221
) { - 222
Ok(plugin) => print_value(&plugin, json, |value| { - 223
println!( - 224
"staged update {} {} disabled\ndigest: {}\ntrace: {}", - 225
value.name, value.version, value.digest, value.trace_id - 226
); - 227
}), - 228
Err(error) => fail(error), - 229
}, - 230
PluginAction::Enable { name, scope, json } => match store(&cwd, scope).enable(&name) { - 231
Ok(plugin) => print_value(&plugin, json, |value| { - 232
println!( - 233
"enabled {} {} ({})", - 234
value.name, value.version, value.digest - 235
); - 236
}), - 237
Err(error) => fail(error), - 238
}, - 239
PluginAction::Disable { name, scope, json } => match store(&cwd, scope).disable(&name) { - 240
Ok(plugin) => print_value(&plugin, json, |value| { - 241
println!( - 242
"disabled {} {} ({})", - 243
value.name, value.version, value.digest - 244
); - 245
}), - 246
Err(error) => fail(error), - 247
}, - 248
PluginAction::Rollback { name, scope, json } => match store(&cwd, scope).rollback(&name) { - 249
Ok(plugin) => print_value(&plugin, json, |value| { - 250
println!( - 251
"rolled back {} {} disabled ({})", - 252
value.name, value.version, value.digest - 253
); - 254
}), - 255
Err(error) => fail(error), - 256
}, - 257
PluginAction::Versions { name, scope, json } => match store(&cwd, scope).versions(&name) { - 258
Ok(plugins) => print_value(&plugins, json, |values| { - 259
for value in values { - 260
let state = if value.enabled { "enabled" } else { "disabled" }; - 261
println!( - 262
"{} {} {} {}", - 263
value.name, value.version, state, value.digest - 264
); - 265
} - 266
}), - 267
Err(error) => fail(error), - 268
}, - 269
PluginAction::List { scope, json } => match store(&cwd, scope).list() { - 270
Ok(plugins) => print_value(&plugins, json, |values| { - 271
if values.is_empty() { - 272
println!("no plugins installed for {} scope", scope_name(scope)); - 273
} - 274
for plugin in values { - 275
let state = if plugin.enabled { - 276
"enabled" - 277
} else { - 278
"disabled" - 279
}; - 280
println!( - 281
"{} {} {} {} {}", - 282
plugin.name, - 283
plugin.version, - 284
state, - 285
plugin.format_name(), - 286
plugin.digest - 287
); - 288
} - 289
}), - 290
Err(error) => fail(error), - 291
}, - 292
PluginAction::Audit { scope, json } => match store(&cwd, scope).load() { - 293
Ok(registry) => print_value(®istry.audit, json, |events| { - 294
if events.is_empty() { - 295
println!("no plugin audit events for {} scope", scope_name(scope)); - 296
} - 297
for event in events { - 298
println!( - 299
"{} {:?} {} {} {}", - 300
event.generation, event.action, event.plugin, event.version, event.trace_id - 301
); - 302
} - 303
}), - 304
Err(error) => fail(error), - 305
}, - 306
PluginAction::Remove { name, scope, json } => match store(&cwd, scope).remove(&name) { - 307
Ok(plugin) => print_value(&plugin, json, |value| { - 308
println!( - 309
"removed {} {} ({})", - 310
value.name, value.version, value.digest - 311
); - 312
}), - 313
Err(error) => fail(error), - 314
}, - 315
} - 316
} - 317
- 318
fn store(cwd: &Path, scope: PluginScopeArg) -> PluginStore { - 319
let home = match scope { - 320
PluginScopeArg::User => vak_config::paths::data_home(), - 321
PluginScopeArg::Workspace => cwd.join(".vak"), - 322
}; - 323
PluginStore::new(home) - 324
} - 325
- 326
fn install_scope(scope: PluginScopeArg) -> InstallScope { - 327
match scope { - 328
PluginScopeArg::User => InstallScope::User, - 329
PluginScopeArg::Workspace => InstallScope::Workspace, - 330
} - 331
} - 332
- 333
fn scope_name(scope: PluginScopeArg) -> &'static str { - 334
match scope { - 335
PluginScopeArg::User => "user", - 336
PluginScopeArg::Workspace => "workspace", - 337
} - 338
} - 339
- 340
fn print_value<T: serde::Serialize>(value: &T, json: bool, text: impl FnOnce(&T)) -> i32 { - 341
if json { - 342
match serde_json::to_string_pretty(value) { - 343
Ok(output) => println!("{output}"), - 344
Err(error) => { - 345
eprintln!("error: could not serialize plugin result: {error}"); - 346
return 1; - 347
} - 348
} - 349
} else { - 350
text(value); - 351
} - 352
0 - 353
} - 354
- 355
fn print_capabilities(value: &vak_plugin::CapabilityInventory) { - 356
println!( - 357
"components: {} skills, {} commands, {} MCP, {} hooks, {} agents, {} LSP", - 358
value.skills.len(), - 359
value.commands.len(), - 360
value.mcp_manifests.len(), - 361
value.hooks.len(), - 362
value.agents.len(), - 363
value.lsp_manifests.len() - 364
); - 365
println!( - 366
"local execution: {} scripts, {} executable files", - 367
value.scripts.len(), - 368
value.executables.len() - 369
); - 370
} - 371
- 372
fn fail(error: vak_plugin::PluginError) -> i32 { - 373
eprintln!("error: {error}"); - 374
1 - 375
} - 376
- 377
trait ManifestFormatName { - 378
fn format_name(&self) -> &'static str; - 379
} - 380
- 381
impl ManifestFormatName for vak_plugin::InstalledPlugin { - 382
fn format_name(&self) -> &'static str { - 383
match self.format { - 384
vak_plugin::ManifestFormat::Vak => "vak", - 385
vak_plugin::ManifestFormat::Codex => "codex", - 386
vak_plugin::ManifestFormat::AgentPlugin => "agent-plugin", - 387
vak_plugin::ManifestFormat::Claude => "claude", - 388
vak_plugin::ManifestFormat::Copilot => "copilot", - 389
vak_plugin::ManifestFormat::Cursor => "cursor", - 390
vak_plugin::ManifestFormat::Gemini => "gemini", - 391
vak_plugin::ManifestFormat::AgentSkill => "agent-skill", - 392
} - 393
} - 394
} - 395
Indexing the workspace…
Vakyartha documentation is discovering safe artifacts, anchors, and source references.