# **REPO-CRAFTER USER MANUAL**
<!--
User Manual for repo-crafter
Copyright (C) 2026 Dharrun Singh
SPDX-License-Identifier: CC-BY-4.0
This work is licensed under the Creative Commons Attribution 4.0 International License.
To view a copy of this license, visit https://creativecommons.org/licenses/by/4.0/.
-->
**Purpose**: Safe, interactive Git repository management for solo developers and small teams
---
## **📋 Table of Contents**
1. [Quick Start (5-Minute Setup)](#quick-start)
2. [Initial Configuration](#initial-configuration)
3. [Core Concepts](#core-concepts)
4. [Understanding Project Types](#understanding-project-types)
5. [Workflow Guides](#workflow-guides)
6. [Command-Line Options](#command-line-options)
7. [Gitignore Modes Explained](#gitignore-modes-explained)
8. [Multi-Platform Projects](#multi-platform-projects)
9. [Troubleshooting](#troubleshooting)
10. [Safety Features](#safety-features)
11. [Advanced Configuration](#advanced-configuration)
12. [Best Practices](#best-practices)
13. [File Locations Reference](#file-locations-reference)
---
## **1. Quick Start (5-Minute Setup)**
### **Step 1: Install Dependencies if they don't exist**
**Dependencies**: git, jq, yq, curl, openssh-client
**Intepreter**: BASH
#### Ubuntu/Debian
```bash
sudo apt update && sudo apt install -y git curl jq openssh-clientsudo dnf install -y git curl jq openssh-clientsbrew install git curl jqfor cmd in git curl jq ssh; do command -v "$cmd" && echo "✓ $cmd installed"; donedefine them in your packages (pkgs following nixpkg) in configuration.nix, if they don't exist, you can also define this script as an executable and assign an alias, even in home.nix (if you use home-manager).
For example, for those packages which don't exist
Good practice in configuration.nix
{config, pkgs, libs}
{
user.user.your-username = {
packages = with pkgs; [
jq
curl
git
yq-go
];
};
} Or in home.nix
(config, pkgs, libs)
{
home.packages = with pkgs [
jq
yq-go
git
curl
];
}make sure you follow nixpkgs (define it in flake if you use it flake).
For GitHub:
# Generate key (if you don't have one)
ssh-keygen -t ed25519 -C "your@email.com" -f ~/.ssh/id_ed25519_github
# Add to GitHub
cat ~/.ssh/id_ed25519_github.pub
# Copy output and paste at: https://github.com/settings/keys
# Click "New SSH Key"
# Test connection
ssh -T git@github.com
# Expected: "Hi username! You've successfully authenticated..."For GitLab:
# Generate key
ssh-keygen -t ed25519 -C "your@email.com" -f ~/.ssh/id_ed25519_gitlab
# Add to GitLab
cat ~/.ssh/id_ed25519_gitlab.pub
# Copy output and paste at: https://gitlab.com/-/profile/keys
# Test connection
ssh -T git@gitlab.com
# Expected: "Welcome to GitLab, @username!"GitHub Token:
- Go to https://github.com/settings/tokens
- Click "Generate new token (classic)"
- Scopes: Select
repo(full repository access) - Expiration: Choose 90 days or custom
- Click "Generate token"
- Copy the token immediately (starts with
ghp_)
GitLab Token:
- Go to https://gitlab.com/-/profile/personal_access_tokens
- Name:
repo-crafter - Expiration: Choose date
- Scopes: Select
api(full API access) - Click "Create personal access token"
- Copy the token immediately (starts with
glpat-)
define the token keys as session variables safely without exposure
Add to your shell profile (~/.bashrc, ~/.zshrc, or ~/.profile):
# GitHub token
export GITHUB_API_TOKEN="ghp_your_token_here"
# GitLab token
export GITLAB_API_TOKEN="glpat_your_token_here"Apply changes:
source ~/.bashrc # or ~/.zshrcCreate the config file:
mkdir -p ~/.config/repo-crafter
nano ~/.config/repo-crafter/platforms.confPaste this template and save:
[github]
enabled = true
api_base = https://api.github.com
ssh_host = github.com
repo_domain = github.com
token_var = GITHUB_API_TOKEN
auth_header = Bearer {token}
accept_header = application/vnd.github+json # Required for GitHub API
repo_delete_endpoint = /repos/{owner}/{repo}
authenticated_user_endpoint = /user
authenticated_user_key = .login # jq path to extract username
repo_check_endpoint = /repos/{owner}/{repo}
repo_check_method = GET
repo_check_success_key = id
repo_create_endpoint = /user/repos
repo_create_method = POST
repo_list_endpoint = /user/repos?per_page=100&sort=updated
repo_list_success_key = .[0].id
work_dir = github.com
owner_not_found_patterns = not found,does not exist
ssh_url_fields = ssh_url,clone_url
visibility_map = {"private":"private","public":"public"}
[gitlab]
enabled = true
api_base = https://gitlab.com/api/v4
ssh_host = gitlab.com
repo_domain = gitlab.com
token_var = GITLAB_API_TOKEN
auth_header = Bearer {token}
accept_header = application/json
repo_delete_endpoint = /projects/{owner}%2F{repo}
authenticated_user_endpoint = /user
authenticated_user_key = .username
repo_check_endpoint = /projects/{owner}%2F{repo}
repo_check_method = GET
repo_check_success_key = id
repo_create_endpoint = /projects
repo_create_method = POST
repo_list_endpoint = /projects?membership=true&per_page=100&order_by=updated_at
repo_list_success_key = .[0].id
work_dir = gitlab.com
owner_not_found_patterns = not found,does not exist
ssh_url_fields = ssh_url,http_url_to_repo
visibility_map = {"private":"private","public":"public"}
[multi]
work_dir = Multi-server# Place it in your PATH
cd ~/.local/bin
wget https://raw.githubusercontent.com/yourusername/repo-crafter/main/repo-crafter.sh
chmod +x repo-crafter.sh
# Add to PATH if not already there
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
source ~/.bashrcProjects ├── Local_Projects │ └── Remote_Projects └── Multi-server create directores similar to the tree Structure You can use the following commands for ease
# Create Projects directory
mkdir ~/Projects
cd ~/Projects
mkdir Local_Projects
mkdir Remote_Projects
cd Remote_Projects
mkdir Multi-server# Run configuration test
repo-crafter --testExpected output:
=== TESTING PLATFORM CONFIGURATION ===
Available platforms: github gitlab
Token status:
github: ✓ Token set
gitlab: ✓ Token set
Testing SSH connection to github.com... ✅ Connected
Testing SSH connection to gitlab.com... ✅ Connected
Testing platform selection...
Choose platform(s):
1) github
2) gitlab
a) All platforms
m) Multiple selection (e.g., 1,3,5)
x) Cancel
Enter your choice: x
No selection made
Press Enter to continue...
If you see ✓ and ✅, your setup is correct.
- Local-only: No remotes, isolated development
- Bound: Connected to one or more remotes, sync-capable
- Multi-platform: Bound to multiple platforms simultaneously
- Archived: Unbound from remotes, moved to
Local_Projects/
- Preview before execute: All destructive operations show exact command
- Confirmation required: Deletions need explicit
yinput - Type-to-confirm: Deleting both local and remote requires typing project name
- Directory isolation: Cannot operate in system directories
- Duplicate detection: Warns about similar names before creation
- Completely disconnected from remotes
- For prototypes, trade secrets, offline work
- No push/pull capability
- Created via "Create local-only project" option
- Bound to a platform like GitHub, Gittea, GitLab, Codeberg, etc
- Can push/pull
- Organized by platform name (e.g.,
github.com/,gitlab.com/)
- Pushed to multiple platforms simultaneously
- Creates
REMOTE_STATE.ymlmanifest with platform state - Perfect for mirroring to GitLab backup while using GitHub primary
Perfect for: Starting a new hardware/embedded project/a project from scratch with new Repository creation
repo-crafterMenu Navigation:
- Select
1) 🆕 Create New Project - Select
1) Create new local + remote repo (API) - Enter project name:
motor-controller - Select platform:
1(GitHub) ora(All platforms) - Select visibility:
1(Private) - Gitignore setup:
- First, choose pattern source:
- Generic patterns only (OS, editors, build artifacts)
- Fetch from GitHub gitignore templates
- Manual entry only (you add everything yourself)
- Enter additional custom patterns line-by-line
- Choose mode:
- Simple: Commit immediately (recommended)
- Cautious: Review in nano first
- Local-only: Move to
.git/info/exclude(never pushed)
- Confirm preview prompts with
y
Result: New repository created, pushed, ready for development at ~/Projects/Remote_Projects/github.com/motor-controller/
Perfect for: Joining an open-source project or team repo
repo-crafterMenu Navigation:
- Select
1) 🆕 Create New Project - Select
2) Clone existing remote repo - Select platform (e.g., GitHub)
- List shows your repositories for reference
- Enter repository path:
organization/project-nameorusername/repo-name - Repository clones to
~/Projects/Remote_Projects/github.com/project-name/
SSH Check: Script verifies SSH authentication before attempting clone to prevent errors.
Perfect for: Prototypes you don't want to push yet
repo-crafterMenu Navigation:
- Select
1) 🆕 Create New Project - Select
3) Create local-only project (no binding) - Enter project name:
secret-sensor-design - Work completely offline in
~/Projects/Local_Projects/secret-sensor-design/
When ready to share:
repo-crafter
# Select: 2) 🔗 Convert Local → Remote
# Choose: secret-sensor-design
# Select binding method: Create new repositories
# Add platform(s) and pushPerfect for: When you want to mirror your repository across multiple platforms (GitHub + GitLab).
Menu Navigation:
- Select
2) 🔗 Convert Local → Remote - Choose your project that's currently bound to a single platform
- Select option:
Convert single-platform to multi-platform - Enter additional repository URLs (one per line) for other platforms
- Example:
git@gitlab.com:username/project.git - Example:
git@github.com:username/project.git
- Example:
- Confirm conversion
Script performs:
- Moves project to
~/Projects/Remote_Projects/Multi-server/ - Adds new remotes for each platform URL
- Creates
REMOTE_STATE.ymlmanifest file - Renames existing "origin" remote to platform name (e.g., "github")
- Shows status of all configured remotes
Important notes:
- Your original single-platform remote is preserved with a new name
- All existing commits and history are maintained
- You can push to all platforms with
git push --all - The manifest file tracks state for future operations
Example output:
Remotes:
github git@github.com:username/project.git (fetch)
github git@github.com:username/project.git (push)
gitlab git@gitlab.com:username/project.git (fetch)
gitlab git@gitlab.com:username/project.git (push)
When to use: You have a project on one platform (e.g., GitHub) and want to add additional platforms (e.g., also on GitLab). Steps:
- Select the existing single-platform project
- Review current remote configuration
- Enter additional repository URLs (one per line)
- Confirm the conversion
- Automatic setup of multiple remotes What happens:
- Project moves from platform-specific directory to
Multi-server/folder - Existing remote renamed to platform name
- Additional remotes added for new platforms
- Multi-platform manifest created for tracking
Result: Project with multiple remotes for different platforms, stored in
Multi-server/directory structure.
: Connect to pre-existing repository.
Use case: You cloned manually before, or need to reconnect after issues.
repo-crafterMenu Navigation:
- Select
2) 🔗 Convert Local → Remote - Choose local project
- Select binding method:
2) Connect to EXISTING remote repository - Enter repository URL(s) (one per line, empty line to finish):
git@github.com:username/project.gitgit@gitlab.com:username/project.git
- Script parses URLs, detects platforms, verifies access
- Project moves to appropriate
REMOTE_ROOTsubdirectory - Remotes added and sync performed
Perfect for: archiving old work, before removing project from remote.
repo-crafterMenu Navigation:
- Select
3) 🔓 Convert Remote → Local - Choose project from list
- Confirm preview → Project moved to
Local_Projects/, ALL remotes removed REMOTE_STATE.ymldeleted if exists
Result: Keep all code locally, no remote sync, safe for personal backup
Perfect for: When you worked offline and the remote repository has new commits.
**Menu Navigation:**
1. Select `2) 🔗 Convert Local → Remote`
2. Choose your local project with divergence
3. Select binding method: `2) Connect to EXISTING remote repository`
4. Enter the remote URL
Script detects divergence and presents options:
You have 2 local commit(s) not pushed
Integration options:
1) Rebase (clean history)
2) Merge (safer for collaboration)
3) Skip - Push anyway (divergent branches)
x) Cancel
Option details:
- 1) Rebase: Places your local commits on top of remote commits (clean linear history). Best for personal projects.
- 2) Merge: Creates a merge commit that preserves both histories. Safest for team collaboration.
- 3) Skip: Creates intentionally divergent branches (advanced users only).
Automatic handling:
- Uncommitted changes are automatically stashed before operations
- Stashed changes are restored after successful operations
- On cancellation, stashed changes remain available (run
git stash popto restore)
Branch mismatch handling: If your local branch name doesn't match the remote's default branch, you'll be prompted:
Rename local branch to match remote? (Y/n)
Perfect for: Finding a project across platforms
repo-crafterMenu Navigation:
- Select
5) 📋 List Remote Repositories - Select platform(s):
a(All) or specific platform - Shows up to 100 repositories per platform with visibility and SSH URL
Perfect for: Adding ignore patterns to existing project
repo-crafterMenu Navigation:
- Select
6) ⚙️ Configure .gitignore - Enter project directory:
~/Projects/Remote_Projects/github.com/project/ - If
.gitignoreexists, choose:1) Review/edit existing2) Overwrite (dangerous)3) Delete (not recommended)
- Add common patterns automatically?
1) Yes - Enter additional patterns line-by-line
- Choose mode:
1) Simple: Commit immediately2) Cautious: Review in nano first3) Local-only: Move to.git/info/exclude(never pushed)
Perfect for: Cleaning up disk space
repo-crafterMenu Navigation:
- Select
4) 🗑️ Manage/Delete Project - Choose option:
1) Delete local copy(keeps remote repo)2) Unbind from remote(keeps local, moves toLocal_Projects/)3) Delete BOTH(TYPE PROJECT NAME TO CONFIRM)
- Select project
- Option 3 uses TWO-FACTOR CONFIRMATION:
- First, you must type the EXACT project name (case-sensitive)
- Then, you must type 'DELETE' to acknowledge permanent data loss
- This prevents accidental deletions from typos or muscle memory
⚠️ DANGER: This permanently deletes remote repository and ALL local files No recovery possible - this is an irreversible operation
# Verify configuration without launching menu
repo-crafter --test
# or
repo-crafter -tUse when: You changed tokens or SSH keys and want quick validation
# Preview all operations without executing
repo-crafter --dry-runUse when: Learning the tool, testing workflows, verifying multi-platform setup before execution What happens: All operations are PREVIEWED but NOT EXECUTED Each action is recorded in a list of planned operations At workflow completion, ALL accumulated actions are displayed together NO files created, NO API calls made, NO directories moved or deleted Final prompt shows: "To execute these actions for real, run without --dry-run" Critical safety feature: No partial execution - all actions are either previewed or fully executed
repo-crafter --help- Create
.gitignorefile - Add specified patterns
- Stage and commit immediately
- Pushable: Yes, shared with team
Best for: Standard development patterns (build artifacts, logs, dependencies)
- Create
.gitignorefile - Add specified patterns
- Open in nano editor for review/editing
- Stage and commit after manual approval
- Pushable: Yes, shared with team
Best for: Complex projects where ignore rules need careful review, or when learning gitignore syntax
- Create temporary
.gitignorefile - Add specified patterns
- Move to
.git/info/exclude - Never staged, never committed, never pushed
- Pushable: No, stays local only
Best for:
- Personal IDE files (
.vscode/settings.json) - Local experiment files
- Machine-specific paths
- API keys or secrets that should never be committed
- Primary + Backup: GitHub for development, GitLab for backup
- Open source + Private mirror: Public GitHub repo, private GitLab mirror
- Platform migration: Testing GitLab while still using GitHub
- Redundancy: Protect against platform outages
- During creation/binding, select multiple platforms (use
mfor multiple selection) - Script creates repositories on all selected platforms
- Adds separate remotes for each platform (
git remote -vshowsgithub,gitlab) - Generates
REMOTE_STATE.ymlmanifest with all platform URLs and sync status - Initial push goes to all platforms
- Manual sync: Use
git push githuborgit push gitlabindividually
└── Multi-server/
└── my-important-project/
├── .git/
├── REMOTE_STATE.yml # Manifest file
└── [project files]
# REPO-CRAFTER REMOTE STATE MANIFEST
# Generated: 2026-01-12T15:30:45+00:00
# Project: my-project
# Action: convert_single_to_multi
# ------------------------------------------------------------------
metadata:
manifest_version: "1.0"
created: "2026-01-12T15:30:45+00:00"
last_updated: "2026-01-12T15:30:45+00:00"
project_name: "my-project"
maintainer: "yourusername"
last_action: "convert_single_to_multi"
last_action_timestamp: "2026-01-12T15:30:45+00:00"
local_state:
primary_branch: "main"
head_commit: "a1b2c3d"
project_path: "/home/user/Projects/Remote_Projects/Multi-server/my-project"
platforms:
github:
remote_name: "github"
ssh_url: "git@github.com:yourusername/my-project.git"
api_config_snapshot:
repo_check_endpoint: "/repos/{owner}/{repo}"
repo_create_endpoint: "/user/repos"
branch_mapping:
main: "a1b2c3d"
last_sync_status: "created"
last_synced: "2026-01-12T15:30:45+00:00"
gitlab:
remote_name: "gitlab"
ssh_url: "git@gitlab.com:yourusername/my-project.git"
api_config_snapshot:
repo_check_endpoint: "/projects/{owner}%2F{repo}"
repo_create_endpoint: "/projects"
branch_mapping:
main: "a1b2c3d"
last_sync_status: "created"
last_synced: "2026-01-12T15:30:45+00:00"Important: This file is automatically generated and should not be edited manually. The manifest tracks:
- Platform URLs and API configuration
- Branch commit mappings
- Last synchronization status and timestamps
- Project metadata for state restoration
Cause: Environment variable not loaded or config mismatch
Solution:
# Check if variable exists
echo $GITHUB_API_TOKEN
# If empty, reload your profile
source ~/.bashrc
# Or set manually for current session
export GITHUB_API_TOKEN="ghp_xxxxxxxxxxxx"
# Verify config references correct variable name
grep "token_var" ~/.config/repo-crafter/platforms.confCause: SSH key not added to platform or wrong key
Solution:
# Test manually
ssh -T git@github.com
# If fails, check key exists
ls ~/.ssh/id_ed25519_github*
# If missing, regenerate
ssh-keygen -t ed25519 -C "your@email.com" -f ~/.ssh/id_ed25519_github
# Add to GitHub again
cat ~/.ssh/id_ed25519_github.pub
# Copy to https://github.com/settings/keysCause: You have another local project with same name in search directories
Solution:
- Use a different project name, OR
- Delete the old project first, OR
- Use
yto continue anyway (manage duplicates manually)
Cause: Token lacks permissions, owner not found, or you're not a collaborator
Solution:
- Regenerate token with
reposcope (GitHub) orapiscope (GitLab) - If organization repo, ensure you're a collaborator with push access
- Verify remote URL:
git remote -v - Check
owner_not_found_patternsin config matches platform error messages
Cause: Remote has commits you don't have locally
Solution:
- Use workflow 5 (Connect to existing) to trigger
sync_with_remote() - Choose Merge if you're not comfortable with rebasing
- For conflicts, script will pause—resolve manually with
git statusandgit add - If stashed, restore with
git stash popafter conflict resolution
Cause: Script not in PATH
Solution:
# Add to PATH permanently
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
# Test
which repo-crafterCause: DRY-RUN mode cannot catch all real-world issues (network, auth, edge cases)
Solution:
- Run
--testto verify SSH and tokens - Check
git statusin project directory - Verify remote URLs:
git remote -v - Review API error messages in output
- Cannot operate in:
/etc,/root,/bin,/sbin,/usr,/ - Directory checks: Warns if project with same name exists elsewhere
- Duplicate remote detection: Shows when multiple locals point to same remote
- Confirmation required: All destructive operations need explicit
yinput - Type-to-confirm: Deleting both local and remote requires typing project name exactly
- Use
--dry-runbefore unfamiliar workflows - Use
--testafter modifyingplatforms.confor tokens - Never share tokens; keep them in environment variables only
- For critical work: Maintain separate backup on different platform
- Review manifests: Check
REMOTE_STATE.ymlbefore deleting multi-platform projects
- SSH keys never leave your machine
- Tokens only stored in environment (not in config file)
- All API calls use HTTPS with proper authentication headers
- SSH preferred over HTTPS for repository operations
Edit ~/.config/repo-crafter/platforms.conf:
[codeberg]
enabled = true
api_base = https://codeberg.org/api/v1
ssh_host = codeberg.org
repo_domain = codeberg.org
token_var = CODEBERG_API_TOKEN
auth_header = Bearer {token}
repo_check_endpoint = /repos/{owner}/{repo}
repo_check_method = GET
repo_check_success_key = id
repo_create_endpoint = /user/repos
repo_create_method = POST
repo_list_endpoint = /user/repos
repo_list_success_key = .[0].id
work_dir = codeberg.org
owner_not_found_patterns = not found,does not exist
ssh_url_fields = ssh_url,clone_url
visibility_map = {"private":"private","public":"public"}Set token:
export CODEBERG_API_TOKEN="your_codeberg_token"Test:
repo-crafter --testSome platforms use different visibility terms. Use visibility_map to translate:
# Example for enterprise GitHub with "internal" visibility
[github-enterprise]
visibility_map = {"private":"private","public":"public","internal":"internal"}If platform API returns SSH URL under non-standard field names:
# Try ssh_url first, then fallback to custom field
ssh_url_fields = ssh_url,ssh_clone_url,git_ssh_urlSome APIs require specific Accept headers for proper response formatting:
# GitHub requires special media type
accept_header = application/vnd.github+json
# GitLab uses standard JSON
accept_header = application/jsonWhen to customize: Enterprise instances of GitHub/GitLab may require different headers. Most users can use the defaults shown above.
Different platforms use different API paths for repository deletion:
# GitHub standard format
repo_delete_endpoint = /repos/{owner}/{repo}
# GitLab requires URL-encoded owner/repo
repo_delete_endpoint = /projects/{owner}%2F{repo}Placeholders:
{owner}: Repository owner (URL-encoded if needed){repo}: Repository name
Note: Most platforms work with standard patterns. Enterprise instances may need custom paths.
- Start local: Use "Create local-only" for prototypes (workflow 3)
- Push when ready: Convert to remote when collaboration is needed (workflow 2)
- Use Mode 3 gitignore: For private keys, local experiments, personal IDE settings
- Multi-platform early: If you might mirror later, create as multi-server from start
- Clean up regularly: Use
--dry-runbefore deletion workflows - Test after changes: Run
--testafter updating tokens or SSH keys - Review manifests: Check
REMOTE_STATE.ymlin multi-platform projects before moving/deleting - Document decisions: Use Mode 2 (cautious) gitignore to add comments explaining patterns
| File | Purpose | Back Up? | Contains Secrets? |
|---|---|---|---|
~/.config/repo-crafter/platforms.conf |
Platform definitions | ✅ Yes | No (config only) |
~/.ssh/id_ed25519_github |
SSH private key | ✅ CRITICAL | ✅ YES |
~/.ssh/id_ed25519_gitlab |
SSH private key | ✅ CRITICAL | ✅ YES |
~/.bashrc or ~/.zshrc |
Token environment variables | ✅ Yes | ✅ YES |
~/Projects/ |
All your projects | ✅ CRITICAL | Maybe (code) |
REMOTE_STATE.yml |
Multi-platform manifest | ✅ Yes | No (URLs only) |
Security Checklist:
- SSH keys have
chmod 600permissions - Tokens are in environment variables, not in config files
-
platforms.confis not shared publicly -
.git/info/exclude(Mode 3) is never committed -
REMOTE_STATE.ymlcan be committed safely (no secrets)