luciq-cli 0.2.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6882405a8d129fe6337191a3fafcf09ce88290166b640acb36c016af9c4bdd35
4
- data.tar.gz: 866a5f76f6d8d1b922b24c73dc33687c9a9cd7dc8a139722d449c129b8e474a5
3
+ metadata.gz: 8a0f4b1ed57b62e9807f17afddbd5e34b5fa411d504cd2fdabd5afc50521650c
4
+ data.tar.gz: b25294d1c535c880a7d8be44385bce9ccdc49bd8be7245ae8417d031177aad27
5
5
  SHA512:
6
- metadata.gz: 2c48c7bbb0a56349d8bd41e9ad78a7b4c459a50e0befa5dd1b69a2eb98beca174c3fb1cfdbfba1d2815b55ab9b8eb96b75738f5b49abf4c9ee45d19b9bc208e9
7
- data.tar.gz: 5eb8bc3833309adfb682cfcf3c13b2c4690803a4086df71f8fa8b6d23be99b865bdcbbb144d390d65b0b4f46b6e6377da79e894dfb168daac14bdd795729f561
6
+ metadata.gz: cf5b2166bc9f2e6910feab38b73d90187d9a928d3207e703bcc97faa55fcd84b6398362150300207c850d49460d9c200e65957615be10c4309e0e61a7ec8e9af
7
+ data.tar.gz: f90266fa97e558b083630cda382d9febe7421a3e3f9c4e03935c93819e28ab26510ed6e2c1d154666b5aa634b44d74f667686155c5783c4b5e10510cf90a8999
data/CHANGELOG.md CHANGED
@@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.3.0] - 2026-08-04
11
+
12
+ ### Changed
13
+
14
+ - **Breaking:** `luciq upload` commands now take `--slug` and `--mode` instead of `--app-token`, matching the query commands. Uploads authenticate with your CLI login, so no application token is needed.
15
+ - `luciq whoami` and `luciq upload` now call the Luciq API gateway instead of the public backend endpoints.
16
+
17
+ ### Added
18
+
19
+ - `--tag-action` on `luciq bugs update` to control how `--tags` is applied: `append` (default), `replace`, or `remove`
20
+ - Homebrew installation: `brew install luciqai/tap/luciq-cli`
21
+ - MIT LICENSE
22
+
23
+ ### Fixed
24
+
25
+ - `luciq bugs update` now rejects `--clear-tags` combined with `--tag-action`, and no longer treats a tags-only change as an empty update
26
+ - Corrected the dashboard URL shown for creating a CLI token
27
+
10
28
  ## [0.2.0] - 2026-07-05
11
29
 
12
30
  ### Added
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Instabug, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md CHANGED
@@ -15,14 +15,13 @@ gem install luciq-cli
15
15
  ### Using Homebrew (macOS/Linux)
16
16
 
17
17
  ```bash
18
- brew tap instabug/tap
19
- brew install luciq-cli
18
+ brew install luciqai/tap/luciq-cli
20
19
  ```
21
20
 
22
21
  ### From Source
23
22
 
24
23
  ```bash
25
- git clone https://github.com/Instabug/luciq-cli.git
24
+ git clone https://github.com/luciqai/luciq-cli.git
26
25
  cd luciq-cli
27
26
  bundle install
28
27
  bundle exec rake install
@@ -32,7 +31,7 @@ bundle exec rake install
32
31
 
33
32
  ### 1. Authenticate
34
33
 
35
- Generate a CLI token from your [Luciq Dashboard](https://dashboard.luciq.ai/company/cli), then:
34
+ Generate a CLI token from your [Luciq Dashboard](https://dashboard.luciq.ai/company/luciq-cli), then:
36
35
 
37
36
  > **Self-hosted clusters:** Replace `dashboard.luciq.ai` with your cluster URL.
38
37
 
@@ -52,14 +51,16 @@ Upload Android mapping files (`mapping.txt`):
52
51
 
53
52
  ```bash
54
53
  luciq upload android-mapping mapping.txt \
55
- --app-token YOUR_APP_TOKEN \
54
+ --slug my-app \
55
+ --mode production \
56
56
  --version-name 1.0.0 \
57
57
  --version-code 1
