strata-cli 0.1.15 → 0.1.16
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 +20 -0
- data/lib/strata/cli/agent_mode.rb +23 -1
- data/lib/strata/cli/descriptions/init.txt +4 -0
- data/lib/strata/cli/generators/project.rb +20 -4
- data/lib/strata/cli/generators/templates/AGENTS.md +4 -0
- data/lib/strata/cli/helpers/browser_auth.rb +52 -0
- data/lib/strata/cli/helpers/command_context.rb +1 -5
- data/lib/strata/cli/helpers/datasource_helper.rb +1 -1
- data/lib/strata/cli/helpers/prompts.rb +27 -0
- data/lib/strata/cli/helpers/semantic_audit_checks.rb +507 -0
- data/lib/strata/cli/sub_commands/audit.rb +24 -6
- data/lib/strata/cli/sub_commands/branch.rb +0 -1
- data/lib/strata/cli/sub_commands/datasource.rb +7 -4
- data/lib/strata/cli/sub_commands/deploy.rb +42 -22
- data/lib/strata/cli/sub_commands/project.rb +2 -42
- data/lib/strata/cli/utils/git.rb +31 -9
- data/lib/strata/cli/utils/sql_expression_parser.rb +153 -0
- data/lib/strata/cli/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b01b5f82ca969d2d8e586722e10a438fefcc97e02107442d66d8de52bad55656
|
|
4
|
+
data.tar.gz: 8acfb5ff8b4ddd7aacb7de62f1f4f8758cb9b2004660422fcc2f3ee298bd46f6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7ce832a4ccb100cb4835ef212c67ee19ba207f51a33a8d36bc884102379827eb59b93f97cf9028a2dc742071a95006a8e361c840a6066c29dde3d01525240772
|
|
7
|
+
data.tar.gz: f7ce4fe653e46d6ccaf2b5945a08bcd866a0dc2bb3c43880e3facfca746c4f913750a04de1f7a5b51fe43c7c514f228d38bde62fc79d1013379cddeee194457c
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.1.16] - 2026-09-05
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **Local semantic audit**: `strata audit` now mirrors the server's deploy-time validations locally — SQL expression parsing (dimensions must reference a table column, measures need an aggregation function), cross-file `[Field]@d`/`@m` reference resolution, required field types and enum checks, table snapshot and partition rules, relationship join validation, and datasource adapter checks — so deploys fail fast with named errors instead of failing on the server.
|
|
8
|
+
- **Browser sign-in for deploy**: `strata deploy` now offers the browser-based auth flow when it needs an API key, with manual paste kept as a fallback.
|
|
9
|
+
- **Next steps guidance**: `strata init` and `strata datasource add` now print how to build the model next — manually with `strata create table` or via a coding agent — then audit and deploy.
|
|
10
|
+
- **Initial commit on init**: `strata init` commits the generated files (skipped when the repo already has history).
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- **Agent mode auto-detection**: non-tty stdin now implies `--agent`, so agents get JSON output without discovering the flag. Prompts in agent mode emit an `interactive_required` JSON error and exit 1 instead of rendering an unanswerable TUI.
|
|
15
|
+
- **Deploy pre-checks**: the uncommitted-changes check now runs before any prompts, server calls, or audit.
|
|
16
|
+
- **AGENTS.md**: now allowlists which files agents may write (`models/**/*.yml`, `migrations/*.yml`, `tests/*.yml`) and points them to `strata datasource meta|exec --agent` instead of scratch scripts. `strata audit all` warns about stray non-YAML files under `models/` and scripts at the project root.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- **Deploy rename detection**: the no-baseline fallback now includes all tracked YAML files (previously diffed only `HEAD~1`, dropping earlier renames), `--find-renames` is passed explicitly so rename records don't depend on local git config, and invalid baselines warn instead of silently degrading to a partial deploy.
|
|
21
|
+
- **Audit cardinality**: `many_to_many` is now rejected locally — the server enum rejects it with a crash.
|
|
22
|
+
|
|
3
23
|
## [0.1.15] - 2026-07-27
|
|
4
24
|
|
|
5
25
|
### Added
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "tty-prompt"
|
|
3
4
|
require_relative "agent_output"
|
|
4
5
|
|
|
5
6
|
module Strata
|
|
@@ -10,10 +11,31 @@ module Strata
|
|
|
10
11
|
desc: "Agent mode: structured output, no interactive prompts"
|
|
11
12
|
end
|
|
12
13
|
|
|
14
|
+
# Implied when stdin is not a terminal. tty-prompt does not fail there --
|
|
15
|
+
# it redraws its menu forever on EOF -- so callers that cannot answer a
|
|
16
|
+
# prompt must be treated as agents whether or not they passed --agent.
|
|
13
17
|
def agent_mode?
|
|
14
18
|
value = options[:agent]
|
|
15
19
|
value = options["agent"] if value.nil?
|
|
16
|
-
value == true
|
|
20
|
+
value == true || !interactive?
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def interactive?
|
|
24
|
+
$stdin.tty?
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Every interactive prompt goes through here so a non-interactive caller
|
|
28
|
+
# gets a JSON error it can read instead of a TUI it cannot answer.
|
|
29
|
+
def prompt
|
|
30
|
+
unless interactive?
|
|
31
|
+
AgentOutput.emit_error(
|
|
32
|
+
"This command needs an interactive terminal. Pass the required arguments, " \
|
|
33
|
+
"use --agent for structured output, or ask the user to run it.",
|
|
34
|
+
code: "interactive_required"
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
@prompt ||= TTY::Prompt.new
|
|
17
39
|
end
|
|
18
40
|
|
|
19
41
|
def reject_agent_mode!(message, code: "agent_mode_unsupported")
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
Initialize a new Strata project or clone from an existing repository.
|
|
2
2
|
|
|
3
|
+
Creates an AGENTS.md in the project so a coding agent (Claude Code, Codex, Cursor)
|
|
4
|
+
can build the semantic model for you:
|
|
5
|
+
https://strata.do/developer-docs/developer-guide/cli/building-your-model
|
|
6
|
+
|
|
3
7
|
PROJECT_NAME is optional when using --source option. If provided, creates a new project
|
|
4
8
|
with that name. If using --source, clones the existing project from the URL.
|
|
5
9
|
|
|
@@ -6,6 +6,8 @@ require_relative "../helpers/project_helper"
|
|
|
6
6
|
require_relative "../api/connection_error_handler"
|
|
7
7
|
require_relative "../api/response_error_handler"
|
|
8
8
|
require_relative "../output"
|
|
9
|
+
require_relative "../utils/git"
|
|
10
|
+
require_relative "../helpers/prompts"
|
|
9
11
|
require "faraday"
|
|
10
12
|
require "json"
|
|
11
13
|
require "uri"
|
|
@@ -126,7 +128,22 @@ module Strata
|
|
|
126
128
|
# Change into the project directory and run the existing add command
|
|
127
129
|
inside(uid) do
|
|
128
130
|
require_relative "../sub_commands/datasource"
|
|
129
|
-
|
|
131
|
+
# from_init: the datasource command skips its own next-steps block,
|
|
132
|
+
# completion_message prints one for the whole init instead.
|
|
133
|
+
SubCommands::Datasource.new([], {"from_init" => true}).add
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def initial_commit
|
|
138
|
+
return if cloned_from_git?
|
|
139
|
+
|
|
140
|
+
inside uid do
|
|
141
|
+
case Utils::Git.initial_commit("chore: initialize Strata project")
|
|
142
|
+
when :committed
|
|
143
|
+
print_status(:created, "Initial git commit", type: :success)
|
|
144
|
+
when :failed
|
|
145
|
+
print_info("\n Could not create the initial commit — set git user.name and user.email, then commit yourself.")
|
|
146
|
+
end
|
|
130
147
|
end
|
|
131
148
|
end
|
|
132
149
|
|
|
@@ -134,9 +151,8 @@ module Strata
|
|
|
134
151
|
Output.print_success("\n✔ Strata project '#{uid}' is ready!", context: self)
|
|
135
152
|
Output.print_warning("\nNext steps:", context: self)
|
|
136
153
|
Output.print_info(" 1. cd #{uid}", context: self)
|
|
137
|
-
Output.print_info(" 2. strata datasource add
|
|
138
|
-
Output.print_info("
|
|
139
|
-
Output.print_info("\n", context: self)
|
|
154
|
+
Output.print_info(" 2. strata datasource add # add more datasources", context: self)
|
|
155
|
+
Output.print_info("\n#{Prompts.build_model_next_steps(uid)}", context: self)
|
|
140
156
|
end
|
|
141
157
|
|
|
142
158
|
private
|
|
@@ -93,6 +93,10 @@ Follow this sequence. Do not skip steps or reorder deploy before audit.
|
|
|
93
93
|
|
|
94
94
|
**Agents must not run:** `strata deploy`, `strata datasource add`, interactive `strata create table` / `strata create relation` / `strata create migration`, or writing secrets into `.strata`.
|
|
95
95
|
|
|
96
|
+
**Agents may create or edit only:** `models/**/*.yml`, `migrations/*.yml`, `tests/*.yml`.
|
|
97
|
+
|
|
98
|
+
**Do not create any other file** — no `.rb` / `.py` / `.sh` scripts, notebooks, READMEs, or scratch validators. To inspect the warehouse use `strata datasource tables|meta|exec --agent`; to check your work use `strata audit all --agent`. `strata audit` warns about stray files. If you think a script is genuinely needed, stop and ask the user.
|
|
99
|
+
|
|
96
100
|
**Branches:** Model on feature branches; deploy to the matching Strata server branch for staging. Production deploys typically use `production_branch` in `project.yml` (default `main`). Renames and swaps that affect production query names should happen on the production branch — see [Renames and swaps](#renames-swaps-and-production-branch).
|
|
97
101
|
|
|
98
102
|
## How the semantic layer maps to the warehouse
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "json"
|
|
5
|
+
require "securerandom"
|
|
6
|
+
require "rbconfig"
|
|
7
|
+
|
|
8
|
+
module Strata
|
|
9
|
+
module CLI
|
|
10
|
+
module Helpers
|
|
11
|
+
# Browser-based sign-in: opens the server's CLI auth page and polls
|
|
12
|
+
# until the server hands back an API key for our one-time state token.
|
|
13
|
+
module BrowserAuth
|
|
14
|
+
AUTH_TIMEOUT_SECONDS = 300
|
|
15
|
+
|
|
16
|
+
def fetch_api_key_via_browser(server)
|
|
17
|
+
state = SecureRandom.hex(16)
|
|
18
|
+
auth_url = "#{server}/cli/auth?state=#{state}"
|
|
19
|
+
poll_url = "#{server}/api/v1/cli/auth/#{state}"
|
|
20
|
+
|
|
21
|
+
say "\nOpening your browser to sign in to Strata...", :cyan
|
|
22
|
+
open_browser(auth_url)
|
|
23
|
+
say "Waiting for authentication (you may close the browser tab once done)", :white
|
|
24
|
+
|
|
25
|
+
deadline = Time.now + AUTH_TIMEOUT_SECONDS
|
|
26
|
+
loop do
|
|
27
|
+
raise Strata::CommandError, "Authentication timed out. Please try again." if Time.now > deadline
|
|
28
|
+
|
|
29
|
+
begin
|
|
30
|
+
response = Net::HTTP.get_response(URI(poll_url))
|
|
31
|
+
return JSON.parse(response.body)["api_key"] if response.code == "200"
|
|
32
|
+
rescue Errno::ECONNREFUSED
|
|
33
|
+
raise Strata::CommandError, "Cannot reach server at #{server}. Is it running?"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
print "."
|
|
37
|
+
$stdout.flush
|
|
38
|
+
sleep 1
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def open_browser(url)
|
|
43
|
+
case RbConfig::CONFIG["host_os"]
|
|
44
|
+
when /darwin/ then system("open", url)
|
|
45
|
+
when /linux/ then system("xdg-open", url)
|
|
46
|
+
when /mswin|mingw/ then system("start", "", url)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -17,12 +17,8 @@ module Strata
|
|
|
17
17
|
@adapter ||= create_adapter(datasource_key)
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
def prompt
|
|
21
|
-
@prompt ||= TTY::Prompt.new
|
|
22
|
-
end
|
|
23
|
-
|
|
24
20
|
def datasource_key
|
|
25
|
-
@datasource_key ||= resolve_datasource
|
|
21
|
+
@datasource_key ||= resolve_datasource
|
|
26
22
|
end
|
|
27
23
|
|
|
28
24
|
def table_fetch_result
|
|
@@ -3,6 +3,27 @@
|
|
|
3
3
|
module Strata
|
|
4
4
|
module CLI
|
|
5
5
|
module Prompts
|
|
6
|
+
# Docs. This URL is printed by the CLI and baked into every shipped gem —
|
|
7
|
+
# strata-docs has no redirects plugin, so renaming that page 404s old binaries.
|
|
8
|
+
DOCS_BUILD_MODEL_URL = "https://strata.do/developer-docs/developer-guide/cli/building-your-model"
|
|
9
|
+
|
|
10
|
+
# Shown after 'strata init' and 'strata datasource add'. %s is the agent hint.
|
|
11
|
+
MSG_AGENTS_MD_PRESENT = "AGENTS.md in this project tells it Strata's rules."
|
|
12
|
+
MSG_AGENTS_MD_MISSING = "See the guide below for what to tell it."
|
|
13
|
+
MSG_BUILD_MODEL_NEXT_STEPS = <<~STEPS
|
|
14
|
+
Build your semantic model — two ways:
|
|
15
|
+
|
|
16
|
+
• One table at a time: strata create table PATH/TABLE_NAME
|
|
17
|
+
• All at once: open this project in a coding agent (Claude Code,
|
|
18
|
+
Codex, Cursor) and ask it to build the semantic
|
|
19
|
+
model. %s
|
|
20
|
+
|
|
21
|
+
Then: strata audit all # validate
|
|
22
|
+
strata deploy # publish
|
|
23
|
+
|
|
24
|
+
Guide: #{DOCS_BUILD_MODEL_URL}
|
|
25
|
+
STEPS
|
|
26
|
+
|
|
6
27
|
# Relation Command Prompts
|
|
7
28
|
MSG_SELECT_LEFT_TABLE = "Select LEFT table (the 'many' side usually):"
|
|
8
29
|
MSG_SELECT_RIGHT_TABLE_ALL = "Select RIGHT table (All):"
|
|
@@ -54,6 +75,12 @@ module Strata
|
|
|
54
75
|
def default_migration_hook(operation)
|
|
55
76
|
(operation == "swap") ? "post (after deployment)" : "pre (before deployment)"
|
|
56
77
|
end
|
|
78
|
+
|
|
79
|
+
# dir is the project root — 'strata init --source' clones repos without an AGENTS.md.
|
|
80
|
+
def build_model_next_steps(dir = ".")
|
|
81
|
+
hint = File.exist?(File.join(dir, "AGENTS.md")) ? MSG_AGENTS_MD_PRESENT : MSG_AGENTS_MD_MISSING
|
|
82
|
+
format(MSG_BUILD_MODEL_NEXT_STEPS, hint)
|
|
83
|
+
end
|
|
57
84
|
end
|
|
58
85
|
end
|
|
59
86
|
end
|