strata-cli 0.1.7 → 0.1.9

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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +25 -0
  3. data/README.md +24 -1
  4. data/lib/strata/cli/ai/services/table_generator.rb +35 -20
  5. data/lib/strata/cli/api/client.rb +23 -63
  6. data/lib/strata/cli/api/response_error_handler.rb +115 -0
  7. data/lib/strata/cli/credentials.rb +5 -9
  8. data/lib/strata/cli/error_reporter.rb +4 -1
  9. data/lib/strata/cli/generators/datasource.rb +4 -3
  10. data/lib/strata/cli/generators/group.rb +37 -0
  11. data/lib/strata/cli/generators/migration.rb +2 -1
  12. data/lib/strata/cli/generators/project.rb +18 -11
  13. data/lib/strata/cli/generators/relation.rb +2 -1
  14. data/lib/strata/cli/generators/table.rb +5 -8
  15. data/lib/strata/cli/generators/templates/adapters/databricks.yml +4 -3
  16. data/lib/strata/cli/generators/templates/adapters/snowflake.yml +1 -21
  17. data/lib/strata/cli/generators/templates/table.table_name.yml +1 -1
  18. data/lib/strata/cli/generators/test.rb +2 -1
  19. data/lib/strata/cli/guard.rb +4 -1
  20. data/lib/strata/cli/helpers/command_context.rb +8 -9
  21. data/lib/strata/cli/helpers/datasource_helper.rb +1 -1
  22. data/lib/strata/cli/helpers/description_helper.rb +2 -1
  23. data/lib/strata/cli/main.rb +15 -3
  24. data/lib/strata/cli/output.rb +103 -0
  25. data/lib/strata/cli/sub_commands/audit.rb +4 -3
  26. data/lib/strata/cli/sub_commands/branch.rb +163 -0
  27. data/lib/strata/cli/sub_commands/create.rb +1 -2
  28. data/lib/strata/cli/sub_commands/datasource.rb +13 -4
  29. data/lib/strata/cli/sub_commands/deploy.rb +14 -13
  30. data/lib/strata/cli/sub_commands/project.rb +4 -3
  31. data/lib/strata/cli/sub_commands/table.rb +9 -8
  32. data/lib/strata/cli/terminal.rb +7 -4
  33. data/lib/strata/cli/ui/field_editor.rb +21 -27
  34. data/lib/strata/cli/utils/deployment_monitor.rb +15 -34
  35. data/lib/strata/cli/utils/git.rb +78 -0
  36. data/lib/strata/cli/utils/import_manager.rb +4 -1
  37. data/lib/strata/cli/utils/test_reporter.rb +4 -32
  38. data/lib/strata/cli/utils/version_checker.rb +4 -8
  39. data/lib/strata/cli/utils.rb +3 -1
  40. data/lib/strata/cli/version.rb +1 -1
  41. data/lib/strata/cli.rb +4 -3
  42. metadata +4 -2
  43. data/lib/strata/cli/helpers/color_helper.rb +0 -103
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../guard"
4
+ require_relative "../output"
5
+ require_relative "../api/client"
6
+ require_relative "../utils/git"
7
+ require "time"
8
+ require "tty-prompt"
9
+
10
+ module Strata
11
+ module CLI
12
+ module SubCommands
13
+ class Branch < Thor
14
+ include Guard
15
+ include Output
16
+
17
+ default_command :list
18
+ class_option :environment, aliases: ["e"], type: :string
19
+
20
+ desc "list", "List local and Strata server branches"
21
+ def list
22
+ config = CLI.config.get_for_environment(options[:environment])
23
+ local_branch_names = Utils::Git.local_branches
24
+ server_branches = fetch_server_branches(config)
25
+
26
+ rows = branch_rows(local_branch_names, server_branches)
27
+ if rows.empty?
28
+ print_info("No local or Strata server branches found.")
29
+ else
30
+ rows.each { |row| print_info(row) }
31
+ end
32
+ end
33
+
34
+ desc "create BRANCH", "Create and checkout a local git branch"
35
+ def create(branch_name)
36
+ branch_name = Utils::Git.create_and_checkout_branch(branch_name)
37
+ print_success("Created and checked out branch '#{branch_name}'.")
38
+ end
39
+
40
+ desc "checkout BRANCH", "Checkout a local git branch"
41
+ def checkout(branch_name)
42
+ branch_name = Utils::Git.checkout_branch(branch_name)
43
+ print_success("Checked out branch '#{branch_name}'.")
44
+ end
45
+
46
+ desc "delete", "Delete a branch from Strata server"
47
+ method_option :branch, aliases: ["b"], type: :string, desc: "Branch to delete. Defaults to current git branch."
48
+ def delete
49
+ branch_id = branch_to_delete
50
+ config = CLI.config.get_for_environment(options[:environment])
51
+ validate_delete_configuration(config)
52
+
53
+ return unless confirm_branch_deletion(branch_id)
54
+
55
+ client = API::Client.new(config["server"], config["api_key"])
56
+ server_deleted = client.delete_branch(config["project_id"], branch_id)
57
+ print_warning("Branch '#{branch_id}' was not found on Strata server. Deleting local branch only.") unless server_deleted
58
+
59
+ delete_local_branch(branch_id)
60
+
61
+ print_success("Deleted branch '#{branch_id}'.")
62
+ end
63
+
64
+ private
65
+
66
+ def branch_to_delete
67
+ explicit_branch = options[:branch].to_s.strip
68
+ branch_id = explicit_branch.empty? ? Utils::Git.current_branch.to_s.strip : explicit_branch
69
+
70
+ if branch_id.empty?
71
+ raise Strata::CommandError, "Branch name is required. Use -b BRANCH to specify one."
72
+ end
73
+
74
+ if branch_id == "HEAD"
75
+ raise Strata::CommandError, "Cannot determine current branch while in detached HEAD. Use -b BRANCH to specify one."
76
+ end
77
+
78
+ branch_id
79
+ end
80
+
81
+ def validate_delete_configuration(config)
82
+ ensure_config_present(config, "server")
83
+ ensure_config_present(config, "api_key")
84
+ ensure_config_present(config, "project_id")
85
+ end
86
+
87
+ def ensure_config_present(config, key)
88
+ return if config[key] && !config[key].to_s.strip.empty?
89
+
90
+ raise Strata::CommandError, "Missing required configuration: #{key}. Check your project.yml or .strata file."
91
+ end
92
+
93
+ def confirm_branch_deletion(branch_id)
94
+ prompt = TTY::Prompt.new
95
+ answer = prompt.ask("Type '#{branch_id}' to delete this branch from Strata:")
96
+ return true if answer.to_s == branch_id
97
+
98
+ print_warning("Branch deletion cancelled.")
99
+ false
100
+ end
101
+
102
+ def fetch_server_branches(config)
103
+ unless server_configured?(config)
104
+ print_warning("Server configuration is incomplete. Showing local branches only.")
105
+ return []
106
+ end
107
+
108
+ API::Client.new(config["server"], config["api_key"]).branches(config["project_id"])
109
+ end
110
+
111
+ def server_configured?(config)
112
+ %w[server api_key project_id].all? { |key| config[key] && !config[key].to_s.strip.empty? }
113
+ end
114
+
115
+ def branch_rows(local_branch_names, server_branches)
116
+ local_names = local_branch_names.to_h { |name| [name, true] }
117
+ server_by_uid = server_branches.to_h { |branch| [branch["uid"], branch] }
118
+ current_branch = Utils::Git.git_repo? ? Utils::Git.current_branch : nil
119
+
120
+ (local_names.keys | server_by_uid.keys).sort.map do |branch_name|
121
+ server_branch = server_by_uid[branch_name]
122
+ "#{current_branch_marker(branch_name, current_branch)}(#{branch_location(local_names.key?(branch_name), server_branch)}) - last deployed at: #{format_last_deployed_at(server_branch && server_branch["last_deployed_at"])}"
123
+ end
124
+ end
125
+
126
+ def current_branch_marker(branch_name, current_branch)
127
+ (current_branch == branch_name) ? "* #{branch_name}" : branch_name
128
+ end
129
+
130
+ def branch_location(local, server_branch)
131
+ tags = []
132
+ tags << "local" if local
133
+ tags << "server" if server_branch
134
+ tags.empty? ? "unknown" : tags.join(", ")
135
+ end
136
+
137
+ def format_last_deployed_at(value)
138
+ return "" if value.nil? || value.to_s.strip.empty?
139
+
140
+ Time.parse(value.to_s).strftime("%Y-%m-%d %H:%M:%S")
141
+ rescue ArgumentError
142
+ value.to_s
143
+ end
144
+
145
+ def delete_local_branch(branch_id)
146
+ case Utils::Git.delete_local_branch(branch_id, fallback_branches: local_delete_fallback_branches)
147
+ when :deleted
148
+ print_info("Deleted local git branch '#{branch_id}'.")
149
+ when :not_found
150
+ print_warning("Local git branch '#{branch_id}' was not found.")
151
+ when :not_git_repo
152
+ print_warning("No local git repository found. Skipped local branch deletion.")
153
+ end
154
+ end
155
+
156
+ def local_delete_fallback_branches
157
+ production_branch = CLI.config["production_branch"].to_s.strip
158
+ ([production_branch] + %w[main master]).reject(&:empty?).uniq
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end
@@ -7,7 +7,6 @@ require_relative "../ui/field_editor"
7
7
  require_relative "../credentials"