58
58
  ```
59
59
 
60
60
  | Option | Description |
61
61
  |--------|-------------|
62
- | `--app-token` | Luciq application token (required) |
62
+ | `--slug` | Application slug (required) |
63
+ | `--mode` | Application mode, e.g., `production` (required) |
63
64
  | `--version-name` | App version name, e.g., `1.0.0` (required) |
64
65
  | `--version-code` | App version code, e.g., `1` (required) |
65
66
 
@@ -125,11 +126,13 @@ Run `luciq upload help SUBCOMMAND` for the full options of any command.
125
126
  | `luciq upload flutter-android-sourcemap FILE` | `.zip` | Android Dart symbol files |
126
127
  | `luciq upload flutter-ndk FILE` | `.zip` | NDK `.so` shared object files (`--arch`) |
127
128
 
128
- Common options: `--app-token` is required for every command. dSYM commands
129
- (`*-ios-dsym`) need only `--app-token`. Mapping, source-map, and Flutter Dart-symbol
130
- commands also require `--version-name` / `--version-code` (React Native source-map
131
- commands additionally accept `--codepush`). NDK commands require `--version-name` and
132
- `--arch` (`armeabi-v7a`, `arm64-v8a`, `x86`, `x86_64`) not `--version-code`.
129
+ Common options: `--slug` and `--mode` are required for every command uploads use
130
+ your CLI login, the same as query commands. dSYM commands (`*-ios-dsym`) need only
131
+ `--slug` / `--mode`. Mapping, source-map, and
132
+ Flutter Dart-symbol commands also require `--version-name` / `--version-code`
133
+ (React Native source-map commands additionally accept `--codepush`). NDK commands
134
+ require `--version-name` and `--arch` (`armeabi-v7a`, `arm64-v8a`, `x86`, `x86_64`)
135
+ — not `--version-code`.
133
136
 
134
137
  ## Query & management commands
135
138
 
@@ -183,16 +186,20 @@ luciq crashes show --slug my-app --mode production --number 42
183
186
  |------------|-------------|
184
187
  | `list` | List bugs. Flags: `--status` (new/closed/in_progress), `--priority` (na/trivial/minor/major/blocker), `--app-version` |
185
188
  | `show --number N` | Details for one bug |
186
- | `update --number N` | Update a bug. Flags: `--status`, `--priority`, `--tags`, `--clear-tags`, `--duplicate-of`, `--action` |
189
+ | `update --number N` | Update a bug. Flags: `--status`, `--priority`, `--tags`, `--tag-action`, `--clear-tags`, `--duplicate-of`, `--action` |
187
190
 
188
191
  ```bash
189
192
  luciq bugs update --slug my-app --mode production --number 7 --status closed --priority major
190
193
  luciq bugs update --slug my-app --mode production --number 7 --duplicate-of 3
