snowglobe-cli 0.1.0__tar.gz

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.
Files changed (82) hide show
  1. snowglobe_cli-0.1.0/LICENSE +202 -0
  2. snowglobe_cli-0.1.0/PKG-INFO +368 -0
  3. snowglobe_cli-0.1.0/README.md +341 -0
  4. snowglobe_cli-0.1.0/pyproject.toml +48 -0
  5. snowglobe_cli-0.1.0/setup.cfg +4 -0
  6. snowglobe_cli-0.1.0/snowglobe/__init__.py +6 -0
  7. snowglobe_cli-0.1.0/snowglobe/__main__.py +3 -0
  8. snowglobe_cli-0.1.0/snowglobe/cli/__init__.py +0 -0
  9. snowglobe_cli-0.1.0/snowglobe/cli/access.py +197 -0
  10. snowglobe_cli-0.1.0/snowglobe/cli/app.py +148 -0
  11. snowglobe_cli-0.1.0/snowglobe/cli/context.py +48 -0
  12. snowglobe_cli-0.1.0/snowglobe/cli/cost.py +291 -0
  13. snowglobe_cli-0.1.0/snowglobe/cli/debug.py +265 -0
  14. snowglobe_cli-0.1.0/snowglobe/cli/diff.py +34 -0
  15. snowglobe_cli-0.1.0/snowglobe/cli/optimizer.py +91 -0
  16. snowglobe_cli-0.1.0/snowglobe/cli/prompts.py +161 -0
  17. snowglobe_cli-0.1.0/snowglobe/cli/report.py +91 -0
  18. snowglobe_cli-0.1.0/snowglobe/cli/shell.py +1437 -0
  19. snowglobe_cli-0.1.0/snowglobe/cli/shell_completer.py +128 -0
  20. snowglobe_cli-0.1.0/snowglobe/collectors/access.py +882 -0
  21. snowglobe_cli-0.1.0/snowglobe/collectors/query_history.py +46 -0
  22. snowglobe_cli-0.1.0/snowglobe/collectors/query_profile.py +101 -0
  23. snowglobe_cli-0.1.0/snowglobe/config/loader.py +42 -0
  24. snowglobe_cli-0.1.0/snowglobe/core/access_service.py +721 -0
  25. snowglobe_cli-0.1.0/snowglobe/core/cost_service.py +929 -0
  26. snowglobe_cli-0.1.0/snowglobe/core/optimizer.py +92 -0
  27. snowglobe_cli-0.1.0/snowglobe/core/query_service.py +48 -0
  28. snowglobe_cli-0.1.0/snowglobe/core/report_service.py +110 -0
  29. snowglobe_cli-0.1.0/snowglobe/core/risk_service.py +358 -0
  30. snowglobe_cli-0.1.0/snowglobe/engines/access/__init__.py +0 -0
  31. snowglobe_cli-0.1.0/snowglobe/engines/access/explainer.py +113 -0
  32. snowglobe_cli-0.1.0/snowglobe/engines/access/resolver.py +199 -0
  33. snowglobe_cli-0.1.0/snowglobe/engines/ai/cortex_optimizer.py +69 -0
  34. snowglobe_cli-0.1.0/snowglobe/engines/optimizer/query_optimizer.py +326 -0
  35. snowglobe_cli-0.1.0/snowglobe/graphs/__init__.py +0 -0
  36. snowglobe_cli-0.1.0/snowglobe/graphs/role_graph.py +140 -0
  37. snowglobe_cli-0.1.0/snowglobe/graphs/user_graph.py +64 -0
  38. snowglobe_cli-0.1.0/snowglobe/models/__init__.py +0 -0
  39. snowglobe_cli-0.1.0/snowglobe/models/access.py +65 -0
  40. snowglobe_cli-0.1.0/snowglobe/models/access_path.py +15 -0
  41. snowglobe_cli-0.1.0/snowglobe/models/object_ref.py +11 -0
  42. snowglobe_cli-0.1.0/snowglobe/models/object_type.py +50 -0
  43. snowglobe_cli-0.1.0/snowglobe/models/optimizer.py +15 -0
  44. snowglobe_cli-0.1.0/snowglobe/models/privilege.py +78 -0
  45. snowglobe_cli-0.1.0/snowglobe/models/query.py +59 -0
  46. snowglobe_cli-0.1.0/snowglobe/output/__init__.py +0 -0
  47. snowglobe_cli-0.1.0/snowglobe/output/cli.py +413 -0
  48. snowglobe_cli-0.1.0/snowglobe/queries/__init__.py +0 -0
  49. snowglobe_cli-0.1.0/snowglobe/queries/query_history.py +37 -0
  50. snowglobe_cli-0.1.0/snowglobe/snowflake/connection.py +75 -0
  51. snowglobe_cli-0.1.0/snowglobe/state/db.py +559 -0
  52. snowglobe_cli-0.1.0/snowglobe/state/state.py +60 -0
  53. snowglobe_cli-0.1.0/snowglobe/templates/report.md.j2 +55 -0
  54. snowglobe_cli-0.1.0/snowglobe/tests/access_tests.py +5 -0
  55. snowglobe_cli-0.1.0/snowglobe/tui/__init__.py +1 -0
  56. snowglobe_cli-0.1.0/snowglobe/tui/__main__.py +3 -0
  57. snowglobe_cli-0.1.0/snowglobe/tui/app.py +299 -0
  58. snowglobe_cli-0.1.0/snowglobe/tui/screens/__init__.py +0 -0
  59. snowglobe_cli-0.1.0/snowglobe/tui/screens/access.py +627 -0
  60. snowglobe_cli-0.1.0/snowglobe/tui/screens/cost.py +831 -0
  61. snowglobe_cli-0.1.0/snowglobe/tui/screens/home.py +222 -0
  62. snowglobe_cli-0.1.0/snowglobe/tui/screens/refresh.py +222 -0
  63. snowglobe_cli-0.1.0/snowglobe/tui/screens/reports.py +252 -0
  64. snowglobe_cli-0.1.0/snowglobe/tui/screens/risk.py +417 -0
  65. snowglobe_cli-0.1.0/snowglobe/tui/screens/tune.py +254 -0
  66. snowglobe_cli-0.1.0/snowglobe/tui/widgets/__init__.py +0 -0
  67. snowglobe_cli-0.1.0/snowglobe/tui/widgets/access_paths.py +63 -0
  68. snowglobe_cli-0.1.0/snowglobe/tui/widgets/cache_badge.py +28 -0
  69. snowglobe_cli-0.1.0/snowglobe/tui/widgets/header.py +21 -0
  70. snowglobe_cli-0.1.0/snowglobe/tui/widgets/nav.py +32 -0
  71. snowglobe_cli-0.1.0/snowglobe_cli.egg-info/PKG-INFO +368 -0
  72. snowglobe_cli-0.1.0/snowglobe_cli.egg-info/SOURCES.txt +80 -0
  73. snowglobe_cli-0.1.0/snowglobe_cli.egg-info/dependency_links.txt +1 -0
  74. snowglobe_cli-0.1.0/snowglobe_cli.egg-info/entry_points.txt +2 -0
  75. snowglobe_cli-0.1.0/snowglobe_cli.egg-info/requires.txt +18 -0
  76. snowglobe_cli-0.1.0/snowglobe_cli.egg-info/top_level.txt +1 -0
  77. snowglobe_cli-0.1.0/tests/test_access.py +122 -0
  78. snowglobe_cli-0.1.0/tests/test_optimizer_engine.py +259 -0
  79. snowglobe_cli-0.1.0/tests/test_output.py +94 -0
  80. snowglobe_cli-0.1.0/tests/test_query_validation.py +32 -0
  81. snowglobe_cli-0.1.0/tests/test_state_db.py +115 -0
  82. snowglobe_cli-0.1.0/tests/test_tui_tune.py +203 -0
