@celsoft/coordinator 0.0.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/README.md +107 -0
- package/THIRD_PARTY_NOTICES.md +2005 -0
- package/agents/console.md +20 -0
- package/agents/free.md +110 -0
- package/build/0-JP6XL6LB.js +17 -0
- package/build/1-A52YZWNS.js +17 -0
- package/build/2-CLH2NM6F.js +32 -0
- package/build/3-4N467IB6.js +45 -0
- package/build/4-WL5H4LUW.js +29 -0
- package/build/_layout.svelte-KPUSX7LQ.js +27 -0
- package/build/_page.svelte-22H56UR6.js +7972 -0
- package/build/_page.svelte-CVWUNRQC.js +135 -0
- package/build/_page.svelte-LTZXBHMT.js +57 -0
- package/build/_server.ts-3KVBYN6P.js +20 -0
- package/build/_server.ts-3POPKVEM.js +47 -0
- package/build/_server.ts-5735T2V5.js +31 -0
- package/build/_server.ts-5VXGNWV2.js +38 -0
- package/build/_server.ts-645KP7WP.js +42 -0
- package/build/_server.ts-A3AA24Z2.js +28 -0
- package/build/_server.ts-CITUOEM6.js +58 -0
- package/build/_server.ts-I7HMTPOA.js +25 -0
- package/build/_server.ts-M5BQ4LLX.js +30 -0
- package/build/_server.ts-OB4O27HF.js +683 -0
- package/build/_server.ts-OJT7HE7M.js +15 -0
- package/build/_server.ts-OYGWVLZM.js +12 -0
- package/build/_server.ts-T75UVMQP.js +38 -0
- package/build/_server.ts-WYZYNHUZ.js +28 -0
- package/build/_server.ts-Z26EB47B.js +486 -0
- package/build/chunk-4CIG53L5.js +33 -0
- package/build/chunk-BJTJS3XY.js +338 -0
- package/build/chunk-F2CNWJZM.js +14 -0
- package/build/chunk-HGYE3UTU.js +416 -0
- package/build/chunk-KAYW2W7H.js +1311 -0
- package/build/chunk-LFP3UPCM.js +10 -0
- package/build/chunk-M44JEFEH.js +62 -0
- package/build/chunk-MC5AGLMY.js +39 -0
- package/build/chunk-NCZ77J5E.js +19798 -0
- package/build/chunk-NVNLDPUJ.js +2122 -0
- package/build/chunk-PDCON4KS.js +494 -0
- package/build/chunk-Q4OQNY5M.js +25 -0
- package/build/chunk-QCUOR2U5.js +75 -0
- package/build/chunk-R6RYZZWA.js +46 -0
- package/build/chunk-SOJTASZC.js +51 -0
- package/build/chunk-SU26PJVX.js +410 -0
- package/build/chunk-U5BSBG4R.js +506 -0
- package/build/chunk-UEKOZGRO.js +37 -0
- package/build/chunk-W6EFUWJ2.js +9 -0
- package/build/chunk-WOPREK6Q.js +117 -0
- package/build/chunk-WWJ3F4QS.js +127 -0
- package/build/chunk-ZGZJEEE3.js +1091 -0
- package/build/client/_app/immutable/assets/0.BGa5ZNDM.css +1 -0
- package/build/client/_app/immutable/chunks/BUrrLYDt.js +2 -0
- package/build/client/_app/immutable/chunks/BgFr_EyZ.js +4 -0
- package/build/client/_app/immutable/chunks/Bjy-W4x2.js +82 -0
- package/build/client/_app/immutable/chunks/C8qZab0y.js +56 -0
- package/build/client/_app/immutable/chunks/C90fLsA5.js +4 -0
- package/build/client/_app/immutable/chunks/CRzLMJPS.js +1 -0
- package/build/client/_app/immutable/chunks/DLT1iIlz.js +2 -0
- package/build/client/_app/immutable/chunks/pIrVVOVY.js +2 -0
- package/build/client/_app/immutable/entry/app.0DERehLW.js +3 -0
- package/build/client/_app/immutable/entry/payload.DSmR2FwN.js +2 -0
- package/build/client/_app/immutable/entry/start.B7srFiqh.js +3 -0
- package/build/client/_app/immutable/nodes/0.aD9vJoxR.js +2 -0
- package/build/client/_app/immutable/nodes/1.Br4FL91i.js +2 -0
- package/build/client/_app/immutable/nodes/2.vl6OYfu3.js +8 -0
- package/build/client/_app/immutable/nodes/3.VYqJOLAF.js +2 -0
- package/build/client/_app/immutable/nodes/4.jF9UVlFN.js +2 -0
- package/build/client/_app/version.json +1 -0
- package/build/client/apple-touch-icon.png +0 -0
- package/build/client/favicon.ico +0 -0
- package/build/client/favicon.svg +48 -0
- package/build/client/icon-192.png +0 -0
- package/build/client/icon-512.png +0 -0
- package/build/client/icon-maskable-512.png +0 -0
- package/build/client/icon.svg +44 -0
- package/build/client/manifest.webmanifest +15 -0
- package/build/error.svelte-JXZDY65F.js +51 -0
- package/build/handler.js +716 -0
- package/build/hooks.server-34IMHRKC.js +43 -0
- package/build/instance-NP3FJIWW.js +2967 -0
- package/client/client.mjs +28 -0
- package/client/hooks.mjs +7 -0
- package/client/register.mjs +5 -0
- package/dist/cli.js +62826 -0
- package/dist/port.js +36691 -0
- package/package.json +20 -1
- package/skills/agents/.claude-plugin/plugin.json +5 -0
- package/skills/agents/hooks/hooks.json +1 -0
- package/skills/agents/hooks/register.ts +57 -0
- package/skills/agents/skills/bus-work/SKILL.md +66 -0
- package/skills/agents/skills/plan/SKILL.md +98 -0
- package/skills/console/.claude-plugin/plugin.json +5 -0
- package/skills/console/hooks/hooks.json +1 -0
- package/skills/console/hooks/register.ts +10 -0
- package/skills/console/skills/bus/SKILL.md +61 -0
- package/skills/default/.claude-plugin/plugin.json +5 -0
- package/skills/default/skills/git-workflow/SKILL.md +294 -0
- package/skills/default/skills/git-workflow/mise-tasks/_lib.sh +107 -0
- package/skills/default/skills/git-workflow/mise-tasks/clone +36 -0
- package/skills/default/skills/git-workflow/mise-tasks/repo-list +8 -0
- package/skills/default/skills/git-workflow/mise-tasks/unpushed +26 -0
- package/skills/default/skills/git-workflow/mise-tasks/worktree-add +60 -0
- package/skills/default/skills/github/SKILL.md +208 -0
- package/skills/default/skills/github/scripts/gh-org +196 -0
- package/skills/default/skills/plan-page/SKILL.md +300 -0
- package/skills/default/skills/plan-page/assets/index.html +854 -0
- package/skills/default/skills/plan-page/scripts/inline +46 -0
- package/skills/default/skills/plan-page/scripts/stage +26 -0
- package/skills/default/skills/secrets/SKILL.md +134 -0
- package/skills/default/skills/secrets/scripts/secrets-check +87 -0
- package/skills/default/skills/writing-style/SKILL.md +45 -0
- package/skills/extra/.claude-plugin/plugin.json +5 -0
- package/skills/extra/skills/artifacts/SKILL.md +52 -0
- package/skills/extra/skills/github-actions/SKILL.md +78 -0
- package/skills/extra/skills/github-actions/scan +32 -0
- package/skills/extra/skills/markdown-review/SKILL.md +70 -0
- package/skills/extra/skills/markdown-review/assets/index.html +201 -0
- package/skills/extra/skills/markdown-review/scripts/stage +36 -0
- package/skills/extra/skills/mise-usage/SKILL.md +155 -0
- package/skills/extra/skills/plugin-authoring/SKILL.md +317 -0
- package/skills/extra/skills/project-conventions/SKILL.md +64 -0
- package/skills/extra/skills/questions/SKILL.md +156 -0
- package/skills/extra/skills/questions/assets/index.html +376 -0
- package/skills/extra/skills/questions/scripts/stage +26 -0
- package/skills/extra/skills/serve/SKILL.md +165 -0
- package/skills/extra/skills/serve/scripts/serve +268 -0
- package/skills/extra/skills/serve/scripts/server.py +345 -0
- package/skills/extra/skills/skill-dependencies/SKILL.md +65 -0
- package/skills/extra/skills/skill-maintenance/SKILL.md +101 -0
- package/skills/extra/skills/ticketing/SKILL.md +125 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/usr/bin/env zsh
|
|
2
|
+
# Shared helpers for the layout tasks. Not a task itself (leading underscore).
|
|
3
|
+
# DEV_ROOT and the two path helpers below are the sole definition of the repo/worktree
|
|
4
|
+
# layout. SKILL.md documents it; every other skill defers to it rather than hardcoding.
|
|
5
|
+
|
|
6
|
+
[[ -n "${MISE_ORIGINAL_CWD}" ]] && cd "$MISE_ORIGINAL_CWD"
|
|
7
|
+
|
|
8
|
+
header() { print -P "\n%B$*%b" >&2; }
|
|
9
|
+
success() { print -P "%F{green}✓%f $*" >&2; }
|
|
10
|
+
info() { print -P "%F{cyan}•%f $*" >&2; }
|
|
11
|
+
warn() { print -P "%F{yellow}!%f $*" >&2; }
|
|
12
|
+
error() { print -P "%F{red}✗%f $*" >&2; }
|
|
13
|
+
dim() { print -P "%F{8}$*%f" >&2; }
|
|
14
|
+
# Every task's only stdout is its final resolved path, printed with `path`, so a
|
|
15
|
+
# caller captures it with $(...) instead of re-deriving repo_path/worktree_path
|
|
16
|
+
# itself. Human-readable progress above goes to stderr and is unaffected.
|
|
17
|
+
path() { print "$*"; }
|
|
18
|
+
|
|
19
|
+
DEV_ROOT="${DEVELOPMENT_ROOT:-$HOME/Development}"
|
|
20
|
+
|
|
21
|
+
repo_path() { print "$DEV_ROOT/repos/$1/$2"; }
|
|
22
|
+
worktree_path() { print "$DEV_ROOT/worktrees/$3/$1/$2"; }
|
|
23
|
+
|
|
24
|
+
# Branches may contain slashes (e.g. env/sean/RKG-7734), which nest as real
|
|
25
|
+
# directories. That makes the branch/org boundary unrecoverable from the path,
|
|
26
|
+
# so anything reading a worktree back asks git rather than parsing.
|
|
27
|
+
branch_of() { git -C "$1" branch --show-current; }
|
|
28
|
+
|
|
29
|
+
# A caller hands over whatever checkout it happens to be sitting in, which may be
|
|
30
|
+
# a linked worktree. Its refs, remotes and worktree list are the main checkout's,
|
|
31
|
+
# so resolve to that one and treat the two identically.
|
|
32
|
+
main_checkout() {
|
|
33
|
+
local common
|
|
34
|
+
common=$(git -C "$1" rev-parse --path-format=absolute --git-common-dir 2>/dev/null) || return 1
|
|
35
|
+
print "${common:h}"
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
# org/repo comes from origin's URL, never from where the checkout sits on disk:
|
|
39
|
+
# that is what lets a clone outside repos/ (a tool's own source directory, say)
|
|
40
|
+
# still land its worktrees under its own org/repo with the rest of the set.
|
|
41
|
+
# Handles ssh aliases (git@github.com-acme:org/repo.git) and https alike.
|
|
42
|
+
repo_slug() {
|
|
43
|
+
local url remote_path org repo
|
|
44
|
+
url=$(git -C "$1" config --get remote.origin.url 2>/dev/null)
|
|
45
|
+
if [[ -n "$url" ]]; then
|
|
46
|
+
remote_path=${url%.git}
|
|
47
|
+
remote_path=${remote_path#*://}
|
|
48
|
+
remote_path=${remote_path#*@}
|
|
49
|
+
remote_path=${remote_path/:/\/}
|
|
50
|
+
else
|
|
51
|
+
# No remote to ask: the checkout and its parent stand in for repo and org.
|
|
52
|
+
remote_path=${1:A}
|
|
53
|
+
fi
|
|
54
|
+
repo=${remote_path:t}
|
|
55
|
+
org=${${remote_path:h}:t}
|
|
56
|
+
if [[ -z "$org" || -z "$repo" ]]; then
|
|
57
|
+
error "Cannot determine org/repo for $1"
|
|
58
|
+
dim " Set a remote: git -C $1 remote add origin git@github.com:<org>/<repo>.git"
|
|
59
|
+
return 1
|
|
60
|
+
fi
|
|
61
|
+
print "$org/$repo"
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
need_git() {
|
|
65
|
+
if ! command -v git &>/dev/null; then
|
|
66
|
+
error "'git' is not on PATH."
|
|
67
|
+
exit 1
|
|
68
|
+
fi
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
require_checkout() {
|
|
72
|
+
if ! git -C "$1" rev-parse --git-dir &>/dev/null; then
|
|
73
|
+
error "Not a git checkout: $1"
|
|
74
|
+
exit 1
|
|
75
|
+
fi
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# origin/HEAD is only populated by a full clone or an explicit set-head, so ask the
|
|
79
|
+
# remote before giving up. Never guess a name: branching a new worktree off the wrong
|
|
80
|
+
# base looks like it worked, so an undeterminable default is a hard error the caller
|
|
81
|
+
# resolves with the user.
|
|
82
|
+
# An explicit base. Prefer the remote ref: a stale local branch of the same name is the
|
|
83
|
+
# likelier mistake. Never invent one: an unresolvable base is a hard error, because
|
|
84
|
+
# branching off the wrong commit reports success just as loudly as branching off the right
|
|
85
|
+
# one.
|
|
86
|
+
resolve_base() {
|
|
87
|
+
local dir="$1" base="$2"
|
|
88
|
+
git -C "$dir" show-ref --verify --quiet "refs/remotes/origin/$base" \
|
|
89
|
+
&& { print "origin/$base"; return }
|
|
90
|
+
git -C "$dir" show-ref --verify --quiet "refs/heads/$base" \
|
|
91
|
+
&& { print "$base"; return }
|
|
92
|
+
error "Base branch '$base' does not exist locally or on origin in $dir"
|
|
93
|
+
dim " Check the name, or fetch first: git -C $dir fetch origin"
|
|
94
|
+
return 1
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
default_branch() {
|
|
98
|
+
local dir="$1" ref
|
|
99
|
+
ref=$(git -C "$dir" symbolic-ref --quiet refs/remotes/origin/HEAD 2>/dev/null) \
|
|
100
|
+
&& { print "${ref#refs/remotes/origin/}"; return }
|
|
101
|
+
git -C "$dir" remote set-head origin --auto >/dev/null 2>&1 \
|
|
102
|
+
&& ref=$(git -C "$dir" symbolic-ref --quiet refs/remotes/origin/HEAD 2>/dev/null) \
|
|
103
|
+
&& { print "${ref#refs/remotes/origin/}"; return }
|
|
104
|
+
error "Cannot determine the default branch of origin in $dir"
|
|
105
|
+
dim " Set it explicitly: git -C $dir remote set-head origin <branch>"
|
|
106
|
+
return 1
|
|
107
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env zsh
|
|
2
|
+
#MISE description="Clone <org>/<repo> into $DEVELOPMENT_ROOT/repos/<org>/<repo> (idempotent)"
|
|
3
|
+
#USAGE arg "<org>" help="GitHub org or user"
|
|
4
|
+
#USAGE arg "<repo>" help="Repository name"
|
|
5
|
+
|
|
6
|
+
source "${0:A:h}/_lib.sh"
|
|
7
|
+
|
|
8
|
+
org="${1:?usage: clone <org> <repo>}"
|
|
9
|
+
repo="${2:?usage: clone <org> <repo>}"
|
|
10
|
+
|
|
11
|
+
need_git
|
|
12
|
+
|
|
13
|
+
dest=$(repo_path "$org" "$repo")
|
|
14
|
+
|
|
15
|
+
if [[ -d "$dest/.git" ]]; then
|
|
16
|
+
success "Already cloned: $dest"
|
|
17
|
+
path "$dest"
|
|
18
|
+
exit 0
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
if [[ -e "$dest" ]]; then
|
|
22
|
+
error "$dest exists but is not a git clone."
|
|
23
|
+
exit 1
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
# SSH so any per-org keys and URL rewrites apply.
|
|
27
|
+
info "Cloning $org/$repo into $dest..."
|
|
28
|
+
mkdir -p "${dest:h}"
|
|
29
|
+
if ! git clone "git@github.com:$org/$repo.git" "$dest"; then
|
|
30
|
+
error "Clone failed for $org/$repo."
|
|
31
|
+
dim " If this is an auth or permission error, the SSH key in use is not one GitHub accepts for '$org'."
|
|
32
|
+
exit 1
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
success "Cloned to $dest"
|
|
36
|
+
path "$dest"
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env zsh
|
|
2
|
+
#MISE description="Print every commit a push would send, oldest first, with full messages"
|
|
3
|
+
|
|
4
|
+
source "${0:A:h}/_lib.sh"
|
|
5
|
+
|
|
6
|
+
need_git
|
|
7
|
+
|
|
8
|
+
default=$(git symbolic-ref --short refs/remotes/origin/HEAD 2>/dev/null)
|
|
9
|
+
git fetch --quiet origin 2>/dev/null || warn "Fetch failed; comparing against the last known remote state."
|
|
10
|
+
|
|
11
|
+
# A linked worktree pushes by fast-forwarding the default branch, so that is the target.
|
|
12
|
+
# A clone pushes its own branch: its upstream if set, else the default branch.
|
|
13
|
+
if [[ "$(git rev-parse --git-dir)" != "$(git rev-parse --git-common-dir)" ]]; then
|
|
14
|
+
base=$default
|
|
15
|
+
else
|
|
16
|
+
base=$(git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null) || base=$default
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
if [[ -z "$base" ]]; then
|
|
20
|
+
error "No upstream and no origin/HEAD to compare against."
|
|
21
|
+
exit 1
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
count=$(git rev-list --count "$base..HEAD")
|
|
25
|
+
info "$count unpushed commit(s) on $(git branch --show-current), compared with $base"
|
|
26
|
+
git log --reverse --format='%h %s%n%n%b' "$base..HEAD"
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env zsh
|
|
2
|
+
#MISE description="Add a worktree at $DEVELOPMENT_ROOT/worktrees/<branch>/<org>/<repo> (idempotent)"
|
|
3
|
+
#USAGE arg "<src>" help="Path to any checkout of the repo; a linked worktree is fine"
|
|
4
|
+
#USAGE arg "<branch>" help="Branch name; slashes nest as directories"
|
|
5
|
+
#USAGE arg "[base]" help="Branch to create from; defaults to the repo's default branch"
|
|
6
|
+
|
|
7
|
+
source "${0:A:h}/_lib.sh"
|
|
8
|
+
|
|
9
|
+
src="${usage_src:?usage: worktree-add <src> <branch> [base]}"
|
|
10
|
+
branch="${usage_branch:?usage: worktree-add <src> <branch> [base]}"
|
|
11
|
+
base="${usage_base:-}"
|
|
12
|
+
|
|
13
|
+
need_git
|
|
14
|
+
require_checkout "$src"
|
|
15
|
+
|
|
16
|
+
# The source is the only thing a caller has to know. Where the worktree goes
|
|
17
|
+
# follows from origin's org/repo, so a checkout anywhere on disk still lands its
|
|
18
|
+
# worktrees with the rest of that branch's set.
|
|
19
|
+
src=$(main_checkout "$src")
|
|
20
|
+
slug=$(repo_slug "$src") || exit 1
|
|
21
|
+
dest=$(worktree_path "${slug%/*}" "${slug#*/}" "$branch")
|
|
22
|
+
|
|
23
|
+
# git worktree add prints "HEAD is now at ..." on stdout, which would otherwise
|
|
24
|
+
# ride along with the path a caller captures. Every one of the three is >&2.
|
|
25
|
+
if [[ -d "$dest" ]]; then
|
|
26
|
+
success "Worktree already exists: $dest"
|
|
27
|
+
dim " On branch: $(branch_of "$dest")"
|
|
28
|
+
path "$dest"
|
|
29
|
+
exit 0
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
git -C "$src" fetch origin --prune --quiet 2>/dev/null
|
|
33
|
+
|
|
34
|
+
mkdir -p "${dest:h}"
|
|
35
|
+
|
|
36
|
+
# The branch existing already settles where it came from, so a base is moot. Say so
|
|
37
|
+
# rather than ignoring it: a caller that passed one believes it is taking effect.
|
|
38
|
+
if git -C "$src" show-ref --verify --quiet "refs/heads/$branch"; then
|
|
39
|
+
[[ -n "$base" ]] && warn "Ignoring base '$base': local branch '$branch' already exists."
|
|
40
|
+
info "Checking out existing local branch '$branch'..."
|
|
41
|
+
git -C "$src" worktree add "$dest" "$branch" >&2
|
|
42
|
+
elif git -C "$src" show-ref --verify --quiet "refs/remotes/origin/$branch"; then
|
|
43
|
+
[[ -n "$base" ]] && warn "Ignoring base '$base': remote branch 'origin/$branch' already exists."
|
|
44
|
+
info "Tracking existing remote branch 'origin/$branch'..."
|
|
45
|
+
git -C "$src" worktree add --track -b "$branch" "$dest" "origin/$branch" >&2
|
|
46
|
+
else
|
|
47
|
+
if [[ -n "$base" ]]; then
|
|
48
|
+
start=$(resolve_base "$src" "$base") || exit 1
|
|
49
|
+
else
|
|
50
|
+
default=$(default_branch "$src") || exit 1
|
|
51
|
+
start="origin/$default"
|
|
52
|
+
fi
|
|
53
|
+
# --no-track: branching off a remote-tracking ref otherwise sets that ref as upstream
|
|
54
|
+
# (branch.autoSetupMerge), so a later bare `git push` would target the base branch.
|
|
55
|
+
info "Creating new branch '$branch' from $start..."
|
|
56
|
+
git -C "$src" worktree add --no-track -b "$branch" "$dest" "$start" >&2
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
success "Worktree ready: $dest"
|
|
60
|
+
path "$dest"
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: github
|
|
3
|
+
verified: 2026-09-03
|
|
4
|
+
description: "MUST READ before running any `gh` command that touches a repo, PR, or issue: several GitHub accounts are logged in at once, and this routes each command to the account that has access to the target org. Also covers stacked pull requests, verifying GitHub features against the live API rather than memory, installing `gh` via Homebrew when it is missing, and adding an account when auth fails."
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# GitHub CLI (`gh`), org-aware
|
|
8
|
+
|
|
9
|
+
The machine has **more than one GitHub account logged into `gh`** (a personal one, one per company org). Only one is "active", and it is whichever was switched to last: so a bare `gh pr create` regularly runs as the wrong identity and fails with a confusing *"Could not resolve to a Repository"*.
|
|
10
|
+
|
|
11
|
+
The fix is per-command, not global: this skill's `scripts/gh-org` picks the account for the **target org** and injects that account's token as `GH_TOKEN` for one invocation. Nothing global is switched, so parallel sessions on different orgs don't fight.
|
|
12
|
+
|
|
13
|
+
This wires the **API** side (`gh`). The **SSH/git** side (push, clone) is separate: an SSH key GitHub accepts for the org. Having one set up says nothing about the other. `git-workflow` owns commit rules, per-repo identity, and the on-disk repo/worktree layout.
|
|
14
|
+
|
|
15
|
+
## The rule
|
|
16
|
+
|
|
17
|
+
Anything that touches a repo, PR, issue, run, or org goes through:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
<skill>/scripts/gh-org run [<org>] -- gh <args>...
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Omit `<org>` inside a git repo: it is parsed from the `origin` remote. Pass it explicitly when outside a repo or when targeting another org.
|
|
24
|
+
|
|
25
|
+
Bare `gh` is fine only for account-agnostic commands: `gh --version`, `gh auth status`, `gh auth login`.
|
|
26
|
+
|
|
27
|
+
**Never `gh repo clone`.** It writes into the current directory and ignores the on-disk layout. Cloning belongs to the `git-workflow` skill: read its **Repo layout** section and run its `clone` task, which places the repo correctly and uses SSH so the org keys apply.
|
|
28
|
+
|
|
29
|
+
Examples:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
scripts/gh-org run -- gh pr create --fill # org from the remote
|
|
33
|
+
scripts/gh-org run -- gh pr list --state open
|
|
34
|
+
scripts/gh-org run -- gh pr checks
|
|
35
|
+
scripts/gh-org run acme -- gh issue view 42 --repo acme/api --json title,body
|
|
36
|
+
scripts/gh-org run octo-user -- gh repo list --limit 20
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The script prints `gh-org: running as '<account>' for org '<org>'` to stderr: quote that account when reporting to the user, so they know which identity acted.
|
|
40
|
+
|
|
41
|
+
## GitHub ships faster than your knowledge
|
|
42
|
+
|
|
43
|
+
Your training cutoff is older than GitHub's release cadence, so a feature you don't remember is more likely new than nonexistent. Assume that by default.
|
|
44
|
+
|
|
45
|
+
**When the user names a GitHub capability, verify before building anything.** Stacked PRs, merge queues, rulesets, sub-issues, Projects, auto-merge: check whether it exists as a first-party feature *before* hand-rolling an equivalent:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
gh <thing> --help # is there a built-in command?
|
|
49
|
+
gh extension list # is it an extension you don't have?
|
|
50
|
+
scripts/gh-org run -- gh api repos/<org>/<repo>/pulls/<n> | jq 'keys' # does the object carry its fields?
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Then check https://github.blog/changelog for anything newer than you remember. If the feature exists and you were about to build a manual substitute, say so and let the user choose: don't quietly ship the substitute.
|
|
54
|
+
|
|
55
|
+
**A user correcting you on a matter of fact is a signal to go verify, not to argue.** If they say "that's a feature now" and you believe otherwise, check before replying. Producing more evidence for your existing answer is the failure mode.
|
|
56
|
+
|
|
57
|
+
## Never claim a first-party feature is in use without reading it back
|
|
58
|
+
|
|
59
|
+
Doing something that *resembles* a feature is not using it. Chaining base branches by hand is not a stack; a label named "merge queue" is not a merge queue.
|
|
60
|
+
|
|
61
|
+
Before telling the user a GitHub feature is active, query the object and show the field that proves it. If the field is absent, say the feature is not in use and name what you actually did instead. This applies to your own prior work in the session: re-check rather than trusting what you said earlier.
|
|
62
|
+
|
|
63
|
+
## Stacked pull requests
|
|
64
|
+
|
|
65
|
+
First-party since 2026-07-30 (public preview). A stack is real GitHub state, not a base-branch convention.
|
|
66
|
+
|
|
67
|
+
**Identifying one:** a stacked PR carries a `stack` object in the REST payload (stack number and size, plus the PR's position and base); GraphQL exposes read-only `stack` and `stackEntry` fields on `PullRequest`. No `stack` key means it is not a stack, whatever the base branch says:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
scripts/gh-org run -- gh api repos/<org>/<repo>/pulls/<n> --jq 'has("stack")'
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**Requirements:** `gh` 2.90.0+, git 2.20+, and the extension:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
gh extension install github/gh-stack
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Working with a stack:**
|
|
80
|
+
|
|
81
|
+
| Command | What it does |
|
|
82
|
+
|---|---|
|
|
83
|
+
| `gh stack init` | Creates the tracking entry and the first branch |
|
|
84
|
+
| `gh stack add <branch>` | Adds a branch to the stack |
|
|
85
|
+
| `gh stack add -Am "<message>"` | Stages, commits, and creates the next branch in one step |
|
|
86
|
+
| `gh stack push` | Pushes every branch in the stack |
|
|
87
|
+
| `gh stack submit` | Opens the PRs and links them as a stack on GitHub |
|
|
88
|
+
| `gh stack view` | Branches, PR links, statuses, and each branch's latest commit |
|
|
89
|
+
|
|
90
|
+
**Merging** is atomic across the stack (the whole group merges, or enters the merge queue, or none of it does) and requires the asynchronous merge endpoint rather than the legacy synchronous one.
|
|
91
|
+
|
|
92
|
+
**Rebasing** is handled for you: trigger a server-side cascading rebase from the PR, or run it locally through the extension. Don't hand-rebase each branch.
|
|
93
|
+
|
|
94
|
+
**Converting PRs that already exist needs no rebuild.** `gh stack link` takes PR numbers, PR URLs, or branch names in stack order (bottom to top) and links them into a real stack, keeping their numbers, review history, and draft state. Branches without a PR get one created with the right base chaining; PRs already in a stack are left alone.
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
GH_REPO=<org>/<repo> scripts/gh-org run <org> -- gh stack link <bottom-pr> <top-pr>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Two gotchas, both learned the hard way:
|
|
101
|
+
|
|
102
|
+
- **A custom SSH host alias breaks repo detection.** With a remote like `git@jll.github.com:org/repo.git`, `gh stack` fails with *"none of the git remotes ... point to a known GitHub host"*. There is no `--repo` flag; set `GH_REPO` instead, as above.
|
|
103
|
+
- **`--open` marks every PR in the stack ready for review**, drafts included. Omit it unless that is what you want.
|
|
104
|
+
|
|
105
|
+
`gh stack init <branch>...` likewise adopts existing branches rather than demanding new ones. So reach for `link` or `init` before ever proposing to rebuild a PR: the CLI's own `--help` is the authority here, and it documents more than the docs pages do.
|
|
106
|
+
|
|
107
|
+
## How the account is resolved
|
|
108
|
+
|
|
109
|
+
1. **The repo's own login:** inside a repo whose config sets `github.user` (a coordinator writes it, with the repo's identity and SSH key, into every repo it keeps), that login acts for the repo's own org, ahead of everything below.
|
|
110
|
+
2. **Cached mapping:** `git config --global gh-org.account.<org>` (field first, org lowercased; git keys are case-insensitive, so `Acme` and `acme` are one entry).
|
|
111
|
+
3. **Probe:** otherwise each logged-in account is tested: does its login equal `<org>` (personal namespace), or is it an active member of `<org>`? First match wins and is **cached** into the git config above.
|
|
112
|
+
4. **Fail:** if no account has access, the script stops and says to run `gh auth login`.
|
|
113
|
+
|
|
114
|
+
Manage mappings directly when the probe guesses wrong:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
scripts/gh-org list # show org -> account mappings
|
|
118
|
+
scripts/gh-org map <org> <account> # pin one
|
|
119
|
+
scripts/gh-org unmap <org> # forget it (re-probes next time)
|
|
120
|
+
scripts/gh-org account [<org>] # print the account that would be used
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
The `gh-org.*` namespace holds the per-account git identity too: see [Git identity setup](#git-identity-setup-one-time).
|
|
124
|
+
|
|
125
|
+
## A pending invite looks like access
|
|
126
|
+
|
|
127
|
+
When an account should have org access but repo operations fail, check the **membership
|
|
128
|
+
state** rather than inferring it from a successful login. An unaccepted org invitation
|
|
129
|
+
authenticates fine and then denies every repo, which reads as a mysterious permissions bug:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
scripts/gh-org run <org> -- gh api user/memberships/orgs/<org> --jq .state
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
- **`active`** access is real; the failure is something else.
|
|
136
|
+
- **`pending`** the invitation was never accepted. Accept it, then re-check.
|
|
137
|
+
- **the call fails** no logged-in account is a member. Add one (see below).
|
|
138
|
+
|
|
139
|
+
Once it reads `active`, pin the mapping so later work cannot pick a different account:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
scripts/gh-org map <org> <account>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Note that **API access and SSH access are independent**. A healthy `gh` login says nothing
|
|
146
|
+
about whether pushes work; that needs an SSH key GitHub accepts for each org you push to.
|
|
147
|
+
|
|
148
|
+
## Git identity setup (one time)
|
|
149
|
+
|
|
150
|
+
Per machine, once. Commits are attributed per repo, from the account that owns the org: never from a global identity, so a work repo can't be committed to with a personal address.
|
|
151
|
+
|
|
152
|
+
1. **Drop the global identity.** Git then refuses to invent one from `$USER@$HOSTNAME`:
|
|
153
|
+
```bash
|
|
154
|
+
git config --global --unset user.name
|
|
155
|
+
git config --global --unset user.email
|
|
156
|
+
git config --global user.useConfigOnly true
|
|
157
|
+
```
|
|
158
|
+
Existing repos relying on the global value now have no identity, and their next commit fails until one is set. That failure is the point.
|
|
159
|
+
|
|
160
|
+
2. **Cache the identity for each logged-in account** (`scripts/gh-org accounts` lists them):
|
|
161
|
+
```bash
|
|
162
|
+
scripts/gh-org set-identity <account> "Full Name" you@example.com
|
|
163
|
+
```
|
|
164
|
+
Stored as `gh-org.name.<account>` / `gh-org.email.<account>` in the global config. Git never reads these: they are a lookup table, not an identity.
|
|
165
|
+
|
|
166
|
+
3. **Verify:** `scripts/gh-org doctor` prints `GLOBAL IDENTITY:`, `USECONFIGONLY:`, and one `IDENTITY:` line per account.
|
|
167
|
+
|
|
168
|
+
Then, in each repo: right after cloning, and any time a commit fails with *"Author identity unknown"*:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
scripts/gh-org apply # origin -> org -> account -> repo's --local user.name/user.email
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
`scripts/gh-org identity [<org>]` prints what `apply` would write without touching the repo. The commit-time rules live in the `git-workflow` skill.
|
|
175
|
+
|
|
176
|
+
## When something fails
|
|
177
|
+
|
|
178
|
+
Run `scripts/gh-org doctor`: it reports `GH:`, `BREW:`, `ACCOUNTS:`, the org mappings, and the git-identity state. Act on it:
|
|
179
|
+
|
|
180
|
+
- **`GH: MISSING`:** install it and retry:
|
|
181
|
+
```bash
|
|
182
|
+
brew install gh
|
|
183
|
+
```
|
|
184
|
+
- **`BREW: MISSING`:** Homebrew isn't set up. Install it from https://brew.sh at its standard prefix, then `brew install gh`.
|
|
185
|
+
- **`ACCOUNTS: NONE`**, or the target org has no account: add one (see below).
|
|
186
|
+
- **Everything `OK`:** auth is not the problem; read the actual error.
|
|
187
|
+
|
|
188
|
+
## Adding an account
|
|
189
|
+
|
|
190
|
+
**Claude drives this; the user only approves in the browser.** Run the login in the **background** with stdin from `/dev/null` so `gh` uses the device-code flow instead of waiting on a TTY:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
gh auth login --hostname github.com --git-protocol ssh --scopes read:org,admin:public_key </dev/null
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Then grep the output for just the code and URL and relay them verbatim:
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
grep -E 'one-time code|Open this URL' <output-file>
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
`read:org` is required: the org-membership probe above depends on it. Monitor until `gh` exits, then re-run `scripts/gh-org doctor`.
|
|
203
|
+
|
|
204
|
+
Logging in a second account does **not** disturb the first; `gh` holds them all and `gh-org` selects between them per command.
|
|
205
|
+
|
|
206
|
+
## Never print tokens
|
|
207
|
+
|
|
208
|
+
Do not run `gh auth token`, `gh auth status --show-token`, or `--with-token` with a pasted PAT: those put a live credential in the transcript. `gh-org` reads the token internally and exports it only into the child process; it never prints it. See `secrets`.
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
HOST="github.com"
|
|
5
|
+
|
|
6
|
+
die() { printf 'gh-org: %s\n' "$*" >&2; exit 1; }
|
|
7
|
+
note() { printf 'gh-org: %s\n' "$*" >&2; }
|
|
8
|
+
lower() { printf '%s' "$1" | tr '[:upper:]' '[:lower:]'; }
|
|
9
|
+
|
|
10
|
+
need_gh() { command -v gh >/dev/null 2>&1 || die "gh is not installed: run: brew install gh"; }
|
|
11
|
+
|
|
12
|
+
accounts() {
|
|
13
|
+
gh auth status --hostname "$HOST" 2>&1 \
|
|
14
|
+
| grep -oE 'account [A-Za-z0-9._-]+' \
|
|
15
|
+
| awk '{print $2}'
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
token_for() { gh auth token --hostname "$HOST" --user "$1" 2>/dev/null; }
|
|
19
|
+
|
|
20
|
+
detect_org() {
|
|
21
|
+
local url owner
|
|
22
|
+
url=$(git remote get-url origin 2>/dev/null) || return 1
|
|
23
|
+
owner=$(printf '%s' "$url" | sed -E 's#^[a-z]+://[^/]+/##; s#^[^@]+@[^:]+:##; s#/.*$##')
|
|
24
|
+
[ -n "$owner" ] || return 1
|
|
25
|
+
printf '%s\n' "$owner"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
resolve() {
|
|
29
|
+
local org="$1" cached u tok login state match="" here
|
|
30
|
+
here=$(git config --local --get github.user 2>/dev/null || true)
|
|
31
|
+
if [ -n "$here" ] && [ "$(lower "$(detect_org 2>/dev/null || true)")" = "$(lower "$org")" ]; then
|
|
32
|
+
printf '%s\n' "$here"; return 0
|
|
33
|
+
fi
|
|
34
|
+
cached=$(git config --global --get "gh-org.account.$(lower "$org")" 2>/dev/null || true)
|
|
35
|
+
if [ -n "$cached" ]; then printf '%s\n' "$cached"; return 0; fi
|
|
36
|
+
|
|
37
|
+
while read -r u; do
|
|
38
|
+
[ -n "$u" ] || continue
|
|
39
|
+
tok=$(token_for "$u") || continue
|
|
40
|
+
[ -n "$tok" ] || continue
|
|
41
|
+
login=$(GH_TOKEN="$tok" gh api user --jq .login 2>/dev/null || true)
|
|
42
|
+
if [ "$(lower "$login")" = "$(lower "$org")" ]; then match="$u"; break; fi
|
|
43
|
+
state=$(GH_TOKEN="$tok" gh api "user/memberships/orgs/${org}" --jq .state 2>/dev/null || true)
|
|
44
|
+
if [ "$state" = "active" ]; then match="$u"; break; fi
|
|
45
|
+
done < <(accounts)
|
|
46
|
+
|
|
47
|
+
[ -n "$match" ] || die "no logged-in gh account has access to '${org}': run: gh auth login"
|
|
48
|
+
git config --global "gh-org.account.$(lower "$org")" "$match"
|
|
49
|
+
note "mapped org '${org}' -> account '${match}'"
|
|
50
|
+
printf '%s\n' "$match"
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
cmd_identity() {
|
|
54
|
+
need_gh
|
|
55
|
+
local org="${1:-}" account name email
|
|
56
|
+
[ -n "$org" ] || org=$(detect_org) || die "could not detect org from the git remote: pass it explicitly"
|
|
57
|
+
account=$(resolve "$org")
|
|
58
|
+
name=$(git config --global --get "gh-org.name.$(lower "$account")" 2>/dev/null || true)
|
|
59
|
+
email=$(git config --global --get "gh-org.email.$(lower "$account")" 2>/dev/null || true)
|
|
60
|
+
printf 'ACCOUNT: %s\n' "$account"
|
|
61
|
+
if [ -z "$name" ] || [ -z "$email" ]; then
|
|
62
|
+
printf 'IDENTITY: NONE\n'
|
|
63
|
+
die "no identity cached for account '${account}': run: gh-org set-identity ${account} \"Full Name\" you@example.com"
|
|
64
|
+
fi
|
|
65
|
+
printf 'NAME: %s\nEMAIL: %s\n' "$name" "$email"
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
cmd_set_identity() {
|
|
69
|
+
local account="${1:-}" name="${2:-}" email="${3:-}"
|
|
70
|
+
[ -n "$account" ] && [ -n "$name" ] && [ -n "$email" ] \
|
|
71
|
+
|| die 'usage: gh-org set-identity <account> "Full Name" <email>'
|
|
72
|
+
git config --global "gh-org.name.$(lower "$account")" "$name"
|
|
73
|
+
git config --global "gh-org.email.$(lower "$account")" "$email"
|
|
74
|
+
printf 'IDENTITY: %s -> %s <%s>\n' "$account" "$name" "$email"
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
cmd_apply() {
|
|
78
|
+
git rev-parse --git-dir >/dev/null 2>&1 || die "not inside a git repository"
|
|
79
|
+
local out name email
|
|
80
|
+
out=$(cmd_identity "$@")
|
|
81
|
+
name=$(printf '%s\n' "$out" | sed -n 's/^NAME: //p')
|
|
82
|
+
email=$(printf '%s\n' "$out" | sed -n 's/^EMAIL: //p')
|
|
83
|
+
git config --local user.name "$name"
|
|
84
|
+
git config --local user.email "$email"
|
|
85
|
+
printf 'LOCAL IDENTITY: %s <%s>\n' "$name" "$email"
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
cmd_doctor() {
|
|
89
|
+
if command -v gh >/dev/null 2>&1; then
|
|
90
|
+
printf 'GH: OK %s (%s)\n' "$(command -v gh)" "$(gh --version | head -1 | awk '{print $3}')"
|
|
91
|
+
else
|
|
92
|
+
printf 'GH: MISSING\n'
|
|
93
|
+
fi
|
|
94
|
+
if command -v brew >/dev/null 2>&1; then
|
|
95
|
+
printf 'BREW: OK %s\n' "$(command -v brew)"
|
|
96
|
+
else
|
|
97
|
+
printf 'BREW: MISSING\n'
|
|
98
|
+
fi
|
|
99
|
+
if command -v gh >/dev/null 2>&1; then
|
|
100
|
+
local list
|
|
101
|
+
list=$(accounts | tr '\n' ' ')
|
|
102
|
+
[ -n "${list// /}" ] && printf 'ACCOUNTS: %s\n' "$list" || printf 'ACCOUNTS: NONE\n'
|
|
103
|
+
fi
|
|
104
|
+
cmd_list
|
|
105
|
+
cmd_identities
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
cmd_identities() {
|
|
109
|
+
local gname gmail line
|
|
110
|
+
gname=$(git config --global --get user.name 2>/dev/null || true)
|
|
111
|
+
gmail=$(git config --global --get user.email 2>/dev/null || true)
|
|
112
|
+
if [ -n "$gname" ] || [ -n "$gmail" ]; then
|
|
113
|
+
printf 'GLOBAL IDENTITY: SET %s <%s>: remove it (see the git-workflow skill)\n' "$gname" "$gmail"
|
|
114
|
+
else
|
|
115
|
+
printf 'GLOBAL IDENTITY: NONE\n'
|
|
116
|
+
fi
|
|
117
|
+
if [ "$(git config --global --get user.useConfigOnly 2>/dev/null || true)" = "true" ]; then
|
|
118
|
+
printf 'USECONFIGONLY: OK\n'
|
|
119
|
+
else
|
|
120
|
+
printf 'USECONFIGONLY: OFF: run: git config --global user.useConfigOnly true\n'
|
|
121
|
+
fi
|
|
122
|
+
if ! git config --global --get-regexp '^gh-org\.email\.' >/dev/null 2>&1; then
|
|
123
|
+
printf 'IDENTITIES: NONE\n'; return 0
|
|
124
|
+
fi
|
|
125
|
+
git config --global --get-regexp '^gh-org\.email\.' | while read -r line; do
|
|
126
|
+
local acct; acct=$(printf '%s' "${line%% *}" | sed -E 's#^gh-org\.email\.##')
|
|
127
|
+
printf 'IDENTITY: %s -> %s <%s>\n' \
|
|
128
|
+
"$acct" "$(git config --global --get "gh-org.name.${acct}" 2>/dev/null || printf '?')" "${line#* }"
|
|
129
|
+
done
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
cmd_list() {
|
|
133
|
+
local line
|
|
134
|
+
if ! git config --global --get-regexp '^gh-org\.account\.' >/dev/null 2>&1; then
|
|
135
|
+
printf 'MAPPINGS: NONE\n'; return 0
|
|
136
|
+
fi
|
|
137
|
+
git config --global --get-regexp '^gh-org\.account\.' | while read -r line; do
|
|
138
|
+
printf 'MAPPING: %s -> %s\n' \
|
|
139
|
+
"$(printf '%s' "${line%% *}" | sed -E 's#^gh-org\.account\.##')" \
|
|
140
|
+
"${line#* }"
|
|
141
|
+
done
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
cmd_account() {
|
|
145
|
+
need_gh
|
|
146
|
+
local org="${1:-}"
|
|
147
|
+
[ -n "$org" ] || org=$(detect_org) || die "could not detect org from the git remote: pass it explicitly"
|
|
148
|
+
resolve "$org"
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
cmd_map() {
|
|
152
|
+
local org="${1:-}" login="${2:-}"
|
|
153
|
+
[ -n "$org" ] && [ -n "$login" ] || die "usage: gh-org map <org> <gh-account>"
|
|
154
|
+
git config --global "gh-org.account.$(lower "$org")" "$login"
|
|
155
|
+
printf 'MAPPING: %s -> %s\n' "$org" "$login"
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
cmd_unmap() {
|
|
159
|
+
local org="${1:-}"
|
|
160
|
+
[ -n "$org" ] || die "usage: gh-org unmap <org>"
|
|
161
|
+
git config --global --unset-all "gh-org.account.$(lower "$org")" 2>/dev/null || true
|
|
162
|
+
printf 'UNMAPPED: %s\n' "$org"
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
cmd_run() {
|
|
166
|
+
need_gh
|
|
167
|
+
local org=""
|
|
168
|
+
if [ "${1:-}" != "--" ]; then org="${1:-}"; shift || true; fi
|
|
169
|
+
[ "${1:-}" = "--" ] || die "usage: gh-org run [<org>] -- <command>..."
|
|
170
|
+
shift
|
|
171
|
+
[ $# -gt 0 ] || die "no command given"
|
|
172
|
+
[ -n "$org" ] || org=$(detect_org) || die "could not detect org from the git remote: pass it explicitly"
|
|
173
|
+
|
|
174
|
+
local user tok
|
|
175
|
+
user=$(resolve "$org")
|
|
176
|
+
tok=$(token_for "$user")
|
|
177
|
+
[ -n "$tok" ] || die "no token for account '${user}': run: gh auth login"
|
|
178
|
+
note "running as '${user}' for org '${org}'"
|
|
179
|
+
export GH_TOKEN="$tok"
|
|
180
|
+
unset GITHUB_TOKEN
|
|
181
|
+
exec "$@"
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
case "${1:-}" in
|
|
185
|
+
doctor) shift; cmd_doctor "$@" ;;
|
|
186
|
+
accounts) shift; need_gh; accounts ;;
|
|
187
|
+
account) shift; cmd_account "$@" ;;
|
|
188
|
+
list) shift; cmd_list ;;
|
|
189
|
+
identity) shift; cmd_identity "$@" ;;
|
|
190
|
+
set-identity) shift; cmd_set_identity "$@" ;;
|
|
191
|
+
apply) shift; cmd_apply "$@" ;;
|
|
192
|
+
map) shift; cmd_map "$@" ;;
|
|
193
|
+
unmap) shift; cmd_unmap "$@" ;;
|
|
194
|
+
run) shift; cmd_run "$@" ;;
|
|
195
|
+
*) die "usage: gh-org {doctor|accounts|account [<org>]|list|map <org> <account>|unmap <org>|identity [<org>]|set-identity <account> <name> <email>|apply [<org>]|run [<org>] -- <command>...}" ;;
|
|
196
|
+
esac
|