yhub

command module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 25, 2026 License: MIT Imports: 22 Imported by: 0

README

yhub

Manage all your Git repositories through a single, centralized repository

MotivationInstallationDocumentationContributingLicense

Motivation

Have you ever experienced one of the following situations?

  • You have Git repositories scattered throughout your filesystem, and it takes some time to find a specific one
  • You have more than one Git account configured on the same computer, and you had to define your own SSH key mapping logic in .gitconfig
  • You and your team share a set of repositories, but each member needs to manually configure each repository locally
  • You need to pass the repository paths to the AI ​​so it has context about which repository you are referring to
  • You simply want an easier way to organize your Git repositories and maintain that organization across different computers

If you identified with any of these points, yhub might be for you. It's a tool for centralizing the organization of your repositories into a single Git repository. Some advantages this can bring you:

  • Portability: Your repository organization is the same across different computers
  • AI Integration: Instead of saying "Given the repository in ~/code/repos/my-repo, do this", you say "Given my-repo, do this" and the AI ​​will know where to look for it
  • Team collaboration: Your team can centralize the organization of all your repositories across team members
  • Simplicity: You don't need to configure your .gitconfig file with SSH key mapping logic

Installation

Download the archive for your platform from the latest release, extract it, and place the yhub binary somewhere in your PATH

Alternatively, if you have Go installed:

go install github.com/willpinha/yhub@latest

Documentation

Get started

yhub revolves around a simple idea: a hub is a regular Git repository whose only job is to describe, in a single yhub.json file, which repositories you have, where they live, and how they should be cloned. The repositories themselves are cloned inside the hub, but only yhub.json is committed, so what you version (and share across computers) is the organization, not the code

Let's build one from scratch

1. Create the hub

mkdir hub && cd hub
git init

2. Describe your repositories in yhub.json

Create a yhub.json file at the root of the hub:

{
	"profiles": {
		"personal": {
			"user_name": "Jane Doe",
			"user_email": "jane.doe@example.com",
			"ssh_key": "~/.ssh/id_rsa"
		}
	},
	"default_profile": "personal",
	"default_platform": "github",
	"repositories": {
		"projects": [
			{
				"repository": "willpinha/yhub",
				"name": "yhub",
				"aliases": ["YH"]
			}
		]
	}
}

This file says: there is one repository, willpinha/yhub, hosted on GitHub, that should be cloned into projects/yhub using the SSH key and Git identity of the personal profile. It can be referred to by its name (yhub) or its alias (YH). Every field is explained in detail in Configuration files

3. Ignore the cloned repositories

The repositories will be cloned inside the hub, but their content must not be committed to it. Add each configured directory to the hub's .gitignore:

projects/

4. Clone

All yhub commands run from the root of the hub (the directory that contains yhub.json):

yhub clone yhub    # by name
yhub clone YH      # or by alias
yhub clone --all   # or everything at once

The repository is now at projects/yhub, already configured with the right SSH key and Git identity, with no .gitconfig tricks needed

5. Use it

yhub list                # see everything that is cloned, as JSON
yhub in yhub git status  # run a command inside the repository

6. Commit the hub

git add yhub.json .gitignore
git commit -m "My repository organization"

Push the hub to your Git platform of choice. On any other computer, clone the hub and run yhub clone --all: your entire organization is reproduced

Configuration files

The yhub.json file

The yhub.json file lives at the root of the hub and is the single source of truth for your organization. All yhub commands must be run from the directory that contains it

It has five top-level keys:

{
	"profiles": {},
	"default_profile": "",
	"platforms": {},
	"default_platform": "",
	"repositories": {}
}

A complete example, used throughout this documentation:

{
	"profiles": {
		"personal": {
			"user_name": "Jane Doe",
			"user_email": "jane.doe@example.com",
			"ssh_key": "~/.ssh/id_rsa"
		},
		"work": {
			"user_name": "Jane Doe",
			"user_email": "jane.doe@acme.com",
			"ssh_key": "~/.ssh/id_rsa_work"
		}
	},
	"default_profile": "personal",
	"platforms": {
		"company-gitlab": {
			"host": "gitlab.company.com"
		}
	},
	"default_platform": "github",
	"repositories": {
		"work/dynamic-routing": [
			{
				"repository": "company/active-task-pool",
				"name": "active-task-pool",
				"aliases": ["ATP"],
				"platform": "company-gitlab",
				"profile": "work"
			},
			{
				"repository": "company/task-pool",
				"name": "task-pool",
				"aliases": ["TP"],
				"platform": "company-gitlab",
				"profile": "work"
			}
		],
		"personal/static-routing": [
			{
				"repository": "willpinha/plan-assignment",
				"name": "plan-assignment",
				"aliases": ["PA"]
			}
		]
	}
}
Profiles

A profile bundles the Git identity and the SSH key used to clone and work on a repository. At least one profile is required

