1pub(crate) mod api;
2pub(crate) mod cli;
3pub(crate) mod engine;
4pub(crate) mod helpers;
5pub(crate) mod permissions;
6pub(crate) mod store;
7pub(crate) mod wasm_host;
8
9pub(crate) use engine::WasmEngine;
10pub(crate) use wasm_host::WasmHost;
11
12use anyhow::Result;
13use api::host_api::repl::api::transport;
14use clap::Parser;
15use cli::{Cli, Commands};
16use helpers::{StatusHandler, StdoutHandler};
17use std::io::Write;
18
19pub async fn run_async() -> Result<()> {
21 let cli = Cli::parse();
23
24 if let Some(command) = &cli.command {
26 match command {
27 Commands::GenerateCompletions { shell } => {
28 return handle_generate_completions(*shell);
29 }
30 }
31 }
32
33 let repl_logic = cli
35 .repl_logic
36 .ok_or_else(|| anyhow::anyhow!("--repl-logic is required when running in REPL mode"))?;
37
38 let debug = cli.debug;
39 let plugins = cli.plugins;
40 let dir = cli.dir;
41 let allow_net = cli.allow_net;
42 let allow_read = cli.allow_read;
43 let allow_write = cli.allow_write;
44 let allow_all = cli.allow_all;
45
46 let repl_cli = Cli {
48 command: None,
49 plugins,
50 repl_logic: Some(repl_logic.clone()),
51 debug,
52 dir,
53 allow_net,
54 allow_read,
55 allow_write,
56 allow_all,
57 };
58
59 println!("[Host] Starting REPL host...");
60
61 let wasi_ctx = WasmEngine::build_wasi_ctx(&repl_cli)?;
64
65 let engine = WasmEngine::new()?;
67
68 let mut host = WasmHost::new(&engine, wasi_ctx, &repl_cli);
70
71 println!("[Host] Loading REPL logic from: {}", repl_logic);
72 host.load_repl_logic(&engine, &repl_logic).await?;
74
75 for plugin_source in &repl_cli.plugins {
77 println!("[Host] Loading plugin: {}", plugin_source);
78 host.load_plugin(&engine, plugin_source).await?;
79 }
80
81 let mut plugins_config: Vec<(String, String)> = Vec::new();
82 for (name, plugin_instance) in &host.plugins {
83 let man = plugin_instance
84 .plugin
85 .repl_api_plugin()
86 .call_man(&mut host.store)
87 .await?;
88 plugins_config.push((name.clone(), man));
89 host.store.data_mut().plugins_names.push(name.clone());
90 }
91 if debug {
92 eprintln!("[Host][Debug] Loaded plugins config: {:?}", plugins_config);
93 }
94
95 {
96 let mut repl_vars = host
97 .store
98 .data_mut()
99 .repl_vars
100 .lock()
101 .expect("Failed to acquire repl_vars lock");
102 repl_vars.insert("ROOT".to_string(), "/Users".to_string());
103 }
104 {
105 let mut repl_vars = host
106 .store
107 .data_mut()
108 .repl_vars
109 .lock()
110 .expect("Failed to acquire repl_vars lock");
111 repl_vars.insert("USER".to_string(), "Tophe".to_string());
112 repl_vars.insert("?".to_string(), "0".to_string());
113 }
114 if debug {
115 eprintln!(
116 "[Host][Debug] Loaded env vars: {:?}",
117 host.store
118 .data()
119 .repl_vars
120 .lock()
121 .expect("Failed to acquire repl_vars lock")
122 );
123 }
124
125 let Some(repl_logic) = host.repl_logic else {
126 return Err(anyhow::anyhow!("No REPL logic loaded"));
127 };
128
129 loop {
130 let mut line = String::new();
131 match host
132 .store
133 .data()
134 .repl_vars
135 .lock()
136 .expect("Failed to acquire repl_vars lock")
137 .get("?")
138 {
139 Some(last_status) => {
140 print!("repl({})> ", last_status);
141 }
142 None => {
143 print!("repl> ");
144 }
145 }
146 std::io::stdout().flush()?;
147 std::io::stdin().read_line(&mut line)?;
148 let result = repl_logic
149 .repl_api_repl_logic()
150 .call_readline(&mut host.store, &line)
151 .await?;
152 match result {
155 transport::ReadlineResponse::Ready(plugin_response) => {
158 if let Some(stdout) = plugin_response.stdout {
159 StdoutHandler::print_and_set_last_result(
160 &mut host.store.data_mut().repl_vars,
161 stdout,
162 );
163 }
164 if let Some(stderr) = plugin_response.stderr {
165 eprintln!("{}", stderr);
166 }
167 StatusHandler::set_exit_status(
168 &mut host.store.data_mut().repl_vars,
169 plugin_response.status
170 == api::host_api::repl::api::transport::ReplStatus::Success,
171 );
172 }
173 transport::ReadlineResponse::ToRun(parsed_line) => {
178 if debug {
179 eprintln!("[Host][Debug] To run: {:?}", parsed_line);
180 }
181
182 if parsed_line.command == "" {
184 continue;
185 }
186
187 if parsed_line.command == "man" {
189 let Some(plugin_instance) = host.plugins.get(&parsed_line.payload) else {
190 println!(
191 "Unknown command: {}. Try `help` to see available commands.",
192 parsed_line.payload
193 );
194 StatusHandler::set_exit_status(&mut host.store.data_mut().repl_vars, false);
195 continue;
196 };
197 let man = plugin_instance
198 .plugin
199 .repl_api_plugin()
200 .call_man(&mut host.store)
201 .await?;
202 StdoutHandler::print_and_set_last_result(
203 &mut host.store.data_mut().repl_vars,
204 man,
205 );
206 StatusHandler::set_exit_status(&mut host.store.data_mut().repl_vars, true);
207 continue;
208 }
209
210 match host.plugins.get(&parsed_line.command) {
212 Some(plugin_instance) => {
213 let result = plugin_instance
214 .plugin
215 .repl_api_plugin()
216 .call_run(&mut host.store, &parsed_line.payload)
217 .await?;
218 if let Ok(result) = result {
219 if let Some(stdout) = result.stdout {
220 StdoutHandler::print_and_set_last_result(
221 &mut host.store.data_mut().repl_vars,
222 stdout,
223 );
224 }
225 if let Some(stderr) = result.stderr {
226 eprintln!("{}", stderr);
227 }
228 StatusHandler::set_exit_status(
229 &mut host.store.data_mut().repl_vars,
230 result.status
231 == api::plugin_api::repl::api::transport::ReplStatus::Success,
232 );
233 } else {
234 eprintln!("Error: {:?}", result);
235 StatusHandler::set_exit_status(
236 &mut host.store.data_mut().repl_vars,
237 false,
238 );
239 }
240 }
241 None => {
242 println!(
243 "Unknown command: {}. Try `help` to see available commands.",
244 parsed_line.command
245 );
246 StatusHandler::set_exit_status(&mut host.store.data_mut().repl_vars, false);
247 continue;
248 }
249 }
250 }
251 }
252 }
253}
254
255fn handle_generate_completions(shell: cli::AvailableShells) -> Result<()> {
257 use clap::CommandFactory;
258 use clap_complete::{generate, Shell};
259 use cli::Cli;
260
261 let mut cmd = Cli::command();
262 let shell_type = match shell {
263 cli::AvailableShells::Bash => Shell::Bash,
264 cli::AvailableShells::Fish => Shell::Fish,
265 cli::AvailableShells::Zsh => Shell::Zsh,
266 };
267
268 generate(shell_type, &mut cmd, "pluginlab", &mut std::io::stdout());
269
270 Ok(())
271}