arbok-inspector 0.0.0__py3-none-any.whl

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.

Potentially problematic release.


This version of arbok-inspector might be problematic. Click here for more details.

@@ -0,0 +1,141 @@
1
+ """Module to build a grid of xarray plots for a given run."""
2
+ from __future__ import annotations
3
+ from typing import TYPE_CHECKING
4
+
5
+ import math
6
+ import copy
7
+ import plotly.graph_objects as go
8
+ from nicegui import ui, app
9
+
10
+ from arbok_inspector.helpers.unit_formater import unit_formatter
11
+ from arbok_inspector.helpers.string_formaters import (
12
+ title_formater, axis_label_formater
13
+ )
14
+
15
+ if TYPE_CHECKING:
16
+ from arbok_inspector.classes.dim import Dim
17
+ from arbok_inspector.classes.run import Run
18
+ from plotly.graph_objs import Figure
19
+
20
+
21
+ def build_xarray_grid() -> None:
22
+ """
23
+ Build a grid of xarray plots for the given run.
24
+
25
+ Args:
26
+ run (Run): The Run object containing the data to plot.
27
+
28
+ Returns:
29
+ Figure: The Plotly Figure object containing the grid of plots.
30
+ """
31
+ #client = await ui.context.client.connected()
32
+ run = app.storage.tab["run"]
33
+ container = app.storage.tab["placeholders"]['plots']
34
+ container.clear()
35
+ if run.dim_axis_option['x-axis'] is None:
36
+ ui.notify(
37
+ 'Please select at least one dimension for the x-axis to display plots.<br>',
38
+ color = 'red')
39
+ return
40
+ ds = run.generate_subset()
41
+ print(f"Found {len(ds.dims)} dimensions to plot in subset:")
42
+ fig_dict = {}
43
+ if len(ds.dims) == 1:
44
+ create_1d_plot(run, ds, container)
45
+ elif len(ds.dims) == 2:
46
+ create_2d_grid(run, ds, container)
47
+ else:
48
+ ui.notify(
49
+ 'The selected dimensions result in more than 2D data.<br>'
50
+ 'Please select only 1 or 2 dimensions to plot)',
51
+ color = 'red')
52
+ return None
53
+
54
+ def create_1d_plot(run: Run, ds: xr.Dataset, container: ui.Row) -> None:
55
+ """
56
+ Create a 1D plot for the given run and dataset.
57
+
58
+ Args:
59
+ run: The Run object containing the data to plot.
60
+ ds: The xarray Dataset containing the data.
61
+ container: The NiceGUI container to hold the plot.
62
+ """
63
+ print("Creating 1D plot")
64
+ x_dim = run.dim_axis_option['x-axis'].name
65
+ traces = []
66
+ plot_dict = copy.deepcopy(app.storage.tab["plot_dict_1D"])
67
+ for key in run.plot_selection:
68
+ da = ds[key]
69
+ traces.append({
70
+ "type": "scatter",
71
+ "mode": "lines+markers",
72
+ "name": key.replace("__", "."),
73
+ "x": da.coords[x_dim].values.tolist(),
74
+ "y": da.values.tolist(),
75
+ })
76
+
77
+ plot_dict["data"] = traces
78
+ plot_dict["layout"]["xaxis"]["title"]["text"] = axis_label_formater(ds, x_dim)
79
+ plot_dict["layout"]["title"]["text"] = title_formater(run)
80
+
81
+ with container:
82
+ fig = go.Figure(plot_dict)
83
+ ui.plotly(fig).classes('w-full').style('min-height: 400px;')
84
+ app.storage.tab["plot_dict_1D"] = plot_dict
85
+
86
+ def create_2d_grid(run, ds, container) -> dict:
87
+ """
88
+ Create a grid of 2D plots for the given run and dataset.
89
+
90
+ Args:
91
+ run: The Run object containing the data to plot.
92
+ ds: The xarray Dataset containing the data.
93
+ container: The NiceGUI container to hold the plots.
94
+ """
95
+ print("Creating 2D grid of plots")
96
+ if not all([run.dim_axis_option[axis]is not None for axis in ['x-axis', 'y-axis']]):
97
+ ui.notify(
98
+ 'Please select both x-axis and y-axis dimensions to display 2D plots.<br>'
99
+ f'x: {run.dim_axis_option["x-axis"]}<br>'
100
+ f'y: {run.dim_axis_option["y-axis"]}',
101
+ color = 'red')
102
+ return
103
+ keys = run.plot_selection
104
+ num_plots = len(keys)
105
+ num_columns = int(min([run.plots_per_column, len(keys)]))
106
+ num_rows = math.ceil(num_plots / num_columns)
107
+ pretty_keys = [key.replace("__", ".") for key in keys]
108
+
109
+ x_dim = run.dim_axis_option['x-axis'].name
110
+ y_dim = run.dim_axis_option['y-axis'].name
111
+ plot_dict = copy.deepcopy(app.storage.tab["plot_dict_2D"])
112
+ plot_dict["layout"]["xaxis"]["title"]["text"] = axis_label_formater(ds, x_dim)
113
+ plot_dict["layout"]["yaxis"]["title"]["text"] = axis_label_formater(ds, y_dim)
114
+ plot_idx = 0
115
+ def create_2d_plot(plot_idx):
116
+ key = keys[plot_idx]
117
+ da = ds[key]
118
+ if x_dim != da.dims[1]:
119
+ da = da.transpose(y_dim, x_dim)
120
+ plot_dict["data"][0]["z"] = da.values.tolist()
121
+ plot_dict["data"][0]["x"] = da.coords[x_dim].values.tolist()
122
+ plot_dict["data"][0]["y"] = da.coords[y_dim].values.tolist()
123
+ plot_dict["layout"]["title"]["text"] = (
124
+ f"<b>{pretty_keys[plot_idx]}</b><br>{title_formater(run)}")
125
+ return go.Figure(plot_dict)
126
+
127
+ with container:
128
+ with ui.column().classes('w-full'):
129
+ for row in range(num_rows):
130
+ with ui.row().classes('w-full justify-start flex-wrap'):
131
+ for col in range(num_columns):
132
+ if plot_idx >= num_plots:
133
+ break
134
+ fig = create_2d_plot(plot_idx)
135
+ width_percent = 100 / num_columns - 2
136
+ with ui.column().style(
137
+ f"width: {width_percent}%; box-sizing: border-box;"
138
+ ):
139
+ ui.plotly(fig).classes('w-full').style('min-height: 300px;')
140
+ plot_idx += 1
141
+ app.storage.tab["plot_dict_2D"] = plot_dict
@@ -0,0 +1,57 @@
1
+ """
2
+ Build and display xarray dataset HTML with dark theme.
3
+ """
4
+ from __future__ import annotations
5
+ from typing import TYPE_CHECKING
6
+
7
+ from nicegui import ui, app
8
+
9
+ if TYPE_CHECKING:
10
+ from arbok_inspector.classes.run import Run
11
+ from xarray import Dataset
12
+
13
+ def build_xarray_html():
14
+ """Display the xarray dataset in a dark-themed style."""
15
+ run: Run = app.storage.tab["run"]
16
+ ds: Dataset = run.full_data_set
17
+ with ui.column().classes('w-full'):
18
+ ui.html('''
19
+ <style>
20
+ /* Wrap styles to only apply inside this container */
21
+ .xarray-dark-wrapper {
22
+ background-color: #343535; /* Tailwind gray-800 */
23
+ color: #ffffff;
24
+ padding: 1rem;
25
+ border-radius: 0.5rem;
26
+ overflow-x: auto;
27
+ }
28
+
29
+ .xarray-dark-wrapper th,
30
+ .xarray-dark-wrapper td {
31
+ color: #d1d5db; /* Tailwind gray-300 */
32
+ background-color: transparent;
33
+ }
34
+
35
+ .xarray-dark-wrapper .xr-var-name {
36
+ color: #93c5fd !important; /* Tailwind blue-300 */
37
+ }
38
+
39
+ .xarray-dark-wrapper .xr-var-dims,
40
+ .xarray-dark-wrapper .xr-var-data {
41
+ color: #d1d5db !important; /* Light gray */
42
+ }
43
+
44
+ /* Optional: override any inline black text */
45
+ .xarray-dark-wrapper * {
46
+ color: inherit !important;
47
+ background-color: transparent !important;
48
+ }
49
+ </style>
50
+ ''')
51
+
52
+ # Wrap the dataset HTML in a div with that class
53
+ ui.html(f'''
54
+ <div class="xarray-dark-wrapper">
55
+ {ds._repr_html_()}
56
+ </div>
57
+ ''')
@@ -0,0 +1,77 @@
1
+ """
2
+ Dialog for editing JSON plot settings.
3
+ """
4
+ import copy
5
+ import json
6
+ import importlib.resources as resources
7
+
8
+ from nicegui import app, ui
9
+
10
+ from arbok_inspector.widgets.build_xarray_grid import build_xarray_grid
11
+
12
+ class JsonPlotSettingsDialog:
13
+ """
14
+ Dialog for editing JSON plot settings.
15
+ """
16
+ def __init__(self, dimension: str):
17
+ self.dimension = dimension
18
+ self.json_editor = None
19
+ self.dialog = self.build_plot_settings_dialog()
20
+
21
+ def build_plot_settings_dialog(self):
22
+ """
23
+ Build the dialog for plot settings.
24
+ """
25
+ plot_dict = app.storage.tab[self.dimension]
26
+
27
+ with ui.dialog() as dialog, ui.card():
28
+ with ui.column().classes('w_full h-screen'):
29
+ ui.label('Plot Settings')
30
+ self.json_editor = ui.json_editor(
31
+ properties = {"content": {"json": plot_dict}},
32
+ ).classes("jse-theme-dark")
33
+ with ui.row().classes('w-full justify-end'):
34
+ ui.button(
35
+ text = 'Apply',
36
+ on_click=lambda: self.set_editor_data(),
37
+ color='green'
38
+ )
39
+ ui.button(
40
+ text = 'Reset',
41
+ on_click=lambda: self.reset_plot_settings(),
42
+ color='blue'
43
+ )
44
+ ui.button(
45
+ text = 'Close',
46
+ color = 'red',
47
+ on_click=dialog.close
48
+ )
49
+ return dialog
50
+
51
+ def open(self):
52
+ """Open the dialog."""
53
+ print("Opening dialog and setting json data")
54
+ self.dialog.open()
55
+ self.json_editor.properties['content']['json'] = copy.deepcopy(
56
+ app.storage.tab[self.dimension])
57
+ self.json_editor.update()
58
+
59
+ async def set_editor_data(self):
60
+ """Sets json data from the JSON editor to the app storage and rebuilds plots."""
61
+ json_data = await self.json_editor.run_editor_method('get')
62
+ json_data = json_data["json"]
63
+ app.storage.tab[self.dimension] = json_data
64
+ build_xarray_grid()
65
+
66
+ def reset_plot_settings(self):
67
+ """Reset plot settings to defaults."""
68
+ if self.dimension == 'plot_dict_1D':
69
+ with resources.files("arbok_inspector.configurations").joinpath("1d_plot.json").open("r") as f:
70
+ app.storage.tab["plot_dict_1D"] = json.load(f)
71
+
72
+ elif self.dimension == 'plot_dict_2D':
73
+ with resources.files("arbok_inspector.configurations").joinpath("2d_plot.json").open("r") as f:
74
+ app.storage.tab["plot_dict_2D"] = json.load(f)
75
+
76
+ ui.notify('Reset to default settings', type='positive', position='top-right')
77
+ build_xarray_grid()
@@ -0,0 +1,36 @@
1
+ from datetime import datetime
2
+ from nicegui import ui, app
3
+
4
+ from arbok_inspector.state import inspector
5
+
6
+ def update_day_selecter(day_grid):
7
+ offset_hours = app.storage.general["timezone"]
8
+ inspector.cursor.execute(f"""
9
+ SELECT
10
+ day,
11
+ MIN(run_timestamp) AS earliest_ts
12
+ FROM (
13
+ SELECT
14
+ run_timestamp,
15
+ DATE(datetime(run_timestamp, 'unixepoch', '{offset_hours} hours')) AS day
16
+ FROM runs
17
+ )
18
+ GROUP BY day
19
+ ORDER BY day;
20
+ """)
21
+ day_grid.clear()
22
+ rows = inspector.cursor.fetchall()
23
+ row_data = []
24
+ for day, ts in rows[::-1]:
25
+ row_data.append({'day': day})
26
+
27
+ day_grid.options['rowData'] = row_data
28
+ day_grid.update()
29
+ ui.notify(
30
+ 'Day selector updated: \n'
31
+ f'found {len(row_data)} days',
32
+ type='positive',
33
+ multi_line=True,
34
+ classes='multi-line-notification',
35
+ position = 'top-right'
36
+ )
@@ -0,0 +1,51 @@
1
+ from datetime import datetime, timedelta
2
+ from nicegui import ui, app
3
+
4
+ from arbok_inspector.state import inspector
5
+
6
+ def update_run_selecter(run_grid, target_day, run_grid_column_defs):
7
+ offset_hours = app.storage.general["timezone"]
8
+ print(target_day)
9
+ print(offset_hours)
10
+ inspector.cursor.execute(
11
+ f"""
12
+ SELECT *
13
+ FROM runs
14
+ WHERE DATE(datetime(run_timestamp, 'unixepoch', '{offset_hours} hours')) = ?
15
+ ORDER BY run_timestamp;
16
+ """,
17
+ (target_day,),
18
+ )
19
+ runs = inspector.cursor.fetchall()
20
+ results = [dict(row) for row in runs]
21
+
22
+ run_grid_rows = []
23
+ columns = [x['field'] for x in run_grid_column_defs]
24
+ for run in results:
25
+ print(f'Run: {run.keys()}')
26
+ run_dict = {}
27
+ for key in columns:
28
+ if key in run:
29
+ value = run[key]
30
+ if 'time' in key:
31
+ if value is not None:
32
+ # Apply offset and format nicely
33
+ local_dt = datetime.utcfromtimestamp(value) + timedelta(hours=offset_hours)
34
+ value = local_dt.strftime('%H:%M:%S')
35
+ else:
36
+ value = 'N/A'
37
+ run_dict[key] = value
38
+ run_grid_rows.insert(0, run_dict)
39
+
40
+ run_grid.options['rowData'] = run_grid_rows
41
+ run_grid.update()
42
+
43
+ ui.notify(
44
+ 'Run selector updated: \n'
45
+ f'found {len(run_grid_rows)} run(s)',
46
+ type='positive',
47
+ multi_line=True,
48
+ classes='multi-line-notification',
49
+ position = 'top-right'
50
+ )
51
+
@@ -0,0 +1,86 @@
1
+ Metadata-Version: 2.4
2
+ Name: arbok-inspector
3
+ Version: 0.0.0
4
+ Summary: Browser based QCoDeS database inspector
5
+ License: MIT
6
+ Classifier: Programming Language :: Python :: 3.12
7
+ Requires-Python: >=3.12
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: jupyterlab>=4.4.8
11
+ Requires-Dist: nicegui>=2.24.2
12
+ Requires-Dist: nodejs>=0.1.1
13
+ Requires-Dist: plotly>=6.3.0
14
+ Requires-Dist: qcodes>=0.53.0
15
+ Requires-Dist: xarray>=2025.9.0
16
+ Dynamic: license-file
17
+
18
+ # arbok_inspector 🐍
19
+ arbok_inspector is an browser based inspection and visualization utility for QCoDeS measurement
20
+ databases.
21
+ It provides a lightweight GUI and CLI to browse runs and visualize data.
22
+
23
+ ## Features 🔎
24
+ The most commonly used used tool to visualize QCoDeS databases is
25
+ [plottr](https://github.com/toolsforexperiments/plottr).
26
+ Plottr is a great tool to get started, but struggles with increasing abounts of data.
27
+
28
+ This is how arbok_inspector streamlines your data inspection:
29
+ - Fast browsing of measurement runs and their metadata
30
+ - Written with [nicegui](https://nicegui.io/) acting as a [tailwind](https://tailwindcss.com/) wrapper
31
+ - Browser based approach ensures cross system compatibily
32
+ - Selected runs are opened in a new tab and run on a separate thread
33
+ - this avoids blocking the entire application when loading big datasets
34
+ - plotting backend is plotly which natively returns html
35
+ - plotly plot customization is declarative and can therefore be tweaked in a simple json editor without implementing each customization by hand
36
+ - runs are only loaded on demand
37
+ - startup time in plottr can be several minutes for large databases
38
+ - SQL queries load only the given days upon database selection, only loads respective runs once day is selected
39
+
40
+ ## Installation 📲
41
+
42
+ From pypi install using pip in your environment:
43
+ ```bash
44
+ pip install arbok-inspector
45
+ ```
46
+ Even better if you are using uv, a uv.lock file is included!
47
+ Launch from CLI:
48
+ ```bash
49
+ arbok-inspector
50
+ ```
51
+
52
+ ## Project layout
53
+
54
+ - `main.py` — app entrypoint and startup logic
55
+ - `state.py` — application state & database handling
56
+ - `pages/` — NiceGUI pages (database browser, run view, greeter, ...)
57
+ - `widgets/` — reusable UI widgets (grid builders, selectors, dialogs)
58
+ - `analysis/` — analysis and data-prep utilities
59
+ - `classes/` — small domain objects used across the app
60
+ - `helpers/` — formatting and utility helpers
61
+
62
+ Development & testing 🛠️
63
+
64
+ Clone this git repository and navigate into it.
65
+ Use an editable install for local development to pick up changes immediately
66
+ ```bash
67
+ pip install -e .
68
+ ```
69
+
70
+ To launch the app in editable mode launch from dev.py file:
71
+ ```bash
72
+ python -m arbok_inspector/dev.py
73
+ ```
74
+ Contributing & help 🙌
75
+
76
+ Contributions, bug reports, and small feature requests are welcome. If you want to add a visualization or a new page, use `pages/` and `widgets/` for examples of how UI components are composed. When opening a PR, please keep changes focused and include a short description of how to exercise the change locally.
77
+
78
+ License
79
+
80
+ See the `LICENSE` file in the project root for license details.
81
+
82
+ Notes & tips
83
+
84
+ - For exact runtime dependencies check `pyproject.toml` — prefer using that manifest (and a virtual environment) for reproducible installs.
85
+ - If you want me to add a short walkthrough for common tasks (open a run, plot data, export CSV), tell me which task you'd like first and I can add a step-by-step example here. 📘
86
+
@@ -0,0 +1,27 @@
1
+ arbok_inspector/__init__.py,sha256=KEDfR87-199tMUCeufnGkPswwjGivDOIwF5w1_cZ6ZM,53
2
+ arbok_inspector/cli.py,sha256=G0gIa4ByfQTMmeXCyLjnsmke5EEXs6DYuR5eVbhszXo,96
3
+ arbok_inspector/dev.py,sha256=F19xar7hlR-fG1QTxGyXSwT5r80x52vZIlZDaimPc4E,413
4
+ arbok_inspector/main.py,sha256=oQEypBKdD5jLARiXmz2u1_Op5OIos0BTqkM--oLU3lo,300
5
+ arbok_inspector/state.py,sha256=FDsnqUq8_oGecTvFgQoVB0-JMgFtmgccsxPX7jYX74A,2295
6
+ arbok_inspector/test_main.py,sha256=brV3O2ZVyuUrqnUmZr3U-tH6VY7PQxthNiElgIax_x0,1955
7
+ arbok_inspector/analysis/analysis_base.py,sha256=7uChxCyoiHazQBId9DHgXZtk166fZshr-E-eF-SNw5Q,933
8
+ arbok_inspector/analysis/prepare_data.py,sha256=DedVxlbTNDppMlNBwt8kN4cWk0_Nnyx9J442GqG1Asw,4393
9
+ arbok_inspector/classes/dim.py,sha256=bPxVNn23ohZ9CVePmiqLUFABbh6iKiuN_dMAxIcqIG0,750
10
+ arbok_inspector/classes/run.py,sha256=i50RhllM-fXanTNRLxBdcpBCkfiqjrNvupDSv4vwg2s,9895
11
+ arbok_inspector/helpers/string_formaters.py,sha256=q8ldfHRlACWKBhjUpuwbDthbUBcP6q8Z9qaRLdiwxJ4,1000
12
+ arbok_inspector/helpers/unit_formater.py,sha256=7h77hqPoLLBil3Z1n72T1iLv3Du84Qt0iZ3B0tzZtaY,1163
13
+ arbok_inspector/pages/__init__.py,sha256=3ZbSQftX4VYoWU6DsAEZvXl89PLjwn89Xtxqwwxy8vc,88
14
+ arbok_inspector/pages/database_browser.py,sha256=22KbzJwKSIPoLnk6Li6i0WYqjL713nH4Q222XDWjCsg,6793
15
+ arbok_inspector/pages/greeter.py,sha256=O1QpLU9nMAOjG0kKNCVMPX00fnUlmoZzOP6Pdt_2H0g,1380
16
+ arbok_inspector/pages/run_view.py,sha256=yEV7k_OdgT61PemMK8l1lDlqtzYNu8-Qguer0Iw5uMk,10980
17
+ arbok_inspector/widgets/build_xarray_grid.py,sha256=TgFYWtFp2DqBI5neP_WqXkhmwTj6Hl6UqzIqcW_Xtsk,5409
18
+ arbok_inspector/widgets/build_xarray_html.py,sha256=NaS8YKidcXOPhSY-QHraHS_Hag8cCqizrtaDJ__WwH0,1679
19
+ arbok_inspector/widgets/json_plot_settings_dialog.py,sha256=tUjx9TqDHhKsT9IGZfw61zuzjY1BqJH00s_gtR5kzio,2913
20
+ arbok_inspector/widgets/update_day_selecter.py,sha256=z5HVUVnxSqtZfoaK4lzMh5J16o9RoEZJwDvnwS-ewZ8,1011
21
+ arbok_inspector/widgets/update_run_selecter.py,sha256=M7hvy80kXO2NA83ly3QeeMQsTfdslit--2K3CbkHrQc,1639
22
+ arbok_inspector-0.0.0.dist-info/licenses/LICENSE,sha256=ZEqWKVZ2qROesXQcWpU2JTdY2yk6OkWR70H5771iIGc,1070
23
+ arbok_inspector-0.0.0.dist-info/METADATA,sha256=LkbxLpru_KRa0pURJZ_Yk4kJ6NsdMPYeDGYI6flU4p0,3426
24
+ arbok_inspector-0.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
25
+ arbok_inspector-0.0.0.dist-info/entry_points.txt,sha256=i1gVPzcOgNNzSly4zUqJ_z38eiUrMEOCNgtH1dr55zE,64
26
+ arbok_inspector-0.0.0.dist-info/top_level.txt,sha256=-jLIWMq05sffOZ-lCiP8dZgVga7Upp95QsBgoE718oE,16
27
+ arbok_inspector-0.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ arbok-inspector = arbok_inspector.main:ui.run
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Andreas Nickl
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.
@@ -0,0 +1 @@
1
+ arbok_inspector