"profiles": {
	"work": {
		"user_name": "Jane Doe",
		"user_email": "jane.doe@acme.com",
		"ssh_key": "~/.ssh/id_rsa_work"
	}
}
  • user_name and user_email (required, email must be valid): written to the repository's local Git config (user.name and user.email) at clone time, so commits in each repository are attributed to the right identity without touching your global .gitconfig
  • ssh_key (required): path to the private SSH key used for this profile. ~ is expanded to your home directory, and the key must exist at clone time. It is persisted in the repository's local Git config as core.sshCommand, so every later Git operation (fetch, pull, push) automatically uses the right key

default_profile (optional) names the profile used by repositories that don't set their own "profile". If set, it must reference an existing profile. A repository without a profile requires a default_profile

This is what replaces manual SSH key mapping logic: with two profiles (say, personal and work), each repository simply declares which identity it belongs to

Repositories

The repositories key maps directories inside the hub to lists of repositories cloned in them:

"repositories": {
	"work/dynamic-routing": [
		{
			"repository": "company/task-pool",
			"name": "task-pool",
			"aliases": ["TP"],
			"platform": "company-gitlab",
			"profile": "work"
		}
	]
}

Directory keys must be clean relative paths inside the hub (work/backend is valid; absolute paths, .., ./x, or trailing slashes are not), and each directory must contain at least one repository. Directories are a free-form hierarchy: organize by context (work, personal), by team, by technology, or anything else

Each repository has the following fields:

  • repository (required): the repository path on the platform, in the <owner>/<name> format. More than two segments are allowed for platforms with nested groups (e.g. group/subgroup/project on GitLab). Each repository can only be configured once
  • name (required): the directory name the repository is cloned into. The final destination is <directory>/<name>: in the example above, work/dynamic-routing/task-pool
  • aliases (optional): shorthand identifiers for the repository, useful for short CLI invocations and AI prompts
  • platform (optional if default_platform is set): the platform the repository is hosted on
  • profile (optional if default_profile is set): the profile used to clone and work on it

Names and aliases share a single, global namespace: every name and every alias must be unique across the entire file. They are the identifiers accepted by clone, unclone, and in, and matched by search, so yhub clone TP and yhub clone task-pool are equivalent

Finally, a configured directory cannot be located inside another repository's clone destination (e.g. you cannot configure the directory work/api/docs if some repository is cloned at work/api)

Platforms

A platform is a Git host repositories are cloned from. Three platforms are built-in and always available:

Name Host
github github.com
gitlab gitlab.com
bitbucket bitbucket.org

You can add your own (e.g. a self-hosted instance) under the platforms key:

"platforms": {
	"company-gitlab": {
		"host": "gitlab.company.com"
	}
}

The host (required) is used to build the SSH clone URL: git@<host>:<owner>/<name>.git. Defining a platform with the same name as a built-in one overrides it

default_platform (optional) names the platform used by repositories that don't set their own "platform". If set, it must reference an existing platform. A repository without a platform requires a default_platform

The yhub.local.json file

The optional yhub.local.json file, placed next to yhub.json, holds machine-specific overrides. It has the same structure as yhub.json and is merged over it using JSON Merge Patch semantics:

  • Objects are merged recursively, so you can override a single field without repeating the rest
  • A null value removes the key it is assigned to
  • Arrays and scalar values are replaced entirely (e.g. an aliases list replaces the original, it is not appended to)

For example, to use a different SSH key on the current machine without touching the shared configuration:

{
	"profiles": {
		"work": {
			"ssh_key": "~/.ssh/id_ed25519_company"
		}
	}
}

Validation runs on the merged result, so the combination of both files must form a valid configuration

Since it is machine-specific, yhub.local.json should not be committed: add it to the hub's .gitignore. This is the key ingredient for team collaboration

Commands

All commands must run from the root of the hub, where yhub.json is located. Run yhub --help or yhub <command> --help at any time for a summary, and yhub --version to print the installed version

yhub clone
yhub clone <repository>...
yhub clone --dir <directory>
yhub clone --all

Clones repositories using the SSH protocol. There are three mutually exclusive ways to select what to clone:

  • <repository>...: one or more names or aliases. Unknown identifiers produce a warning and are skipped; duplicates are cloned only once
  • --dir <directory>: every repository configured in the given directory or in any directory nested under it. Matching is by whole path segments, so --dir work includes work/dynamic-routing but not workshop
  • --all: every repository in the configuration

For each selected repository, yhub verifies that the profile's SSH key exists, builds the clone URL from the platform host (git@<host>:<owner>/<name>.git), and runs git clone into <directory>/<name>. Repositories that are already cloned are skipped with a warning

The clone persists three values in the repository's local Git config:

  • core.sshCommand, pointing at the profile's SSH key (with IdentitiesOnly=yes)
  • user.name and user.email, from the profile

This means every repository cloned through yhub is immediately ready to use: git pull, git push, and commits all use the right key and identity, with no global .gitconfig setup

The clone is interactive: progress, host key confirmations, and key passphrase prompts all pass through to your terminal. If some repositories fail to clone, yhub still processes the remaining ones and exits with an error at the end

