Skip to content
103 changes: 94 additions & 9 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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) with REST API integration
- **registries.go**: Registry operations (list, config, add, update, registrator) with REST API integration
- **projects.go**: Project management using GraphQL API with user filtering
- **user.go**: User information retrieval using GraphQL API and REST API for listing users
- **tokens.go**: Token management operations (list) with REST API integration
Expand All @@ -31,20 +31,25 @@ The application follows a command-line interface pattern using the Cobra library
- Stores tokens securely in `~/.juliahub` with 0600 permissions

2. **API Integration**:
- **REST API**: Used for dataset operations (`/api/v1/datasets`, `/datasets/{uuid}/url/{version}`), registry operations (`/api/v1/ui/registries/descriptions`), token management (`/app/token/activelist`) and user management (`/app/config/features/manage`)
- **GraphQL API**: Used for projects and user info (`/v1/graphql`)
- **Headers**: All GraphQL requests require `X-Hasura-Role: jhuser` header
- **REST API**: Used for dataset operations (`/api/v1/datasets`, `/datasets/{uuid}/url/{version}`), registry operations (`/api/v1/registry/registries/descriptions`, `/api/v1/registry/config/registry/{name}`, `/api/v1/registry/config/registrator/{name}`), token management (`/app/token/activelist`), user management (`/app/config/features/manage`), and admin group management (`/app/config/groups`)
- **GraphQL API**: Used for projects, user info, user list (`public_users`), and group list (`/v1/graphql`)
- **Headers**: All GraphQL requests require `Authorization: Bearer <id_token>`, `X-Hasura-Role: jhuser`, and `X-Juliahub-Ensure-JS: true`
- **Authentication**: Uses ID tokens (`token.IDToken`) for API calls

3. **Command Structure**:
- `jh auth`: Authentication commands (login, refresh, status, env)
- `jh dataset`: Dataset operations (list, download, upload, status)
- `jh registry`: Registry operations (list with REST API, supports verbose mode)
- `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 project`: Project management (list with GraphQL, supports user filtering)
- `jh user`: User information (info with GraphQL)
- `jh admin`: Administrative commands (user management, token management)
- `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)
- `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 clone`: Git clone with JuliaHub authentication and project name resolution
- `jh push/fetch/pull`: Git operations with JuliaHub authentication
- `jh git-credential`: Git credential helper for seamless authentication
Expand Down Expand Up @@ -94,6 +99,46 @@ go run . dataset upload --new ./file.tar.gz
```bash
go run . registry list
go run . registry list --verbose
go run . registry config JuliaSimRegistry
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have unrelated changes here in the PR

Copy link
Contributor Author

@thelonewolf1603 thelonewolf1603 Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, it includes changes from the prev registry prs

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you remove those unrelated changes from this PR?

go run . registry config JuliaSimRegistry -s nightly.juliahub.dev

# Add a registry (JSON via stdin or --file)
echo '{
"name": "MyRegistry",
"license_detect": true,
"artifact": {"download": true},
"docs": {"download": true, "docgen_check_installable": false, "html_size_threshold_bytes": null},
"metadata": {"download": true},
"pkg": {"download": true, "static_analysis_runs": []},
"enabled": true, "display_apps": true, "owner": "", "sync_schedule": null,
"download_providers": [{
"type": "cacheserver", "host": "https://pkg.juliahub.com",
"credential_key": "JC Auth Token",
"server_type": "", "github_credential_type": "", "api_host": "", "url": "", "user_name": ""
}]
}' | go run . registry config add
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
Expand All @@ -102,8 +147,11 @@ go run . project list
go run . project list --user
go run . project list --user john
go run . user info
go run . user list
go run . group list
go run . admin user list
go run . admin user list --verbose
go run . admin group list
```

### Test token operations
Expand Down Expand Up @@ -285,7 +333,7 @@ jh run setup
## Development Notes

- All ID fields in GraphQL responses should be typed correctly (string for UUIDs, int64 for user IDs)
- GraphQL queries are embedded as strings (consider external .gql files for complex queries)
- GraphQL queries are embedded at compile time using `go:embed` from `.gql` files (`userinfo.gql`, `users.gql`, `groups.gql`, `projects.gql`)
- Error handling includes both HTTP and GraphQL error responses
- Token refresh is automatic via `ensureValidToken()`
- File uploads use multipart form data with proper content types
Expand All @@ -301,16 +349,53 @@ jh run setup
- Clone command supports `project` (without username) and defaults to the logged-in user's username
- Folder naming conflicts are resolved with automatic numbering (project-1, project-2, etc.)
- Credential helper follows Git protocol: responds only to JuliaHub URLs, ignores others
- `jh user list` uses GraphQL `public_users` query (via `users.gql`) and displays `<name> (<username>)` per line
- `jh group list` uses GraphQL groups query (via `groups.gql`) and displays one group name per line
- Admin user list command (`jh admin user list`) uses REST API endpoint `/app/config/features/manage` which requires appropriate permissions
- User list output is concise by default (Name and Email only); use `--verbose` flag for detailed information (UUID, groups, features)
- Admin group list command (`jh admin group list`) uses REST API endpoint `/app/config/groups` which requires appropriate permissions
- Admin user list output is compact by default (`<name> (<email>)`); use `--verbose` flag for detailed information (UUID, groups, features)
- Registry list output is concise by default (UUID and Name only); use `--verbose` flag for detailed information (owner, creation date, package count, description)
- Registry config command (`jh registry config <name>`) uses REST API endpoint `/api/v1/registry/config/registry/{name}` (GET) and prints the full JSON response
- 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 <path>` 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 <name>`) 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 <name>`) 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)
- Token dates are formatted in human-readable format and converted to local timezone (respects system timezone or TZ environment variable)
- Token expiration estimate indicator only shown when `expires_at_is_estimate` is true in API response

