giterm 2.0.2 → 2.0.4
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.
- checksums.yaml +4 -4
- data/giterm +125 -42
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a01a43a0c33b199d4c4295839aeaa21accde4c8e9dee216c635ecc7835176e53
|
|
4
|
+
data.tar.gz: 1f0ac3d3d407e7e25e1e13513b67fff172b84d82d3ca15a70067941c4b685128
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5c421275ade0738dbe055be7e0b2581471610111d125dfee77e99fb8a6a711e8416f5cf2d225a7fda1b745ebf2c41dc0c23159d37f861d2fcd09f29f20e0f1da
|
|
7
|
+
data.tar.gz: 003f4737089d19deecbac244b25201ca5fcacb97642c64abdb0f100c9b97c3abbe6519dd201f4e15e9940e0448d02f075419085f6499a1d81661ed2054c1b128
|
data/giterm
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
# Author: Geir Isene <g@isene.com> (adapted from RTFM)
|
|
8
8
|
# Github: https://github.com/isene/GiTerm
|
|
9
9
|
# License: Public domain
|
|
10
|
-
@version = '2.0.
|
|
10
|
+
@version = '2.0.4'
|
|
11
11
|
|
|
12
12
|
# SAVE & STORE TERMINAL {{{1
|
|
13
13
|
ORIG_STTY = `stty -g 2>/dev/null`.chomp rescue ''
|
|
@@ -911,7 +911,26 @@ def github_request_http(endpoint)
|
|
|
911
911
|
|
|
912
912
|
JSON.parse(body)
|
|
913
913
|
else
|
|
914
|
-
|
|
914
|
+
error_msg = case response.code
|
|
915
|
+
when '401'
|
|
916
|
+
'Authentication failed (token expired or invalid). Press T to update token.'
|
|
917
|
+
when '403'
|
|
918
|
+
remaining = response['X-RateLimit-Remaining']
|
|
919
|
+
if remaining == '0'
|
|
920
|
+
reset_time = response['X-RateLimit-Reset']
|
|
921
|
+
reset_in = reset_time ? ((reset_time.to_i - Time.now.to_i) / 60.0).ceil : '?'
|
|
922
|
+
"GitHub API rate limit exceeded. Resets in ~#{reset_in} minutes."
|
|
923
|
+
else
|
|
924
|
+
"Access forbidden (403). Check token permissions."
|
|
925
|
+
end
|
|
926
|
+
when '404'
|
|
927
|
+
'Resource not found (404). Repository may be private or deleted.'
|
|
928
|
+
when '429'
|
|
929
|
+
'Too many requests (429). Please wait a moment and retry.'
|
|
930
|
+
else
|
|
931
|
+
"GitHub API error: #{response.code} - #{response.message}"
|
|
932
|
+
end
|
|
933
|
+
{ error: error_msg }
|
|
915
934
|
end
|
|
916
935
|
end
|
|
917
936
|
rescue JSON::ParserError => e
|
|
@@ -931,33 +950,62 @@ end
|
|
|
931
950
|
|
|
932
951
|
def github_request_curl(endpoint)
|
|
933
952
|
begin
|
|
934
|
-
# Build curl command
|
|
953
|
+
# Build curl command with separate connect and total timeouts
|
|
935
954
|
url = "https://api.github.com#{endpoint}"
|
|
936
|
-
curl_cmd = "curl -s -
|
|
955
|
+
curl_cmd = "curl -s --connect-timeout 10 --max-time 20 " \
|
|
956
|
+
"-w '\\n%{http_code}' " \
|
|
937
957
|
"-H 'Authorization: token #{@github_token}' " \
|
|
938
958
|
"-H 'Accept: application/vnd.github.v3+json' " \
|
|
939
959
|
"-H 'User-Agent: GiTerm/1.0' " \
|
|
940
960
|
"'#{url}'"
|
|
941
|
-
|
|
961
|
+
|
|
942
962
|
# Debug: log the curl command (without showing full token)
|
|
943
963
|
debug_cmd = curl_cmd.sub(@github_token, "#{@github_token[0..10]}...")
|
|
944
964
|
log_debug("Executing curl: #{debug_cmd}")
|
|
945
|
-
|
|
965
|
+
|
|
946
966
|
# Capture both stdout and stderr for better debugging
|
|
947
|
-
|
|
967
|
+
raw_result = `#{curl_cmd} 2>&1`
|
|
948
968
|
exit_code = $?.exitstatus
|
|
949
|
-
|
|
950
|
-
if exit_code == 0
|
|
951
|
-
|
|
969
|
+
|
|
970
|
+
if exit_code == 0
|
|
971
|
+
# Extract HTTP status code from the last line (-w appends it)
|
|
972
|
+
lines = raw_result.rstrip.split("\n")
|
|
973
|
+
http_code = lines.last.strip
|
|
974
|
+
body = lines[0...-1].join("\n")
|
|
975
|
+
|
|
976
|
+
if http_code == '200' && !body.empty?
|
|
977
|
+
JSON.parse(body)
|
|
978
|
+
elsif body.empty?
|
|
979
|
+
{ error: 'Empty response body from GitHub API.' }
|
|
980
|
+
else
|
|
981
|
+
error_msg = case http_code
|
|
982
|
+
when '401'
|
|
983
|
+
'Authentication failed (token expired or invalid). Press T to update token.'
|
|
984
|
+
when '403'
|
|
985
|
+
parsed = JSON.parse(body) rescue {}
|
|
986
|
+
if parsed['message']&.include?('rate limit')
|
|
987
|
+
'GitHub API rate limit exceeded. Wait a few minutes and retry.'
|
|
988
|
+
else
|
|
989
|
+
"Access forbidden (403). Check token permissions."
|
|
990
|
+
end
|
|
991
|
+
when '404'
|
|
992
|
+
'Resource not found (404). Repository may be private or deleted.'
|
|
993
|
+
when '429'
|
|
994
|
+
'Too many requests (429). Please wait a moment and retry.'
|
|
995
|
+
else
|
|
996
|
+
"GitHub API error (HTTP #{http_code})."
|
|
997
|
+
end
|
|
998
|
+
{ error: error_msg }
|
|
999
|
+
end
|
|
952
1000
|
else
|
|
953
1001
|
error_msg = case exit_code
|
|
954
|
-
when
|
|
955
|
-
when
|
|
956
|
-
when
|
|
957
|
-
when
|
|
958
|
-
else "
|
|
1002
|
+
when 6 then 'Could not resolve host. Check network connection.'
|
|
1003
|
+
when 7 then 'Failed to connect to GitHub. Check network connection.'
|
|
1004
|
+
when 28 then 'Request timed out. GitHub may be slow or unreachable.'
|
|
1005
|
+
when 35 then 'SSL/TLS connection error.'
|
|
1006
|
+
else "Network error (curl exit code: #{exit_code})."
|
|
959
1007
|
end
|
|
960
|
-
{ error:
|
|
1008
|
+
{ error: error_msg }
|
|
961
1009
|
end
|
|
962
1010
|
rescue JSON::ParserError => e
|
|
963
1011
|
{ error: "Invalid JSON from curl: #{e.message}" }
|
|
@@ -1225,17 +1273,40 @@ rescue => e
|
|
|
1225
1273
|
log_debug("Error in extended fetch: #{e.message}")
|
|
1226
1274
|
end
|
|
1227
1275
|
|
|
1276
|
+
def filter_markdown_clutter(text)
|
|
1277
|
+
# Remove badge markdown (both simple and nested)
|
|
1278
|
+
# Matches:  and [](link)
|
|
1279
|
+
text = text.gsub(/!\[(?:[^\]]*\[[^\]]*\][^\]]*|[^\]]*)\]\([^\)]+\)/, '')
|
|
1280
|
+
|
|
1281
|
+
# Remove HTML image tags
|
|
1282
|
+
text = text.gsub(/<img[^>]*>/, '')
|
|
1283
|
+
|
|
1284
|
+
# Remove HTML break tags
|
|
1285
|
+
text = text.gsub(/<br[^>]*>/, '')
|
|
1286
|
+
|
|
1287
|
+
# Remove lines that are only badges/links (leftover clutter)
|
|
1288
|
+
text = text.lines.reject { |line| line.strip.match?(/^[\[\]():\/.a-zA-Z0-9_-]+$/) && line.include?('](') }.join
|
|
1289
|
+
|
|
1290
|
+
# Remove excessive blank lines (more than 2 consecutive)
|
|
1291
|
+
text = text.gsub(/\n{3,}/, "\n\n")
|
|
1292
|
+
|
|
1293
|
+
# Remove lines that are only whitespace
|
|
1294
|
+
text = text.lines.reject { |line| line.strip.empty? && line != "\n" }.join
|
|
1295
|
+
|
|
1296
|
+
text.strip
|
|
1297
|
+
end
|
|
1298
|
+
|
|
1228
1299
|
def fetch_readme(repo_full_name)
|
|
1229
1300
|
return nil unless repo_full_name
|
|
1230
|
-
|
|
1301
|
+
|
|
1231
1302
|
# Try different README variations
|
|
1232
1303
|
readme_files = ['README.md', 'readme.md', 'README.txt', 'README', 'readme.txt']
|
|
1233
|
-
|
|
1304
|
+
|
|
1234
1305
|
readme_files.each do |filename|
|
|
1235
1306
|
begin
|
|
1236
1307
|
result = github_request("/repos/#{repo_full_name}/contents/#{filename}")
|
|
1237
1308
|
next if result.is_a?(Hash) && result[:error]
|
|
1238
|
-
|
|
1309
|
+
|
|
1239
1310
|
# Decode base64 content
|
|
1240
1311
|
if result.is_a?(Hash) && result['content'] && result['encoding'] == 'base64'
|
|
1241
1312
|
decoded = Base64.decode64(result['content'].gsub(/\s/, ''))
|
|
@@ -1244,15 +1315,16 @@ def fetch_readme(repo_full_name)
|
|
|
1244
1315
|
unless decoded.valid_encoding?
|
|
1245
1316
|
decoded = decoded.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '?')
|
|
1246
1317
|
end
|
|
1247
|
-
#
|
|
1248
|
-
|
|
1318
|
+
# Filter markdown clutter and limit README display
|
|
1319
|
+
filtered = filter_markdown_clutter(decoded)
|
|
1320
|
+
return filtered.lines.first(20).join.strip
|
|
1249
1321
|
end
|
|
1250
1322
|
rescue => e
|
|
1251
1323
|
log_debug("Error fetching README #{filename}: #{e.message}")
|
|
1252
1324
|
next
|
|
1253
1325
|
end
|
|
1254
1326
|
end
|
|
1255
|
-
|
|
1327
|
+
|
|
1256
1328
|
nil
|
|
1257
1329
|
end
|
|
1258
1330
|
|
|
@@ -1282,20 +1354,19 @@ def fetch_repo_files(repo_full_name, path = '')
|
|
|
1282
1354
|
# Show directories first, then files
|
|
1283
1355
|
(dirs + files).each do |item|
|
|
1284
1356
|
icon = case item['type']
|
|
1285
|
-
when 'dir' then '
|
|
1357
|
+
when 'dir' then '▶'
|
|
1286
1358
|
when 'file'
|
|
1287
1359
|
case File.extname(item['name']).downcase
|
|
1288
|
-
when '.md' then '
|
|
1289
|
-
when '.rb' then '
|
|
1290
|
-
when '.py' then '
|
|
1291
|
-
when '.js' then '
|
|
1292
|
-
when '.json' then '
|
|
1293
|
-
|
|
1294
|
-
else '📄'
|
|
1360
|
+
when '.md' then '•'
|
|
1361
|
+
when '.rb' then '★'
|
|
1362
|
+
when '.py' then '▪'
|
|
1363
|
+
when '.js' then '▷'
|
|
1364
|
+
when '.json', '.yml', '.yaml' then '⚙'
|
|
1365
|
+
else '◦'
|
|
1295
1366
|
end
|
|
1296
|
-
else '
|
|
1367
|
+
else '?'
|
|
1297
1368
|
end
|
|
1298
|
-
|
|
1369
|
+
|
|
1299
1370
|
size_info = item['size'] ? " (#{format_file_size(item['size'])})" : ""
|
|
1300
1371
|
content += "#{icon} #{item['name']}#{size_info}\n"
|
|
1301
1372
|
end
|
|
@@ -1325,15 +1396,21 @@ def github_issues
|
|
|
1325
1396
|
return if @selected_repo.empty?
|
|
1326
1397
|
|
|
1327
1398
|
result = github_request("/repos/#{@selected_repo}/issues?state=open&per_page=50")
|
|
1328
|
-
|
|
1329
|
-
if result[:error]
|
|
1399
|
+
|
|
1400
|
+
if result.is_a?(Hash) && result[:error]
|
|
1330
1401
|
@p_left.say(result[:error].fg(196))
|
|
1402
|
+
@p_bottom.say('Failed to load issues.')
|
|
1331
1403
|
return
|
|
1332
1404
|
end
|
|
1333
|
-
|
|
1405
|
+
|
|
1406
|
+
unless result.is_a?(Array)
|
|
1407
|
+
@p_left.say('Unexpected response format from GitHub API.'.fg(196))
|
|
1408
|
+
return
|
|
1409
|
+
end
|
|
1410
|
+
|
|
1334
1411
|
result.each do |issue|
|
|
1335
1412
|
next if issue['pull_request'] # Skip PRs
|
|
1336
|
-
|
|
1413
|
+
|
|
1337
1414
|
@github_issues << {
|
|
1338
1415
|
number: issue['number'],
|
|
1339
1416
|
title: issue['title'],
|
|
@@ -1412,12 +1489,18 @@ def github_pull_requests
|
|
|
1412
1489
|
return if @selected_repo.empty?
|
|
1413
1490
|
|
|
1414
1491
|
result = github_request("/repos/#{@selected_repo}/pulls?state=open&per_page=50")
|
|
1415
|
-
|
|
1416
|
-
if result[:error]
|
|
1492
|
+
|
|
1493
|
+
if result.is_a?(Hash) && result[:error]
|
|
1417
1494
|
@p_left.say(result[:error].fg(196))
|
|
1495
|
+
@p_bottom.say('Failed to load pull requests.')
|
|
1418
1496
|
return
|
|
1419
1497
|
end
|
|
1420
|
-
|
|
1498
|
+
|
|
1499
|
+
unless result.is_a?(Array)
|
|
1500
|
+
@p_left.say('Unexpected response format from GitHub API.'.fg(196))
|
|
1501
|
+
return
|
|
1502
|
+
end
|
|
1503
|
+
|
|
1421
1504
|
result.each do |pr|
|
|
1422
1505
|
@github_prs << {
|
|
1423
1506
|
number: pr['number'],
|
|
@@ -1534,14 +1617,14 @@ def github_search_repositories
|
|
|
1534
1617
|
# Perform the search
|
|
1535
1618
|
result = github_request("/search/repositories?q=#{CGI.escape(query)}&sort=stars&order=desc&per_page=50")
|
|
1536
1619
|
|
|
1537
|
-
if result[:error]
|
|
1620
|
+
if result.is_a?(Hash) && result[:error]
|
|
1538
1621
|
@p_left.clear
|
|
1539
1622
|
@p_left.say("Search Error: #{result[:error]}".fg(196))
|
|
1540
1623
|
return
|
|
1541
1624
|
end
|
|
1542
|
-
|
|
1625
|
+
|
|
1543
1626
|
# Process search results
|
|
1544
|
-
if result['items'] && result['items'].any?
|
|
1627
|
+
if result.is_a?(Hash) && result['items'] && result['items'].any?
|
|
1545
1628
|
result['items'].each do |repo|
|
|
1546
1629
|
@github_search_results << {
|
|
1547
1630
|
id: repo['id'],
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: giterm
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.0.
|
|
4
|
+
version: 2.0.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Geir Isene
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: "."
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-03-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rcurses
|
|
@@ -54,7 +54,8 @@ dependencies:
|
|
|
54
54
|
version: '13.0'
|
|
55
55
|
description: 'GiTerm is a powerful terminal interface for Git and GitHub, providing
|
|
56
56
|
an intuitive TUI for repository management, issue tracking, and pull request handling.
|
|
57
|
-
Version 2.0.
|
|
57
|
+
Version 2.0.4: Improved GitHub API error recovery with specific messages for auth,
|
|
58
|
+
rate-limit, and network failures.'
|
|
58
59
|
email:
|
|
59
60
|
- g@isene.com
|
|
60
61
|
executables:
|