omaship 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eb563c444317757eeb0a2a8907d0c6b6b26bd1dc03e2373124270430e8565949
4
- data.tar.gz: cbee20a1cd5b4e9e7c3e6af65246f632e4c024522d0caff7a68027bc8c43a0c5
3
+ metadata.gz: 6cb3e55ca287cec243c7b73d01ecbd5d37b5830fed0707175e3fce4c92168008
4
+ data.tar.gz: 7f38d4db65faaf778be3af3dd13472793f0d6bc396d82d040a20b39e7c770e13
5
5
  SHA512:
6
- metadata.gz: 70ac4ca7b97b49bdfd46b0ac34d9193f183c57cad9c72a695434c705f92e342246d28ff2cf27a5d5b321bad22fe2ada28102c9277de888b131689c39bf66f147
7
- data.tar.gz: 0432cb652ead9e22d92898bb17bd8d9babf45525b6f2e273248fdd55d47ce2deadf54a014002c9184dce302c024d4bad59abc4d7f2c371aa73217b964e981534
6
+ metadata.gz: 0f3aba37dd0ab910fb8132e192c9f14dab1678982723c9805d18cd1a2e47e242f5806ce25c847ec38bdb05fa54105f59eec55888c6ad6d8d633e5fe0a6d71636
7
+ data.tar.gz: 50fc535b1dea46931d96d8e17f894e94af3e6b8a035137aa7b5425f4bdbc8a07df23ec386affff5abc5b3641c2eabf6ea5c140a0c1508c738504607e0d0d734d
data/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.5.0](https://github.com/bloomedai/omaship/compare/omaship/v0.4.0...omaship/v0.5.0) (2026-04-04)
4
+
5
+
6
+ ### Features
7
+
8
+ * ✨ enforce cli semver gate ([39952c0](https://github.com/bloomedai/omaship/commit/39952c0e855043acd1f1bcb3c4f963457032de4c))
9
+ * adopt run-first ship flow and terminology ([#381](https://github.com/bloomedai/omaship/issues/381)) ([8bc857f](https://github.com/bloomedai/omaship/commit/8bc857fe3fcec6213f7d9d90fc9e8511411c8b00))
10
+
11
+
12
+ ### Bug Fixes
13
+
14
+ * 🐛 align onboarding cli flow ([611425a](https://github.com/bloomedai/omaship/commit/611425a8d84446312daa33afb0c5ed49e8500e01))
15
+ * 🐛 HTML-escape landing page inputs, prompt injection defense, regenerate on rename ([#353](https://github.com/bloomedai/omaship/issues/353)) ([85822df](https://github.com/bloomedai/omaship/commit/85822dfdc4dba3758ff506b4e08422d9c8be3187))
16
+ * 🐛 move cli ship identity into api ([c331548](https://github.com/bloomedai/omaship/commit/c331548e8953de09524aa780b550bf2100cab502))
17
+ * 🐛 remove cli skip-purpose flag ([b815ff7](https://github.com/bloomedai/omaship/commit/b815ff767430a5301012a1ba44583e305446f1a8))
18
+ * 🐛 support landing ships in cli ([1a2f7fe](https://github.com/bloomedai/omaship/commit/1a2f7fecfa1f1b1ad277e6e007dd7303a1d2420a))
19
+
20
+
21
+ ### Code Refactoring
22
+
23
+ * ♻️ rename LandingPage to Landingpage ([#337](https://github.com/bloomedai/omaship/issues/337)) ([16e9467](https://github.com/bloomedai/omaship/commit/16e9467dceea8e9a595f4be2edf8b89e9759bdfa))
24
+
3
25
  ## [0.4.0](https://github.com/bloomedai/omaship/compare/omaship/v0.3.0...omaship/v0.4.0) (2026-03-14)
4
26
 
5
27
 
data/README.md CHANGED
@@ -55,9 +55,22 @@ Numeric ids are also accepted.
55
55
  - `omaship complete <bash|zsh|fish>` (print shell completion script)
56
56
  - `omaship logout`
57
57
 
58
- ## Local Development Provisioning
58
+ ## Product Direction
59
59
 
60
- When you run `omaship new <name>` against local Omaship development (`OMASHIP_HOST=http://localhost:3000`), provisioning first installs gems from `https://packages.omaship.com`.
60
+ Omaship CLI is evolving to support two complementary use cases:
61
+
62
+ - standalone Omaship users launching and operating ship runs directly
63
+ - Sokrates-orchestrated workflows where Sokrates calls Omaship capabilities
64
+
65
+ Related docs:
66
+
67
+ - [Sokrates × Omaship Vision](../docs/sokrates-omaship-vision.md)
68
+ - [Sokrates Integration Task List](../docs/sokrates-integration-tasklist.md)
69
+ - [Landing Pages Improvement Plan](../docs/landingpages-improvement-plan.md)
70
+
71
+ ## Local Development Run Execution
72
+
73
+ When you run `omaship new <name>` against local Omaship development (`OMASHIP_HOST=http://localhost:3000`), the initial run first installs gems from `https://packages.omaship.com`.
61
74
 
62
75
  If that registry endpoint is unavailable, Omaship automatically retries `bundle install` through the local package proxy mirror at `http://build.localhost:3000/packages`.
63
76
 
@@ -107,14 +120,14 @@ omaship complete fish > ~/.config/fish/completions/omaship.fish
107
120
  Run from repository root:
108
121
 
109
122
  ```bash
110
- mise exec ruby@4.0.1 -- env BUNDLE_GEMFILE=cli/Gemfile bundle exec ruby cli/bin/omaship -h
123
+ mise exec ruby@4.0.2 -- env BUNDLE_GEMFILE=cli/Gemfile bundle exec ruby cli/bin/omaship -h
111
124
  ```
112
125
 
113
126
  Run all CLI tests:
114
127
 
115
128
  ```bash
116
129
  cd cli
117
- mise exec ruby@4.0.1 -- bundle exec ruby -Itest -e 'Dir.glob("test/**/*_test.rb").sort.each { |f| require "./#{f}" }'
130
+ mise exec ruby@4.0.2 -- bundle exec ruby -Itest -e 'Dir.glob("test/**/*_test.rb").sort.each { |f| require "./#{f}" }'
118
131
  ```
119
132
 
120
133
  ## Release Automation
@@ -8,6 +8,7 @@ module Omaship
8
8
  class UnauthorizedError < Error; end
9
9
  class PermissionDeniedError < Error; end
10
10
  class NotFoundError < Error; end
11
+ class UpgradeRequiredError < Error; end
11
12
 
12
13
  def initialize(host:, token:)
13
14
  @host = host.to_s
@@ -26,9 +27,9 @@ module Omaship
26
27
  post_json("/api/v1/cli/ships", { ship: { root_domain: root_domain, visibility: visibility } })
27
28
  end
28
29
 
29
- def create_landing_page(name:, color_scheme: "mono-dark", purpose_profile: {})
30
+ def create_landingpage(name:, color_scheme: "mono-dark", purpose_profile: {})
30
31
  post_json("/api/v1/cli/ships", {
31
- ship: { name: name, landing_page: true, color_scheme: color_scheme },
32
+ ship: { name: name, landingpage: true, color_scheme: color_scheme },
32
33
  purpose_profile: purpose_profile
33
34
  })
34
35
  end
@@ -60,11 +61,9 @@ module Omaship
60
61
  def parse_response(response)
61
62
  case response.status
62
63
  when 200..299
63
- if response.body.to_s.empty?
64
- {}
65
- else
66
- JSON.parse(response.body)
67
- end
64
+ response_json(response)
65
+ when 426
66
+ raise upgrade_required_error(response)
68
67
  when 401
69
68
  raise UnauthorizedError, "Unauthorized"
70
69
  when 403
@@ -80,9 +79,55 @@ module Omaship
80
79
  @connection ||= Faraday.new(url: @host) do |faraday|
81
80
  faraday.request :retry, max: 2, interval: 0.1
82
81
  faraday.adapter Faraday.default_adapter
83
- faraday.headers["Authorization"] = "Bearer #{@token}"
84
- faraday.headers["Accept"] = "application/json"
82
+ faraday.headers.update(request_headers)
85
83
  end
86
84
  end
85
+
86
+ def request_headers
87
+ {
88
+ "Authorization" => "Bearer #{@token}",
89
+ "Accept" => "application/json",
90
+ "X-Omaship-CLI-Version" => Omaship::VERSION
91
+ }
92
+ end
93
+
94
+ def response_json(response)
95
+ if response.body.to_s.empty?
96
+ {}
97
+ else
98
+ JSON.parse(response.body)
99
+ end
100
+ rescue JSON::ParserError
101
+ {}
102
+ end
103
+
104
+ def upgrade_required_error(response)
105
+ payload = response_json(response)
106
+ minimum_version = payload["minimum_version"].to_s.strip
107
+ current_version = payload["current_version"].to_s.strip
108
+ upgrade_command = payload["upgrade_command"].to_s.strip
109
+
110
+ message = []
111
+
112
+ if current_version.empty?
113
+ message << "Your CLI is no longer supported."
114
+ else
115
+ message << "CLI #{current_version} is no longer supported."
116
+ end
117
+
118
+ if minimum_version.empty?
119
+ message << "Install the latest version."
120
+ else
121
+ message << "Minimum supported version is #{minimum_version}."
122
+ end
123
+
124
+ if upgrade_command.empty?
125
+ message << "Upgrade your CLI and try again."
126
+ else
127
+ message << "Run `#{upgrade_command}` and try again."
128
+ end
129
+
130
+ UpgradeRequiredError.new(message.join(" "))
131
+ end
87
132
  end
88
133
  end
data/lib/omaship/cli.rb CHANGED
@@ -143,8 +143,7 @@ module Omaship
143
143
  say "Ships:"
144
144
  ships.each do |ship|
145
145
  marker = default_ship_match?(ship: ship, ship_reference: default_ship_reference) ? "*" : " "
146
- app_url = ship["app_url"] || "-"
147
- say "#{marker} #{ship.fetch("id")} #{ship.fetch("full_name")} #{ship.fetch("status")} #{app_url}"
146
+ say "#{marker} #{ship.fetch("id")} #{ship.fetch("display_name")} #{ship.fetch("status")} #{ship.fetch("primary_url", "-") || "-"}"
148
147
  end
149
148
 
150
149
  if !default_ship_reference.empty?
@@ -161,6 +160,7 @@ module Omaship
161
160
 
162
161
  Get available values with `omaship list`.
163
162
  Preferred format is full ship name (`org/repo`), for example `omaship/acme`.
163
+ Landing pages can also be selected by root domain, for example `acme.omaship.app`.
164
164
  Numeric ship ids from `omaship list` also work.
165
165
 
166
166
  Examples:
@@ -173,44 +173,49 @@ module Omaship
173
173
  ship = find_ship_by_reference(token: token, ship_reference: ship_reference)
174
174
  persist_default_ship(ship)
175
175
 
176
- say "Default ship set to #{ship.fetch("full_name")}."
176
+ say "Default ship set to #{ship.fetch("reference")}."
177
177
  end
178
178
  end
179
179
 
180
180
  desc "info", "Show ship info"
181
- method_option :ship, type: :string, desc: "Ship from `omaship list` (preferred: omaship/acme; id also works)"
181
+ method_option :ship, type: :string, desc: "Ship from `omaship list` (preferred: omaship/acme or acme.omaship.app; id also works)"
182
182
  def info
183
183
  with_api_error_handling(command: :info) do
184
184
  token = current_token
185
185
  client = build_api_client(token: token)
186
186
  resolved_ship = resolve_ship(token: token)
187
187
  ship = client.ship(ship_id: resolved_ship.fetch("id")).fetch("ship")
188
- deploy = client.latest_deploy(ship_id: ship.fetch("id")).fetch("deploy")
189
188
  default_ship_reference = credentials.default_ship.to_s.strip
190
189
 
191
- say "Ship: #{ship.fetch("full_name")}"
190
+ say "Ship: #{ship.fetch("display_name")}"
192
191
  say "ID: #{ship.fetch("id")}"
193
192
  say "Status: #{ship.fetch("status")}"
194
193
  say "App URL: #{ship["app_url"] || "-"}"
194
+ say "Landing URL: #{ship["landing_url"] || "-"}"
195
195
  say "Repo URL: #{ship["repo_url"] || "-"}"
196
196
  say "Default Ship: #{default_ship_reference.empty? ? "-" : default_ship_reference}"
197
- say "Last Deploy: #{deploy_status_summary(deploy)}"
198
- if deploy["run_number"]
199
- say "Run: ##{deploy.fetch("run_number")}"
200
- end
201
- if deploy["started_at"]
202
- say "Started At: #{deploy.fetch("started_at")}"
197
+
198
+ if ship.fetch("deployable")
199
+ deploy = client.latest_deploy(ship_id: ship.fetch("id")).fetch("deploy")
200
+
201
+ say "Last Deploy: #{deploy_status_summary(deploy)}"
202
+ if deploy["run_number"]
203
+ say "Run: ##{deploy.fetch("run_number")}"
204
+ end
205
+ if deploy["started_at"]
206
+ say "Started At: #{deploy.fetch("started_at")}"
207
+ end
203
208
  end
204
209
  end
205
210
  end
206
211
 
207
212
  desc "new NAME", "Create and provision a new ship"
208
213
  method_option :domain, type: :string, desc: "Root domain (defaults to NAME.com)"
209
- method_option :skip_purpose, type: :boolean, default: false, desc: "Skip purpose profile questions"
210
214
  def new_ship(name)
211
215
  with_api_error_handling(command: :new_ship) do
212
216
  token = current_token
213
- plan = fetch_plan(token: token)
217
+ auth = fetch_auth(token: token)
218
+ plan = auth.dig("user", "plan") || "free"
214
219
 
215
220
  if plan == "free"
216
221
  create_free_ship(name: name, token: token)
@@ -221,7 +226,7 @@ module Omaship
221
226
  end
222
227
 
223
228
  desc "deploy", "Deploy a ship (requires Full CLI access)"
224
- method_option :ship, type: :string, desc: "Ship from `omaship list` (preferred: omaship/acme; id also works)"
229
+ method_option :ship, type: :string, desc: "Ship from `omaship list` (preferred: omaship/acme or acme.omaship.app; id also works)"
225
230
  def deploy
226
231
  with_api_error_handling(command: :deploy) do
227
232
  token = current_token
@@ -328,7 +333,7 @@ module Omaship
328
333
  COMPREPLY=( $(compgen -W "--ship --host -h --help" -- "${cur}") )
329
334
  ;;
330
335
  new)
331
- COMPREPLY=( $(compgen -W "--domain --skip-purpose --host -h --help" -- "${cur}") )
336
+ COMPREPLY=( $(compgen -W "--domain --host -h --help" -- "${cur}") )
332
337
  ;;
333
338
  complete)
334
339
  COMPREPLY=( $(compgen -W "bash zsh fish" -- "${cur}") )
@@ -397,7 +402,7 @@ module Omaship
397
402
  _arguments '--ship[Ship from omaship list]:ship:_omaship_ship_refs' '--host[API host]:host:'
398
403
  ;;
399
404
  new)
400
- _arguments '--domain[Root domain]:domain:' '--skip-purpose[Skip purpose questions]' '--host[API host]:host:' '1:name:'
405
+ _arguments '--domain[Root domain]:domain:' '--host[API host]:host:' '1:name:'
401
406
  ;;
402
407
  complete)
403
408
  _arguments '1:shell:(bash zsh fish)'
@@ -426,16 +431,14 @@ module Omaship
426
431
 
427
432
  complete -c omaship -n '__fish_seen_subcommand_from login' -l token -d 'API token from omaship settings'
428
433
  complete -c omaship -n '__fish_seen_subcommand_from new' -l domain -d 'Root domain'
429
- complete -c omaship -n '__fish_seen_subcommand_from new' -l skip-purpose -d 'Skip purpose questions'
430
434
  complete -c omaship -n '__fish_seen_subcommand_from info status ship deploy' -l ship -d 'Ship from omaship list' -a '(__fish_omaship_ship_refs)'
431
435
  complete -c omaship -n '__fish_seen_subcommand_from use' -f -a '(__fish_omaship_ship_refs)'
432
436
  complete -c omaship -n '__fish_seen_subcommand_from complete' -f -a 'bash zsh fish'
433
437
  FISH
434
438
  end
435
439
 
436
- def fetch_plan(token:)
437
- auth = build_api_client(token: token).authenticate
438
- auth.dig("user", "plan") || "free"
440
+ def fetch_auth(token:)
441
+ build_api_client(token: token).authenticate
439
442
  end
440
443
 
441
444
  def create_paid_ship(name:, token:)
@@ -460,17 +463,15 @@ module Omaship
460
463
  purpose_profile = {}
461
464
  color_scheme = "mono-dark"
462
465
 
463
- unless options[:skip_purpose]
464
- if ask_purpose_profile?
465
- purpose_profile = collect_purpose_profile
466
- end
467
- color_scheme = pick_color_scheme
466
+ if ask_purpose_profile?
467
+ purpose_profile = collect_purpose_profile
468
468
  end
469
+ color_scheme = pick_color_scheme
469
470
 
470
471
  say
471
472
  progress_renderer.step("Creating your landing page...")
472
473
 
473
- payload = build_api_client(token: token).create_landing_page(
474
+ payload = build_api_client(token: token).create_landingpage(
474
475
  name: name,
475
476
  color_scheme: color_scheme,
476
477
  purpose_profile: purpose_profile
@@ -614,7 +615,7 @@ module Omaship
614
615
  if ship
615
616
  ship
616
617
  else
617
- raise Thor::Error, "Unknown ship `#{ship_reference}`. Run `omaship list` and use the full ship name (for example: `omaship use omaship/acme`) or the numeric id."
618
+ raise Thor::Error, "Unknown ship `#{ship_reference}`. Run `omaship list` and use a ship name or root domain from the list, or the numeric id."
618
619
  end
619
620
  end
620
621
 
@@ -625,11 +626,11 @@ module Omaship
625
626
  end
626
627
 
627
628
  def default_ship_match?(ship:, ship_reference:)
628
- ship.fetch("id").to_s == ship_reference || ship.fetch("full_name") == ship_reference
629
+ ship.fetch("id").to_s == ship_reference || ship.fetch("reference") == ship_reference
629
630
  end
630
631
 
631
632
  def persist_default_ship(ship)
632
- credentials.write_default_ship(ship.fetch("full_name"))
633
+ credentials.write_default_ship(ship.fetch("reference"))
633
634
  end
634
635
 
635
636
  def resolve_ship_for_deploy(token:)
@@ -651,7 +652,7 @@ module Omaship
651
652
  end
652
653
 
653
654
  def multiple_ships_message
654
- "Multiple ships found. Run `omaship list` and `omaship use <org/repo>` (or a ship id)."
655
+ "Multiple ships found. Run `omaship list` and `omaship use <ship-from-list>` (or a ship id)."
655
656
  end
656
657
 
657
658
  def with_api_error_handling(command:)
@@ -665,7 +666,9 @@ module Omaship
665
666
  end
666
667
 
667
668
  def permission_denied_message(command:)
668
- if command == :new_ship
669
+ if current_access_level == "onboarding"
670
+ "This onboarding token only supports `omaship new` while you create your first ship. Create a full-access token in Settings after onboarding to use other CLI commands."
671
+ elsif command == :new_ship
669
672
  "`omaship new` requires a full-access token. Create a token with Full CLI access and run `omaship login` again."
670
673
  elsif command == :deploy
671
674
  "`omaship deploy` requires a full-access token. Create a token with Full CLI access and run `omaship login` again."
@@ -674,6 +677,15 @@ module Omaship
674
677
  end
675
678
  end
676
679
 
680
+ def current_access_level
681
+ token = credentials.token
682
+ return "unknown" if token.to_s.strip.empty?
683
+
684
+ build_api_client(token: token).authenticate.dig("token", "access_level").to_s
685
+ rescue NoMethodError, Omaship::ApiClient::Error
686
+ "unknown"
687
+ end
688
+
677
689
  def build_api_client(token:)
678
690
  Omaship::ApiClient.new(host: resolved_host, token: token)
679
691
  end
@@ -1,3 +1,3 @@
1
1
  module Omaship
2
- VERSION = "0.4.0".freeze
2
+ VERSION = "0.5.0".freeze
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: omaship
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Omaship
@@ -91,7 +91,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
91
91
  - !ruby/object:Gem::Version
92
92
  version: '0'
93
93
  requirements: []
94
- rubygems_version: 4.0.3
94
+ rubygems_version: 4.0.6
95
95
  specification_version: 4
96
96
  summary: Omaship command-line interface
97
97
  test_files: []