Skip to content

Commit fce7031

Browse files
resolved conflicts
2 parents 417c8ab + fadd2b0 commit fce7031

File tree

7 files changed

+237
-17
lines changed

7 files changed

+237
-17
lines changed

CLAUDE.md

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
44

55
## Project Overview
66

7-
This is a Go-based CLI tool for interacting with JuliaHub, a platform for Julia computing. The CLI provides commands for authentication, dataset management, project management, user information, Git integration, and Julia integration.
7+
This is a Go-based CLI tool for interacting with JuliaHub, a platform for Julia computing. The CLI provides commands for authentication, dataset management, registry management, project management, user information, Git integration, and Julia integration.
88

99
## Architecture
1010

@@ -13,6 +13,7 @@ The application follows a command-line interface pattern using the Cobra library
1313
- **main.go**: Core CLI structure with command definitions and configuration management
1414
- **auth.go**: OAuth2 device flow authentication with JWT token handling
1515
- **datasets.go**: Dataset operations (list, download, upload, status) with REST API integration
16+
- **registries.go**: Registry operations (list) with REST API integration
1617
- **projects.go**: Project management using GraphQL API with user filtering
1718
- **user.go**: User information retrieval using GraphQL API and REST API for listing users
1819
- **git.go**: Git integration (clone, push, fetch, pull) with JuliaHub authentication
@@ -29,14 +30,19 @@ The application follows a command-line interface pattern using the Cobra library
2930
- Stores tokens securely in `~/.juliahub` with 0600 permissions
3031

3132
2. **API Integration**:
33+
<<<<<<< HEAD
3234
- **REST API**: Used for dataset operations (`/api/v1/datasets`, `/datasets/{uuid}/url/{version}`) and user management (`/app/config/features/manage`)
35+
=======
36+
- **REST API**: Used for dataset operations (`/api/v1/datasets`, `/datasets/{uuid}/url/{version}`) and registry operations (`/api/v1/ui/registries/descriptions`)
37+
>>>>>>> fadd2b0ea19a8b11eb903d5884ffa50371e337e4
3338
- **GraphQL API**: Used for projects and user info (`/v1/graphql`)
3439
- **Headers**: All GraphQL requests require `X-Hasura-Role: jhuser` header
3540
- **Authentication**: Uses ID tokens (`token.IDToken`) for API calls
3641

3742
3. **Command Structure**:
3843
- `jh auth`: Authentication commands (login, refresh, status, env)
3944
- `jh dataset`: Dataset operations (list, download, upload, status)
45+
- `jh registry`: Registry operations (list with REST API, supports verbose mode)
4046
- `jh project`: Project management (list with GraphQL, supports user filtering)
4147
- `jh user`: User information (info with GraphQL)
4248
- `jh admin`: Administrative commands (user management)
@@ -86,6 +92,12 @@ go run . dataset download <dataset-name>
8692
go run . dataset upload --new ./file.tar.gz
8793
```
8894

95+
### Test registry operations
96+
```bash
97+
go run . registry list
98+
go run . registry list --verbose
99+
```
100+
89101
### Test project and user operations
90102
```bash
91103
go run . project list
@@ -98,7 +110,8 @@ go run . admin user list --verbose
98110

