gate-stack-cli 1.0.5 → 1.0.6

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/scripts/gate +369 -71
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gate-stack-cli",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "private": false,
5
5
  "description": "Gate CLI wrapper for git (gate clone/init/push/pull/status)",
6
6
  "bin": {
package/scripts/gate CHANGED
@@ -1,81 +1,122 @@
1
1
  #!/usr/bin/env bash
2
2
  # Gate CLI — wrapper sobre git com convenções da GateStack
3
- # Suporta:
4
- # gate clone <slug|url> [dir]
5
- # gate init [dir]
6
- # gate pull [args...]
7
- # gate push [args...]
8
- # gate status [args...]
9
- # gate fetch [args...]
10
- # gate checkout [args...]
11
- # gate branch [args...]
12
- # gate remote [args...]
13
- # gate log [args...]
14
- # gate commit [args...]
15
- # gate <qualquer-comando-git> [...]
3
+ #
4
+ # Comandos:
5
+ # clone init pull push status fetch checkout branch remote
6
+ # log commit diff stash stash-sub config login open pr
7
+ # reset completion
8
+ # Aliases: st br co ci f lg df rm
9
+ # Pass-through: qualquer outro comando vai pro git
16
10
 
17
11
  set -euo pipefail
18
12
 
19
13
  GATE_HOST="${GATE_HOST:-http://localhost:5000}"
20
14
  GIT_BIN="${GIT_BIN:-git}"
21
- VERSION="1.0.5"
15
+ VERSION="1.0.6"
16
+
17
+ # ── Config dir ────────────────────────────────────────────────────────
18
+ GATE_CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/gate"
19
+ GATE_CONFIG="$GATE_CONFIG_DIR/config"
22
20
 
23
21
  # ── Cores ─────────────────────────────────────────────────────────────
24
22
  if [ -t 1 ]; then
25
23
  R='\033[0;31m'; G='\033[0;32m'; Y='\033[1;33m'; B='\033[0;34m'
26
- C='\033[0;36m'; W='\033[0;37m'; N='\033[0m'
24
+ C='\033[0;36m'; M='\033[0;35m'; W='\033[0;37m'
25
+ K='\033[0;90m'; N='\033[0m'
26
+ BOLD='\033[1m'; DIM='\033[2m'
27
27
  else
28
- R=''; G=''; Y=''; B=''; C=''; W=''; N=''
28
+ R=''; G=''; Y=''; B=''; C=''; M=''; W=''; K=''; N=''
29
+ BOLD=''; DIM=''
29
30
  fi
30
31
 
31
32
  info() { printf "${G}▸${N} %s\n" "$*"; }
32
- warn() { printf "${Y}▸${N} %s\n" "$*" >&2; }
33
+ warn() { printf "${Y}⚠${N} %s\n" "$*" >&2; }
33
34
  error() { printf "${R}✖${N} %s\n" "$*" >&2; }
34
- header() { printf "\n${B}❯${N} %s\n" "$*"; }
35
+ header() { printf "\n${BOLD}${B}❯❯${N} %s\n\n" "$*"; }
36
+ dim() { printf "${DIM}%s${N}" "$*"; }
37
+ success() { printf "${G}✓${N} %s\n" "$*"; }
35
38
 
36
39
  # ── Versão ────────────────────────────────────────────────────────────
37
- version() {
38
- printf "gate-stack-cli %s\n" "$VERSION"
39
- }
40
+ version() { printf "gate-stack-cli %s\n" "$VERSION"; }
40
41
 
41
42
  # ── Usage ─────────────────────────────────────────────────────────────
42
43
  usage() {
43
44
  cat <<EOF
44
- Gate CLI (wrapper para git) v${VERSION}
45
-
46
- ${B}Usage:${N}
47
- gate clone <slug|url> [dir] Clona pelo slug ou URL completa
48
- gate init [dir] Inicializa repositório local
49
- gate pull [args...] git pull
50
- gate push [args...] git push
51
- gate status [args...] git status
52
- gate <comando-git> [...] Pass-through para qualquer git
53
-
54
- ${B}Aliases:${N}
55
- st → status ci → commit
56
- br → branch co → checkout
57
- f → fetch rm → remote
58
- lg → log
59
-
60
- ${B}Opções:${N}
61
- -h, --help, help Mostra esta ajuda
62
- --version, -v Mostra versão
63
-
64
- ${B}Env vars:${N}
65
- GATE_HOST Base URL para o Git HTTP (default: ${GATE_HOST})
66
- GIT_BIN Caminho do binário git (default: git)
45
+ ${BOLD}${C}Gate CLI${N} wrapper sobre git v${VERSION}
46
+
47
+ ${BOLD}${B}Comandos principais:${N}
48
+ init [dir] Inicializa repositório
49
+ clone <slug|url> [dir] Clona por slug ou URL
50
+ pull [args...] git pull
51
+ push [args...] git push
52
+ st [args...] git status (alias: status)
53
+ ci [args...] git commit (alias: commit)
54
+ fetch [args...] git remote fetch (alias: f)
55
+ df [args...] git diff --stat (alias: diff)
56
+ lg [args...] git log bonito (alias: log)
57
+
58
+ ${BOLD}${B}Branch & checkout:${N}
59
+ br [args...] git branch (alias: branch)
60
+ co <ref> [args...] git checkout (alias: checkout)
61
+
62
+ ${BOLD}${B}Stash:${N}
63
+ stash Stash rápido (salva mudanças)
64
+ stash [save|pop|list|drop|apply|clear]
65
+
66
+ ${BOLD}${B}Operações avançadas:${N}
67
+ open Abre repo no browser
68
+ reset [soft|hard|mixed] Reset com confirmação de segurança
69
+ config [key] [value] Ler/setar configurações
70
+ login [token] Salvar token de API
71
+ pr [--open] Criar/listar PR
72
+ remote [args...] Gerencia remotes (alias: rm)
73
+
74
+ ${BOLD}${B}Shell completion:${N}
75
+ completion Gera script bash/zsh para autocomplete
76
+
77
+ ${BOLD}${B}Opções:${N}
78
+ -h --help help Esta ajuda
79
+ -v --version Versão
80
+
81
+ ${BOLD}${B}Env vars:${N}
82
+ GATE_HOST Base URL GateStack (default: ${GATE_HOST})
83
+ GIT_BIN Caminho do git (default: git)
67
84
  EOF
68
85
  }
69
86
 
70
87
  # ── Helpers ───────────────────────────────────────────────────────────
71
88
  ensure_args() {
72
89
  local count=$1; shift
73
- if [ $# -lt "$count" ]; then
74
- usage
75
- exit 1
90
+ if [ $# -lt "$count" ]; then usage; exit 1; fi
91
+ }
92
+
93
+ read_config() {
94
+ local key=$1
95
+ if [ -f "$GATE_CONFIG" ]; then
96
+ grep "^${key}=" "$GATE_CONFIG" 2>/dev/null | tail -1 | cut -d= -f2-
76
97
  fi
77
98
  }
78
99
 
100
+ write_config() {
101
+ local key=$1 val=$2
102
+ mkdir -p "$GATE_CONFIG_DIR"
103
+ if [ -f "$GATE_CONFIG" ]; then
104
+ if grep -q "^${key}=" "$GATE_CONFIG" 2>/dev/null; then
105
+ sed -i "s|^${key}=.*|${key}=${val}|" "$GATE_CONFIG"
106
+ else
107
+ printf '%s=%s\n' "$key" "$val" >> "$GATE_CONFIG"
108
+ fi
109
+ else
110
+ printf '%s=%s\n' "$key" "$val" > "$GATE_CONFIG"
111
+ fi
112
+ }
113
+
114
+ get_gate_host() {
115
+ local configured
116
+ configured=$(read_config "host")
117
+ printf '%s' "${configured:-$GATE_HOST}"
118
+ }
119
+
79
120
  check_host() {
80
121
  local url=$1
81
122
  if command -v curl &>/dev/null; then
@@ -85,28 +126,66 @@ check_host() {
85
126
  fi
86
127
  }
87
128
 
129
+ is_git_repo() {
130
+ "$GIT_BIN" rev-parse --git-dir &>/dev/null
131
+ }
132
+
88
133
  normalize_clone_url() {
89
134
  local target=$1
90
135
  if [[ "$target" =~ ^https?:// ]] || [[ "$target" =~ ^git@ ]] || [[ "$target" =~ ^ssh:// ]]; then
91
136
  echo "$target"
92
137
  else
93
- echo "${GATE_HOST%/}/git/${target}"
138
+ echo "$(get_gate_host)/git/${target}"
139
+ fi
140
+ }
141
+
142
+ # Spinner: roda comando no fundo enquanto mostra ...
143
+ with_spinner() {
144
+ local msg=$1; shift
145
+ if [ -t 2 ]; then
146
+ printf "${DIM}%s${N}" "$msg " >&2
147
+ local pid=$!
148
+ "$@" 2>&1 &
149
+ local cmd_pid=$!
150
+ trap "kill $cmd_pid 2>/dev/null" EXIT
151
+ local spin='⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'
152
+ while kill -0 "$cmd_pid" 2>/dev/null; do
153
+ for ((i=0; i<${#spin}; i+=3)); do
154
+ if kill -0 "$cmd_pid" 2>/dev/null; then
155
+ printf "\r${DIM}%s %s${N}" "$msg" "${spin:$i:3}" >&2
156
+ sleep 0.1
157
+ else
158
+ break
159
+ fi
160
+ done
161
+ done
162
+ wait "$cmd_pid"
163
+ local ret=$?
164
+ printf "\r${DIM}%s${N}\n" "$msg" >&2
165
+ return $ret
166
+ else
167
+ "$@"
168
+ fi
169
+ }
170
+
171
+ # Detecta remote URL pra abrir no browser
172
+ detect_repo_url() {
173
+ local remote_url
174
+ remote_url=$("$GIT_BIN" remote get-url origin 2>/dev/null) || return 1
175
+ # Converte SSH → HTTPS
176
+ if [[ "$remote_url" =~ ^git@([^:]+):(.+)$\.git$?$ ]]; then
177
+ printf "https://%s/%s" "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}"
178
+ else
179
+ # Remove .git suffix
180
+ printf '%s' "${remote_url%.git}"
94
181
  fi
95
182
  }
96
183
 
97
184
  # ── Main ──────────────────────────────────────────────────────────────
98
185
  cmd="${1:-}"
99
- if [ -z "$cmd" ]; then
100
- usage
101
- exit 1
102
- fi
186
+ if [ -z "$cmd" ]; then usage; exit 1; fi
103
187
  shift || true
104
188
 
105
- # --version
106
- case "$cmd" in
107
- -v|--version) version; exit 0 ;;
108
- esac
109
-
110
189
  # Aliases curtos
111
190
  case "$cmd" in
112
191
  st) cmd="status" ;;
@@ -116,61 +195,280 @@ case "$cmd" in
116
195
  f) cmd="fetch" ;;
117
196
  rm) if [ -z "${1:-}" ] || [[ "${1:-}" != ==* ]]; then cmd="remote"; fi ;;
118
197
  lg) cmd="log" ;;
198
+ df) cmd="diff" ;;
199
+ esac
200
+
201
+ # --version
202
+ case "$cmd" in
203
+ -v|--version) version; exit 0 ;;
119
204
  esac
120
205
 
121
206
  case "$cmd" in
122
207
  clone)
123
208
  ensure_args 1 "$@"
124
209
  url=$(normalize_clone_url "$1"); shift
125
-
126
210
  base_url="${url%%/*}"
127
211
  if check_host "$base_url"; then
128
- info "Clonando de ${C}${url}${N} ..."
212
+ info "Clonando ${C}${url}${N} ..."
129
213
  exec "$GIT_BIN" clone "$url" "$@"
130
214
  else
131
- error "Não foi possível conectar a ${R}${base_url}${N}"
132
- error "Verifique GATE_HOST (atual: ${GATE_HOST})"
133
- error "Use URL completa: gate clone https://..."
215
+ error "Não conecta a ${R}${base_url}${N}"
216
+ error "Verifique: GATE_HOST=${GATE_HOST}"
217
+ error "Ou use URL completa: gate clone https://..."
134
218
  exit 1
135
219
  fi
136
220
  ;;
221
+
137
222
  init)
