gh-devlake is a GitHub CLI extension (gh extension) that automates the full lifecycle of Apache DevLake — deploy, configure, and monitor — from a single terminal. Built with Go + Cobra, output uses emoji and Unicode box-drawing (no ANSI color codes).
cmd/ # Cobra commands — all user-facing terminal output lives here
internal/
azure/ # Azure CLI wrapper + Bicep templates
devlake/ # REST API client, auto-discovery, state file management
docker/ # Docker CLI wrapper (build, compose up/down)
download/ # HTTP file downloads + GitHub release tag fetch
envfile/ # .devlake.env parser
gh/ # GitHub CLI wrapper (repo list, repo details)
prompt/ # Interactive terminal prompts (confirm, select, masked input)
repofile/ # CSV/TXT repo list parser
secrets/ # Cryptographic secret generation
token/ # PAT resolution chain (flag → envfile → env → prompt)
- Command constructors:
newDeployLocalCmd()returns*cobra.Command - Run functions:
runStatus,runDeployLocal—run<CommandName> - Plugin registry:
connection_types.go— ordered list of plugin definitions - State files:
.devlake-azure.json/.devlake-local.jsonpersist deployment info - Discovery chain: explicit
--url→ state file → well-known ports - Generic API helpers:
doPost[T],doGet[T],doPut[T],doPatch[T]ininternal/devlake/client.go
gh devlake
├── init # Interactive wizard (deploy + configure full)
├── deploy
│ ├── local # Docker Compose on this machine
│ └── azure # Azure Container Apps
├── configure
│ ├── full # connection + scope + project in one session
│ ├── connection # Manage plugin connections (CRUD)
│ │ ├── add # Create a new connection
│ │ ├── list # List all connections
│ │ ├── update # Update token/settings
│ │ ├── delete # Remove a connection
│ │ └── test # Test a saved connection
│ ├── scope # Manage scopes on connections
│ │ ├── add # Add repo/org scopes to a connection
│ │ ├── list # List scopes on a connection
│ │ └── delete # Remove a scope from a connection
│ └── project # Manage DevLake projects
│ ├── add # Create project + blueprint + trigger sync
│ ├── list # List all projects
│ └── delete # Delete a project
├── status # Health check + connection summary
└── cleanup # Tear down (local or Azure)
Plugins are defined via ConnectionDef structs in cmd/connection_types.go. Each entry declares the plugin slug, endpoint, rate limits, prompt labels, PAT resolution keys, and scope handler. To add a new DevOps tool, add a ConnectionDef to connectionRegistry — token resolution, org prompts, and connection creation all derive from these fields automatically. See the devlake-dev-integration skill for full details.
Key ConnectionDef field groups:
- Identity & endpoints:
Plugin,DisplayName,Endpoint,Available - Org/enterprise:
NeedsOrg,NeedsEnterprise,NeedsOrgOrEnt,OrgPrompt,EnterprisePrompt - Auth:
AuthMethod(default"AccessToken"; set"BasicAuth"for Jenkins/Bitbucket/Jira),NeedsUsername,UsernamePrompt,UsernameEnvVars,UsernameEnvFileKeys - Token/PAT:
TokenPrompt,EnvVarNames,EnvFileKeys,RequiredScopes,ScopeHint - Scope metadata:
ScopeIDField(JSON field name for scope IDs, e.g."githubId","id"),HasRepoScopes(true when scopes carry a FullName tracked as repos),ScopeFunc - Dynamic flag validation:
ConnectionFlags,ScopeFlags— slices ofFlagDefthat declare which plugin-specific flags apply. At runtime,warnIrrelevantFlags()warns when a user passes a flag that doesn't apply to the selected plugin, andprintContextualFlagHelp()shows applicable flags in interactive mode.
One plugin per invocation. Flag-based commands target a single --plugin. Interactive mode walks through plugins sequentially.
- Tool-agnostic: No hardcoded plugin names outside
connectionRegistry - Per-plugin resolution: Orchestrators resolve token, org, and enterprise independently for each plugin
- Declarative over imperative: Plugin behavior comes from
ConnectionDeffields, not switch/case branches - Interactive orchestrators:
initandconfigure fullare interactive-only; flag-driven automation uses individual commands
The gh-copilot plugin computes scope IDs as: enterprise + org → "enterprise/org", enterprise only → "enterprise", org only → "org". See copilotScopeID() in cmd/configure_scopes.go. The scope ID must match the plugin's listGhCopilotRemoteScopes logic exactly or blueprint references will break.
See .github/skills/devlake-dev-planning/SKILL.md for version plan, milestones, and design decisions. Project board: https://github.com/orgs/DevExpGbb/projects/21
The terminal IS the UI. See .github/instructions/terminal-output.instructions.md for the full formatting rules (auto-applied to cmd/**/*.go).
Quick reference — standard emoji vocabulary:
| Emoji | Meaning | Example |
|---|---|---|
| 🔍 | Discovery / search | Discovering DevLake instance... |
| 🔑 | Authentication / secrets | Resolving PAT... |
| 📡 | Connection | Creating GitHub connection... |
| 🏗️ | Building / creating | Creating DevLake project... |
| 🚀 | Deploy / launch | Deploying infrastructure... |
| 🐳 | Docker operations | Starting containers... |
| 📦 | Resources / packages | Creating Resource Group... |
| 📝 | Writing / scopes | Adding repository scopes... |
| ⏳ | Waiting / polling | Waiting for DevLake to be ready... |
| ✅ | Success | Inline after steps, completion banners |
| ❌ | Failure | Status ping failure |
| Warning (non-fatal) | Could not reach endpoint |
|
| 💾 | Save / persist | State saved to ... |
| 🧹 | Cleanup | Cleaning up resources... |
These repos provide essential context. Use MCP tools (mcp_github_get_file_contents, mcp_github_search_code) to read them.
| Repository | Purpose |
|---|---|
apache/incubator-devlake |
Official upstream — backend Go code, plugin framework, domain models, REST API routes |
DevExpGBB/incubator-devlake |
Fork with unreleased custom plugins (gh-copilot). Rolls up to apache upstream |
eldrick-test-org/devlake-demo |
Demo stack — docker-compose, simulation scripts, API payload examples |
- All command
RunEfunctions returnerror - Wrap errors:
fmt.Errorf("context: %w", err) - Non-fatal errors → print
⚠️warning and continue - State file write failures → warning, not fatal
- Package vars for flags:
camelCase(deployLocalDir,azureRG) - Command constructors:
newXxxCmd()returning*cobra.Command - Run functions:
runXxx(runStatus,runDeployLocal) - Packages: short lowercase (
devlake,azure,docker,prompt)
Standard Go convention — stdlib, then external, then internal, separated by blank lines:
import (
"fmt"
"os"
"github.com/spf13/cobra"
"github.com/DevExpGBB/gh-devlake/internal/devlake"
)- Global flags on
rootCmd.PersistentFlags() - Command-specific flags as package-level vars with
cmd.Flags().StringVar() - Required flags validated in
RunE(not viaMarkFlagRequired)
Each command group has a reference file in docs/. When adding a new command, create or update the matching docs/<command>.md and add a row to the Command Reference table in README.md.
- Unit tests:
*_test.goalongside source - Run:
go test ./... - Suffix generation test:
internal/azure/suffix_test.go
go build -o gh-devlake.exe . # Windows (always use .exe suffix)
go build -o gh-devlake . # Linux/macOS
gh extension install . # Install locally for testing
gh devlake init # Guided wizard (deploy + configure)
gh devlake configure full # Configure connections + scopes + project
gh devlake status # Health check and connection summaryWindows: Always build with
-o gh-devlake.exe. PowerShell resolves.exepreferentially — a stale.exewill shadow a freshly built binary without the extension.