luciq-cli 0.1.0 → 0.2.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 +4 -4
- data/CHANGELOG.md +17 -0
- data/README.md +212 -4
- data/lib/luciq/api/client.rb +159 -10
- data/lib/luciq/cli.rb +46 -17
- data/lib/luciq/commands/query.rb +228 -0
- data/lib/luciq/commands/query_arguments.rb +166 -0
- data/lib/luciq/commands/upload.rb +167 -12
- data/lib/luciq/config.rb +2 -1
- data/lib/luciq/query_cli.rb +579 -0
- data/lib/luciq/upload_cli.rb +188 -0
- data/lib/luciq/version.rb +1 -1
- metadata +8 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6882405a8d129fe6337191a3fafcf09ce88290166b640acb36c016af9c4bdd35
|
|
4
|
+
data.tar.gz: 866a5f76f6d8d1b922b24c73dc33687c9a9cd7dc8a139722d449c129b8e474a5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2c48c7bbb0a56349d8bd41e9ad78a7b4c459a50e0befa5dd1b69a2eb98beca174c3fb1cfdbfba1d2815b55ab9b8eb96b75738f5b49abf4c9ee45d19b9bc208e9
|
|
7
|
+
data.tar.gz: 5eb8bc3833309adfb682cfcf3c13b2c4690803a4086df71f8fa8b6d23be99b865bdcbbb144d390d65b0b4f46b6e6377da79e894dfb168daac14bdd795729f561
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.0] - 2026-07-05
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `luciq crashes` - Query crashes: `list`, `show`, `patterns`, `diagnostics`, `hangs`, `occurrence-tokens`, `occurrence`
|
|
15
|
+
- `luciq bugs` - Query and update bugs: `list`, `show`, `update` (status, priority, tags, duplicate marking)
|
|
16
|
+
- `luciq apm` - Query APM data: `groups`, `group`, `occurrence`, and funnels (`funnel-events`, `funnel-create`, `funnel-update`, `funnel-delete`)
|
|
17
|
+
- `luciq reviews list` - Query app reviews
|
|
18
|
+
- `luciq surveys` - Query surveys: `list`, `show`
|
|
19
|
+
- `luciq apps list` - List accessible applications
|
|
20
|
+
- `luciq insights` - Show aggregated app-health insights
|
|
21
|
+
- `luciq issues list` - List issues across sources ranked by impact
|
|
22
|
+
- `luciq opportunities` - Query opportunities: `list`, `show`
|
|
23
|
+
- `luciq alerts` - Manage alert rules: `list`, `show`, `init`, `create`, `update`, `delete`
|
|
24
|
+
- `luciq incidents` - Manage triggered alerts: `list`, `show`, `resolve`, `reopen`
|
|
25
|
+
- Typed filter flags with a raw `--filters` JSON escape hatch on query commands
|
|
26
|
+
|
|
10
27
|
## [0.1.0] - 2025-12-16
|
|
11
28
|
|
|
12
29
|
### Added
|
data/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# Luciq CLI
|
|
2
2
|
|
|
3
|
-
Command-line interface for
|
|
3
|
+
Command-line interface for Luciq: upload symbol files and query or manage your
|
|
4
|
+
app data — crashes, bugs, APM, reviews, surveys, issues, opportunities, alerts,
|
|
5
|
+
and more.
|
|
4
6
|
|
|
5
7
|
## Installation
|
|
6
8
|
|
|
@@ -46,10 +48,10 @@ luciq login --auth-token YOUR_CLI_TOKEN
|
|
|
46
48
|
|
|
47
49
|
### 2. Upload Symbol Files
|
|
48
50
|
|
|
49
|
-
Upload Android mapping files (
|
|
51
|
+
Upload Android mapping files (`mapping.txt`):
|
|
50
52
|
|
|
51
53
|
```bash
|
|
52
|
-
luciq upload android-mapping mapping.
|
|
54
|
+
luciq upload android-mapping mapping.txt \
|
|
53
55
|
--app-token YOUR_APP_TOKEN \
|
|
54
56
|
--version-name 1.0.0 \
|
|
55
57
|
--version-code 1
|
|
@@ -69,8 +71,214 @@ luciq upload android-mapping mapping.zip \
|
|
|
69
71
|
| `luciq logout` | Remove saved authentication |
|
|
70
72
|
| `luciq whoami` | Show current authenticated user |
|
|
71
73
|
| `luciq info` | Show CLI configuration |
|
|
72
|
-
| `luciq upload android-mapping FILE` | Upload Android mapping file |
|
|
73
74
|
| `luciq version` | Show CLI version |
|
|
75
|
+
| `luciq upload` | Upload symbol files (see below) |
|
|
76
|
+
| `luciq crashes` | Query crashes, hangs, and occurrences |
|
|
77
|
+
| `luciq bugs` | Query and update bugs |
|
|
78
|
+
| `luciq apm` | Query APM metrics and manage funnels |
|
|
79
|
+
| `luciq reviews` | Query app store reviews |
|
|
80
|
+
| `luciq surveys` | Query surveys and their responses |
|
|
81
|
+
| `luciq apps` | List accessible applications |
|
|
82
|
+
| `luciq issues` | List issues across sources, ranked by impact |
|
|
83
|
+
| `luciq opportunities` | Query product opportunities |
|
|
84
|
+
| `luciq alerts` | Manage alert rules |
|
|
85
|
+
| `luciq incidents` | Manage triggered alerts |
|
|
86
|
+
| `luciq insights` | Show aggregated app-health insights |
|
|
87
|
+
|
|
88
|
+
See [Query & management commands](#query--management-commands) for the full option
|
|
89
|
+
set of the data commands.
|
|
90
|
+
|
|
91
|
+
### Upload commands
|
|
92
|
+
|
|
93
|
+
Run `luciq upload help SUBCOMMAND` for the full options of any command.
|
|
94
|
+
|
|
95
|
+
**iOS**
|
|
96
|
+
|
|
97
|
+
| Command | File | Description |
|
|
98
|
+
|---------|------|-------------|
|
|
99
|
+
| `luciq upload ios-dsym FILE` | `.zip` | iOS dSYM symbols |
|
|
100
|
+
|
|
101
|
+
**Android**
|
|
102
|
+
|
|
103
|
+
| Command | File | Description |
|
|
104
|
+
|---------|------|-------------|
|
|
105
|
+
| `luciq upload android-mapping FILE` | `mapping.txt` | Proguard/R8 mapping file |
|
|
106
|
+
| `luciq upload android-ndk FILE` | `.zip` | NDK `.so` shared object files (`--arch`) |
|
|
107
|
+
|
|
108
|
+
**React Native**
|
|
109
|
+
|
|
110
|
+
| Command | File | Description |
|
|
111
|
+
|---------|------|-------------|
|
|
112
|
+
| `luciq upload react-native-ios-dsym FILE` | `.zip` | iOS native dSYM symbols |
|
|
113
|
+
| `luciq upload react-native-ios-sourcemap FILE` | `.json`/`.txt` | iOS JavaScript source map |
|
|
114
|
+
| `luciq upload react-native-android-mapping FILE` | `mapping.txt` | Android native Proguard/R8 mapping |
|
|
115
|
+
| `luciq upload react-native-android-sourcemap FILE` | `.json`/`.txt` | Android JavaScript source map |
|
|
116
|
+
| `luciq upload react-native-ndk FILE` | `.zip` | NDK `.so` shared object files (`--arch`) |
|
|
117
|
+
|
|
118
|
+
**Flutter**
|
|
119
|
+
|
|
120
|
+
| Command | File | Description |
|
|
121
|
+
|---------|------|-------------|
|
|
122
|
+
| `luciq upload flutter-ios-dsym FILE` | `.zip` | iOS native dSYM symbols |
|
|
123
|
+
| `luciq upload flutter-ios-sourcemap FILE` | `.zip` | iOS Dart symbol files |
|
|
124
|
+
| `luciq upload flutter-android-mapping FILE` | `mapping.txt` | Android native Proguard/R8 mapping |
|
|
125
|
+
| `luciq upload flutter-android-sourcemap FILE` | `.zip` | Android Dart symbol files |
|
|
126
|
+
| `luciq upload flutter-ndk FILE` | `.zip` | NDK `.so` shared object files (`--arch`) |
|
|
127
|
+
|
|
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`.
|
|
133
|
+
|
|
134
|
+
## Query & management commands
|
|
135
|
+
|
|
136
|
+
Beyond uploading symbols, the CLI queries and manages your Luciq data. Every
|
|
137
|
+
response is printed as pretty-printed JSON, so it pipes cleanly into tools like
|
|
138
|
+
`jq`.
|
|
139
|
+
|
|
140
|
+
### Shared options
|
|
141
|
+
|
|
142
|
+
Most data commands target one application in one environment and share a common
|
|
143
|
+
set of options:
|
|
144
|
+
|
|
145
|
+
| Option | Description |
|
|
146
|
+
|--------|-------------|
|
|
147
|
+
| `--slug` | Application slug (required) |
|
|
148
|
+
| `--mode` | Environment: `production`, `beta`, `staging`, `alpha`, `qa`, `development` (required) |
|
|
149
|
+
| `--offset`, `--limit` | Pagination |
|
|
150
|
+
| `--sort-by`, `--direction` | Sort field and direction (`asc`/`desc`) — some commands use `--sort-direction` instead |
|
|
151
|
+
| `--filters` | Raw filter JSON object, merged with the typed filter flags (typed flags win on key conflicts) |
|
|
152
|
+
|
|
153
|
+
Common filters have dedicated typed flags (`--status`, `--platform`,
|
|
154
|
+
`--app-version`, …). Anything a tool supports but that has no flag can be passed
|
|
155
|
+
through `--filters`, e.g. `--filters '{"devices":["iPhone15,2"],"os_versions":["17.4"]}'`.
|
|
156
|
+
Run `luciq <group> help <subcommand>` for the full option list and the filter
|
|
157
|
+
keys each command accepts.
|
|
158
|
+
|
|
159
|
+
### Crashes
|
|
160
|
+
|
|
161
|
+
`luciq crashes SUBCOMMAND`
|
|
162
|
+
|
|
163
|
+
| Subcommand | Description |
|
|
164
|
+
|------------|-------------|
|
|
165
|
+
| `list` | List crashes. Flags: `--status` (open/closed/in_progress), `--platform` (IOS/ANDROID/DART/JAVASCRIPT), `--type` (CRASH/ANR/OOM/NON_FATAL), `--app-version` |
|
|
166
|
+
| `show --number N` | Details for one crash |
|
|
167
|
+
| `patterns --number N` | Grouped patterns. Flags: `--pattern-key`, `--sort-by`, `--direction` |
|
|
168
|
+
| `diagnostics --number N` | Diagnostics for one crash |
|
|
169
|
+
| `hangs` | List app hangs (same filters as `list`) |
|
|
170
|
+
| `occurrence-tokens --number N` | Occurrence cursor tokens. Flags: `--current-token`, `--direction` (first/last) |
|
|
171
|
+
| `occurrence --number N --ulid U` | Details for a single occurrence |
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
luciq crashes list --slug my-app --mode production --status open --limit 20
|
|
175
|
+
luciq crashes show --slug my-app --mode production --number 42
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Bugs
|
|
179
|
+
|
|
180
|
+
`luciq bugs SUBCOMMAND`
|
|
181
|
+
|
|
182
|
+
| Subcommand | Description |
|
|
183
|
+
|------------|-------------|
|
|
184
|
+
| `list` | List bugs. Flags: `--status` (new/closed/in_progress), `--priority` (na/trivial/minor/major/blocker), `--app-version` |
|
|
185
|
+
| `show --number N` | Details for one bug |
|
|
186
|
+
| `update --number N` | Update a bug. Flags: `--status`, `--priority`, `--tags`, `--clear-tags`, `--duplicate-of`, `--action` |
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
luciq bugs update --slug my-app --mode production --number 7 --status closed --priority major
|
|
190
|
+
luciq bugs update --slug my-app --mode production --number 7 --duplicate-of 3
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
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.
|
|
196
|
+
|
|
197
|
+
### APM
|
|
198
|
+
|
|
199
|
+
`luciq apm SUBCOMMAND`
|
|
200
|
+
|
|
201
|
+
| Subcommand | Description |
|
|
202
|
+
|------------|-------------|
|
|
203
|
+
| `groups --metric M` | Rank groups worst-first. `--metric`: network, launch, flows, screen_loading, frame_drop, funnels. `--sort` is a JSON object `{"by":…,"direction":…}` |
|
|
204
|
+
| `group --metric M` | Panels for one group; identify with `--group-uuid` or `--group-url`. `--views` is a JSON array (default `["summary"]`); `--method` applies to network |
|
|
205
|
+
| `occurrence --metric M --selector S` | Inspect occurrences (`--selector`: worst/by_token/list). Flags: `--token`, `--current-token`, `--direction`, `--limit` |
|
|
206
|
+
| `funnel-events` | List events pickable for funnels. Flags: `--event-type`, `--q`, `--limit` |
|
|
207
|
+
| `funnel-create --name N --events JSON` | Create a funnel from 2–20 steps |
|
|
208
|
+
| `funnel-update --ulid U` | Update a funnel (`--name`, `--events`) |
|
|
209
|
+
| `funnel-delete --ulid U` | Delete a funnel |
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
luciq apm groups --slug my-app --mode production --metric network \
|
|
213
|
+
--sort '{"by":"p95","direction":"desc"}'
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Reviews
|
|
217
|
+
|
|
218
|
+
`luciq reviews list` — Flags: `--rating` (1–5), `--country`, `--os` (ios/android),
|
|
219
|
+
`--sort-by date`, `--sort-direction`.
|
|
220
|
+
|
|
221
|
+
### Surveys
|
|
222
|
+
|
|
223
|
+
`luciq surveys SUBCOMMAND`
|
|
224
|
+
|
|
225
|
+
| Subcommand | Description |
|
|
226
|
+
|------------|-------------|
|
|
227
|
+
| `list` | List surveys. Flags: `--type` (0=custom 1=nps 2=app_store), `--status` (0=draft 1=published 2=paused) |
|
|
228
|
+
| `show --id N` | A survey's questions and response statistics. `--page` paginates responses |
|
|
229
|
+
|
|
230
|
+
### Apps
|
|
231
|
+
|
|
232
|
+
`luciq apps list` — List the applications you can access (no `--slug`/`--mode`).
|
|
233
|
+
Optional `--platform` (ios/android/react_native/flutter).
|
|
234
|
+
|
|
235
|
+
### Insights
|
|
236
|
+
|
|
237
|
+
`luciq insights --slug my-app --mode production` — Aggregated app-health insights.
|
|
238
|
+
Narrow with `--filters` (`date_ms`, `app_version`).
|
|
239
|
+
|
|
240
|
+
### Issues
|
|
241
|
+
|
|
242
|
+
`luciq issues list` — Issues across crashes, APM, AI, and bugs, ranked by Apdex
|
|
243
|
+
impact. Flags: `--limit`, `--sort-by` (apdex_impact/occurrences_counter),
|
|
244
|
+
`--sort-direction`, `--top-issues` (curated shortlist), `--pagination`
|
|
245
|
+
(per-source cursor tokens JSON).
|
|
246
|
+
|
|
247
|
+
### Opportunities
|
|
248
|
+
|
|
249
|
+
`luciq opportunities SUBCOMMAND`
|
|
250
|
+
|
|
251
|
+
| Subcommand | Description |
|
|
252
|
+
|------------|-------------|
|
|
253
|
+
| `list` | List opportunities. Flags: `--status`, `--priority` (1–4/unset), `--team-id` |
|
|
254
|
+
| `show --id N` | Details for one opportunity |
|
|
255
|
+
|
|
256
|
+
### Alerts
|
|
257
|
+
|
|
258
|
+
`luciq alerts SUBCOMMAND` — Manage alert rules.
|
|
259
|
+
|
|
260
|
+
| Subcommand | Description |
|
|
261
|
+
|------------|-------------|
|
|
262
|
+
| `list` | List rules (`--sort-by`, `--sort-direction`) |
|
|
263
|
+
| `show --ulid U` | Show one rule |
|
|
264
|
+
| `init` | Show the menu (types, triggers, conditions, actions) for building a rule |
|
|
265
|
+
| `create --payload JSON` | Create a rule |
|
|
266
|
+
| `update --ulid U --payload JSON` | Update a rule |
|
|
267
|
+
| `delete --ulid U` | Delete a rule |
|
|
268
|
+
|
|
269
|
+
Run `luciq alerts init` first to discover the valid rule building blocks, then
|
|
270
|
+
pass the rule body as `--payload`.
|
|
271
|
+
|
|
272
|
+
### Incidents
|
|
273
|
+
|
|
274
|
+
`luciq incidents SUBCOMMAND` — Manage triggered alerts.
|
|
275
|
+
|
|
276
|
+
| Subcommand | Description |
|
|
277
|
+
|------------|-------------|
|
|
278
|
+
| `list` | List incidents. Flags: `--sort-by`, `--sort-direction`, `--status`, `--type` |
|
|
279
|
+
| `show --ulid U` | Show one incident |
|
|
280
|
+
| `resolve --ulid U` | Resolve an incident |
|
|
281
|
+
| `reopen --ulid U` | Reopen an incident |
|
|
74
282
|
|
|
75
283
|
## Configuration
|
|
76
284
|
|
data/lib/luciq/api/client.rb
CHANGED
|
@@ -9,6 +9,11 @@ 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
|
+
|
|
12
17
|
def initialize
|
|
13
18
|
@token = Config.load_token
|
|
14
19
|
@base_url = Config.load_base_url
|
|
@@ -21,24 +26,166 @@ module Luciq
|
|
|
21
26
|
execute(uri, request)
|
|
22
27
|
end
|
|
23
28
|
|
|
29
|
+
# Invokes a query tool by name with a hash of arguments (slug, mode,
|
|
30
|
+
# filters, pagination, ...) sent as a JSON body.
|
|
31
|
+
def invoke_tool(tool_name, arguments = {})
|
|
32
|
+
uri = build_uri("/api/cli/tools/#{tool_name}")
|
|
33
|
+
request = Net::HTTP::Post.new(uri)
|
|
34
|
+
request['Content-Type'] = 'application/json'
|
|
35
|
+
request.body = arguments.to_json
|
|
36
|
+
apply_headers(request)
|
|
37
|
+
execute(uri, request)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def upload_ios_dsym(file_path:, app_token:)
|
|
41
|
+
upload_dsym(file_path: file_path, app_token: app_token)
|
|
42
|
+
end
|
|
43
|
+
|
|
24
44
|
def upload_android_mapping(file_path:, app_token:, version_code:, version_name:)
|
|
25
|
-
|
|
45
|
+
upload_mapping(
|
|
46
|
+
file_path: file_path, app_token: app_token,
|
|
47
|
+
version_code: version_code, version_name: version_name
|
|
48
|
+
)
|
|
49
|
+
end
|
|
26
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)
|
|
27
117
|
File.open(file_path, 'rb') do |file|
|
|
28
118
|
params = {
|
|
29
|
-
'
|
|
119
|
+
'symbols_file' => UploadIO.new(file, 'application/octet-stream', File.basename(file_path)),
|
|
30
120
|
'application_token' => app_token,
|
|
31
|
-
'
|
|
32
|
-
'app_version_name' => version_name
|
|
121
|
+
'os' => 'ios'
|
|
33
122
|
}
|
|
123
|
+
post_multipart(uri, params)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
34
126
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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)
|
|
38
137
|
end
|
|
39
138
|
end
|
|
40
139
|
|
|
41
|
-
|
|
140
|
+
def upload_react_native_sourcemap(file_path:, app_token:, os:, app_version:)
|
|
141
|
+
uri = build_uri(SYMBOLS_FILES_PATH)
|
|
142
|
+
File.open(file_path, 'rb') do |file|
|
|
143
|
+
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
|
+
}
|
|
150
|
+
post_multipart(uri, params)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
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
|
|
179
|
+
|
|
180
|
+
def post_multipart(uri, params)
|
|
181
|
+
request = Net::HTTP::Post::Multipart.new(uri.path, params)
|
|
182
|
+
apply_headers(request)
|
|
183
|
+
execute(uri, request)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def source_map_content_type(file_path)
|
|
187
|
+
file_path.downcase.end_with?('.json') ? 'application/json' : 'text/plain'
|
|
188
|
+
end
|
|
42
189
|
|
|
43
190
|
def build_uri(path)
|
|
44
191
|
URI("#{@base_url}#{path}")
|
|
@@ -56,9 +203,11 @@ module Luciq
|
|
|
56
203
|
http.read_timeout = 300
|
|
57
204
|
|
|
58
205
|
response = http.request(request)
|
|
59
|
-
|
|
206
|
+
raise "Request failed (#{response.code}): #{response.body}" unless response.is_a?(Net::HTTPSuccess)
|
|
60
207
|
|
|
61
|
-
|
|
208
|
+
JSON.parse(response.body)
|
|
209
|
+
rescue JSON::ParserError
|
|
210
|
+
response.body
|
|
62
211
|
end
|
|
63
212
|
end
|
|
64
213
|
end
|
data/lib/luciq/cli.rb
CHANGED
|
@@ -3,25 +3,10 @@
|
|
|
3
3
|
require 'thor'
|
|
4
4
|
require 'luciq/version'
|
|
5
5
|
require 'luciq/commands/auth'
|
|
6
|
-
require 'luciq/
|
|
6
|
+
require 'luciq/upload_cli'
|
|
7
|
+
require 'luciq/query_cli'
|
|
7
8
|
|
|
8
9
|
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
10
|
class CLI < Thor
|
|
26
11
|
def self.exit_on_failure?
|
|
27
12
|
true
|
|
@@ -56,6 +41,50 @@ module Luciq
|
|
|
56
41
|
desc 'upload SUBCOMMAND', 'Upload symbol files to Luciq'
|
|
57
42
|
subcommand 'upload', UploadCLI
|
|
58
43
|
|
|
44
|
+
desc 'crashes SUBCOMMAND', 'Query crashes (list, show, patterns, diagnostics, hangs, occurrence-tokens, occurrence)'
|
|
45
|
+
subcommand 'crashes', CrashesCLI
|
|
46
|
+
|
|
47
|
+
desc 'bugs SUBCOMMAND', 'Query bugs (list, show, update)'
|
|
48
|
+
subcommand 'bugs', BugsCLI
|
|
49
|
+
|
|
50
|
+
desc 'apm SUBCOMMAND', 'Query APM data (groups, group, occurrence, funnels)'
|
|
51
|
+
subcommand 'apm', ApmCLI
|
|
52
|
+
|
|
53
|
+
desc 'reviews SUBCOMMAND', 'Query app reviews (list)'
|
|
54
|
+
subcommand 'reviews', ReviewsCLI
|
|
55
|
+
|
|
56
|
+
desc 'surveys SUBCOMMAND', 'Query surveys (list, show)'
|
|
57
|
+
subcommand 'surveys', SurveysCLI
|
|
58
|
+
|
|
59
|
+
desc 'apps SUBCOMMAND', 'List accessible applications'
|
|
60
|
+
subcommand 'apps', AppsCLI
|
|
61
|
+
|
|
62
|
+
desc 'issues SUBCOMMAND', 'Query issues ranked by impact (list)'
|
|
63
|
+
subcommand 'issues', IssuesCLI
|
|
64
|
+
|
|
65
|
+
desc 'opportunities SUBCOMMAND', 'Query opportunities (list, show)'
|
|
66
|
+
subcommand 'opportunities', OpportunitiesCLI
|
|
67
|
+
|
|
68
|
+
desc 'alerts SUBCOMMAND', 'Manage alert rules (list, show, init, create, update, delete)'
|
|
69
|
+
subcommand 'alerts', AlertsCLI
|
|
70
|
+
|
|
71
|
+
desc 'incidents SUBCOMMAND', 'Manage triggered alerts (list, show, resolve, reopen)'
|
|
72
|
+
subcommand 'incidents', IncidentsCLI
|
|
73
|
+
|
|
74
|
+
desc 'insights', 'Show aggregated app-health insights for an application'
|
|
75
|
+
long_desc(<<~DESC, wrap: false)
|
|
76
|
+
Show aggregated app-health insights for an application.
|
|
77
|
+
|
|
78
|
+
Filters via --filters:
|
|
79
|
+
date_ms {"gte": <ms>, "lte": <ms>}
|
|
80
|
+
app_version ["1.2.3", ...]
|
|
81
|
+
DESC
|
|
82
|
+
QueryOptions.app(self)
|
|
83
|
+
QueryOptions.raw_filters(self)
|
|
84
|
+
def insights
|
|
85
|
+
Commands::Query.new(options).insights
|
|
86
|
+
end
|
|
87
|
+
|
|
59
88
|
desc 'version', 'Show CLI version'
|
|
60
89
|
def version
|
|
61
90
|
puts "luciq-cli #{Luciq::VERSION}"
|