8
8
  require_relative "../helpers/table_filter"
9
9
  require_relative "../ai/services/table_generator"
10
- require_relative "../helpers/color_helper"
11
10
  require_relative "../helpers/prompts"
12
11
  require_relative "../helpers/description_helper"
13
12
  require_relative "../helpers/command_context"
@@ -406,7 +405,7 @@ module Strata
406
405
 
407
406
  return unless from.strip == to.strip
408
407
 
409
- say "Warning: --from and --to have the same value. Migration will have no effect.", ColorHelper.warning
408
+ print_warning("Warning: --from and --to have the same value. Migration will have no effect.")
410
409
  end
411
410
 
412
411
  def prompt_migration_hook(operation)
@@ -3,6 +3,7 @@
3
3
  require_relative "../guard"
4
4
  require_relative "../credentials"
5
5
  require_relative "../terminal"
6
+ require_relative "../output"
6
7
  require_relative "../utils/git"
7
8
  require "tty-prompt"
8
9
  require_relative "../helpers/datasource_helper"
@@ -15,6 +16,7 @@ module Strata
15
16
  include Thor::Actions
16
17
  include Guard
17
18
  include Terminal
19
+ include Output
18
20
  include DatasourceHelper
19
21
  extend Helpers::DescriptionHelper
20
22
 