yhub clone TP plan-assignment  # clone by alias and by name
yhub clone --dir work          # clone everything under work/
yhub clone --all               # clone everything
yhub unclone
yhub unclone <repository>...
yhub unclone --dir <directory>
yhub unclone --all

Removes the local directories of cloned repositories (the inverse of clone), with the same three selection modes (<repository>..., --dir, --all). Repositories that are not cloned are skipped with a warning

Before removing anything, yhub checks that you would not lose work. A repository is kept, with a warning explaining why, if it has:

  • Uncommitted changes: any modified, staged, or untracked files
  • Unpushed commits: commits on any local branch that are not on any remote

Pass --force to remove repositories even if they have unsaved work

yhub unclone TP            # refuses if TP has unsaved work
yhub unclone --force TP    # removes it regardless
yhub unclone --dir work    # remove everything cloned under work/

Because the configuration still describes the repository, an uncloned repository can always be restored later with yhub clone

yhub list
yhub list

Lists all repositories that are currently cloned, as a JSON array on stdout:

[
	{
		"repository": "company/task-pool",
		"name": "task-pool",
		"aliases": ["TP"],
		"platform": "company-gitlab",
		"profile": "work",
		"directory": "work/dynamic-routing",
		"path": "work/dynamic-routing/task-pool"
	}
]

The platform and profile fields are always resolved (defaults applied), and path is the repository's location relative to the hub root. If nothing is cloned, the output is []

Logs go to stderr and JSON to stdout, so the output can be piped directly into tools like jq:

yhub list | jq -r '.[].path'
yhub search <text>

Searches a free-form text for mentions of repository names and aliases, and prints the matching repositories as a JSON array in the same format as yhub list

An identifier is considered mentioned when it appears in the text not glued to other word characters (letters, digits, _, or -). Matching is case-sensitive. For example, with a repository named task-pool aliased TP:

yhub search "Given TP, run the tests"       # matches (alias mentioned)
yhub search "look at task-pool please"      # matches (name mentioned)
yhub search "look at task-pools please"     # no match (part of a larger word)
yhub search "tp is not the alias"           # no match (case-sensitive)

This command exists primarily for tooling and AI integration: it lets an agent discover which repositories a piece of text refers to, and where they live. See Integration with AI

yhub in
yhub in <repository> <command> [<args>...]

Runs a command inside the directory of a repository, identified by name or alias. The repository must be cloned

Everything after the repository identifier is passed to the command untouched (flags included), so commands with their own flags just work:

yhub in TP git status
yhub in TP go test -v ./...

The command runs with the repository directory as its working directory and inherits your terminal, so interactive commands work as expected. The command is executed directly, not through a shell. For pipes or command chaining, invoke a shell explicitly:

yhub in TP sh -c 'go build && ./bin/app'

If the command fails, yhub in fails with it

Integration with AI

AI agents work much better with short, stable identifiers than with filesystem paths. Since every repository in the hub has a globally unique name and aliases, and since search and list emit machine-readable JSON, connecting yhub to an agent is straightforward: the agent discovers which repositories a prompt refers to, and gets their exact paths inside the hub

For example, with Claude Code, a UserPromptSubmit hook can enrich every prompt automatically. In the hub's .claude/settings.json:

{
	"hooks": {
		"UserPromptSubmit": [
			{
				"hooks": [
					{
						"type": "command",
						"command": "yhub search \"$(jq -r .prompt)\""
					}
				]
			}
		]
	}
}

Claude Code sends the prompt as JSON on the hook's stdin; jq extracts the text, yhub search finds the repositories it mentions, and the hook output is injected into the model's context. Now, with the session opened at the hub root, a prompt like

Given TP, run the tests and fix any failure

automatically tells the agent that TP is company/task-pool, cloned at work/dynamic-routing/task-pool, with no paths needed in the prompt

Agents can also run yhub list to see everything that is cloned, and yhub in <repository> <command> to act on a specific repository

Team collaboration

A hub does not have to be personal. A team can share a single hub repository that describes every repository the team works on:

  1. Create a shared hub in your organization and commit a yhub.json describing the team's repositories, organized in directories that make sense for the team (per squad, per product, etc.)
  2. Each member clones the hub and creates a yhub.local.json with their machine-specific settings, typically the SSH key path and email:
{
	"profiles": {
		"work": {
			"user_email": "someone@company.com",
			"ssh_key": "~/.ssh/id_company"
		}
	}
}
  1. Each member clones what they need: yhub clone --all for everything, or yhub clone --dir squad-payments for just their area

Onboarding a new member becomes: clone the hub, write a small yhub.local.json, run one command. And when the team reorganizes repositories (new directories, new names, new repositories), it's a regular pull request on the hub: every member receives the new organization with a git pull

Contributing

Contributions are welcome! If you plan a larger change, open an issue first so the approach can be discussed. Before submitting a pull request:

  • Run the tests with go test ./...
  • Keep the code style consistent with the rest of the project

License

This project is licensed under the MIT License

Documentation

The Go Gopher

There is no documentation for this package.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL