Add a new nixpkgs-search CLI that combines NixOS options search with Nix package search functionality. This provides two MCP servers from a single binary: - `nixpkgs-search options serve` for NixOS options - `nixpkgs-search packages serve` for Nix packages Key changes: - Add packages table to database schema (version 3) - Add Package type and search methods to database layer - Create internal/packages/ with indexer and parser for nix-env JSON - Add MCP server mode (options/packages) with separate tool sets - Add package handlers: search_packages, get_package - Create cmd/nixpkgs-search with combined indexing support - Update flake.nix with nixpkgs-search package (now default) - Bump version to 0.2.0 The index command can index both options and packages together, or use --no-packages/--no-options flags for partial indexing. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
136 lines
4.2 KiB
Go
136 lines
4.2 KiB
Go
package database
|
|
|
|
// SchemaVersion is the current database schema version.
|
|
// When this changes, the database will be dropped and recreated.
|
|
const SchemaVersion = 3
|
|
|
|
// Common SQL statements shared between implementations.
|
|
const (
|
|
// SchemaInfoTable creates the schema version tracking table.
|
|
SchemaInfoTable = `
|
|
CREATE TABLE IF NOT EXISTS schema_info (
|
|
version INTEGER NOT NULL
|
|
)`
|
|
|
|
// RevisionsTable creates the revisions table.
|
|
RevisionsTable = `
|
|
CREATE TABLE IF NOT EXISTS revisions (
|
|
id INTEGER PRIMARY KEY,
|
|
git_hash TEXT NOT NULL UNIQUE,
|
|
channel_name TEXT,
|
|
commit_date TIMESTAMP,
|
|
indexed_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
option_count INTEGER NOT NULL DEFAULT 0,
|
|
package_count INTEGER NOT NULL DEFAULT 0
|
|
)`
|
|
|
|
// OptionsTable creates the options table.
|
|
OptionsTable = `
|
|
CREATE TABLE IF NOT EXISTS options (
|
|
id INTEGER PRIMARY KEY,
|
|
revision_id INTEGER NOT NULL REFERENCES revisions(id) ON DELETE CASCADE,
|
|
name TEXT NOT NULL,
|
|
parent_path TEXT NOT NULL,
|
|
type TEXT,
|
|
default_value TEXT,
|
|
example TEXT,
|
|
description TEXT,
|
|
read_only BOOLEAN NOT NULL DEFAULT FALSE
|
|
)`
|
|
|
|
// DeclarationsTable creates the declarations table.
|
|
DeclarationsTable = `
|
|
CREATE TABLE IF NOT EXISTS declarations (
|
|
id INTEGER PRIMARY KEY,
|
|
option_id INTEGER NOT NULL REFERENCES options(id) ON DELETE CASCADE,
|
|
file_path TEXT NOT NULL,
|
|
line INTEGER
|
|
)`
|
|
|
|
// FilesTable creates the files table.
|
|
FilesTable = `
|
|
CREATE TABLE IF NOT EXISTS files (
|
|
id INTEGER PRIMARY KEY,
|
|
revision_id INTEGER NOT NULL REFERENCES revisions(id) ON DELETE CASCADE,
|
|
file_path TEXT NOT NULL,
|
|
extension TEXT,
|
|
content TEXT NOT NULL,
|
|
byte_size INTEGER NOT NULL DEFAULT 0,
|
|
line_count INTEGER NOT NULL DEFAULT 0
|
|
)`
|
|
|
|
// PackagesTable creates the packages table.
|
|
PackagesTable = `
|
|
CREATE TABLE IF NOT EXISTS packages (
|
|
id INTEGER PRIMARY KEY,
|
|
revision_id INTEGER NOT NULL REFERENCES revisions(id) ON DELETE CASCADE,
|
|
attr_path TEXT NOT NULL,
|
|
pname TEXT NOT NULL,
|
|
version TEXT,
|
|
description TEXT,
|
|
long_description TEXT,
|
|
homepage TEXT,
|
|
license TEXT,
|
|
platforms TEXT,
|
|
maintainers TEXT,
|
|
broken BOOLEAN NOT NULL DEFAULT FALSE,
|
|
unfree BOOLEAN NOT NULL DEFAULT FALSE,
|
|
insecure BOOLEAN NOT NULL DEFAULT FALSE
|
|
)`
|
|
)
|
|
|
|
// Index creation statements.
|
|
const (
|
|
// IndexOptionsRevisionName creates an index on options(revision_id, name).
|
|
IndexOptionsRevisionName = `
|
|
CREATE INDEX IF NOT EXISTS idx_options_revision_name
|
|
ON options(revision_id, name)`
|
|
|
|
// IndexOptionsRevisionParent creates an index on options(revision_id, parent_path).
|
|
IndexOptionsRevisionParent = `
|
|
CREATE INDEX IF NOT EXISTS idx_options_revision_parent
|
|
ON options(revision_id, parent_path)`
|
|
|
|
// IndexFilesRevisionPath creates an index on files(revision_id, file_path).
|
|
IndexFilesRevisionPath = `
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_files_revision_path
|
|
ON files(revision_id, file_path)`
|
|
|
|
// IndexDeclarationsOption creates an index on declarations(option_id).
|
|
IndexDeclarationsOption = `
|
|
CREATE INDEX IF NOT EXISTS idx_declarations_option
|
|
ON declarations(option_id)`
|
|
|
|
// IndexPackagesRevisionAttr creates an index on packages(revision_id, attr_path).
|
|
IndexPackagesRevisionAttr = `
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_packages_revision_attr
|
|
ON packages(revision_id, attr_path)`
|
|
|
|
// IndexPackagesRevisionPname creates an index on packages(revision_id, pname).
|
|
IndexPackagesRevisionPname = `
|
|
CREATE INDEX IF NOT EXISTS idx_packages_revision_pname
|
|
ON packages(revision_id, pname)`
|
|
)
|
|
|
|
// Drop statements for schema recreation.
|
|
const (
|
|
DropSchemaInfo = `DROP TABLE IF EXISTS schema_info`
|
|
DropDeclarations = `DROP TABLE IF EXISTS declarations`
|
|
DropOptions = `DROP TABLE IF EXISTS options`
|
|
DropPackages = `DROP TABLE IF EXISTS packages`
|
|
DropFiles = `DROP TABLE IF EXISTS files`
|
|
DropRevisions = `DROP TABLE IF EXISTS revisions`
|
|
)
|
|
|
|
// ParentPath extracts the parent path from an option name.
|
|
// For example, "services.nginx.enable" returns "services.nginx".
|
|
// Top-level options return an empty string.
|
|
func ParentPath(name string) string {
|
|
for i := len(name) - 1; i >= 0; i-- {
|
|
if name[i] == '.' {
|
|
return name[:i]
|
|
}
|
|
}
|
|
return ""
|
|
}
|