@@ -86,7 +88,7 @@ module Strata
86
88
  # Collect adapter-specific fields
87
89
  adapter_fields(adapter).each do |field, default_value|
88
90
  config[field] = if field == "auth_mode"
89
- prompt.select(" Authentication mode:", %w[pat kp oauth], default: default_value)
91
+ default_value
90
92
  elsif %w[ssl azure].include?(field)
91
93
  prompt.yes?(" #{field.tr("_", " ").capitalize}?", default: default_value)
92
94
  elsif field == "port"
@@ -103,11 +105,14 @@ module Strata
103
105
  say "\n✔ Added #{adapter} config to datasources.yml", :green
104
106
 
105
107
  # Automatically collect credentials if required
106
- creds = Credentials.new(adapter)
108
+ creds = Credentials.new(adapter, datasource_config: config)
107
109
  if creds.required?
108
110
  say "\n Now let's set up credentials:\n", :yellow
109
111
  say " Note: Credentials are stored securely in the local .strata file", :cyan
110
112
  say " and are NOT committed to the repository (ensured by .gitignore).", :cyan
113
+ if adapter == "databricks"
114
+ say " Databricks uses OAuth M2M with service principal credentials (client ID + client secret).", :cyan
115
+ end
111
116
  say ""
112
117
  creds.collect
113
118
  creds.write_local(ds_key, self)
@@ -137,7 +142,7 @@ module Strata
137
142
  end
138
143
 
139
144
  adapter = datasources[ds_key]["adapter"]
140
- creds = Credentials.new(adapter)
145
+ creds = Credentials.new(adapter, datasource_config: datasources[ds_key])
141
146
 
142
147
  unless creds.required?
