diff --git a/CLAUDE.md b/CLAUDE.md index 0b749c4..fc7716b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -13,7 +13,7 @@ The application follows a command-line interface pattern using the Cobra library - **main.go**: Core CLI structure with command definitions and configuration management - **auth.go**: OAuth2 device flow authentication with JWT token handling - **datasets.go**: Dataset operations (list, download, upload, status) with REST API integration -- **registries.go**: Registry operations (list, config, add, update) with REST API integration +- **registries.go**: Registry operations (list, config, add, update, registrator) with REST API integration - **packages.go**: Package operations (search, dependency) with REST API primary path (`/packages/info`), GraphQL fallback, and documentation API (`/docs/{registry}/{package}/stable/pkg.json`) - **projects.go**: Project management using GraphQL API with user filtering - **user.go**: User information retrieval using GraphQL API and REST API for listing users @@ -44,12 +44,16 @@ The application follows a command-line interface pattern using the Cobra library - `jh dataset`: Dataset operations (list, download, upload, status) - `jh registry`: Registry operations (list, config — all via REST API) - `jh registry config`: Show registry JSON config by name; subcommands add/update accept JSON via stdin or `--file` + - `jh registry permission`: Registry permission management (list, set, remove) + - `jh registry registrator`: Show registrator config by name; subcommand update accepts JSON via stdin or `--file` - `jh package`: Package search and dependency (REST primary via `/packages/info`, GraphQL fallback; dependency data from `/docs/{registry}/{package}/stable/pkg.json`) - `jh project`: Project management (list with GraphQL, supports user filtering) - - `jh user`: User information (info with GraphQL) - - `jh admin`: Administrative commands (user management, token management, credential management, landing page) + - `jh user`: User information (info, list via GraphQL `public_users`) + - `jh group`: Group information (list via GraphQL) + - `jh admin`: Administrative commands (user management, token management, group management, credential management, landing page) - `jh admin user`: User management (list all users with REST API, supports verbose mode) - `jh admin token`: Token management (list all tokens with REST API, supports verbose mode) + - `jh admin group`: Group management (list all groups via REST API) - `jh admin credential`: Registry credential management (list, add, update, delete via REST API) - `jh admin credential list`: List all registry credentials (tokens, SSH keys, GitHub Apps); supports verbose mode - `jh admin credential add`: Add a credential — subcommands: `token`, `ssh`, `github-app`; accepts JSON argument or stdin @@ -141,6 +145,24 @@ go run . registry config add --file registry.json # Update an existing registry (same JSON schema, same flags) go run . registry config update --file registry.json + +# Show registrator config for a registry +go run . registry registrator MyRegistry + +# Update registrator config (JSON via stdin or --file) +echo '{ + "enabled": true, + "email": "pkg@example.com", + "authorization": true, + "ssl_verify": true, + "registry_fork_url": null, + "registry_deps": ["General"] +}' | go run . registry registrator update MyRegistry +go run . registry registrator update MyRegistry --file registrator.json + +# Get, edit, push back +go run . registry registrator MyRegistry > registrator.json +go run . registry registrator update MyRegistry --file registrator.json ``` ### Test project and user operations @@ -430,6 +452,10 @@ jh run setup - Registry add/update commands (`jh registry config add` / `jh registry config update`) use REST API endpoint `/api/v1/registry/config/registry/{name}` (POST); the backend creates or updates based on whether the registry already exists - Both commands accept the full registry JSON payload via `--file ` or stdin; the payload `name` field identifies the registry - Registry add/update always poll `/api/v1/registry/config/registry/{name}/savestatus` every 3 seconds up to a 2-minute timeout +- Registry registrator command (`jh registry registrator `) uses REST API endpoint `/api/v1/registry/config/registrator/{name}` (GET) and prints the full JSON response +- Registry registrator update command (`jh registry registrator update `) uses REST API endpoint `/api/v1/registry/config/registrator/{name}` (POST); the registry name comes from the positional argument (`RegistratorInfo` has no `name` field) +- Registrator update validates that `"email"` is non-empty when `"enabled"` is true +- GET returns 404 "Registry not found" when no registrator has been configured for that registry yet - Bundle provider type automatically sets `license_detect: false` in the payload - Admin token list command (`jh admin token list`) uses REST API endpoint `/app/token/activelist` which requires appropriate permissions - Token list output is concise by default (Subject, Created By, and Expired status only); use `--verbose` flag for detailed information (signature, creation date, expiration date with estimate indicator) @@ -476,6 +502,12 @@ jh run setup - `submitRegistry` POSTs to `/api/v1/registry/config/registry/{name}` with retry on 500s, then calls `pollRegistrySaveStatus()` - `pollRegistrySaveStatus` GETs `/api/v1/registry/config/registry/{name}/savestatus` every 3 seconds up to a 2-minute deadline +**`jh registry registrator ` / `jh registry registrator update`:** + +- `getRegistrator` uses `apiGet` to GET `/api/v1/registry/config/registrator/{name}` and pretty-prints the JSON response +- `setRegistrator(server, name, filePath)` reads `RegistratorInfo` JSON from `--file` or stdin, validates `"email"` is set when `"enabled"` is true, then POSTs to `/api/v1/registry/config/registrator/{name}` +- No polling — the POST response is the final result + ### Julia Credentials Management (`run.go`) The Julia credentials system consists of three main functions: diff --git a/main.go b/main.go index e86195e..0142571 100644 --- a/main.go +++ b/main.go @@ -1128,6 +1128,57 @@ Exactly one of --user or --group must be provided.`, }, } +var registryRegistratorCmd = &cobra.Command{ + Use: "registrator ", + Short: "Show the registrator configuration for a registry", + Example: " jh registry registrator MyRegistry\n jh registry registrator MyRegistry -s nightly.juliahub.dev", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + server, err := getServerFromFlagOrConfig(cmd) + if err != nil { + fmt.Printf("Failed to get server config: %v\n", err) + os.Exit(1) + } + if err := getRegistrator(server, args[0]); err != nil { + fmt.Printf("Failed to get registrator config: %v\n", err) + os.Exit(1) + } + }, +} + +var registryRegistratorUpdateCmd = &cobra.Command{ + Use: "update ", + Short: "Update the registrator configuration for a registry", + Long: `Update the registrator configuration for a Julia package registry. + +Reads the registrator configuration from a JSON file (--file) or stdin. + +REGISTRATOR JSON SCHEMA + + { + "enabled": true, // enable/disable registrator + "email": "", // required when enabled + "authorization": true, // allow only package authors to register + "ssl_verify": true, // verify SSL certificates + "registry_fork_url": "", // URL to a forked registry with write access (optional, null to unset) + "registry_deps": ["", ...] // registries whose packages may be dependencies + }`, + Example: " jh registry registrator update MyRegistry --file registrator.json\n jh registry registrator MyRegistry | jh registry registrator update MyRegistry", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + server, err := getServerFromFlagOrConfig(cmd) + if err != nil { + fmt.Printf("Failed to get server config: %v\n", err) + os.Exit(1) + } + filePath, _ := cmd.Flags().GetString("file") + if err := setRegistrator(server, args[0], filePath); err != nil { + fmt.Printf("Failed to update registrator config: %v\n", err) + os.Exit(1) + } + }, +} + var projectCmd = &cobra.Command{ Use: "project", Short: "Project management commands", @@ -2200,7 +2251,11 @@ func init() { registryPermissionRemoveCmd.Flags().String("user", "", "Username to remove permission for") registryPermissionRemoveCmd.Flags().String("group", "", "Group name to remove permission for") registryPermissionCmd.AddCommand(registryPermissionListCmd, registryPermissionSetCmd, registryPermissionRemoveCmd) - registryCmd.AddCommand(registryListCmd, registryConfigCmd, registryPermissionCmd) + registryRegistratorCmd.Flags().StringP("server", "s", "", "JuliaHub server") + registryRegistratorUpdateCmd.Flags().StringP("server", "s", "", "JuliaHub server") + registryRegistratorUpdateCmd.Flags().StringP("file", "f", "", "Path to JSON config file (reads from stdin if omitted)") + registryRegistratorCmd.AddCommand(registryRegistratorUpdateCmd) + registryCmd.AddCommand(registryListCmd, registryConfigCmd, registryPermissionCmd, registryRegistratorCmd) projectCmd.AddCommand(projectListCmd) userListGQLCmd.Flags().StringP("server", "s", "juliahub.com", "JuliaHub server") userCmd.AddCommand(userInfoCmd, userListGQLCmd)