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

@@ -16,6 +16,7 @@ import (
"time"
"git.t-juice.club/torjus/labmcp/internal/database"
"git.t-juice.club/torjus/labmcp/internal/options"
)
// revisionPattern validates revision strings to prevent injection attacks.
@@ -40,13 +41,8 @@ func NewIndexer(store database.Store) *Indexer {
}
// IndexResult contains the results of an indexing operation.
type IndexResult struct {
Revision *database.Revision
OptionCount int
FileCount int
Duration time.Duration
AlreadyIndexed bool // True if revision was already indexed (skipped)
}
// Deprecated: Use options.IndexResult instead.
type IndexResult = options.IndexResult
// ValidateRevision checks if a revision string is safe to use.
// Returns an error if the revision contains potentially dangerous characters.
@@ -305,7 +301,30 @@ func (idx *Indexer) getCommitDate(ctx context.Context, ref string) (time.Time, e
return commit.Commit.Committer.Date, nil
}
// resolveRevision resolves a channel name or ref to a git ref.
// ResolveRevision resolves a channel name or ref to a git ref.
func (idx *Indexer) ResolveRevision(revision string) string {
// Check if it's a known channel alias
if ref, ok := ChannelAliases[revision]; ok {
return ref
}
return revision
}
// GetChannelName returns the channel name if the revision matches one.
func (idx *Indexer) GetChannelName(revision string) string {
if _, ok := ChannelAliases[revision]; ok {
return revision
}
// Check if the revision is a channel ref value
for name, ref := range ChannelAliases {
if ref == revision {
return name
}
}
return ""
}
// resolveRevision is a helper that calls the method.
func resolveRevision(revision string) string {
// Check if it's a known channel alias
if ref, ok := ChannelAliases[revision]; ok {
@@ -314,7 +333,7 @@ func resolveRevision(revision string) string {
return revision
}
// getChannelName returns the channel name if the revision matches one.
// getChannelName is a helper that returns the channel name.
func getChannelName(revision string) string {
if _, ok := ChannelAliases[revision]; ok {
return revision