143
148
  say "Credentials not required for #{adapter} adapter.", :yellow
@@ -147,6 +152,9 @@ module Strata
147
152
  say "\nEnter credentials for #{ds_key}", :red
148
153
  say " Note: Credentials are stored securely in the local .strata file", :cyan
149
154
  say " and are NOT committed to the repository (ensured by .gitignore).", :cyan
155
+ if adapter == "databricks"
156
+ say " Databricks uses OAuth M2M with service principal credentials (client ID + client secret).", :cyan
157
+ end
150
158
  say ""
151
159
  creds.collect
152
160
  creds.write_local(ds_key, self)
@@ -343,7 +351,8 @@ module Strata
343
351
  "host" => "workspace-id.cloud.databricks.com",
344
352
  "warehouse" => "warehouse_id",
345
353
  "catalog" => "main",
346
- "schema" => "default"
354
+ "schema" => "default",
355
+ "auth_mode" => "oauth_m2m"
347
356
  }
348
357
  else
349
358
  {}
@@ -2,7 +2,7 @@
2
2
 
3
3
  require_relative "../guard"
4
4
  require_relative "../terminal"
5
- require_relative "../helpers/color_helper"
5
+ require_relative "../output"
6
6
  require_relative "../helpers/project_helper"
7
7
  require_relative "../helpers/description_helper"
8
8
  require_relative "../api/client"
@@ -22,6 +22,7 @@ module Strata
22
22
  class Deploy < Thor
23
23
  include Guard
24
24
  include Terminal
25
+ include Output
25
26
  extend Helpers::DescriptionHelper
26
27
 
27
28
  default_command :deploy
@@ -57,7 +58,7 @@ module Strata
57
58
  # If only external imports changed, generate synthetic commit identifier
58
59
  if refreshed_imports.any? && local_file_changes.empty?
59
60
  import_commit_hash = Utils::ImportManager.generate_import_commit_hash(project_path)
60
- say "\nExternal imports change found. Proceeding with deployment", ColorHelper.info
61
+ print_info("\nExternal imports change found. Proceeding with deployment")
61
62
  metadata[:commit] = "imports-#{import_commit_hash}"
62
63
  metadata[:commit_message] = "External imports updated: #{refreshed_imports.map do |c|
63
64
  File.basename(c[:source])
@@ -68,7 +69,7 @@ module Strata
68
69
 
69
70
  env_display = options[:environment] ? " (#{options[:environment]} environment)" : ""
70
71
  server_info = config["server"] ? " (#{config["server"]})" : ""
71
- say "\nDeploying to branch '#{branch_id}' on Strata server#{env_display}#{server_info}\n", ColorHelper.info
72
+ print_info("\nDeploying to branch '#{branch_id}' on Strata server#{env_display}#{server_info}\n")
72
73
 
73
74
  deployment = submit_deployment(config, branch_id, archive_path, metadata)
74
75
 
@@ -87,7 +88,7 @@ module Strata
87
88
  deployment = client.latest_deployment(config["project_id"], branch_id)
88
89
 
89
90
  unless deployment
90
- say "No deployments found for branch '#{branch_id}'.\n", ColorHelper.info
91
+ print_info("No deployments found for branch '#{branch_id}'.\n")
91
92
  return
92
93
  end
93
94
 
@@ -331,8 +332,8 @@ module Strata
331
332
 
332
333
  if changed_paths.empty? && refreshed_imports.empty?
333
334
  unless options[:force]
334
- say "\nNo files changed since last deployment.", ColorHelper.warning
335
- say "To force deploy, run command with --force or -f flag.\n", ColorHelper.info
335
+ print_warning("\nNo files changed since last deployment.")
336
+ print_info("To force deploy, run command with --force or -f flag.\n")
336
337
  exit(0)
337
338
  end
338
339
  Utils::Archive.create(project_path, file_overrides: file_overrides)
@@ -345,7 +346,7 @@ module Strata
345
346
  change_count = files_to_include.length
346
347
  change_count += refreshed_imports.length if refreshed_imports.any?
347
348
 
348
- say "Including #{change_count} changed file(s) in archive...\n", ColorHelper.info
349
+ print_info("Including #{change_count} changed file(s) in archive...\n")
349
350
  Utils::Archive.create(project_path, files_to_include: files_to_include, file_overrides: file_overrides)