@@ -0,0 +1,202 @@
1
+ Copyright 2025 Jaryd Thornton
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
@@ -0,0 +1,368 @@
1
+ Metadata-Version: 2.4
2
+ Name: snowglobe-cli
3
+ Version: 0.1.0
4
+ Summary: Explainable cost and access visibility for Snowflake
5
+ Author-email: Jaryd Thornton <jaryd90@gmail.com>
6
+ License-Expression: Apache-2.0
7
+ Requires-Python: >=3.12
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: cryptography==45.0.5
11
+ Requires-Dist: Jinja2==3.1.6
12
+ Requires-Dist: pandas==2.3.1
13
+ Requires-Dist: prompt_toolkit>=3.0.0
14
+ Requires-Dist: pydantic==2.11.7
15
+ Requires-Dist: pyOpenSSL==25.1.0
16
+ Requires-Dist: PyYAML==6.0.2
17
+ Requires-Dist: rich==14.0.0
18
+ Requires-Dist: snowflake_connector_python==3.16.0
19
+ Requires-Dist: sqlglot==27.1.0
20
+ Requires-Dist: typer==0.16.0
21
+ Requires-Dist: typing_extensions==4.14.1
22
+ Provides-Extra: dev
23
+ Requires-Dist: pytest>=8.0; extra == "dev"
24
+ Provides-Extra: tui
25
+ Requires-Dist: textual>=0.80; extra == "tui"
26
+ Dynamic: license-file
27
+
28
+ <p align="center">
29
+ <img src="assets/logo.svg" alt="Snowglobe" width="600"/>
30
+ </p>
31
+
32
+ <p align="center">
33
+ <a href="https://pypi.org/project/snowglobe-cli/"><img src="https://img.shields.io/pypi/v/snowglobe-cli" alt="PyPI version"/></a>
34
+ <a href="https://pypi.org/project/snowglobe-cli/"><img src="https://img.shields.io/pypi/pyversions/snowglobe-cli" alt="Python versions"/></a>
35
+ <a href="LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-blue" alt="License"/></a>
36
+ </p>
37
+
38
+ <p align="center">
39
+ <strong>Explainable cost and access visibility for Snowflake — read-only by design.</strong>
40
+ </p>
41
+
42
+ Snowglobe helps analytics, data platform, and security teams answer three questions about their Snowflake account:
43
+
44
+ 1. **Where did our Snowflake spend come from?**
45
+ 2. **Who owns and can access what?**
46
+ 3. **Who is responsible when cost or access looks wrong?**
47
+
48
+ It observes, explains, and recommends — and it never writes to your account.
49
+
50
+ ---
51
+
52
+ ## Table of contents
53
+
54
+ - [Three ways to use it](#three-ways-to-use-it)
55
+ - [Installation](#installation)
56
+ - [Requirements](#requirements)
57
+ - [Configuration](#configuration)
58
+ - [Quickstart](#quickstart)
59
+ - [Features](#features)
60
+ - [The TUI](#the-tui)
61
+ - [The interactive shell](#the-interactive-shell)
62
+ - [Headless CLI](#headless-cli)
63
+ - [Security](#security)
64
+ - [Limitations](#limitations)
65
+ - [Contributing](#contributing)
66
+ - [License](#license)
67
+
68
+ ---
69
+
70
+ ## Three ways to use it
71
+
72
+ | Interface | Command | When to use it |
73
+ |---|---|---|
74
+ | **TUI** *(default)* | `snowglobe` | Day-to-day exploration. Seven screens, mouse + keyboard, optional vim navigation, theme switcher. |
75
+ | **Interactive shell** | `snowglobe shell` | Wizards and quick lookups in a REPL. Useful when you only want one or two checks. |
76
+ | **Headless CLI** | `snowglobe <subcommand>` | CI, cron jobs, scripts, piping into other tools. Outputs `table`, `json`, or `csv` per command. |
77
+
78
+ All three share the same local SQLite cache and the same service layer, so anything you do in one is reflected in the others.
79
+
80
+ ---
81
+
82
+ ## Installation
83
+
84
+ ```bash
85
+ pip install 'snowglobe-cli[tui]' # includes the Textual TUI (recommended)
86
+ pip install snowglobe-cli # CLI + shell only, no Textual dependency
87
+ ```
88
+
89
+ Or from source:
90
+
91
+ ```bash
92
+ git clone https://github.com/jcolethornton/snowglobe.git
93
+ cd snowglobe
94
+ pip install -e '.[tui]'
95
+ ```
96
+
97
+ ---
98
+
99
+ ## Requirements
100
+
101
+ - Python **3.12+**
102
+ - A Snowflake account
103
+ - A Snowflake role with `IMPORTED PRIVILEGES ON DATABASE SNOWFLAKE` (grants read access to `ACCOUNT_USAGE`)
104
+ - Optionally: `IMPORTED PRIVILEGES ON DATABASE SNOWFLAKE` for `ORGANIZATION_USAGE` to get your contracted storage rate
105
+
106
+ **Grant access if needed:**
107
+ ```sql
108
+ GRANT IMPORTED PRIVILEGES ON DATABASE SNOWFLAKE TO ROLE <your_role>;
109
+ ```
110
+
111
+ ---
112
+
113
+ ## Configuration
114
+
115
+ Snowglobe loads connection profiles from `~/.snowglobe/config.yaml`. Multiple profiles are supported and selected with `--profile <name>`.
116
+
117
+ ```yaml
118
+ # ~/.snowglobe/config.yaml
119
+
120
+ default:
121
+ account: "abc123.us-east-1"
122
+ user: "jdoe@example.com"
123
+ role: "ANALYST"
124
+ warehouse: "ANALYTICS_WH"
125
+
126
+ # Auth — choose one:
127
+ password: "hunter2" # password auth
128
+ # private_key_path: "~/.ssh/snowflake.p8" # key-pair auth
129
+ # private_key_pwd: "passphrase" # key passphrase (if encrypted)
130
+
131
+ # Optional settings:
132
+ vim: true # enable vim navigation in the TUI
133
+ cortex_model: "claude-haiku-4-5" # Cortex AI model for query optimiser
134
+
135
+ prod:
136
+ account: "abc123.us-east-1"
137
+ user: "admin_user"
138
+ password: "${SNOWFLAKE_PROD_PASSWORD}" # environment variables are expanded
139
+ role: "SYSADMIN"
140
+ warehouse: "ETL_WH"
141
+ ```
142
+
143
+ ---
144
+
145
+ ## Quickstart
146
+
147
+ ```bash
148
+ # 1. Populate the local SQLite cache from Snowflake
149
+ snowglobe refresh
150
+
151
+ # 2. Launch the TUI (the default)
152
+ snowglobe
153
+
154
+ # Or explicitly, with vim-style navigation
155
+ snowglobe tui --vim
156
+
157
+ # Drop into the REPL shell instead
158
+ snowglobe shell
159
+
160
+ # Headless commands
161
+ snowglobe access check --user jdoe --object-type TABLE \
162
+ --object-name MY_DB.PUBLIC.ORDERS --privilege SELECT
163
+ snowglobe cost summary --days 30
164
+ snowglobe optimize query --query-id <query_id>
165
+ snowglobe report full --days 30 --output report.md
166
+ ```
167
+
168
+ ---
169
+
170
+ ## Features
171
+
172
+ ### Access explainability
173
+
174
+ - **Why does X have access?** — Trace every role-inheritance path that grants a user or role a privilege on an object, not just a flat list of grants.
175
+ - **Who can access this?** — Reverse lookup an object: every role and user with access, grouped by privilege, including paths through inherited roles.
176
+ - **Where can this role create things?** — CREATE privilege visibility across account / database / schema scopes with the granting roles called out.
177
+ - **What roles does a user have?** — Direct, excluded, and inherited roles plus the total effective set.
178
+ - **Who has a specific role?** — Direct and inherited members.
179
+ - **Does role X inherit from role Y?** — Every inheritance path between two roles.
180
+ - **What's changed since last refresh?** — Drift detection across grants, role edges, and user assignments.
181
+
182
+ ### Risk & privilege escalation
183
+
184
+ - **Escalation scan** — Walk every role in the account, find paths to admin roles (ACCOUNTADMIN, SYSADMIN, SECURITYADMIN, USERADMIN, or any role with `MANAGE GRANTS` / DB OWNERSHIP / `IMPORTED PRIVILEGES ON SNOWFLAKE`), and score each path with a composite risk score (target weight × inverse hops × log of user count).
185
+ - **Single-role escalation check** — Pick one role and see exactly which admin targets it can reach and through which chain.
186
+ - **Dormant escalation risks** — Cross-references inactive users (no successful login in 90 days) against risk-bearing roles.
187
+ - **Direct privilege risks** — Roles with dangerous account-level grants that bypass the role graph entirely.
188
+ - **Unused privileges** — Roles with data grants but no recent `QUERY_HISTORY` activity, surfaced for cleanup.
189
+
190
+ ### Cost & query attribution
191
+
192
+ - **12 cost views** — Account summary, daily trend with rolling average, top expensive queries, per-warehouse, per-user (warehouse + Cortex AI), AI services (Cortex Functions / Analyst / Agent / Code / Snowflake Intelligence), services (pipes / tasks / SPCS / auto-clustering / search optimisation), per-DB storage with monthly cost estimate, replication, materialized-view refresh costs, and Snowflake-native budget status.
193
+ - **Real query attribution** — Uses `QUERY_ATTRIBUTION_HISTORY.CREDITS_ATTRIBUTED_COMPUTE` for per-query credit cost where available; falls back gracefully to `QUERY_HISTORY` with a clear note.
194
+ - **Cortex AI cost tracking** — Each Cortex view is queried individually so accounts without certain features still get partial data rather than a crash.
195
+ - **1-hour TTL local cache** — Cost views are cached in SQLite so repeated visits within the hour are instant; `Re-fetch` forces a fresh Snowflake call.
196
+ - **Drill-downs** — Click a warehouse for its daily trend; click a user for their per-warehouse credit breakdown; click a top query to load it into the Tune screen.
197
+
198
+ ### Query optimiser
199
+
200
+ - **Eight heuristic detectors** over `GET_QUERY_OPERATOR_STATS` — join explosion, disk spill, pruning failure, cartesian joins, large scans, large window functions, large aggregations, heavy network shuffle.
201
+ - **Snowflake-native insights** from `QUERY_INSIGHTS` (where available).
202
+ - **Operator tree** with per-operator score and time percentage.
203
+ - **Cortex AI suggestions** — opt-in `AI_COMPLETE` call that takes the SQL + heuristic findings + cost attribution and produces a narrative optimisation recommendation. Model is configurable per profile (`cortex_model` in config); defaults to `claude-haiku-4-5`.
204
+
205
+ ### Reports
206
+
207
+ - **Full report** — cost summary + AI costs + storage + top queries, rendered as markdown via Jinja templates.
208
+ - **Cost-only report** — same shape minus the query section.
209
+ - **User access report** — every effective role and grant for a user, formatted as a markdown audit trail.
210
+ - **CSV exports** — most cost commands accept `--csv <path>` to write directly to a file; the risk scan supports `--csv` and `--json`.
211
+
212
+ ### Local SQLite cache
213
+
214
+ - All grants, role edges, and user assignments cached locally for instant lookups.
215
+ - **Incremental refresh** — uses `MODIFIED_ON` / `DELETED_ON` watermarks to fetch only changed rows.
216
+ - Cost data uses a 1-hour TTL in the same SQLite store.
217
+
218
+ ---
219
+
220
+ ## The TUI
221
+
222
+ `snowglobe` (or `snowglobe tui`) opens a full-screen Textual app. Persistent header at the top (profile / role / cache age), nav sidebar on the left, footer with active keybindings at the bottom.
223
+
224
+ ### Screens
225
+
226
+ Number keys `1`–`7` jump directly:
227
+
228
+ | # | Screen | What's on it |
229
+ |---|---|---|
230
+ | **1** | **Home** | KPI cards (cache health, connection, this week's spend + risk count), recent expensive queries, hotkeys (`a` access · `w` who-access · `c` cost · `s` risk · `r` refresh) |
231
+ | **2** | **Access** | Seven tabs: Check / Who-access / Create / Roles / Members / Path / Drift. Object-type aware privilege filtering. |
232
+ | **3** | **Risk** | Five tabs: Scan / Escalation / Dormant / Direct grants / Unused. Re-scan + CSV / JSON export. |
233
+ | **4** | **Cost** | All twelve cost views in one place. Window selector (7d / 30d / 90d), Re-fetch button, drill-downs into per-warehouse / per-user / per-service trends. |
234
+ | **5** | **Tune** | Query optimiser. Three-pane: SQL with syntax highlighting; Heuristics / Native insights / Operator tree / Expensive ops / AI on the right. |
235
+ | **6** | **Reports** | Generate Full / Cost-only / User-access reports with live markdown preview + Save. |
236
+ | **7** | **Refresh** | State counts, refresh actions, connection diagnostics, streaming log. |
237
+
238
+ ### Navigation
239
+
240
+ | Key | Action |
241
+ |---|---|
242
+ | `Tab` / `Shift-Tab` | Cycle focus between widgets |
243
+ | `Enter` | Activate (button press, fire query, open Select dropdown, expand tree node) |
244
+ | `↑` / `↓` | Navigate within lists / tables / dropdowns / trees |
245
+ | `Esc` | Close drill-downs / cancel running workers / blur input (vim mode) |
246
+ | `1`–`7` | Jump to screen by number |
247
+ | `Ctrl-P` | Command palette (switch themes, change profile, etc.) |
248
+ | `q` | Quit |
249
+
250
+ ### Vim mode *(optional)*
251
+
252
+ Pass `--vim` or set `vim: true` in your profile config:
253
+
254
+ | Key | Action | Where |
255
+ |---|---|---|
256
+ | `j` / `k` | Cursor down / up | Lists, tables, trees |
257
+ | `g` / `G` | Top / bottom | Same |
258
+ | `h` / `l` | Collapse / expand | Tree nodes |
259
+ | `Ctrl-d` / `Ctrl-u` | Half-page down / up | Any scrollable |
260
+ | `Esc` | Blur the focused input | Lets `j`/`k` navigate again after typing |
261
+
262
+ Typing into form fields works as expected — Input widgets consume keypresses before vim bindings fire.
263
+
264
+ ### Themes
265
+
266
+ Two branded themes ship with the app: `snowglobe-dark` (default) and `snowglobe-light`. Open the command palette with **`Ctrl-P`**, type `theme`, and pick any — Textual's built-ins (`nord`, `monokai`, `dracula`, `catppuccin-*`, etc.) are also listed.
267
+
268
+ ---
269
+
270
+ ## The interactive shell
271
+
272
+ `snowglobe shell` drops you into a REPL with fuzzy tab-completion on usernames, roles, and object FQNs.
273
+
274
+ | Command | Description |
275
+ |---|---|
276
+ | `check` | Guided wizard — covers all seven access-style checks step by step |
277
+ | `roles <user>` | Direct + inherited roles for a user |
278
+ | `members <role>` | Direct + inherited users for a role |
279
+ | `path <from> <to>` | Inheritance paths between two roles |
280
+ | `escalation <role>` | Can this role reach admin privileges? |
281
+ | `scan` | Full privilege-escalation scan with risk scoring; `--csv` / `--json` export |
282
+ | `cost` | Cost wizard, or use sub-verbs: `cost summary`, `cost warehouses`, `cost users`, `cost ai`, `cost queries`, `cost trend`, `cost storage`, `cost budget`, `cost replication`, `cost mv` |
283
+ | `optimize <query_id>` | Run the optimiser on a specific query |
284
+ | `drift` | Show access changes since last refresh (or `--days N`) |
285
+ | `unused` | Find roles with data privileges but no recent activity |
286
+ | `report <user>` | Markdown access report for a user |
287
+ | `report full` / `report cost` | Cost / AI / storage / top queries report saved to `.md` |
288
+ | `refresh` | Refresh cached state from Snowflake (`--full` for a complete rebuild) |
289
+ | `status` | Current working state + cache age |
290
+ | `debug` | Connection diagnostics |
291
+ | `help` / `?` | List commands |
292
+ | `use role <name>` / `use user <name>` | Set the active role / user for subsequent commands |
293
+ | `access` / `whoaccess` / `create` | Direct shortcuts that skip the wizard |
294
+ | `exit` | Quit |
295
+
296
+ ---
297
+
298
+ ## Headless CLI
299
+
300
+ For CI, cron, and scripts. Output formats: `--output table|json` plus per-command `--csv <path>`.
301
+
302
+ | Command | Description |
303
+ |---|---|
304
+ | `snowglobe` | Launch the TUI (falls back to `shell` if Textual isn't installed) |
305
+ | `snowglobe tui [--vim]` | Launch the TUI explicitly |
306
+ | `snowglobe shell` | Launch the REPL shell |
307
+ | `snowglobe refresh [--full]` | Incremental (default) or full state refresh |
308
+ | `snowglobe debug` | Connection diagnostics (eight-step checklist) |
309
+ | `snowglobe access check` | Explain access for a user / role on an object |
310
+ | `snowglobe access create` | Where can a role create objects? |
311
+ | `snowglobe access whoaccess` | Reverse lookup — who can access this object? |
312
+ | `snowglobe cost summary` | Account spend by service type |
313
+ | `snowglobe cost warehouses` | Per-warehouse credit breakdown |
314
+ | `snowglobe cost users` | Complete cost per user (warehouse + Cortex) |
315
+ | `snowglobe cost ai` | AI/ML token costs by service |
316
+ | `snowglobe cost ai-users` | AI costs per user with service split |
317
+ | `snowglobe cost queries` | Top expensive queries by credits or bytes |
318
+ | `snowglobe cost trend` | Daily spend trend with Δ% and rolling 7-day average |
319
+ | `snowglobe cost storage` | Per-DB storage with estimated monthly cost |
320
+ | `snowglobe cost services` | Pipes / serverless tasks / SPCS / clustering / search optimisation |
321
+ | `snowglobe cost budget` | Snowflake-native budget status |
322
+ | `snowglobe cost replication` | Replication credits |
323
+ | `snowglobe cost mv` | Materialized-view refresh credits |
324
+ | `snowglobe optimize query --query-id <id>` | Full query analysis + Cortex AI suggestion |
325
+ | `snowglobe optimize top-queries [--analyze]` | List top expensive queries; optionally analyse each |
326
+ | `snowglobe report full` | Generate the full markdown report |
327
+ | `snowglobe report cost` | Cost-only markdown report |
328
+ | `snowglobe report queries` | Top-queries CSV export |
329
+
330
+ ### Global options
331
+
332
+ - `--profile <name>` — connection profile (default: `default`)
333
+ - `--role <name>` — override the role from the profile
334
+ - `--output table|json` — output format
335
+ - `--verbose` / `-v` — verbose output
336
+
337
+ ---
338
+
339
+ ## Security
340
+
341
+ Snowglobe is **read-only by design** — its guiding principle is that it earns trust by never touching production state.
342
+
343
+ - The Snowflake connection layer actively blocks `CREATE`, `ALTER`, `DROP`, `GRANT`, `REVOKE`, and `TRUNCATE` statements before they reach Snowflake.
344
+ - All metadata is fetched via bulk queries against `SNOWFLAKE.ACCOUNT_USAGE` (and `ORGANIZATION_USAGE.RATE_SHEET_DAILY` if available).
345
+ - All data stays in your environment. No telemetry, no callbacks, no external requests.
346
+ - Credentials are read from the local config file and never logged.
347
+ - Cortex AI calls run inside your Snowflake account via `AI_COMPLETE` — the SQL and operator stats never leave Snowflake.
348
+
349
+ ---
350
+
351
+ ## Limitations
352
+
353
+ - **`ACCOUNT_USAGE` has up to ~45 minutes of latency** — very recent grant or query changes won't appear until the next refresh.
354
+ - **STREAMLIT, NOTEBOOK, DYNAMIC TABLE, ALERT, TAG, SECRET** grants aren't in `GRANTS_TO_ROLES`; Snowglobe falls back to live `SHOW GRANTS ON` for those types (slower, but works).
355
+ - **Query Attribution** (`QUERY_ATTRIBUTION_HISTORY`) requires Snowflake's Query Attribution feature, which is not available on Standard tier or older accounts. Snowglobe falls back to `QUERY_HISTORY` with a note in the UI.
356
+ - **Cortex AI views** are not available in all Snowflake regions or tiers. Snowglobe queries each view independently and silently skips missing ones.
357
+
358
+ ---
359
+
360
+ ## Contributing
361
+
362
+ Pull requests and issues are welcome on [GitHub](https://github.com/jcolethornton/snowglobe).
363
+
364
+ ---
365
+
366
+ ## License
367
+
368
+ Apache-2.0 © 2026 Jaryd Thornton. See `LICENSE` for the full text.