138
223
  info "Inicializando repositório"
139
224
  exec "$GIT_BIN" init "$@"
140
225
  ;;
226
+
141
227
  pull)
142
- info "Pull"
143
- exec "$GIT_BIN" pull "$@"
228
+ with_spinner "Pull" "$GIT_BIN" pull "$@"
144
229
  ;;
230
+
145
231
  push)
146
- info "Push"
147
- exec "$GIT_BIN" push "$@"
232
+ with_spinner "Push" "$GIT_BIN" push "$@"
148
233
  ;;
234
+
149
235
  status)
150
- exec "$GIT_BIN" status "$@"
236
+ exec "$GIT_BIN" status -sb "$@" 2>/dev/null || exec "$GIT_BIN" status "$@"
151
237
  ;;
238
+
152
239
  fetch)
153
- info "Fetch"
154
- exec "$GIT_BIN" fetch "$@"
240
+ with_spinner "Fetch" "$GIT_BIN" fetch "$@"
155
241
  ;;
242
+
156
243
  checkout)
244
+ if [ $# -eq 1 ] && ! [[ "$1" == -* ]]; then
245
+ info "Checkout ${C}${1}${N}"
246
+ fi
157
247
  exec "$GIT_BIN" checkout "$@"
158
248
  ;;
249
+
159
250
  branch)
