feat: add hm-options package for Home Manager options

Add a new MCP server for Home Manager options, mirroring the
functionality of nixos-options but targeting the home-manager
repository.

Changes:
- Add shared options.Indexer interface for both implementations
- Add internal/homemanager package with indexer and channel aliases
- Add cmd/hm-options CLI entry point
- Parameterize MCP server with ServerConfig for name/instructions
- Parameterize nix/package.nix for building both packages
- Add hm-options package and NixOS module to flake.nix
- Add nix/hm-options-module.nix for systemd deployment

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-03 22:51:30 +01:00
parent 6b6be83e50
commit ea2d73d746
15 changed files with 1693 additions and 58 deletions

View File

@@ -9,11 +9,11 @@ import (
"time"
"git.t-juice.club/torjus/labmcp/internal/database"
"git.t-juice.club/torjus/labmcp/internal/nixos"
"git.t-juice.club/torjus/labmcp/internal/options"
)
// RegisterHandlers registers all tool handlers on the server.
func (s *Server) RegisterHandlers(indexer *nixos.Indexer) {
func (s *Server) RegisterHandlers(indexer options.Indexer) {
s.tools["search_options"] = s.handleSearchOptions
s.tools["get_option"] = s.handleGetOption
s.tools["get_file"] = s.handleGetFile
@@ -213,7 +213,7 @@ func (s *Server) handleGetFile(ctx context.Context, args map[string]interface{})
}
// makeIndexHandler creates the index_revision handler with the indexer.
func (s *Server) makeIndexHandler(indexer *nixos.Indexer) ToolHandler {
func (s *Server) makeIndexHandler(indexer options.Indexer) ToolHandler {
return func(ctx context.Context, args map[string]interface{}) (CallToolResult, error) {
revision, _ := args["revision"].(string)
if revision == "" {
@@ -252,7 +252,10 @@ func (s *Server) makeIndexHandler(indexer *nixos.Indexer) ToolHandler {
}
sb.WriteString(fmt.Sprintf("Options: %d\n", result.OptionCount))
sb.WriteString(fmt.Sprintf("Files: %d\n", fileCount))
sb.WriteString(fmt.Sprintf("Duration: %s\n", result.Duration.Round(time.Millisecond)))
// Handle Duration which may be time.Duration or interface{}
if dur, ok := result.Duration.(time.Duration); ok {
sb.WriteString(fmt.Sprintf("Duration: %s\n", dur.Round(time.Millisecond)))
}
return CallToolResult{
Content: []Content{TextContent(sb.String())},
@@ -316,8 +319,12 @@ func (s *Server) handleDeleteRevision(ctx context.Context, args map[string]inter
// resolveRevision resolves a revision string to a Revision object.
func (s *Server) resolveRevision(ctx context.Context, revision string) (*database.Revision, error) {
if revision == "" {
// Try to find a default revision
rev, err := s.store.GetRevisionByChannel(ctx, "nixos-stable")
// Try to find a default revision using config
defaultChannel := s.config.DefaultChannel
if defaultChannel == "" {
defaultChannel = "nixos-stable" // fallback for backwards compatibility
}
rev, err := s.store.GetRevisionByChannel(ctx, defaultChannel)
if err != nil {
return nil, err
}