@@ -2117,15 +2117,6 @@ fn list_reference_docs(source: String) -> Result<Vec<ReferenceDoc>, String> {
21172117 Ok ( docs)
21182118}
21192119
2120- #[ tauri:: command]
2121- fn get_reference_doc ( path : String ) -> Result < String , String > {
2122- let doc_path = PathBuf :: from ( & path) ;
2123- if !doc_path. exists ( ) {
2124- return Err ( format ! ( "Document not found: {}" , path) ) ;
2125- }
2126- fs:: read_to_string ( & doc_path) . map_err ( |e| e. to_string ( ) )
2127- }
2128-
21292120#[ tauri:: command]
21302121fn list_distill_documents ( ) -> Result < Vec < DistillDocument > , String > {
21312122 let distill_dir = get_distill_dir ( ) ;
@@ -2158,18 +2149,6 @@ fn list_distill_documents() -> Result<Vec<DistillDocument>, String> {
21582149 Ok ( docs)
21592150}
21602151
2161- #[ tauri:: command]
2162- fn get_distill_document ( file : String ) -> Result < String , String > {
2163- let distill_dir = get_distill_dir ( ) ;
2164- let doc_path = distill_dir. join ( & file) ;
2165-
2166- if !doc_path. exists ( ) {
2167- return Err ( format ! ( "Document not found: {}" , file) ) ;
2168- }
2169-
2170- fs:: read_to_string ( & doc_path) . map_err ( |e| e. to_string ( ) )
2171- }
2172-
21732152#[ tauri:: command]
21742153fn find_session_project ( session_id : String ) -> Result < Option < Session > , String > {
21752154 let projects_dir = get_claude_dir ( ) . join ( "projects" ) ;
@@ -2218,17 +2197,6 @@ fn find_session_project(session_id: String) -> Result<Option<Session>, String> {
22182197 Ok ( None )
22192198}
22202199
2221- #[ tauri:: command]
2222- fn get_distill_command_file ( ) -> Result < String , String > {
2223- let cmd_path = get_claude_dir ( ) . join ( "commands/distill.md" ) ;
2224-
2225- if !cmd_path. exists ( ) {
2226- return Err ( "distill.md command file not found" . to_string ( ) ) ;
2227- }
2228-
2229- fs:: read_to_string ( & cmd_path) . map_err ( |e| e. to_string ( ) )
2230- }
2231-
22322200#[ tauri:: command]
22332201fn get_distill_watch_enabled ( ) -> bool {
22342202 DISTILL_WATCH_ENABLED . load ( std:: sync:: atomic:: Ordering :: Relaxed )
@@ -2267,13 +2235,20 @@ const PLUGIN_SOURCES: &[PluginSource] = &[
22672235 name : "Lovstudio" ,
22682236 icon : "💜" ,
22692237 priority : 2 ,
2270- path : "../lovstudio-plugins-official" , // External path
2238+ path : "marketplace/lovstudio" ,
2239+ } ,
2240+ PluginSource {
2241+ id : "lovstudio-plugins" ,
2242+ name : "Lovstudio Plugins" ,
2243+ icon : "💜" ,
2244+ priority : 3 ,
2245+ path : "../lovstudio-plugins-official" ,
22712246 } ,
22722247 PluginSource {
22732248 id : "community" ,
22742249 name : "Community" ,
22752250 icon : "🌍" ,
2276- priority : 3 ,
2251+ priority : 4 ,
22772252 path : "third-parties/claude-code-templates/docs/components.json" ,
22782253 } ,
22792254] ;
@@ -2330,6 +2305,7 @@ pub struct TemplatesCatalog {
23302305 pub hooks : Vec < TemplateComponent > ,
23312306 pub settings : Vec < TemplateComponent > ,
23322307 pub skills : Vec < TemplateComponent > ,
2308+ pub statuslines : Vec < TemplateComponent > ,
23332309 #[ serde( default ) ]
23342310 pub sources : Vec < SourceInfo > ,
23352311}
@@ -2755,6 +2731,46 @@ fn load_single_plugin(
27552731 } ) ;
27562732 }
27572733
2734+ // Scan statuslines/ (.sh files)
2735+ let statuslines_dir = base_path. join ( "statuslines" ) ;
2736+ if statuslines_dir. exists ( ) {
2737+ if let Ok ( entries) = fs:: read_dir ( & statuslines_dir) {
2738+ for entry in entries. filter_map ( |e| e. ok ( ) ) {
2739+ let path = entry. path ( ) ;
2740+ if path. extension ( ) . map_or ( false , |e| e == "sh" ) {
2741+ let name = path
2742+ . file_stem ( )
2743+ . unwrap_or_default ( )
2744+ . to_string_lossy ( )
2745+ . to_string ( ) ;
2746+ let content = fs:: read_to_string ( & path) . ok ( ) ;
2747+
2748+ // Parse description from script header comment
2749+ let description = content. as_ref ( ) . and_then ( |c| {
2750+ c. lines ( )
2751+ . find ( |l| l. starts_with ( "# Description:" ) )
2752+ . map ( |l| l. trim_start_matches ( "# Description:" ) . trim ( ) . to_string ( ) )
2753+ } ) ;
2754+
2755+ components. push ( TemplateComponent {
2756+ name : name. clone ( ) ,
2757+ path : format ! ( "statuslines/{}.sh" , name) ,
2758+ category : plugin_name. clone ( ) ,
2759+ component_type : "statusline" . to_string ( ) ,
2760+ description,
2761+ downloads : None ,
2762+ content,
2763+ source_id : Some ( source. id . to_string ( ) ) ,
2764+ source_name : Some ( source. name . to_string ( ) ) ,
2765+ source_icon : Some ( source. icon . to_string ( ) ) ,
2766+ plugin_name : Some ( plugin_name. clone ( ) ) ,
2767+ author : author. clone ( ) ,
2768+ } ) ;
2769+ }
2770+ }
2771+ }
2772+ }
2773+
27582774 components
27592775}
27602776
@@ -2787,6 +2803,7 @@ fn get_templates_catalog(app_handle: tauri::AppHandle) -> Result<TemplatesCatalo
27872803 let mut hooks = Vec :: new ( ) ;
27882804 let mut settings = Vec :: new ( ) ;
27892805 let mut skills = Vec :: new ( ) ;
2806+ let mut statuslines = Vec :: new ( ) ;
27902807
27912808 for comp in all_components {
27922809 match comp. component_type . as_str ( ) {
@@ -2796,6 +2813,7 @@ fn get_templates_catalog(app_handle: tauri::AppHandle) -> Result<TemplatesCatalo
27962813 "hook" => hooks. push ( comp) ,
27972814 "setting" => settings. push ( comp) ,
27982815 "skill" => skills. push ( comp) ,
2816+ "statusline" => statuslines. push ( comp) ,
27992817 _ => { } // Ignore unknown types
28002818 }
28012819 }
@@ -2818,6 +2836,7 @@ fn get_templates_catalog(app_handle: tauri::AppHandle) -> Result<TemplatesCatalo
28182836 hooks,
28192837 settings,
28202838 skills,
2839+ statuslines,
28212840 sources,
28222841 } )
28232842}
@@ -3006,6 +3025,62 @@ fn install_setting_template(config: String) -> Result<String, String> {
30063025 Ok ( "Settings updated" . to_string ( ) )
30073026}
30083027
3028+ #[ tauri:: command]
3029+ fn update_settings_statusline ( statusline : serde_json:: Value ) -> Result < ( ) , String > {
3030+ let settings_path = get_claude_dir ( ) . join ( "settings.json" ) ;
3031+ let mut settings: serde_json:: Value = if settings_path. exists ( ) {
3032+ let content = fs:: read_to_string ( & settings_path) . map_err ( |e| e. to_string ( ) ) ?;
3033+ serde_json:: from_str ( & content) . map_err ( |e| e. to_string ( ) ) ?
3034+ } else {
3035+ serde_json:: json!( { } )
3036+ } ;
3037+
3038+ settings[ "statusLine" ] = statusline;
3039+
3040+ let output = serde_json:: to_string_pretty ( & settings) . map_err ( |e| e. to_string ( ) ) ?;
3041+ fs:: write ( & settings_path, output) . map_err ( |e| e. to_string ( ) ) ?;
3042+ Ok ( ( ) )
3043+ }
3044+
3045+ #[ tauri:: command]
3046+ fn remove_settings_statusline ( ) -> Result < ( ) , String > {
3047+ let settings_path = get_claude_dir ( ) . join ( "settings.json" ) ;
3048+ if !settings_path. exists ( ) {
3049+ return Ok ( ( ) ) ;
3050+ }
3051+
3052+ let content = fs:: read_to_string ( & settings_path) . map_err ( |e| e. to_string ( ) ) ?;
3053+ let mut settings: serde_json:: Value =
3054+ serde_json:: from_str ( & content) . map_err ( |e| e. to_string ( ) ) ?;
3055+
3056+ if let Some ( obj) = settings. as_object_mut ( ) {
3057+ obj. remove ( "statusLine" ) ;
3058+ }
3059+
3060+ let output = serde_json:: to_string_pretty ( & settings) . map_err ( |e| e. to_string ( ) ) ?;
3061+ fs:: write ( & settings_path, output) . map_err ( |e| e. to_string ( ) ) ?;
3062+ Ok ( ( ) )
3063+ }
3064+
3065+ #[ tauri:: command]
3066+ fn write_statusline_script ( content : String ) -> Result < String , String > {
3067+ let script_path = get_claude_dir ( ) . join ( "statusline.sh" ) ;
3068+ fs:: write ( & script_path, & content) . map_err ( |e| e. to_string ( ) ) ?;
3069+
3070+ // Make executable on Unix
3071+ #[ cfg( unix) ]
3072+ {
3073+ use std:: os:: unix:: fs:: PermissionsExt ;
3074+ let mut perms = fs:: metadata ( & script_path)
3075+ . map_err ( |e| e. to_string ( ) ) ?
3076+ . permissions ( ) ;
3077+ perms. set_mode ( 0o755 ) ;
3078+ fs:: set_permissions ( & script_path, perms) . map_err ( |e| e. to_string ( ) ) ?;
3079+ }
3080+
3081+ Ok ( script_path. to_string_lossy ( ) . to_string ( ) )
3082+ }
3083+
30093084// ============================================================================
30103085// Context Feature
30113086// ============================================================================
@@ -4145,8 +4220,8 @@ fn workspace_set_active_project(id: String) -> Result<(), String> {
41454220}
41464221
41474222#[ tauri:: command]
4148- fn workspace_create_feature ( project_id : String , name : String ) -> Result < workspace_store:: Feature , String > {
4149- workspace_store:: create_feature ( & project_id, name)
4223+ fn workspace_create_feature ( project_id : String , name : String , description : Option < String > ) -> Result < workspace_store:: Feature , String > {
4224+ workspace_store:: create_feature ( & project_id, name, description )
41504225}
41514226
41524227#[ tauri:: command]
@@ -4614,6 +4689,9 @@ pub fn run() {
46144689 check_mcp_installed,
46154690 install_hook_template,
46164691 install_setting_template,
4692+ update_settings_statusline,
4693+ remove_settings_statusline,
4694+ write_statusline_script,
46174695 open_in_editor,
46184696 open_session_in_editor,
46194697 reveal_session_file,
@@ -4633,14 +4711,11 @@ pub fn run() {
46334711 test_openai_connection,
46344712 test_claude_cli,
46354713 list_distill_documents,
4636- get_distill_document,
46374714 find_session_project,
4638- get_distill_command_file,
46394715 get_distill_watch_enabled,
46404716 set_distill_watch_enabled,
46414717 list_reference_sources,
46424718 list_reference_docs,
4643- get_reference_doc,
46444719 get_claude_code_version_info,
46454720 install_claude_code_version,
46464721 set_claude_code_autoupdater,
0 commit comments