160
- exec "$GIT_BIN" branch "$@"
251
+ if [ $# -eq 0 ]; then
252
+ exec "$GIT_BIN" branch --sort=-committerdate --format='%(HEAD) %(color:cyan)%(refname:short)%(color:reset) %(color:green)(%(committerdate:relative))%(color:reset)'
253
+ else
254
+ exec "$GIT_BIN" branch "$@"
255
+ fi
161
256
  ;;
257
+
162
258
  remote)
163
259
  exec "$GIT_BIN" remote "$@"
164
260
  ;;
261
+
165
262
  log)
166
- exec "$GIT_BIN" log "$@"
263
+ # Se sem args, log bonitão padrão
264
+ if [ $# -eq 0 ]; then
265
+ exec "$GIT_BIN" log --graph --pretty=format:"${C}%h${N} ${BOLD}%s${N} ${DIM}%cr${N} ${M}<%an>${N}" --abbrev-commit --date=relative "$@"
266
+ else
267
+ exec "$GIT_BIN" log "$@"
268
+ fi
167
269
  ;;
270
+
168
271
  commit)
169
272
  exec "$GIT_BIN" commit "$@"
170
273
  ;;
274
+
275
+ diff)
276
+ if [ $# -eq 0 ]; then
277
+ exec "$GIT_BIN" diff --stat --color
278
+ else
279
+ # Se passar --, usa diff normal
280
+ exec "$GIT_BIN" diff "$@"
281
+ fi
282
+ ;;
283
+
284
+ stash)
285
+ sub="${1:-save}"
286
+ case "$sub" in
287
+ save|pop|list|drop|apply|clear)
288
+ if [ "$sub" = "list" ]; then
289
+ exec "$GIT_BIN" stash list --format="${C}%gd${N} ${BOLD}%gs${N} ${DIM}%cr${N}"
290
+ elif [ "$sub" = "save" ]; then
291
+ if [ $# -le 1 ]; then
292
+ info "Salvando mudanças locais"
293
+ exec "$GIT_BIN" stash push -m "gate$(date +%H%M)"
294
+ else
295
+ shift
296
+ exec "$GIT_BIN" stash push -m "gate: $*" "$@"
297
+ fi
298
+ else
299
+ shift
300
+ exec "$GIT_BIN" stash "$sub" "$@"
301
+ fi
302
+ ;;
303
+ *)
304
+ # Sem args → save por default
305
+ info "Salvando mudanças locais"
306
+ exec "$GIT_BIN" stash push -m "gate$(date +%H%M)"
307
+ ;;
308
+ esac
309
+ ;;
310
+
311
+ config)
312
+ if [ $# -eq 0 ]; then
313
+ header "Configuração atual"
314
+ if [ -f "$GATE_CONFIG" ]; then
315
+ while IFS='=' read -r k v; do
316
+ printf " ${C}%s${N} = %s\n" "$k" "$v"
317
+ done < "$GATE_CONFIG"
318
+ else
319
+ warn "Nenhuma configuração definida"
320
+ info "Use: gate config <key> <value>"
321
+ fi
322
+ elif [ $# -eq 1 ]; then
323
+ val=$(read_config "$1")
324
+ if [ -n "$val" ]; then
325
+ printf '%s\n' "$val"
326
+ else
327
+ error "Chave '${1}' não encontrada"
328
+ fi
329
+ else
330
+ write_config "$1" "$2"
331
+ success "Configurado: ${C}${1}${N} = ${2}"
332
+ fi
333
+ ;;
334
+
335
+ login)
336
+ ensure_args 1 "$@"
337
+ write_config "token" "$1"
338
+ success "Token salvo em ${GATE_CONFIG}"
339
+ info "Use este token para autenticação via API GateStack"
340
+ ;;
341
+
342
+ open)
343
+ if ! is_git_repo; then
344
+ error "Não está em um repositório git"
345
+ exit 1
346
+ fi
347
+ repo_url=$(detect_repo_url) || { error "Nenhum remote 'origin' encontrado"; exit 1; }
348
+ info "Abrindo ${C}${repo_url}${N} ..."
349
+ if command -v xdg-open &>/dev/null; then
350
+ exec xdg-open "$repo_url"
351
+ elif command -v open &>/dev/null; then
352
+ exec open "$repo_url"
353
+ elif command -v cmd.exe &>/dev/null; then
354
+ exec cmd.exe /c start "$repo_url"
355
+ else
356
+ error "Nenhum browser detectado"
357
+ printf " ${C}%s${N}\n" "$repo_url"
358
+ fi
359
+ ;;
360
+
361
+ reset)
362
+ mode="${1:-soft}"
363
+ case "$mode" in
364
+ soft|mixed|hard) ;;
365
+ *) error "Modo inválido: '${mode}'. Use: soft, mixed, hard"; exit 1 ;;
366
+ esac
367
+ if [ "$mode" = "hard" ]; then
368
+ warn "reset --hard descarta alterações não-committadas"
369
+ fi
370
+ shift
371
+ target="${1:-HEAD}"
372
+ with_spinner "Reset ${mode}" "$GIT_BIN" reset --"$mode" "$target"
373
+ success "Reset ${mode} → ${C}${target}${N}"
374
+ ;;
375
+
376
+ pr)
377
+ # Integra com o backend GateStack
378
+ if ! is_git_repo; then
379
+ error "Não está em um repositório git"
380
+ exit 1
381
+ fi
382
+ host=$(get_gate_host)
383
+ branch=$("$GIT_BIN" rev-parse --abbrev-ref HEAD 2>/dev/null) || branch="unknown"
384
+ remote_url=$(detect_repo_url) || remote_url=""
385
+ info "Branch atual: ${C}${branch}${N}"
386
+ if [ "${1:-}" = "--open" ] || [ "${1:-}" = "-o" ]; then
387
+ if [ -z "$remote_url" ]; then
388
+ error "Nenhum remote origin detectado"
389
+ exit 1
390
+ fi
391
+ if [[ "$remote_url" =~ github\.com ]] || [[ "$remote_url" =~ gitlab\.com ]]; then
392
+ info "Abrindo para criar PR ..."
393
+ if command -v gh &>/dev/null; then
394
+ exec gh pr create --web 2>/dev/null || exec xdg-open "${remote_url}/compare/main...${branch}"
395
+ else
396
+ exec xdg-open "${remote_url}/compare/main...${branch}"
397
+ fi
398
+ else
399
+ exec xdg-open "${host}/projects"
400
+ fi
401
+ else
402
+ info "Use ${C}gate pr --open${N} para criar/abrir no browser"
403
+ if command -v gh &>/dev/null; then
404
+ echo
405
+ gh pr list --limit 5 2>/dev/null || warn "Não foi possível listar PRs via gh"
406
+ fi
407
+ fi
408
+ ;;
409
+
410
+ completion)
411
+ shell="${1:-bash}"
412
+ case "$shell" in
413
+ bash)
414
+ cat <<'COMP'
415
+ _gate_completion() {
416
+ local cur="${COMP_WORDS[COMP_CWORD]}"
417
+ local cmds="clone init pull push status fetch checkout branch remote log commit diff stash config login open pr reset completion st ci br co f rm lg df -h --help help -v --version"
418
+ if [ "$COMP_CWORD" -eq 1 ]; then
419
+ COMPREPLY=( $(compgen -W "$cmds" -- "$cur") )
420
+ else
421
+ COMPREPLY=( $(compgen -f -- "$cur") )
422
+ fi
423
+ }
424
+ complete -F _gate_completion gate
425
+ COMP
426
+ ;;
427
+ zsh)
428
+ cat <<'COMP'
429
+ #compdef gate
430
+ _gate() {
431
+ local -a cmds
432
+ cmds=(
433
+ 'clone:Clone by slug or URL'
434
+ 'init:Initialize repository'
435
+ 'pull:git pull'
436
+ 'push:git push'
437
+ 'status:git status short-branch'
438
+ 'fetch:git remote fetch'
439
+ 'checkout:git checkout'
440
+ 'branch:List branches'
441
+ 'remote:Manage remotes'
442
+ 'log:Pretty git log'
443
+ 'commit:git commit'
444
+ 'diff:git diff --stat'
445
+ 'stash:Quick stash save'
446
+ 'stash-sub:Stash subcommands'
447
+ 'config:Read/set config'
448
+ 'login:Save API token'
449
+ 'open:Open repo in browser'
450
+ 'pr:Pull requests'
451
+ 'reset:Git reset with safety'
452
+ 'completion:Generate shell completion'
453
+ )
454
+ _arguments '*: :->cmds'
455
+ case $state in
456
+ cmds) _describe 'command' cmds ;;
457
+ esac
458
+ }
459
+ COMP
460
+ ;;
461
+ *)
462
+ error "Shell '${shell}' não suportado. Use: bash ou zsh"
463
+ exit 1
464
+ ;;
465
+ esac
466
+ ;;
467
+
171
468
  -h|--help|help)
172
469
  usage
173
470
  ;;
471
+
174
472
  *)
175
473
  exec "$GIT_BIN" "$cmd" "$@"
176
474
  ;;