99111
### Test Git operations
100112
```bash
101-
go run . clone john/my-project
113+
go run . clone john/my-project # Clone from another user
114+
go run . clone my-project # Clone from logged-in user
102115
go run . push
103116
go run . fetch
104117
go run . pull
@@ -182,7 +195,8 @@ The application uses OAuth2 device flow:
182195
The CLI provides seamless Git integration with JuliaHub authentication through two approaches:
183196

184197
### Method 1: JuliaHub CLI Wrapper Commands
185-
- **Clone**: `jh clone username/project` - resolves project names to UUIDs and clones with authentication
198+
- **Clone**: `jh clone [username/]project` - resolves project names to UUIDs and clones with authentication
199+
- Format: `jh clone username/project` or `jh clone project` (defaults to logged-in user)
186200
- **Push/Fetch/Pull**: `jh push/fetch/pull [args...]` - wraps Git commands with authentication headers
187201
- **Authentication**: Uses `http.extraHeader="Authorization: Bearer <id_token>"` for Git operations
188202
- **Argument passthrough**: All Git arguments are passed through to underlying commands
@@ -229,7 +243,8 @@ The CLI provides Julia installation and execution with JuliaHub configuration:
229243
- Installs latest stable Julia version
230244

231245
### Julia Credentials
232-
- **Authentication file**: Automatically creates `~/.julia/servers/<server>/auth.toml`
246+
- **Authentication file**: Automatically creates `$JULIA_DEPOT_PATH/servers/<server>/auth.toml` (or `~/.julia/servers/<server>/auth.toml` if `JULIA_DEPOT_PATH` is not set)
247+
- **Depot path detection**: Respects `JULIA_DEPOT_PATH` environment variable, uses first path if multiple are specified
233248
- **Atomic writes**: Uses temporary file + rename for safe credential updates
234249
- **Automatic updates**: Credentials are automatically refreshed when:
235250
- User runs `jh auth login`
@@ -257,7 +272,7 @@ jh run -- --project=. --threads=4 script.jl # Run with flags
257272
```bash
258273
jh run setup
259274
```
260-
- Creates/updates `~/.julia/servers/<server>/auth.toml` with current credentials
275+
- Creates/updates `$JULIA_DEPOT_PATH/servers/<server>/auth.toml` with current credentials (or `~/.julia/servers/<server>/auth.toml` if not set)
261276
- Does not start Julia
262277
- Useful for explicitly updating credentials
263278

@@ -270,16 +285,19 @@ jh run setup
270285
- File uploads use multipart form data with proper content types
271286
- Julia auth files use TOML format with `preferred_username` from JWT claims
272287
- Julia auth files use atomic writes (temp file + rename) to prevent corruption
288+
- Julia credentials respect `JULIA_DEPOT_PATH` environment variable (uses first path if multiple are specified)
273289
- Julia credentials are automatically updated after login and token refresh
274290
- Git commands use `http.extraHeader` for authentication and pass through all arguments
275291
- Git credential helper provides seamless authentication for standard Git commands
276292
- Multi-server authentication handled automatically via credential helper
277293
- Project filtering supports `--user` parameter for showing specific user's projects or own projects
278294
- Clone command automatically resolves `username/project` format to project UUIDs
295+
- Clone command supports `project` (without username) and defaults to the logged-in user's username
279296
- Folder naming conflicts are resolved with automatic numbering (project-1, project-2, etc.)
280297
- Credential helper follows Git protocol: responds only to JuliaHub URLs, ignores others
281298
- Admin user list command (`jh admin user list`) uses REST API endpoint `/app/config/features/manage` which requires appropriate permissions
282299
- User list output is concise by default (Name and Email only); use `--verbose` flag for detailed information (UUID, groups, features)
300+
- Registry list output is concise by default (UUID and Name only); use `--verbose` flag for detailed information (owner, creation date, package count, description)
283301

284302
## Implementation Details
285303

@@ -288,7 +306,9 @@ jh run setup
288306
The Julia credentials system consists of three main functions:
289307

290308
1. **`createJuliaAuthFile(server, token)`**:
291-
- Creates `~/.julia/servers/<server>/auth.toml` with TOML-formatted credentials
309+
- Determines depot path from `JULIA_DEPOT_PATH` environment variable (uses first path if multiple)
310+
- Falls back to `~/.julia` if `JULIA_DEPOT_PATH` is not set
311+
- Creates `{depot}/servers/<server>/auth.toml` with TOML-formatted credentials
292312
- Uses atomic writes: writes to temporary file, syncs, then renames
293313
- Includes all necessary fields: tokens, expiration, refresh URL, user info
294314
- Called by `setupJuliaCredentials()` and `updateJuliaCredentialsIfNeeded()`
@@ -313,7 +333,8 @@ The Julia credentials system consists of three main functions:
313333

314334
The `updateJuliaCredentialsIfNeeded(server, token)` function:
315335
- Called automatically by `ensureValidToken()` after token refresh
316-
- Checks if `~/.julia/servers/<server>/auth.toml` exists
336+
- Determines depot path from `JULIA_DEPOT_PATH` (same logic as `createJuliaAuthFile`)
337+
- Checks if `{depot}/servers/<server>/auth.toml` exists
317338
- If exists, updates it with refreshed token
318339
- If not exists, does nothing (user hasn't used Julia integration yet)
319340
- Errors are silently ignored to avoid breaking token operations

README.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ A command-line interface for interacting with JuliaHub, a platform for Julia com
66

77
- **Authentication**: OAuth2 device flow authentication with JWT token handling
88
- **Dataset Management**: List, download, upload, and check status of datasets
9+
- **Registry Management**: List and manage Julia package registries
910
- **Project Management**: List and filter projects using GraphQL API
1011
- **Git Integration**: Clone, push, fetch, and pull with automatic JuliaHub authentication
1112
- **Julia Integration**: Install Julia and run with JuliaHub package server configuration
@@ -149,6 +150,12 @@ go build -o jh .
149150
- `jh dataset upload [dataset-id] <file-path>` - Upload a dataset
150151
- `jh dataset status <dataset-id> [version]` - Show dataset status
151152

153+
### Registry Management (`jh registry`)
154+
155+
- `jh registry list` - List all package registries on JuliaHub
156+
- Default: Shows only UUID and Name
157+
- `jh registry list --verbose` - Show detailed registry information including owner, creation date, package count, and description
158+
152159
### Project Management (`jh project`)
153160

154161
- `jh project list` - List all accessible projects
@@ -221,6 +228,19 @@ jh dataset upload --new ./my-data.tar.gz
221228
jh dataset upload my-dataset ./updated-data.tar.gz
222229
```
223230

