luciq-cli 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0b148325e3ea055acf54acad389f7d5617534b687bf06125fb2b5fd565150d23
4
+ data.tar.gz: 6faf85d4ff073213742b72294db3e95a0e22e802e559d3cfdd0e8957822e1849
5
+ SHA512:
6
+ metadata.gz: 924ab969fefff13e013d4412414626b530dbcd81e46683dc471d70357e190122eec1eb730dfd6b12636dbd49573c1498d7b8ce5593e920bac9082635e945f9f8
7
+ data.tar.gz: 8ef521174571a0db1a2eefc181b39c291ace667f3dd21251a15cf193bb35aed06d4b310b93f28bcd1cdfced0a09407a157b0f9a46456722214ae21221eb4df34
data/CHANGELOG.md ADDED
@@ -0,0 +1,22 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.0] - 2025-12-16
11
+
12
+ ### Added
13
+
14
+ - Initial release
15
+ - `luciq login` - Authenticate with CLI token
16
+ - `luciq logout` - Remove saved authentication
17
+ - `luciq whoami` - Show current authenticated user
18
+ - `luciq info` - Show CLI configuration
19
+ - `luciq upload android-mapping` - Upload Android mapping files
20
+ - `luciq version` - Show CLI version
21
+ - Support for environment variables (`LUCIQ_AUTH_TOKEN`, `LUCIQ_URL`)
22
+ - Support for config file (`~/.luciqrc`)
data/README.md ADDED
@@ -0,0 +1,125 @@
1
+ # Luciq CLI
2
+
3
+ Command-line interface for uploading symbol files to Luciq.
4
+
5
+ ## Installation
6
+
7
+ ### Using RubyGems
8
+
9
+ ```bash
10
+ gem install luciq-cli
11
+ ```
12
+
13
+ ### Using Homebrew (macOS/Linux)
14
+
15
+ ```bash
16
+ brew tap instabug/tap
17
+ brew install luciq-cli
18
+ ```
19
+
20
+ ### From Source
21
+
22
+ ```bash
23
+ git clone https://github.com/Instabug/luciq-cli.git
24
+ cd luciq-cli
25
+ bundle install
26
+ bundle exec rake install
27
+ ```
28
+
29
+ ## Quick Start
30
+
31
+ ### 1. Authenticate
32
+
33
+ Generate a CLI token from your [Luciq Dashboard](https://dashboard.luciq.ai/company/cli), then:
34
+
35
+ > **Self-hosted clusters:** Replace `dashboard.luciq.ai` with your cluster URL.
36
+
37
+ ```bash
38
+ luciq login
39
+ ```
40
+
41
+ Or pass the token directly:
42
+
43
+ ```bash
44
+ luciq login --auth-token YOUR_CLI_TOKEN
45
+ ```
46
+
47
+ ### 2. Upload Symbol Files
48
+
49
+ Upload Android mapping files (`.zip` containing a single mapping text file):
50
+
51
+ ```bash
52
+ luciq upload android-mapping mapping.zip \
53
+ --app-token YOUR_APP_TOKEN \
54
+ --version-name 1.0.0 \
55
+ --version-code 1
56
+ ```
57
+
58
+ | Option | Description |
59
+ |--------|-------------|
60
+ | `--app-token` | Luciq application token (required) |
61
+ | `--version-name` | App version name, e.g., `1.0.0` (required) |
62
+ | `--version-code` | App version code, e.g., `1` (required) |
63
+
64
+ ## Commands
65
+
66
+ | Command | Description |
67
+ |---------|-------------|
68
+ | `luciq login` | Authenticate with Luciq |
69
+ | `luciq logout` | Remove saved authentication |
70
+ | `luciq whoami` | Show current authenticated user |
71
+ | `luciq info` | Show CLI configuration |
72
+ | `luciq upload android-mapping FILE` | Upload Android mapping file |
73
+ | `luciq version` | Show CLI version |
74
+
75
+ ## Configuration
76
+
77
+ Configuration is read from environment variables and `~/.luciqrc` file.
78
+
79
+ ### Environment Variables
80
+
81
+ | Variable | Description |
82
+ |----------|-------------|
83
+ | `LUCIQ_AUTH_TOKEN` | Authentication token |
84
+ | `LUCIQ_URL` | API base URL (default: https://api.luciq.ai) |
85
+
86
+ ### Config File
87
+
88
+ ```
89
+ # ~/.luciqrc
90
+ token=your-cli-token
91
+ url=https://api.luciq.ai
92
+ ```
93
+
94
+ ### Self-Hosted / Single Tenant
95
+
96
+ If you're using a self-hosted or single tenant cluster, you need to configure the API URL:
97
+
98
+ **Option 1: Environment variable**
99
+
100
+ ```bash
101
+ export LUCIQ_URL=https://api.your-cluster.luciq.ai
102
+ ```
103
+
104
+ **Option 2: Config file**
105
+
106
+ Add the `url` to your `~/.luciqrc`:
107
+
108
+ ```
109
+ token=your-cli-token
110
+ url=https://api.your-cluster.luciq.ai
111
+ ```
112
+
113
+ ## Development
114
+
115
+ ```bash
116
+ git clone https://github.com/Instabug/luciq-cli.git
117
+ cd luciq-cli
118
+ bundle install
119
+
120
+ # Run tests
121
+ bundle exec rspec
122
+
123
+ # Test locally
124
+ bundle exec bin/luciq --help
125
+ ```
data/bin/luciq ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ lib_path = File.expand_path('../lib', __dir__)
5
+ $LOAD_PATH.unshift(lib_path) unless $LOAD_PATH.include?(lib_path)
6
+
7
+ require 'luciq/cli'
8
+
9
+ Luciq::CLI.start(ARGV)
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require 'net/http/post/multipart'
5
+ require 'json'
6
+ require 'uri'
7
+ require 'luciq/config'
8
+
9
+ module Luciq
10
+ module API
11
+ class Client
12
+ def initialize
13
+ @token = Config.load_token
14
+ @base_url = Config.load_base_url
15
+ end
16
+
17
+ def whoami
18
+ uri = build_uri('/api/web/public/cli/whoami')
19
+ request = Net::HTTP::Get.new(uri)
20
+ apply_headers(request)
21
+ execute(uri, request)
22
+ end
23
+
24
+ def upload_android_mapping(file_path:, app_token:, version_code:, version_name:)
25
+ uri = build_uri('/api/web/public/mappings')
26
+
27
+ File.open(file_path, 'rb') do |file|
28
+ params = {
29
+ 'mapping_file' => UploadIO.new(file, 'application/octet-stream', File.basename(file_path)),
30
+ 'application_token' => app_token,
31
+ 'app_version_code' => version_code,
32
+ 'app_version_name' => version_name
33
+ }
34
+
35
+ request = Net::HTTP::Post::Multipart.new(uri.path, params)
36
+ apply_headers(request)
37
+ execute(uri, request)
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def build_uri(path)
44
+ URI("#{@base_url}#{path}")
45
+ end
46
+
47
+ def apply_headers(request)
48
+ request['User-Agent'] = "luciq-cli/#{Luciq::VERSION}"
49
+ request['Authorization'] = @token
50
+ end
51
+
52
+ def execute(uri, request)
53
+ http = Net::HTTP.new(uri.host, uri.port)
54
+ http.use_ssl = uri.scheme == 'https'
55
+ http.open_timeout = 30
56
+ http.read_timeout = 300
57
+
58
+ response = http.request(request)
59
+ return JSON.parse(response.body) if response.is_a?(Net::HTTPSuccess)
60
+
61
+ raise "Request failed (#{response.code}): #{response.body}"
62
+ end
63
+ end
64
+ end
65
+ end
data/lib/luciq/cli.rb ADDED
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+ require 'luciq/version'
5
+ require 'luciq/commands/auth'
6
+ require 'luciq/commands/upload'
7
+
8
+ module Luciq
9
+ class UploadCLI < Thor
10
+ desc 'android-mapping FILE', 'Upload Android mapping file'
11
+ long_desc <<~DESC
12
+ Upload Android mapping files to Luciq for crash symbolication.
13
+ File format: .zip containing a single text file
14
+ Example:
15
+ luciq upload android-mapping mapping.zip --app-token APP_TOKEN --version-name 1.0.0 --version-code 1
16
+ DESC
17
+ option :app_token, type: :string, required: true, desc: 'Your Luciq application token'
18
+ option :version_name, type: :string, required: true, desc: 'App version name (e.g., 1.0.0)'
19
+ option :version_code, type: :string, required: true, desc: 'App version code (e.g., 1)'
20
+ def android_mapping(file)
21
+ Commands::Upload.new(options).android_mapping(file)
22
+ end
23
+ end
24
+
25
+ class CLI < Thor
26
+ def self.exit_on_failure?
27
+ true
28
+ end
29
+
30
+ desc 'login', 'Authenticate with Luciq'
31
+ long_desc <<~DESC
32
+ Authenticate with Luciq using a CLI token.
33
+ Generate a CLI token from your Luciq dashboard: https://dashboard.luciq.ai/company/cli
34
+ The token will be saved in ~/.luciqrc
35
+ DESC
36
+ option :auth_token, type: :string, desc: 'CLI authentication token'
37
+ def login
38
+ Commands::Auth.new(options).login
39
+ end
40
+
41
+ desc 'logout', 'Remove saved authentication'
42
+ def logout
43
+ Commands::Auth.new(options).logout
44
+ end
45
+
46
+ desc 'whoami', 'Show current authenticated user'
47
+ def whoami
48
+ Commands::Auth.new(options).whoami
49
+ end
50
+
51
+ desc 'info', 'Show CLI configuration'
52
+ def info
53
+ Commands::Auth.new(options).info
54
+ end
55
+
56
+ desc 'upload SUBCOMMAND', 'Upload symbol files to Luciq'
57
+ subcommand 'upload', UploadCLI
58
+
59
+ desc 'version', 'Show CLI version'
60
+ def version
61
+ puts "luciq-cli #{Luciq::VERSION}"
62
+ end
63
+
64
+ map %w[--version] => :version
65
+ end
66
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'luciq/api/client'
4
+ require 'luciq/config'
5
+
6
+ module Luciq
7
+ module Commands
8
+ class Auth
9
+ def initialize(options = {})
10
+ @options = options
11
+ end
12
+
13
+ def login
14
+ token = @options[:auth_token]
15
+
16
+ unless token
17
+ puts 'Login to Luciq'
18
+ puts '=' * 40
19
+ puts 'Generate a CLI token from your Luciq dashboard: https://dashboard.luciq.ai/company/cli'
20
+ puts
21
+ puts "Note: For self-hosted clusters, replace 'dashboard' with your cluster name."
22
+ puts
23
+ print 'Paste your CLI token: '
24
+ token = $stdin.gets.chomp
25
+ puts
26
+ end
27
+
28
+ if token.empty?
29
+ puts '✗ No token provided.'
30
+ exit 1
31
+ end
32
+
33
+ Config.save_token(token)
34
+
35
+ puts '✓ Token saved to ~/.luciqrc'
36
+ puts "Run 'luciq info' to verify your configuration."
37
+ end
38
+
39
+ def whoami
40
+ client = API::Client.new
41
+
42
+ unless Config.load_token
43
+ puts '✗ Not authenticated. Run: luciq login'
44
+ exit 1
45
+ end
46
+
47
+ response = client.whoami
48
+
49
+ puts 'Authenticated as:'
50
+ puts " Email: #{response['email']}"
51
+ puts " Name: #{response['name']}"
52
+ rescue StandardError => e
53
+ puts "✗ #{e.message}"
54
+ exit 1
55
+ end
56
+
57
+ def logout
58
+ Config.clear_token
59
+ puts '✓ Logged out.'
60
+ end
61
+
62
+ def info
63
+ puts "Luciq CLI version: #{Luciq::VERSION}"
64
+ puts '=' * 40
65
+ puts
66
+ puts 'Configuration:'
67
+ puts " URL: #{Config.load_base_url}"
68
+ puts " Authentication Token: #{Config.load_token || '(not set)'}"
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'luciq/api/client'
4
+
5
+ module Luciq
6
+ module Commands
7
+ class Upload
8
+ def initialize(options = {})
9
+ @options = options
10
+ @client = API::Client.new
11
+ end
12
+
13
+ def android_mapping(file_path)
14
+ validate_file!(file_path)
15
+ validate_zip_extension!(file_path)
16
+
17
+ puts "Uploading Android mapping file: #{File.basename(file_path)}"
18
+ puts
19
+
20
+ @client.upload_android_mapping(
21
+ file_path: file_path,
22
+ app_token: @options[:app_token],
23
+ version_code: @options[:version_code],
24
+ version_name: @options[:version_name]
25
+ )
26
+
27
+ puts '✓ Android mapping file uploaded successfully!'
28
+ rescue StandardError => e
29
+ puts "✗ Upload failed: #{e.message}"
30
+ exit 1
31
+ end
32
+
33
+ private
34
+
35
+ def validate_file!(file_path)
36
+ unless File.exist?(file_path)
37
+ puts "✗ File not found: #{file_path}"
38
+ exit 1
39
+ end
40
+
41
+ return if File.readable?(file_path)
42
+
43
+ puts "✗ Cannot read file: #{file_path}"
44
+ exit 1
45
+ end
46
+
47
+ def validate_zip_extension!(file_path)
48
+ return if file_path.downcase.end_with?('.zip')
49
+
50
+ puts "✗ File must be a .zip archive: #{file_path}"
51
+ puts ' The ZIP should contain a single text file.'
52
+ exit 1
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Luciq
4
+ class Config
5
+ CONFIG_FILE = File.expand_path('~/.luciqrc')
6
+ DEFAULT_BASE_URL = 'https://api.luciq.ai'
7
+
8
+ class << self
9
+ def load_base_url
10
+ ENV['LUCIQ_URL'] || load_value('url') || DEFAULT_BASE_URL
11
+ end
12
+
13
+ def load_token
14
+ ENV['LUCIQ_AUTH_TOKEN'] || load_value('token')
15
+ end
16
+
17
+ def save_token(token)
18
+ add_value('token', token)
19
+ end
20
+
21
+ def clear_token
22
+ delete_value('token')
23
+ end
24
+
25
+ private
26
+
27
+ def load_value(key)
28
+ return nil unless File.exist?(CONFIG_FILE)
29
+
30
+ File.readlines(CONFIG_FILE).each do |line|
31
+ line = line.strip
32
+ next if line.empty? || line.start_with?('#')
33
+
34
+ if line.include?('=')
35
+ k, v = line.split('=', 2)
36
+ return v if k == key
37
+ end
38
+ end
39
+
40
+ nil
41
+ end
42
+
43
+ def add_value(key, value)
44
+ lines = File.exist?(CONFIG_FILE) ? File.readlines(CONFIG_FILE) : []
45
+ key_found = false
46
+ result = []
47
+
48
+ # Modify the existing value if it exists
49
+ lines.each do |line|
50
+ if line.strip.start_with?("#{key}=")
51
+ result << "#{key}=#{value}\n"
52
+ key_found = true
53
+ else
54
+ result << line
55
+ end
56
+ end
57
+
58
+ result << "#{key}=#{value}\n" unless key_found
59
+
60
+ File.write(CONFIG_FILE, result.join)
61
+ end
62
+
63
+ def delete_value(key)
64
+ return unless File.exist?(CONFIG_FILE)
65
+
66
+ lines = File.readlines(CONFIG_FILE)
67
+ result = lines.reject { |line| line.strip.start_with?("#{key}=") }
68
+
69
+ File.write(CONFIG_FILE, result.join)
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Luciq
4
+ VERSION = '0.1.0'
5
+ end
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: luciq-cli
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ahmed Hany
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2025-12-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: multipart-post
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.3'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: thor
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.3'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.3'
41
+ description: Interact with Luciq from the command line
42
+ email:
43
+ - ahany@luciq.ai
44
+ executables:
45
+ - luciq
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - CHANGELOG.md
50
+ - README.md
51
+ - bin/luciq
52
+ - lib/luciq/api/client.rb
53
+ - lib/luciq/cli.rb
54
+ - lib/luciq/commands/auth.rb
55
+ - lib/luciq/commands/upload.rb
56
+ - lib/luciq/config.rb
57
+ - lib/luciq/version.rb
58
+ homepage: https://github.com/Instabug/luciq-cli
59
+ licenses:
60
+ - MIT
61
+ metadata:
62
+ homepage_uri: https://github.com/Instabug/luciq-cli
63
+ source_code_uri: https://github.com/Instabug/luciq-cli
64
+ changelog_uri: https://github.com/Instabug/luciq-cli/blob/main/CHANGELOG.md
65
+ rubygems_mfa_required: 'false'
66
+ post_install_message:
67
+ rdoc_options: []
68
+ require_paths:
69
+ - lib
70
+ required_ruby_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: 2.7.0
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ requirements: []
81
+ rubygems_version: 3.5.11
82
+ signing_key:
83
+ specification_version: 4
84
+ summary: Luciq CLI for developers
85
+ test_files: []