{"TotalCount":2,"Files":[{"Ident":"rwtrcsc.librarysystem","Path":"Editor/LibraryTools.cs","FileName":"LibraryTools.cs","PackageType":"library","CodeKind":"Editor","AssetVersionId":421927,"IsPrivate":false,"Code":"using Editor;\nusing Editor.Mcp;\nusing Sandbox;\nusing System.Collections.Generic;\r\nusing System.Linq;\nusing System.Threading.Tasks;\n\nnamespace LibraryTools;\n\n/// \u003Csummary\u003E\n/// The Library Manager and the Publish wizard, exposed to the s\u0026box editor\u0027s MCP server, so scripts and AI agents\n/// can list, install, remove and publish sbox.game libraries the same way the editor\u0027s own buttons do.\n/// \u003C/summary\u003E\n[McpToolset( \u0022libraries\u0022, \u0022Library Manager from MCP: list versions, install, uninstall and publish sbox.game libraries\u0022 )]\npublic static class LibraryTools\n{\n\t// Installed libraries report idents like \u0022rwtrcsc.lobbysystem#local\u0022; compare the part before the #.\n\tstatic bool Matches( LibraryProject lib, string ident ) =\u003E (lib.Project?.Package?.FullIdent ?? \u0022\u0022).Split( \u0027#\u0027 )[0] == ident;\n\n\t/// \u003Csummary\u003EPublished versions of a library on sbox.game (newest first) and the version installed here, if any.\u003C/summary\u003E\n\t/// \u003Cparam name=\u0022ident\u0022\u003EFull package ident, e.g. \u0027rwtrcsc.lobbysystem\u0027.\u003C/param\u003E\n\t[McpTool.ReadOnly( \u0022library_versions\u0022 )]\n\tpublic static async Task\u003Cstring\u003E LibraryVersions( string ident )\n\t{\n\t\tvar versions = await Package.FetchVersions( ident, default );\n\t\tvar installed = Installed().FirstOrDefault( x =\u003E Matches( x, ident ) );\n\t\tvar lines = (versions ?? Enumerable.Empty\u003CPackage.IRevision\u003E())\n\t\t\t.Select( v =\u003E $\u0022{v.VersionId}  {v.Created:yyyy-MM-dd HH:mm}  files={v.FileCount}  {v.Summary}\u0022 );\n\t\treturn $\u0022installed: {(installed is null ? \u0022no\u0022 : installed.Version?.ToString() ?? \u0022local\u0022)}\\n\u0022 \u002B string.Join( \u0022\\n\u0022, lines );\n\t}\n\n\t/// \u003Csummary\u003EInstall a published library version into Libraries/ (newest when versionId is 0).\u003C/summary\u003E\n\t/// \u003Cparam name=\u0022ident\u0022\u003EFull package ident, e.g. \u0027rwtrcsc.lobbysystem\u0027.\u003C/param\u003E\n\t/// \u003Cparam name=\u0022versionId\u0022\u003EA version id from library_versions, or 0 for the newest.\u003C/param\u003E\n\t[McpTool( \u0022library_install\u0022 )]\n\tpublic static async Task\u003Cstring\u003E LibraryInstall( string ident, long versionId = 0 )\n\t{\n\t\tvar versions = (await Package.FetchVersions( ident, default ))?.ToList();\n\t\tvar v = versionId == 0 ? versions?.FirstOrDefault() : versions?.FirstOrDefault( x =\u003E x.VersionId == versionId );\n\t\tif ( v is null ) return $\u0022No published version found for {ident}.\u0022;\n\n\t\tawait LibrarySystem.Install( ident, v.VersionId, default );\n\t\treturn $\u0022Installed {ident} version {v.VersionId} (published {v.Created:yyyy-MM-dd}).\u0022;\n\t}\n\n\t/// \u003Csummary\u003ERemove an installed library and delete its folder, as the Library Manager\u0027s uninstall button does.\u003C/summary\u003E\n\t/// \u003Cparam name=\u0022ident\u0022\u003EFull package ident, e.g. \u0027rwtrcsc.lobbysystem\u0027.\u003C/param\u003E\n\t[McpTool( \u0022library_uninstall\u0022 )]\n\tpublic static string LibraryUninstall( string ident )\n\t{\n\t\tvar lib = Installed().FirstOrDefault( x =\u003E Matches( x, ident ) );\n\t\tif ( lib is null )\n\t\t\treturn $\u0022{ident} is not installed. Installed: \u0022 \u002B string.Join( \u0022, \u0022, Installed().Select( x =\u003E x.Project?.Package?.FullIdent ) );\n\n\t\t// Delete the folder ourselves rather than lib.RemoveAndDelete(), which does the same delete but then pops\n\t\t// s\u0026box\u0027s own modal \u0022Library Removed\u0022 notice. The editor keeps the library loaded until it restarts.\n\t\tvar path = lib.Project.GetRootPath();\n\t\tfor ( int i = 0; i \u003C 10 \u0026\u0026 System.IO.Directory.Exists( path ); i\u002B\u002B )\n\t\t{\n\t\t\ttry { System.IO.Directory.Delete( path, true ); }\n\t\t\tcatch ( System.IO.DirectoryNotFoundException ) { break; }\n\t\t\tcatch { System.Threading.Thread.Sleep( 100 ); }   // files briefly locked by the asset system\n\t\t}\n\t\tif ( System.IO.Directory.Exists( path ) ) return $\u0022Couldn\u0027t delete {path}: a file in it is locked. Close anything using it and try again.\u0022;\n\t\treturn $\u0022Removed {ident} and deleted its folder. Restart the editor to finish unloading it.\u0022;\n\t}\n\n\t/// \u003Csummary\u003ELibraries still on disk. A removed one stays loaded (and listed by s\u0026amp;box) until the editor restarts.\u003C/summary\u003E\n\tpublic static IEnumerable\u003CLibraryProject\u003E Installed() =\u003E\n\t\tLibrarySystem.All.Where( x =\u003E x.Project is not null \u0026\u0026 System.IO.Directory.Exists( x.Project.GetRootPath() ) );\n\n\t/// \u003Csummary\u003E\n\t/// Publish a library from Libraries/ as a new revision on sbox.game, running the Publish wizard\u0027s own steps:\n\t/// compile, gather files, upload, publish. Visibility is not set here: a new package starts Hidden (org members only)\n\t/// and you switch it to Released on its sbox.game Edit page.\n\t/// \u003C/summary\u003E\n\t/// \u003Cparam name=\u0022ident\u0022\u003EFull package ident, e.g. \u0027rwtrcsc.combatsystem\u0027.\u003C/param\u003E\n\t/// \u003Cparam name=\u0022changeTitle\u0022\u003ERevision title shown on the package page.\u003C/param\u003E\n\t[McpTool( \u0022library_publish\u0022 )]\n\tpublic static async Task\u003Cstring\u003E LibraryPublish( string ident, string changeTitle = \u0022\u0022 )\n\t{\n\t\tvar project = Installed().FirstOrDefault( x =\u003E Matches( x, ident ) )?.Project;\n\t\tif ( project is null ) return $\u0022{ident} is not in Libraries/.\u0022;\n\t\tif ( project.Config.Org == \u0022local\u0022 ) return \u0022Set an organisation in the .sbproj first.\u0022;\n\n\t\tvar publisher = await ProjectPublisher.FromProject( project );\n\n\t\t// Same as the Publish wizard: compile only when there is game code (editor-only libraries ship as source).\n\t\tif ( project.HasCodePath() )\n\t\t{\n\t\t\tvar output = await EditorUtility.Projects.Compile( project, _ =\u003E { } );\n\t\t\tif ( output is null || output.Length == 0 ) return \u0022Compile produced no code.\u0022;\n\n\t\t\tawait publisher.AddFile( System.Text.Json.JsonSerializer.Serialize( output.Select( x =\u003E x.Compiler.AssemblyName ).ToList() ), \u0022.bin/manifest.json\u0022 );\n\t\t\tforeach ( var asm in output )\n\t\t\t{\n\t\t\t\tawait publisher.AddFile( asm.XmlDocumentation, $\u0022.bin/{asm.Compiler.AssemblyName}.xml\u0022 );\n\t\t\t\tawait publisher.AddFile( asm.Archive.Serialize(), $\u0022.bin/{asm.Compiler.AssemblyName}.cll\u0022 );\n\t\t\t}\n\t\t}\n\t\tawait publisher.PrePublish();\n\n\t\tforeach ( var file in publisher.Files.Where( x =\u003E !x.Skip ).ToList() )\n\t\t{\n\t\t\tbool ok = file.Contents is not null\n\t\t\t\t? await project.Package.UploadFile( file.Contents, file.Name, _ =\u003E { }, default )\n\t\t\t\t: file.AbsolutePath is not null \u0026\u0026 await project.Package.UploadFile( file.AbsolutePath, file.Name, _ =\u003E { }, default );\n\t\t\tif ( ok ) file.Skip = true;\n\t\t}\n\t\tvar failed = publisher.Files.Where( x =\u003E !x.Skip ).Select( x =\u003E x.Name ).ToList();\n\t\tif ( failed.Count \u003E 0 ) return \u0022Upload failed for: \u0022 \u002B string.Join( \u0022, \u0022, failed );\n\n\t\tpublisher.SetChangeDetails( string.IsNullOrWhiteSpace( changeTitle ) ? $\u0022Changes on {System.DateTime.UtcNow:yyyy-MM-dd}\u0022 : changeTitle, \u0022\u0022 );\n\t\tawait publisher.Publish();\n\t\treturn $\u0022Published {ident} ({publisher.Files.Count()} files). Visibility is unchanged: new packages start Hidden; release them on sbox.game.\u0022;\n\t}\n}"},{"Ident":"rwtrcsc.librarysystem","Path":"Editor/LibrarySystemWindow.cs","FileName":"LibrarySystemWindow.cs","PackageType":"library","CodeKind":"Editor","AssetVersionId":421927,"IsPrivate":false,"Code":"using Editor;\nusing Sandbox;\nusing System;\nusing System.Linq;\nusing System.Threading.Tasks;\n\nnamespace LibraryTools;\n\n/// \u003Csummary\u003E\n/// Library System as an editor window (Tools \u003E Library System). Same actions as the MCP tools, which it\n/// calls directly: list the project\u0027s libraries, check published versions, install, remove and publish.\n/// \u003C/summary\u003E\n[EditorApp( \u0022Library System\u0022, \u0022library_books\u0022, \u0022List, install, remove and publish the project\u0027s libraries\u0022 )]\npublic sealed class LibrarySystemWindow : Widget\n{\n\treadonly Widget _list;\n\treadonly LineEdit _ident;\n\treadonly Label _output;\n\n\tpublic LibrarySystemWindow() : base( null )\n\t{\n\t\tWindowTitle = \u0022Library System\u0022;\n\t\tSetWindowIcon( \u0022library_books\u0022 );\n\t\tWindowFlags = WindowFlags.Dialog | WindowFlags.Customized | WindowFlags.WindowTitle | WindowFlags.CloseButton | WindowFlags.WindowSystemMenuHint;\n\t\tDeleteOnClose = true;\n\t\tMinimumSize = new Vector2( 620, 420 );\n\n\t\tLayout = Layout.Column();\n\t\tLayout.Margin = 12;\n\t\tLayout.Spacing = 8;\n\n\t\tLayout.Add( new Label.Subtitle( \u0022Libraries in this project\u0022 ) );\n\t\t_list = Layout.Add( new Widget( this ) );\n\t\t_list.Layout = Layout.Column();\n\t\t_list.Layout.Spacing = 4;\n\n\t\tLayout.Add( new Label.Subtitle( \u0022Install from sbox.game\u0022 ) );\n\t\tvar row = Layout.AddRow();\n\t\trow.Spacing = 4;\n\t\t_ident = row.Add( new LineEdit( this ) { PlaceholderText = \u0022org.package, e.g. rwtrcsc.lobbysystem\u0022 }, 1 );\n\t\trow.Add( new Button( \u0022Versions\u0022, \u0022history\u0022, this ) { Clicked = () =\u003E Run( \u0022Versions\u0022, () =\u003E LibraryTools.LibraryVersions( Ident() ) ) } );\n\t\trow.Add( new Button.Primary( \u0022Install\u0022, \u0022download\u0022, this ) { Clicked = () =\u003E Run( \u0022Install\u0022, async () =\u003E { var r = await LibraryTools.LibraryInstall( Ident() ); Refresh(); return r; } ) } );\n\n\t\tLayout.AddStretchCell();\n\t\t_output = Layout.Add( new Label( \u0022Ready.\u0022, this ) { WordWrap = true, TextSelectable = true } );\n\n\t\tRefresh();\n\t}\n\n\tstring Ident() =\u003E _ident.Value?.Trim() ?? \u0022\u0022;\n\n\tvoid Refresh()\n\t{\n\t\t_list.Layout.Clear( true );\n\t\tvar libs = LibraryTools.Installed().ToList();\n\t\tif ( libs.Count == 0 ) _list.Layout.Add( new Label( \u0022No libraries in Libraries/ yet.\u0022, _list ) );\n\n\t\tforeach ( var lib in libs )\n\t\t{\n\t\t\tvar ident = (lib.Project?.Package?.FullIdent ?? \u0022\u0022).Split( \u0027#\u0027 )[0];\n\t\t\tvar title = lib.Project?.Config?.Title ?? ident;\n\t\t\tvar row = _list.Layout.AddRow();\n\t\t\trow.Spacing = 4;\n\t\t\trow.Add( new Label( $\u0022{title}   {ident}   {(lib.Version?.ToString() ?? \u0022local\u0022)}\u0022, _list ), 1 );\n\t\t\trow.Add( new Button( \u0022Versions\u0022, \u0022history\u0022, _list ) { Clicked = () =\u003E Run( $\u0022Versions of {ident}\u0022, () =\u003E LibraryTools.LibraryVersions( ident ) ) } );\n\t\t\trow.Add( new Button( \u0022Publish\u0022, \u0022publish\u0022, _list )\n\t\t\t{\n\t\t\t\tClicked = () =\u003E Dialog.AskString( change =\u003E Run( $\u0022Publish {ident}\u0022, () =\u003E LibraryTools.LibraryPublish( ident, change ) ),\n\t\t\t\t\t$\u0022Publish a new revision of {title}. Revision title:\u0022, \u0022Publish\u0022, \u0022Cancel\u0022, \u0022\u0022, \u0022Publish library\u0022 )\n\t\t\t} );\n\t\t\trow.Add( new Button( \u0022Remove\u0022, \u0022delete\u0022, _list )\n\t\t\t{\n\t\t\t\tClicked = () =\u003E Dialog.AskConfirm( () =\u003E { _output.Text = LibraryTools.LibraryUninstall( ident ); Refresh(); },\n\t\t\t\t\t$\u0022Remove {title} and delete its folder from Libraries/?\u0022, \u0022Remove library\u0022, \u0022Remove\u0022, \u0022Cancel\u0022 )\n\t\t\t} );\n\t\t}\n\t}\n\n\t// Runs an action, showing progress and the result (or the error) in the output line.\n\tasync void Run( string what, Func\u003CTask\u003Cstring\u003E\u003E action )\n\t{\n\t\t_output.Text = $\u0022{what}...\u0022;\n\t\ttry { _output.Text = await action(); }\n\t\tcatch ( Exception e ) { _output.Text = $\u0022{what} failed: {e.Message}\u0022; }\n\t}\n}\n"}]}