## Implementation Details

### Registry Operations (`registries.go`)

**Shared helpers:**

- **`apiGet(url, idToken)`**: shared GET helper used by `listRegistries` and `getRegistryConfig`; retries up to 3 times on network errors or 500s, returns `[]byte` body on success
- **`readRegistryPayload(filePath)`**: reads JSON from `filePath` or stdin; validates `name` and `download_providers` are present and non-empty; returns raw `map[string]interface{}` for direct API forwarding

**`jh registry list` / `jh registry config`:**

- Both use `apiGet` for the HTTP call
- `listRegistries` unmarshals into `[]Registry` and formats output; `--verbose` adds owner, date, package count, and description
- `getRegistryConfig` pretty-prints the raw JSON response

**`jh registry config add` / `jh registry config update`:**

- Both call `submitRegistry(server, payload, operation)` with `operation` set to `"creation"` or `"update"` for status messages
- `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 <name>` / `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:
Expand Down
63 changes: 58 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ A command-line interface for interacting with JuliaHub, a platform for Julia com

- **Authentication**: OAuth2 device flow authentication with JWT token handling
- **Dataset Management**: List, download, upload, and check status of datasets
- **Registry Management**: List and manage Julia package registries
- **Registry Management**: List, add, and update Julia package registries
- **Project Management**: List and filter projects using GraphQL API
- **Git Integration**: Clone, push, fetch, and pull with automatic JuliaHub authentication
- **Julia Integration**: Install Julia and run with JuliaHub package server configuration
- **User Management**: Display user information and view profile details
- **Administrative Commands**: Manage users, tokens, and system resources (requires admin permissions)
- **User Management**: Display user information, list users and groups
- **Administrative Commands**: Manage users, groups, tokens, and system resources (requires admin permissions)

## Installation

Expand Down Expand Up @@ -155,6 +155,12 @@ go build -o jh .
- `jh registry list` - List all package registries on JuliaHub
- Default: Shows only UUID and Name
- `jh registry list --verbose` - Show detailed registry information including owner, creation date, package count, and description
- `jh registry config <name>` - Show the full JSON configuration for a registry
- `jh registry config add` - Add a new registry (JSON payload via stdin or `--file`)
- `jh registry config update` - Update an existing registry (same JSON schema as add, same flags)
- `jh registry permission list <registry>` - List permissions for a registry
- `jh registry permission set <registry> --user|--group <name> --privilege download|register` - Add or update a permission
- `jh registry permission remove <registry> --user|--group <name>` - Remove a permission

### Project Management (`jh project`)

Expand Down Expand Up @@ -182,15 +188,23 @@ go build -o jh .

### User Information (`jh user`)

- `jh user info` - Show detailed user information
- `jh user info` - Show detailed information about the logged-in user
- `jh user list` - List all users (`<name> (<username>)` format, via GraphQL)

### Group Information (`jh group`)

- `jh group list` - List all groups (one per line, via GraphQL)

### Administrative Commands (`jh admin`)

#### User Management
- `jh admin user list` - List all users (requires appropriate permissions)
- Default: Shows only Name and Email
- Default: Shows `<name> (<email>)` per line
- `jh admin user list --verbose` - Show detailed user information including UUID, groups, and features

#### Group Management
- `jh admin group list` - List all groups via REST API (requires appropriate permissions)

#### Token Management
- `jh admin token list` - List all tokens (requires appropriate permissions)
- Default: Shows only Subject, Created By, and Expired status
Expand Down Expand Up @@ -245,6 +259,32 @@ jh registry list --verbose

# List registries on custom server
jh registry list -s yourinstall

# Show full configuration for a registry
jh registry config JuliaSimRegistry
jh registry config JuliaSimRegistry -s nightly.juliahub.dev

# Add a registry (JSON via stdin or --file)
echo '{
"name": "MyRegistry",
"license_detect": true,
"artifact": {"download": true},
"docs": {"download": true, "docgen_check_installable": false, "html_size_threshold_bytes": null},
"metadata": {"download": true},
"pkg": {"download": true, "static_analysis_runs": []},
"enabled": true, "display_apps": true, "owner": "", "sync_schedule": null,
"download_providers": [{
"type": "cacheserver", "host": "https://pkg.juliahub.com",
"credential_key": "JC Auth Token",
"server_type": "", "github_credential_type": "", "api_host": "", "url": "", "user_name": ""
}]
}' | jh registry config add

# Or use a file
jh registry config add --file registry.json

# Update an existing registry (same JSON schema, registry identified by "name" field)
jh registry config update --file registry.json
```

### Project Operations
Expand All @@ -260,6 +300,16 @@ jh project list --user
jh project list --user alice
```

### User and Group Operations

```bash
# List users (GraphQL)
jh user list

# List groups (GraphQL)
jh group list
```

### Administrative Operations

```bash
Expand All @@ -269,6 +319,9 @@ jh admin user list
# List users with detailed information
jh admin user list --verbose

# List all groups via REST (requires admin permissions)
jh admin group list

# List all tokens (requires admin permissions)
jh admin token list

Expand Down
12 changes: 12 additions & 0 deletions groups.gql
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
query Groups($name: String = "%", $limit: Int = 100) {
groups(where: { name: { _ilike: $name } }, limit: $limit) {
name
group_id
}
products {
name
display_name
id
compute_type_name
}
}
Loading