194
+ luciq bugs update --slug my-app --mode production --number 7 --tags urgent regression
195
+ luciq bugs update --slug my-app --mode production --number 7 --tags stale --tag-action remove
196
+ luciq bugs update --slug my-app --mode production --number 7 --tags only-this --tag-action replace
191
197
  ```
192
198
 
193
199
  Provide at least one change. Duplicate marking (`--duplicate-of` / `--action`)
194
- can't be combined with `--status`/`--priority`; use `--clear-tags` to remove all
195
- tags.
200
+ can't be combined with `--status`/`--priority`. Tags: `--tags` appends to the existing
201
+ tags by default; `--tag-action replace` sets `--tags` as the full list (removing others),
202
+ `--tag-action remove` deletes `--tags` and keeps the rest; `--clear-tags` removes all.
196
203
 
197
204
  ### APM
198
205
 
@@ -320,14 +327,25 @@ url=https://api.your-cluster.luciq.ai
320
327
 
321
328
  ## Development
322
329
 
323
- ```bash
324
- git clone https://github.com/Instabug/luciq-cli.git
325
- cd luciq-cli
326
- bundle install
330
+ Clone the repo and install dependencies as in [From Source](#from-source), then:
327
331
 
332
+ ```bash
328
333
  # Run tests
329
334
  bundle exec rspec
330
335
 
331
336
  # Test locally
332
337
  bundle exec bin/luciq --help
333
338
  ```
339
+
340
+ ## Releasing
341
+
342
+ After publishing a new gem version to RubyGems, sync the Homebrew formula in
343
+ the [homebrew-tap](https://github.com/luciqai/homebrew-tap) repo:
344
+
345
+ ```bash
346
+ bundle exec rake formula:bump # writes to ../homebrew-tap, override with TAP_DIR
347
+ ```
348
+
349
+ Open a pull request in the tap repo with the result: Homebrew users stay on
350
+ the previous version until the bumped formula lands on its `main` branch.
351
+ `brew livecheck luciq-cli` reports when the formula lags RubyGems.
@@ -9,18 +9,13 @@ require 'luciq/config'
9
9
  module Luciq
10
10
  module API
11
11
  class Client
12
- SYMBOLS_FILES_PATH = '/api/sdk/v3/symbols_files'
13
- SO_FILES_PATH = '/api/web/public/so_files'
14
- FLUTTER_IOS_SYMBOLS_PATH = '/api/web/public/flutter-symbol-files/ios'
15
- FLUTTER_ANDROID_SYMBOLS_PATH = '/api/web/public/flutter-symbol-files/android'
16
-
17
12
  def initialize
18
13
  @token = Config.load_token
19
14
  @base_url = Config.load_base_url
20
15
  end
21
16
 
22
17
  def whoami
23
- uri = build_uri('/api/web/public/cli/whoami')
18
+ uri = build_uri('/api/cli/whoami')
24
19
  request = Net::HTTP::Get.new(uri)
25
20
  apply_headers(request)
26
21
  execute(uri, request)
@@ -37,145 +32,17 @@ module Luciq
37
32
  execute(uri, request)
38
33
  end
39
34
 
40
- def upload_ios_dsym(file_path:, app_token:)
41
- upload_dsym(file_path: file_path, app_token: app_token)
42
- end
43
-
44
- def upload_android_mapping(file_path:, app_token:, version_code:, version_name:)
45
- upload_mapping(
46
- file_path: file_path, app_token: app_token,
47
- version_code: version_code, version_name: version_name
48
- )
49
- end
50
-
51
- def upload_android_ndk(file_path:, app_token:, app_version:, arch:)
52
- upload_so_file(file_path: file_path, app_token: app_token, app_version: app_version, arch: arch)
53
- end
54
-
55
- def upload_react_native_ios_dsym(file_path:, app_token:)
56
- upload_dsym(file_path: file_path, app_token: app_token)
57
- end
58
-
59
- def upload_react_native_ios_sourcemap(file_path:, app_token:, app_version:)
60
- upload_react_native_sourcemap(
61
- file_path: file_path, app_token: app_token, os: 'ios', app_version: app_version
62
- )
63
- end
64
-
65
- def upload_react_native_android_mapping(file_path:, app_token:, version_code:, version_name:)
66
- upload_mapping(
67
- file_path: file_path, app_token: app_token,
68
- version_code: version_code, version_name: version_name
69
- )
70
- end
71
-
72
- def upload_react_native_android_sourcemap(file_path:, app_token:, app_version:)
73
- upload_react_native_sourcemap(
74
- file_path: file_path, app_token: app_token, os: 'android', app_version: app_version
75
- )
76
- end
77
-
78
- def upload_react_native_ndk(file_path:, app_token:, app_version:, arch:)
79
- upload_so_file(file_path: file_path, app_token: app_token, app_version: app_version, arch: arch)
80
- end
81
-
82
- def upload_flutter_ios_dsym(file_path:, app_token:)
83
- upload_dsym(file_path: file_path, app_token: app_token)
84
- end
85
-
86
- def upload_flutter_ios_sourcemap(file_path:, app_token:, version_name:, version_code:)
87
- upload_flutter_symbols(
88
- FLUTTER_IOS_SYMBOLS_PATH,
89
- file_path: file_path, app_token: app_token,
90
- version_name: version_name, version_code: version_code
91
- )
92
- end
93
-
94
- def upload_flutter_android_mapping(file_path:, app_token:, version_code:, version_name:)
95
- upload_mapping(
96
- file_path: file_path, app_token: app_token,
97
- version_code: version_code, version_name: version_name
98
- )
99
- end
100
-
101
- def upload_flutter_android_sourcemap(file_path:, app_token:, version_name:, version_code:)
102
- upload_flutter_symbols(
103
- FLUTTER_ANDROID_SYMBOLS_PATH,
104
- file_path: file_path, app_token: app_token,
105
- version_name: version_name, version_code: version_code
106
- )
107
- end
108
-
109
- def upload_flutter_ndk(file_path:, app_token:, app_version:, arch:)
110
- upload_so_file(file_path: file_path, app_token: app_token, app_version: app_version, arch: arch)
111
- end
112
-
113
- private
114
-
115
- def upload_dsym(file_path:, app_token:)
116
- uri = build_uri(SYMBOLS_FILES_PATH)
117
- File.open(file_path, 'rb') do |file|
118
- params = {
119
- 'symbols_file' => UploadIO.new(file, 'application/octet-stream', File.basename(file_path)),
120
- 'application_token' => app_token,
121
- 'os' => 'ios'
122
- }
123
- post_multipart(uri, params)
124
- end
125
- end
126
-
127
- def upload_mapping(file_path:, app_token:, version_code:, version_name:)
128
- uri = build_uri(SYMBOLS_FILES_PATH)
129
- File.open(file_path, 'rb') do |file|
130
- params = {
131
- 'symbols_file' => UploadIO.new(file, 'application/octet-stream', File.basename(file_path)),
132
- 'application_token' => app_token,
133
- 'os' => 'android',
134
- 'app_version' => { code: version_code, name: version_name }.to_json
135
- }
136
- post_multipart(uri, params)
137
- end
138
- end
139
-
140
- def upload_react_native_sourcemap(file_path:, app_token:, os:, app_version:)
141
- uri = build_uri(SYMBOLS_FILES_PATH)
35
+ def upload(command, file_path, fields = {})
36
+ uri = build_uri("/api/cli/uploads/#{command}")
142
37
  File.open(file_path, 'rb') do |file|
143
38
  params = {
144
- 'symbols_file' => UploadIO.new(file, source_map_content_type(file_path), File.basename(file_path)),
145
- 'application_token' => app_token,
146
- 'platform' => 'react_native',
147
- 'os' => os,
148
- 'app_version' => app_version.to_json
149
- }
39
+ 'file' => UploadIO.new(file, content_type_for(file_path), File.basename(file_path))
40
+ }.merge(fields)
150
41
  post_multipart(uri, params)
151
42
  end
152
43
  end
153
44
 
154
- def upload_so_file(file_path:, app_token:, app_version:, arch:)
155
- uri = build_uri(SO_FILES_PATH)
156
- File.open(file_path, 'rb') do |file|
157
- params = {
158
- 'so_file' => UploadIO.new(file, 'application/octet-stream', File.basename(file_path)),
159
- 'application_token' => app_token,
160
- 'app_version' => app_version,
161
- 'arch' => arch
162
- }
163
- post_multipart(uri, params)
164
- end
165
- end
166
-
167
- def upload_flutter_symbols(path, file_path:, app_token:, version_name:, version_code:)
168
- uri = build_uri(path)
169
- File.open(file_path, 'rb') do |file|
170
- params = {
171
- 'file' => UploadIO.new(file, 'application/octet-stream', File.basename(file_path)),
172
- 'application_token' => app_token,
173
- 'app_version_name' => version_name,
174
- 'app_version_code' => version_code
175
- }
176
- post_multipart(uri, params)
177
- end
178
- end
45
+ private
179
46
 
180
47
  def post_multipart(uri, params)
181
48
  request = Net::HTTP::Post::Multipart.new(uri.path, params)
@@ -183,8 +50,12 @@ module Luciq
183
50
  execute(uri, request)
184
51
  end
185
52
 
186
- def source_map_content_type(file_path)
187
- file_path.downcase.end_with?('.json') ? 'application/json' : 'text/plain'
53
+ def content_type_for(file_path)
54
+ case File.extname(file_path).downcase
55
+ when '.json' then 'application/json'
56
+ when '.txt' then 'text/plain'
57
+ else 'application/octet-stream'
58
+ end
188
59
  end
189
60
 
190
61
  def build_uri(path)
data/lib/luciq/cli.rb CHANGED
@@ -15,7 +15,7 @@ module Luciq
15
15
  desc 'login', 'Authenticate with Luciq'
16
16
  long_desc <<~DESC
17
17
  Authenticate with Luciq using a CLI token.
18
- Generate a CLI token from your Luciq dashboard: https://dashboard.luciq.ai/company/cli
18
+ Generate a CLI token from your Luciq dashboard: https://dashboard.luciq.ai/company/luciq-cli
19
19
  The token will be saved in ~/.luciqrc
20
20
  DESC
21
21
  option :auth_token, type: :string, desc: 'CLI authentication token'
@@ -16,7 +16,7 @@ module Luciq
16
16
  unless token
17
17
  puts 'Login to Luciq'
18
18
  puts '=' * 40
19
- puts 'Generate a CLI token from your Luciq dashboard: https://dashboard.luciq.ai/company/cli'
19
+ puts 'Generate a CLI token from your Luciq dashboard: https://dashboard.luciq.ai/company/luciq-cli'
20
20
  puts
21
21
  puts "Note: For self-hosted clusters, replace 'dashboard' with your cluster name."
22
22
  puts
@@ -101,6 +101,9 @@ module Luciq
101
101
  if action && (@options[:status] || @options[:priority])
102
102
  raise 'Duplicate marking cannot be combined with --status/--priority'
103
103
  end
104
+ if @options[:clear_tags] && @options[:tag_action]
105
+ raise '--clear-tags cannot be combined with --tag-action (--clear-tags already replaces with an empty set)'
106
+ end
104
107
 
105
108
  changes = {
106
109
  status_id: map_one(BUG_STATUS_IDS, @options[:status]),
@@ -109,9 +112,19 @@ module Luciq
109
112
  action: action,
110
113
  original_bug_number: @options[:duplicate_of]
111
114
  }.compact
112
- return changes unless changes.empty?
113
115
 
114
- raise 'Provide at least one change (--status/--priority/--tags/--clear-tags/--duplicate-of/--action)'
116
+ if changes.empty?
117
+ raise 'Provide at least one change (--status/--priority/--tags/--clear-tags/--duplicate-of/--action)'
118
+ end
119
+
120
+ # tag_action only modifies how --tags is applied; on its own it is not a change
121
+ if @options[:clear_tags]
122
+ changes[:tag_action] = 'replace'
123
+ elsif @options[:tag_action]
124
+ changes[:tag_action] = @options[:tag_action]
125
+ end
126
+
127
+ changes
115
128
  end
116
129
 
117
130
  def bug_duplicate_action
@@ -13,144 +13,68 @@ module Luciq
13
13
  end
14
14
 
15
15
  def ios_dsym(file_path)
16
- run_upload(file_path, 'iOS dSYM file', format: :zip) do
17
- @client.upload_ios_dsym(file_path: file_path, app_token: @options[:app_token])
18
- end
16
+ run_upload('ios-dsym', file_path, 'iOS dSYM file', format: :zip)
19
17
  end
20
18
 
21
19
  def android_mapping(file_path)
22
- run_upload(file_path, 'Android mapping file') do
23
- @client.upload_android_mapping(
24
- file_path: file_path,
25
- app_token: @options[:app_token],
26
- version_code: @options[:version_code],
27
- version_name: @options[:version_name]
28
- )
29
- end
20
+ run_upload('android-mapping', file_path, 'Android mapping file')
30
21
  end
31
22
 
32
23
  def android_ndk(file_path)
33
- run_upload(file_path, 'Android NDK .so files', format: :zip) do
34
- validate_arch!
35
- @client.upload_android_ndk(
36
- file_path: file_path,
37
- app_token: @options[:app_token],
38
- app_version: @options[:version_name],
39
- arch: @options[:arch]
40
- )
41
- end
24
+ run_upload('android-ndk', file_path, 'Android NDK .so files', format: :zip, arch: true)
42
25
  end
43
26
 
44
27
  def react_native_ios_dsym(file_path)
45
- run_upload(file_path, 'React Native iOS dSYM', format: :zip) do
46
- @client.upload_react_native_ios_dsym(file_path: file_path, app_token: @options[:app_token])
47
- end
28
+ run_upload('react-native-ios-dsym', file_path, 'React Native iOS dSYM', format: :zip)
48
29
  end
49
30
 
50
31
  def react_native_ios_sourcemap(file_path)
51
- run_upload(file_path, 'React Native iOS source map', format: :sourcemap) do
52
- @client.upload_react_native_ios_sourcemap(
53
- file_path: file_path,
54
- app_token: @options[:app_token],
55
- app_version: build_app_version
56
- )
57
- end
32
+ run_upload('react-native-ios-sourcemap', file_path, 'React Native iOS source map', format: :sourcemap)
58
33
  end
59
34
 
60
35
  def react_native_android_mapping(file_path)
61
- run_upload(file_path, 'React Native Android mapping file') do
62
- @client.upload_react_native_android_mapping(
63
- file_path: file_path,
64
- app_token: @options[:app_token],
65
- version_code: @options[:version_code],
66
- version_name: @options[:version_name]
67
- )
68
- end
36
+ run_upload('react-native-android-mapping', file_path, 'React Native Android mapping file')
69
37
  end
70
38
 
71
39
  def react_native_android_sourcemap(file_path)
72
- run_upload(file_path, 'React Native Android source map', format: :sourcemap) do
73
- @client.upload_react_native_android_sourcemap(
74
- file_path: file_path,
75
- app_token: @options[:app_token],
76
- app_version: build_app_version
77
- )
78
- end
40
+ run_upload('react-native-android-sourcemap', file_path, 'React Native Android source map', format: :sourcemap)
79
41
  end
80
42
 
81
43
  def react_native_ndk(file_path)
82
- run_upload(file_path, 'React Native NDK .so files', format: :zip) do
83
- validate_arch!
84
- @client.upload_react_native_ndk(
85
- file_path: file_path,
86
- app_token: @options[:app_token],
87
- app_version: @options[:version_name],
88
- arch: @options[:arch]
89
- )
90
- end
44
+ run_upload('react-native-ndk', file_path, 'React Native NDK .so files', format: :zip, arch: true)
91
45
  end
92
46
 
93
47
  def flutter_ios_dsym(file_path)
94
- run_upload(file_path, 'Flutter iOS dSYM', format: :zip) do
95
- @client.upload_flutter_ios_dsym(file_path: file_path, app_token: @options[:app_token])
96
- end
48
+ run_upload('flutter-ios-dsym', file_path, 'Flutter iOS dSYM', format: :zip)
97
49
  end
98
50
 
99
51
  def flutter_ios_sourcemap(file_path)
100
- run_upload(file_path, 'Flutter iOS Dart sourcemap', format: :zip) do
101
- @client.upload_flutter_ios_sourcemap(
102
- file_path: file_path,
103
- app_token: @options[:app_token],
104
- version_name: @options[:version_name],
105
- version_code: @options[:version_code]
106
- )
107
- end
52
+ run_upload('flutter-ios-sourcemap', file_path, 'Flutter iOS Dart sourcemap', format: :zip)
108
53
  end
109
54
 
110
55
  def flutter_android_mapping(file_path)
111
- run_upload(file_path, 'Flutter Android mapping file') do
112
- @client.upload_flutter_android_mapping(
113
- file_path: file_path,
114
- app_token: @options[:app_token],
115
- version_code: @options[:version_code],
116
- version_name: @options[:version_name]
117
- )
118
- end
56
+ run_upload('flutter-android-mapping', file_path, 'Flutter Android mapping file')
119
57
  end
120
58
 
121
59
  def flutter_android_sourcemap(file_path)
122
- run_upload(file_path, 'Flutter Android Dart sourcemap', format: :zip) do
123
- @client.upload_flutter_android_sourcemap(
124
- file_path: file_path,
125
- app_token: @options[:app_token],
126
- version_name: @options[:version_name],
127
- version_code: @options[:version_code]
128
- )
129
- end
60
+ run_upload('flutter-android-sourcemap', file_path, 'Flutter Android Dart sourcemap', format: :zip)
130
61
  end
131
62
 
132
63
  def flutter_ndk(file_path)
133
- run_upload(file_path, 'Flutter NDK .so files', format: :zip) do
134
- validate_arch!
135
- @client.upload_flutter_ndk(
136
- file_path: file_path,
137
- app_token: @options[:app_token],
138
- app_version: @options[:version_name],
139
- arch: @options[:arch]
140
- )
141
- end
64
+ run_upload('flutter-ndk', file_path, 'Flutter NDK .so files', format: :zip, arch: true)
142
65
  end
143
66
 
144
67
  private
145
68
 
146
- def run_upload(file_path, label, format: nil)
69
+ def run_upload(command, file_path, label, format: nil, arch: false)
147
70
  validate_file!(file_path)
148
71
  validate_format!(file_path, format)
72
+ validate_arch! if arch
149
73
 
150
74
  puts "Uploading #{label}: #{File.basename(file_path)}"
151
75
  puts
152
76
 
153
- yield
77
+ @client.upload(command, file_path, @options)
154
78
 
155
79
  puts "✓ #{label} uploaded successfully!"
156
80
  rescue StandardError => e
@@ -198,14 +122,6 @@ module Luciq
198
122
  puts " Allowed values: #{ALLOWED_ARCHITECTURES.join(', ')}"
199
123
  exit 1
200
124
  end
201
-
202
- def build_app_version
203
- {
204
- code: @options[:version_code],
205
- name: @options[:version_name],
206
- codepush: @options[:codepush]
207
- }.compact
208
- end
209
125
  end
210
126
  end
211
127
  end
@@ -199,6 +199,10 @@ module Luciq
199
199
  Update a bug. Provide at least one change: --status/--priority/--tags/--clear-tags
200
200
  change the bug; duplicate marking is a separate action and can't be combined with them.
201
201
 
202
+ Tags: --tags appends to the existing tags by default. Use --tag-action to change
203
+ that: replace sets --tags as the full list (removing others); remove deletes --tags
204
+ and keeps the rest. --clear-tags removes all tags.
205
+
202
206
  Mark a duplicate: --duplicate-of <master-number> (implies --action mark_as_duplicate).
203
207
  Detach a duplicate: --action unmark_as_duplicate.
204
208
  DESC
@@ -206,8 +210,10 @@ module Luciq
206
210
  option :number, type: :numeric, required: true, desc: 'Bug number'
207
211
  option :status, type: :string, enum: %w[new closed in_progress], desc: 'New status'
208
212
  option :priority, type: :string, enum: %w[na trivial minor major blocker], desc: 'New priority'
209
- option :tags, type: :array, desc: 'Tags to set (replaces all existing tags)'
210
- option :clear_tags, type: :boolean, desc: 'Remove all tags'
213
+ option :tags, type: :array, desc: 'Tags to apply (see --tag-action; appends to existing tags by default)'
214
+ option :tag_action, type: :string, enum: %w[replace append remove],
215
+ desc: 'How --tags is applied: append (default, keep existing), replace, remove'
216
+ option :clear_tags, type: :boolean, desc: 'Remove all tags (cannot be combined with --tag-action)'
211
217
  option :duplicate_of, type: :numeric, desc: 'Master bug number (marks this a duplicate)'
212
218
  option :action, type: :string, enum: %w[mark_as_duplicate unmark_as_duplicate],
213
219
  desc: 'Duplicate action (defaults to mark_as_duplicate with --duplicate-of)'
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'thor'
4
4
  require 'luciq/commands/upload'
5
+ require 'luciq/query_cli'
5
6
 
6
7
  module Luciq
7
8
  class UploadCLI < Thor
@@ -12,9 +13,9 @@ module Luciq
12
13
  Upload iOS dSYM files to Luciq for crash symbolication.
13
14
  File format: .zip containing dSYM files
14
15
  Example:
15
- luciq upload ios-dsym MyApp.dSYM.zip --app-token APP_TOKEN
16
+ luciq upload ios-dsym MyApp.dSYM.zip --slug my-app --mode production
16
17
  DESC
17
- option :app_token, type: :string, required: true, desc: 'Your Luciq application token'
18
+ QueryOptions.app(self)
18
19
  def ios_dsym(file)
19
20
  Commands::Upload.new(options).ios_dsym(file)
20
21
  end
@@ -24,9 +25,9 @@ module Luciq
24
25
  Upload Android Proguard/R8 mapping files to Luciq for crash deobfuscation.
25
26
  File format: mapping.txt
26
27
  Example:
27
- luciq upload android-mapping mapping.txt --app-token APP_TOKEN --version-name 1.0.0 --version-code 1
28
+ luciq upload android-mapping mapping.txt --slug my-app --mode production --version-name 1.0.0 --version-code 1
28
29
  DESC
29
- option :app_token, type: :string, required: true, desc: 'Your Luciq application token'
30
+ QueryOptions.app(self)
30
31
  option :version_name, type: :string, required: true, desc: 'App version name (e.g., 1.0.0)'
31
32
  option :version_code, type: :string, required: true, desc: 'App version code (e.g., 1)'
32
33
  def android_mapping(file)
@@ -38,9 +39,9 @@ module Luciq
38
39
  Upload Android NDK shared object (.so) files to Luciq for native crash symbolication.
39
40
  File format: .zip containing the .so files
40
41
  Example:
41
- luciq upload android-ndk so-files.zip --app-token APP_TOKEN --version-name 1.0.0 --arch arm64-v8a
42
+ luciq upload android-ndk so-files.zip --slug my-app --mode production --version-name 1.0.0 --arch arm64-v8a
42
43
  DESC
43
- option :app_token, type: :string, required: true, desc: 'Your Luciq application token'
44
+ QueryOptions.app(self)
44
45
  option :version_name, type: :string, required: true, desc: 'App version name (e.g., 1.0.0)'
45
46
  option :arch, type: :string, required: true, enum: ARCHITECTURES, desc: 'CPU architecture'
46
47
  def android_ndk(file)
@@ -52,9 +53,9 @@ module Luciq
52
53
  Upload React Native iOS dSYM files to Luciq for native crash symbolication.
53
54
  File format: .zip containing dSYM files
54
55
  Example:
55
- luciq upload react-native-ios-dsym dsyms.zip --app-token APP_TOKEN
56
+ luciq upload react-native-ios-dsym dsyms.zip --slug my-app --mode production
56
57
  DESC
57
- option :app_token, type: :string, required: true, desc: 'Your application token'
58
+ QueryOptions.app(self)
58
59
  def react_native_ios_dsym(file)
59
60
  Commands::Upload.new(options).react_native_ios_dsym(file)
60
61
  end
@@ -64,9 +65,9 @@ module Luciq
64
65
  Upload React Native iOS JavaScript source maps to Luciq for crash symbolication.
65
66
  File format: .json or .txt source map
66
67
  Example:
67
- luciq upload react-native-ios-sourcemap ios-sourcemap.json --app-token APP_TOKEN --version-code 1 --version-name 1.0.0
68
+ luciq upload react-native-ios-sourcemap ios-sourcemap.json --slug my-app --mode production --version-code 1 --version-name 1.0.0
68
69
  DESC
69
- option :app_token, type: :string, required: true, desc: 'Your application token'
70
+ QueryOptions.app(self)
70
71
  option :version_code, type: :string, required: true, desc: 'App version code (e.g., 1)'
71
72
  option :version_name, type: :string, required: true, desc: 'App version name (e.g., 1.0.0)'
72
73
  option :codepush, type: :string, desc: 'CodePush version label (e.g., v42)'
@@ -79,9 +80,9 @@ module Luciq
79
80
  Upload React Native Android Proguard/R8 mapping files to Luciq for native crash deobfuscation.
80
81
  File format: mapping.txt
81
82
  Example:
82
- luciq upload react-native-android-mapping mapping.txt --app-token APP_TOKEN --version-code 1 --version-name 1.0.0
83
+ luciq upload react-native-android-mapping mapping.txt --slug my-app --mode production --version-code 1 --version-name 1.0.0
83
84
  DESC
84
- option :app_token, type: :string, required: true, desc: 'Your application token'
85
+ QueryOptions.app(self)
85
86
  option :version_code, type: :string, required: true, desc: 'App version code (e.g., 1)'
86
87
  option :version_name, type: :string, required: true, desc: 'App version name (e.g., 1.0.0)'
87
88
  def react_native_android_mapping(file)
@@ -93,9 +94,9 @@ module Luciq
93
94
  Upload React Native Android JavaScript source maps to Luciq for crash deobfuscation.
94
95
  File format: .json or .txt source map
95
96
  Example:
96
- luciq upload react-native-android-sourcemap android-sourcemap.json --app-token APP_TOKEN --version-code 1 --version-name 1.0.0
97
+ luciq upload react-native-android-sourcemap android-sourcemap.json --slug my-app --mode production --version-code 1 --version-name 1.0.0
97
98
  DESC
98
- option :app_token, type: :string, required: true, desc: 'Your application token'
99
+ QueryOptions.app(self)
99
100
  option :version_code, type: :string, required: true, desc: 'App version code (e.g., 1)'
100
101
  option :version_name, type: :string, required: true, desc: 'App version name (e.g., 1.0.0)'
101
102
  option :codepush, type: :string, desc: 'CodePush version label (e.g., v42)'
@@ -108,9 +109,9 @@ module Luciq
108
109
  Upload React Native NDK shared object (.so) files to Luciq for native crash symbolication.
109
110
  File format: .zip containing the .so files
110
111
  Example:
111
- luciq upload react-native-ndk so-files.zip --app-token APP_TOKEN --version-name 1.0.0 --arch arm64-v8a
112
+ luciq upload react-native-ndk so-files.zip --slug my-app --mode production --version-name 1.0.0 --arch arm64-v8a
112
113
  DESC
113
- option :app_token, type: :string, required: true, desc: 'Your application token'
114
+ QueryOptions.app(self)
114
115
  option :version_name, type: :string, required: true, desc: 'App version name (e.g., 1.0.0)'
115
116
  option :arch, type: :string, required: true, enum: ARCHITECTURES, desc: 'CPU architecture'
116
117
  def react_native_ndk(file)
@@ -122,9 +123,9 @@ module Luciq
122
123
  Upload Flutter iOS dSYM files to Luciq for native crash symbolication.
123
124
  File format: .zip containing dSYM files
124
125
  Example:
125
- luciq upload flutter-ios-dsym MyApp.dSYM.zip --app-token APP_TOKEN
126
+ luciq upload flutter-ios-dsym MyApp.dSYM.zip --slug my-app --mode production
126
127
  DESC
127
- option :app_token, type: :string, required: true, desc: 'Your Luciq application token'
128
+ QueryOptions.app(self)
128
129
  def flutter_ios_dsym(file)
129
130
  Commands::Upload.new(options).flutter_ios_dsym(file)
130
131
  end
@@ -134,9 +135,9 @@ module Luciq
134
135
  Upload Flutter iOS Dart sourcemap files to Luciq for crash symbolication.
135
136
  File format: .zip containing Flutter debug symbols
136
137
  Example:
137
- luciq upload flutter-ios-sourcemap app.ios-arm64.symbols.zip --app-token APP_TOKEN --version-name 1.0.0 --version-code 1
138
+ luciq upload flutter-ios-sourcemap app.ios-arm64.symbols.zip --slug my-app --mode production --version-name 1.0.0 --version-code 1
138
139
  DESC
139
- option :app_token, type: :string, required: true, desc: 'Your Luciq application token'
140
+ QueryOptions.app(self)
140
141
  option :version_name, type: :string, required: true, desc: 'App version name (e.g., 1.0.0)'
141
142
  option :version_code, type: :string, required: true, desc: 'App version code (e.g., 1)'
142
143
  def flutter_ios_sourcemap(file)
@@ -148,9 +149,9 @@ module Luciq
148
149
  Upload Flutter Android Proguard/R8 mapping files to Luciq for native crash deobfuscation.
149
150
  File format: mapping.txt
150
151
  Example:
151
- luciq upload flutter-android-mapping mapping.txt --app-token APP_TOKEN --version-code 1 --version-name 1.0.0
152
+ luciq upload flutter-android-mapping mapping.txt --slug my-app --mode production --version-code 1 --version-name 1.0.0
152
153
  DESC
153
- option :app_token, type: :string, required: true, desc: 'Your Luciq application token'
154
+ QueryOptions.app(self)
154
155
  option :version_code, type: :string, required: true, desc: 'App version code (e.g., 1)'
155
156
  option :version_name, type: :string, required: true, desc: 'App version name (e.g., 1.0.0)'
156
157
  def flutter_android_mapping(file)
@@ -162,9 +163,9 @@ module Luciq
162
163
  Upload Flutter Android Dart sourcemap files to Luciq for crash symbolication.
163
164
  File format: .zip containing Flutter debug symbols
164
165
  Example:
165
- luciq upload flutter-android-sourcemap app.android-arm64.symbols.zip --app-token APP_TOKEN --version-name 1.0.0 --version-code 1
166
+ luciq upload flutter-android-sourcemap app.android-arm64.symbols.zip --slug my-app --mode production --version-name 1.0.0 --version-code 1
166
167
  DESC
167
- option :app_token, type: :string, required: true, desc: 'Your Luciq application token'
168
+ QueryOptions.app(self)
168
169
  option :version_name, type: :string, required: true, desc: 'App version name (e.g., 1.0.0)'
169
170
  option :version_code, type: :string, required: true, desc: 'App version code (e.g., 1)'
170
171
  def flutter_android_sourcemap(file)
@@ -176,9 +177,9 @@ module Luciq
176
177
  Upload Flutter NDK shared object (.so) files to Luciq for native crash symbolication.
177
178
  File format: .zip containing the .so files
178
179
  Example:
179
- luciq upload flutter-ndk so-files.zip --app-token APP_TOKEN --version-name 1.0.0 --arch arm64-v8a
180
+ luciq upload flutter-ndk so-files.zip --slug my-app --mode production --version-name 1.0.0 --arch arm64-v8a
180
181
  DESC
181
- option :app_token, type: :string, required: true, desc: 'Your Luciq application token'
182
+ QueryOptions.app(self)
182
183
  option :version_name, type: :string, required: true, desc: 'App version name (e.g., 1.0.0)'
183
184
  option :arch, type: :string, required: true, enum: ARCHITECTURES, desc: 'CPU architecture'
184
185
  def flutter_ndk(file)
data/lib/luciq/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Luciq
4
- VERSION = '0.2.0'
4
+ VERSION = '0.3.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: luciq-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ahmed Hany
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-07-06 00:00:00.000000000 Z
11
+ date: 2026-08-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: multipart-post
@@ -47,6 +47,7 @@ extensions: []
47
47
  extra_rdoc_files: []
48
48
  files:
49
49
  - CHANGELOG.md
50
+ - LICENSE
50
51
  - README.md
51
52
  - bin/luciq
52
53
  - lib/luciq/api/client.rb
@@ -59,13 +60,13 @@ files:
59
60
  - lib/luciq/query_cli.rb
60
61
  - lib/luciq/upload_cli.rb
61
62
  - lib/luciq/version.rb
62
- homepage: https://github.com/Instabug/luciq-cli
63
+ homepage: https://github.com/luciqai/luciq-cli
63
64
  licenses:
64
65
  - MIT
65
66
  metadata:
66
- homepage_uri: https://github.com/Instabug/luciq-cli
67
- source_code_uri: https://github.com/Instabug/luciq-cli
68
- changelog_uri: https://github.com/Instabug/luciq-cli/blob/main/CHANGELOG.md
67
+ homepage_uri: https://github.com/luciqai/luciq-cli
68
+ source_code_uri: https://github.com/luciqai/luciq-cli
69
+ changelog_uri: https://github.com/luciqai/luciq-cli/blob/main/CHANGELOG.md
69
70
  rubygems_mfa_required: 'true'
70
71
  post_install_message:
71
72
  rdoc_options: []