350
351
  end
351
352
  else
@@ -412,7 +413,7 @@ module Strata
412
413
  message = "Deploying to production branch '#{branch_id}'. Continue? [y/N]"
413
414
  return if prompt.yes?(message, default: false)
414
415
 
415
- say "Deployment cancelled.", ColorHelper.warning
416
+ print_warning("Deployment cancelled.")
416
417
  exit(0)
417
418
  end
418
419
 
@@ -422,7 +423,7 @@ module Strata
422
423
  project_config = YAML.safe_load_file("project.yml", permitted_classes: [Date, Time], aliases: true) || {}
423
424
  project_config["production_branch"] || "main"
424
425
  rescue => e
425
- warn "Failed to load project.yml: #{e.message}" if ENV["DEBUG"]
426
+ print_warning("Failed to load project.yml: #{e.message}") if ENV["DEBUG"]
426
427
  "main"
427
428
  end
428
429
 
@@ -436,11 +437,11 @@ module Strata
436
437
  return nil unless last_deployment && last_deployment["commit"]
437
438
 
438
439
  commit_hash = last_deployment["commit"]
439
- say "Found last successful deployment at commit: #{commit_hash[0..7]}...\n", ColorHelper.info
440
+ print_info("Found last successful deployment at commit: #{commit_hash[0..7]}...\n")
440
441
  commit_hash
441
442
  rescue Strata::CommandError
442
443
  # If we can't get last deployment (e.g., first deployment), continue with all files
443
- say "No previous deployment found. Including all files.\n", ColorHelper.info
444
+ print_info("No previous deployment found. Including all files.\n")
444
445
  nil
445
446
  end
446
447
  end
@@ -477,10 +478,10 @@ module Strata
477
478
  refreshed = Utils::ImportManager.refresh_external_imports(project_path)
478
479
 
479
480
  if refreshed.any?
480
- say "\n Skimmed #{refreshed.length} external import(s):", ColorHelper.info
481
+ print_info("\n Skimmed #{refreshed.length} external import(s):")
481
482
  refreshed.each do |import|
482
483
  filename = File.basename(import[:source])
483
- say " • #{filename}", ColorHelper.info
484
+ print_info(" • #{filename}")
484
485
  end
485
486
  end
486
487
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  require_relative "../guard"
4
4
  require_relative "../terminal"
5
- require_relative "../helpers/color_helper"
5
+ require_relative "../output"
6
6
  require_relative "../helpers/project_helper"
7
7
  require_relative "../api/client"
8
8
  require "yaml"
@@ -13,6 +13,7 @@ module Strata
13
13
  class Project < Thor
14
14
  include Guard
15
15
  include Terminal
16
+ include Output
16
17
 
17
18
  desc "link PROJECT_ID", "Link local project to an existing project on the server"
18
19
  def link(project_id)
@@ -22,13 +23,13 @@ module Strata
22
23
  project_config = YAML.safe_load_file(project_yml_path, permitted_classes: [Date, Time], aliases: true) || {}
23
24
 
24
25
  if project_config["project_id"] && !project_config["project_id"].to_s.strip.empty?
25
- say "Project is already linked to project_id: #{project_config["project_id"]}", ColorHelper.warning
26
+ print_warning("Project is already linked to project_id: #{project_config["project_id"]}")
26
27
  return
27
28
  end
28
29
 
29
30
  return unless Helpers::ProjectHelper.persist_project_id_to_yml(project_id, project_yml_path: project_yml_path)
30
31
 
31
- say "✓ Project linked successfully. project_id: #{project_id}", ColorHelper.info
32
+ print_info("✓ Project linked successfully. project_id: #{project_id}")
32
33
  end
33
34
  end
34
35
  end
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../helpers/color_helper"
4
3
  require_relative "../helpers/prompts"
5
4
  require_relative "../helpers/command_context"
5
+ require_relative "../output"
6
6
 
7
7
  module Strata
8
8
  module CLI
@@ -10,6 +10,7 @@ module Strata
10
10
  class Table < Thor
11
11
  include Guard
