intelli_shell/service/
mod.rs1use std::{
2 env,
3 path::{Path, PathBuf},
4 sync::{Arc, Mutex},
5};
6
7use tokio::fs::File;
8use tracing::instrument;
9
10use crate::{
11 config::{AiConfig, SearchTuning},
12 errors::Result,
13 model::{CATEGORY_WORKSPACE, SOURCE_WORKSPACE},
14 service::import::parse_import_items,
15 storage::SqliteStorage,
16 utils::get_working_dir,
17};
18
19mod ai;
20mod command;
21mod completion;
22mod export;
23mod import;
24mod tldr;
25mod variable;
26mod version;
27
28pub use ai::AiFixProgress;
29pub use completion::{FORBIDDEN_COMPLETION_ROOT_CMD_CHARS, FORBIDDEN_COMPLETION_VARIABLE_CHARS};
30pub use tldr::{RepoStatus, TldrFetchProgress};
31
32#[derive(Clone)]
34pub struct IntelliShellService {
35 check_updates: bool,
36 storage: SqliteStorage,
37 tuning: SearchTuning,
38 ai: AiConfig,
39 tldr_repo_path: PathBuf,
40 version_check_state: Arc<Mutex<version::VersionCheckState>>,
41}
42
43impl IntelliShellService {
44 pub fn new(
46 storage: SqliteStorage,
47 tuning: SearchTuning,
48 ai: AiConfig,
49 data_dir: impl AsRef<Path>,
50 check_updates: bool,
51 ) -> Self {
52 Self {
53 check_updates,
54 storage,
55 tuning,
56 ai,
57 tldr_repo_path: data_dir.as_ref().join("tldr"),
58 version_check_state: Arc::new(Mutex::new(version::VersionCheckState::NotStarted)),
59 }
60 }
61
62 #[cfg(debug_assertions)]
63 pub async fn query(&self, sql: String) -> crate::errors::Result<String> {
64 self.storage.query(sql).await
65 }
66
67 #[instrument(skip_all)]
72 pub async fn load_workspace_items(&self) -> Result<bool> {
73 if env::var("INTELLI_SKIP_WORKSPACE")
74 .map(|v| v != "1" && v.to_lowercase() != "true")
75 .unwrap_or(true)
76 && let Some((workspace_file, folder_name)) = find_workspace_file()
77 {
78 tracing::debug!("Found workspace file at {}", workspace_file.display());
79
80 self.storage.setup_workspace_storage().await?;
82
83 let file = File::open(&workspace_file).await?;
85 let tag = format!("#{}", folder_name.as_deref().unwrap_or("workspace"));
86 let items_stream = parse_import_items(file, vec![tag], CATEGORY_WORKSPACE, SOURCE_WORKSPACE);
87
88 let stats = self.storage.import_items(items_stream, false, true).await?;
90
91 tracing::info!(
92 "Loaded {} commands and {} completions from workspace {}",
93 stats.commands_imported,
94 stats.completions_imported,
95 workspace_file.display()
96 );
97
98 Ok(true)
99 } else {
100 Ok(false)
101 }
102 }
103}
104
105fn find_workspace_file() -> Option<(PathBuf, Option<String>)> {
110 let working_dir = PathBuf::from(get_working_dir());
111 let mut current = Some(working_dir.as_path());
112 while let Some(parent) = current {
113 let candidate = parent.join(".intellishell");
114 if candidate.is_file() {
115 let folder_name = parent.file_name().and_then(|n| n.to_str()).map(String::from);
116 return Some((candidate, folder_name));
117 }
118
119 if parent.join(".git").is_dir() {
120 return None;
122 }
123
124 current = parent.parent();
125 }
126 None
127}