231+
### Registry Operations
232+
233+
```bash
234+
# List all registries (UUID and Name only)
235+
jh registry list
236+
237+
# List registries with detailed information
238+
jh registry list --verbose
239+
240+
# List registries on custom server
241+
jh registry list -s yourinstall
242+
```
243+
224244
### Project Operations
225245

226246
```bash
@@ -277,7 +297,7 @@ jh run -- --project=. --threads=4 script.jl
277297
```
278298

279299
Note: Arguments after `--` are passed directly to Julia. The `jh run` command:
280-
1. Sets up JuliaHub credentials in `~/.julia/servers/<server>/auth.toml`
300+
1. Sets up JuliaHub credentials in `$JULIA_DEPOT_PATH/servers/<server>/auth.toml` (or `~/.julia/servers/<server>/auth.toml` if `JULIA_DEPOT_PATH` is not set)
281301
2. Configures `JULIA_PKG_SERVER` environment variable
282302
3. Starts Julia with your specified arguments
283303

auth.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -339,13 +339,14 @@ func ensureValidToken() (*StoredToken, error) {
339339
// updateJuliaCredentialsIfNeeded updates Julia credentials if the auth file exists
340340
// This is called after token refresh to keep credentials in sync
341341
func updateJuliaCredentialsIfNeeded(server string, token *StoredToken) error {
342-
homeDir, err := os.UserHomeDir()
342+
// Determine Julia depot path
343+
depotPath, err := getJuliaDepotPath()
343344
if err != nil {
344345
return err
345346
}
346347

347348
// Check if the auth.toml file exists
348-
authFilePath := filepath.Join(homeDir, ".julia", "servers", server, "auth.toml")
349+
authFilePath := filepath.Join(depotPath, "servers", server, "auth.toml")
349350
if _, err := os.Stat(authFilePath); os.IsNotExist(err) {
350351
// File doesn't exist, so user hasn't used Julia integration yet
351352
return nil

git.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,14 @@ func cloneProject(server, projectIdentifier, localPath string) error {
222222
username = parts[0]
223223
projectName = parts[1]
224224
} else {
225-
return fmt.Errorf("project identifier must be in format 'username/project'")
225+
// No username provided, use the logged-in user's username
226+
userInfo, err := getUserInfo(server)
227+
if err != nil {
228+
return fmt.Errorf("failed to get user info for default username: %w", err)
229+
}
230+
username = userInfo.Username
231+
projectName = projectIdentifier
232+
fmt.Printf("Using logged-in user '%s' as project owner\n", username)
226233
}
227234

228235
// Find the project by username and project name

main.go

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,9 @@ func getServerFromFlagOrConfig(cmd *cobra.Command) (string, error) {
143143
}
144144

145145
func normalizeServer(server string) string {
146+
if server == "juliahub" {
147+
return "juliahub.com"
148+
}
146149
if strings.HasSuffix(server, ".com") || strings.HasSuffix(server, ".dev") {
147150
return server
148151
}
@@ -161,6 +164,7 @@ job execution, project management, Git integration, and package hosting capabili
161164
Available command categories:
162165
auth - Authentication and token management
163166
dataset - Dataset operations (list, download, upload, status)
167+
registry - Registry management (list registries)
164168
project - Project management (list, filter by user)
165169
user - User information and profile
166170
admin - Administrative commands (user management)
@@ -551,6 +555,47 @@ Displays:
551555
},
552556
}
553557

558+
var registryCmd = &cobra.Command{
559+
Use: "registry",
560+
Short: "Registry management commands",
561+
Long: `Manage Julia package registries on JuliaHub.
562+
563+
Registries are collections of Julia packages that can be registered and
564+
installed. JuliaHub supports multiple registries including the General
565+
registry, custom organizational registries, and test registries.`,
566+
}
567+
568+
var registryListCmd = &cobra.Command{
569+
Use: "list",
570+
Short: "List registries",
571+
Long: `List all package registries on JuliaHub.
572+
573+
By default, displays only UUID and Name for each registry.
574+
Use --verbose flag to display comprehensive information including:
575+
- Registry UUID
576+
- Registry name and ID
577+
- Owner information
578+
- Creation date
579+
- Package count
580+
- Description
581+
- Registration status`,
582+
Example: " jh registry list\n jh registry list --verbose\n jh registry list -s custom-server.com",
583+
Run: func(cmd *cobra.Command, args []string) {
584+
server, err := getServerFromFlagOrConfig(cmd)
585+
if err != nil {
586+
fmt.Printf("Failed to get server config: %v\n", err)
587+
os.Exit(1)
588+
}
589+
590+
verbose, _ := cmd.Flags().GetBool("verbose")
591+
592+
if err := listRegistries(server, verbose); err != nil {
593+
fmt.Printf("Failed to list registries: %v\n", err)
594+
os.Exit(1)
595+
}
596+
},
597+
}
598+
554599
var projectCmd = &cobra.Command{
555600
Use: "project",
556601
Short: "Project management commands",
@@ -1035,6 +1080,8 @@ func init() {
10351080
datasetUploadCmd.Flags().StringP("server", "s", "juliahub.com", "JuliaHub server")
10361081
datasetUploadCmd.Flags().Bool("new", false, "Create a new dataset")
10371082
datasetStatusCmd.Flags().StringP("server", "s", "juliahub.com", "JuliaHub server")
1083+
registryListCmd.Flags().StringP("server", "s", "juliahub.com", "JuliaHub server")
1084+
registryListCmd.Flags().Bool("verbose", false, "Show detailed registry information")
10381085
projectListCmd.Flags().StringP("server", "s", "juliahub.com", "JuliaHub server")
10391086
projectListCmd.Flags().String("user", "", "Filter projects by user (leave empty to show only your own projects)")
10401087
userInfoCmd.Flags().StringP("server", "s", "juliahub.com", "JuliaHub server")
@@ -1049,6 +1096,7 @@ func init() {
10491096
authCmd.AddCommand(authLoginCmd, authRefreshCmd, authStatusCmd, authEnvCmd)
10501097
jobCmd.AddCommand(jobListCmd, jobStartCmd)
10511098
datasetCmd.AddCommand(datasetListCmd, datasetDownloadCmd, datasetUploadCmd, datasetStatusCmd)
1099+
registryCmd.AddCommand(registryListCmd)
10521100
projectCmd.AddCommand(projectListCmd)
10531101
userCmd.AddCommand(userInfoCmd)
10541102
adminUserCmd.AddCommand(userListCmd)
@@ -1057,7 +1105,7 @@ func init() {
10571105
runCmd.AddCommand(runSetupCmd)
10581106
gitCredentialCmd.AddCommand(gitCredentialHelperCmd, gitCredentialGetCmd, gitCredentialStoreCmd, gitCredentialEraseCmd, gitCredentialSetupCmd)
10591107

1060-
rootCmd.AddCommand(authCmd, jobCmd, datasetCmd, projectCmd, userCmd, adminCmd, juliaCmd, cloneCmd, pushCmd, fetchCmd, pullCmd, runCmd, gitCredentialCmd, updateCmd)
1108+
rootCmd.AddCommand(authCmd, jobCmd, datasetCmd, projectCmd, registryCmd, userCmd, adminCmd, juliaCmd, cloneCmd, pushCmd, fetchCmd, pullCmd, runCmd, gitCredentialCmd, updateCmd)
10611109
}
10621110

10631111
func main() {

0 commit comments

Comments
 (0)