12
12
  include Terminal
13
+ include Output
13
14
  include Prompts
14
15
  include Thor::Actions
15
16
  include Helpers::CommandContext
@@ -18,24 +19,24 @@ module Strata
18
19
  desc "list", "List all semantic models in the project"
19
20
  def list
20
21
  unless Dir.exist?("models")
21
- say MSG_NO_MODELS_DIR, :yellow
22
+ print_warning(MSG_NO_MODELS_DIR)
22
23
  return
23
24
  end
24
25
 
25
- model_files = Dir.glob("models/tbl[._]*.yml").sort
26
+ model_files = Dir.glob("models/**/tbl[._]*.yml").sort
26
27
 
27
28
  if model_files.empty?
28
- say MSG_NO_MODELS_FOUND, :yellow
29
+ print_warning(MSG_NO_MODELS_FOUND)
29
30
  return
30
31
  end
31
32
 
32
- say MSG_MODELS_LIST_HEADER, :cyan
33
+ print_info(MSG_MODELS_LIST_HEADER)
33
34
 
34
35
  model_files.each do |file|
35
36
  display_model_item(file)
36
37
  end
37
38
 
38
- say MSG_MODELS_COUNT % model_files.length, :cyan
39
+ print_info(MSG_MODELS_COUNT % model_files.length)
39
40
  end
40
41
 
41
42
  private
@@ -47,9 +48,9 @@ module Strata
47
48
  desc = "#{desc[0..50]}..." if desc.length > 50
48
49
 
49
50
  if desc.empty?
50
- say " #{name}", :white
51
+ print_info(" #{name}")
51
52
  else
52
- say " #{name.ljust(25)} #{desc}", :white
53
+ print_info(" #{name.ljust(25)} #{desc}")
53
54
  end
54
55
  end
55
56
  end
@@ -4,6 +4,7 @@ require "tty-spinner"
4
4
  require "pastel"
5
5
  require "tty-table"
6
6
  require "io/console"
7
+ require_relative "output"
7
8
 
8
9
  module Strata
9
10
  module CLI
@@ -23,13 +24,15 @@ module Strata
23
24
 
24
25
  # Shows a loader while running IO tasks
25
26
  # Uses consistent format matching deployment monitor: cyan spinner that transitions to checkmark
26
- def with_spinner(message = "Loading...",
27
+ def with_spinner(
28
+ message = "Loading...",
27
29
  success_message: "",
28
30
  failed_message: "",
29
31
  clear: false,
30
32
  message_color: :cyan,
31
33
  spinner_color: :cyan,
32
- format: :dots)
34
+ format: :dots
35
+ )
33
36
  spinner = create_spinner(message,
34
37
  message_color: message_color,
35
38
  spinner_color: spinner_color,
@@ -39,10 +42,10 @@ module Strata
39
42
 
40
43
  begin
41
44
  result = yield
42
- spinner.success(success_message.empty? ? "" : Pastel.new.green(success_message))
45
+ spinner.success(success_message.empty? ? "" : Output.format(:success, success_message))
43
46
  result
44
47
  rescue => e
45
- spinner.error(failed_message.empty? ? "" : Pastel.new.red(failed_message))
48
+ spinner.error(failed_message.empty? ? "" : Output.format(:error, failed_message))
46
49
  raise e
47
50
  end
48
51
  end
@@ -3,7 +3,7 @@
3
3
  require "tty-prompt"
4
4
  require "tty-table"
5
5
  require "pastel"
6
- require_relative "../helpers/color_helper"
6
+ require_relative "../output"
7
7
 
8
8
  module Strata
9
9
  module CLI
@@ -77,21 +77,19 @@ module Strata
77
77
  end
78
78
 
79
79
  def display_table
80
- colors = ColorHelper
81
- puts colors.highlight("\n Field Editor - Review & Edit Generated Fields\n")
80
+ puts Output.format(:highlight, "\n Field Editor - Review & Edit Generated Fields\n")
82
81
 
83
82
  rows = @fields.each_with_index.map do |field, idx|
84
- indicator = (idx == @selected_index) ? colors.selected("❯") : " "
85
- status = field[:skipped] ? colors.disabled("[SKIP]") : ""
83
+ indicator = (idx == @selected_index) ? Output.format(:selected, "❯") : " "
84
+ status = field[:skipped] ? Output.format(:disabled, "[SKIP]") : ""
86
85
 
87
- name_cell = field[:skipped] ? colors.disabled(field[:name]) : field[:name]
86
+ name_cell = field[:skipped] ? Output.format(:disabled, field[:name]) : field[:name]
88
87
  desc_cell = if field[:skipped]
89
- colors.disabled(truncate(field[:description],
90
- 25))
88
+ Output.format(:disabled, truncate(field[:description], 25))
91
89
  else
92
90
  truncate(field[:description], 25)
93
91
  end
94
- expr_cell = field[:skipped] ? colors.disabled(field[:expression]) : field[:expression]
92
+ expr_cell = field[:skipped] ? Output.format(:disabled, field[:expression]) : field[:expression]
95
93
 
96
94
  [
97
95
  "#{indicator} #{status}",
@@ -112,14 +110,13 @@ module Strata
112
110
  end
113
111
 
114
112
  def display_help
115
- colors = ColorHelper
116
113
  puts ""
117
- puts " #{colors.info("↑/↓")} Navigate " \
118
- "#{colors.success("[Enter]")} Edit " \
119
- "#{colors.warning("[S]")}kip " \
120
- "#{colors.primary("[P]")}rompt " \
121
- "#{colors.secondary("[C]")}onfirm " \
122
- "#{colors.error("[Q/Esc]")}uit"
114
+ puts " #{Output.format(:info, "↑/↓")} Navigate " \
115
+ "#{Output.format(:success, "[Enter]")} Edit " \
116
+ "#{Output.format(:warning, "[S]")}kip " \
117
+ "#{Output.format(:primary, "[P]")}rompt " \
118
+ "#{Output.format(:secondary, "[C]")}onfirm " \
119
+ "#{Output.format(:error, "[Q/Esc]")}uit"
123
120
  puts ""
124
121
  end
125
122
 
@@ -129,10 +126,9 @@ module Strata
129
126
 
130
127
  def edit_current_field
131
128
  field = @fields[@selected_index]
132
- colors = ColorHelper
133
129
 
134
- puts colors.highlight("\n Editing: #{field[:name]}\n")
135
- puts colors.dim(" (Press Ctrl+C or type 'back' to go back without saving)\n")
130
+ puts Output.format(:highlight, "\n Editing: #{field[:name]}\n")
131
+ puts Output.format(:dim, " (Press Ctrl+C or type 'back' to go back without saving)\n")
136
132
 
137
133
  begin
138
134
  field[:name] = prompt.ask(" Field Name:", default: field[:name])
@@ -170,20 +166,18 @@ module Strata
170
166
  end
171
167
 
172
168
  def handle_prompt_mode
173
- require_relative "../ai/services/model_generator"
169
+ require_relative "../ai/services/table_generator"
174
170
  require_relative "../terminal"
175
- generator = AI::Services::ModelGenerator.new
176
-
177
- colors = ColorHelper
171
+ generator = AI::Services::TableGenerator.new
178
172
 
179
173
  unless generator.ai_available?
180
- puts colors.warning("\n AI is not available. Configure AI in .strata file.")
174
+ puts Output.format(:warning, "\n AI is not available. Configure AI in .strata file.")
181
175
  sleep 1.5
182
176
  return
183
177
  end
184
178
 
185
179
  unless @table_context
186
- puts colors.warning("\n Prompt mode requires table context.")
180
+ puts Output.format(:warning, "\n Prompt mode requires table context.")
187
181
  sleep 1.5
188
182
  return
189
183
  end
@@ -208,11 +202,11 @@ module Strata
208
202
  @selected_index = 0
209
203
  # No message needed - table will refresh and show new fields
210
204
  else
211
- puts colors.warning(" No changes - keeping current fields")
205
+ puts Output.format(:warning, " No changes - keeping current fields")
212
206
  sleep 1
213
207
  end
214
208
  rescue => e
215
- puts colors.error("\n Error: #{e.message}")
209
+ puts Output.format(:error, "\n Error: #{e.message}")
216
210
  sleep 2
217
211
  end
218
212
  end