<% if notice.present? %>
<%= notice %>
diff --git a/engine/config/routes.rb b/engine/config/routes.rb index 0eb616b..032bebb 100644 --- a/engine/config/routes.rb +++ b/engine/config/routes.rb @@ -15,6 +15,7 @@ end namespace :settings do + root "settings#index" resources :tokens, only: [:index, :create, :destroy] end @@ -46,5 +47,8 @@ end end + get "llms.txt", to: "llms#show", as: :llms_txt + get "agent-instructions", to: "agent_instructions#show", as: :agent_instructions + root "plans#index" end diff --git a/engine/lib/coplan/configuration.rb b/engine/lib/coplan/configuration.rb index 97bcfcc..a52801a 100644 --- a/engine/lib/coplan/configuration.rb +++ b/engine/lib/coplan/configuration.rb @@ -4,6 +4,9 @@ class Configuration attr_accessor :ai_base_url, :ai_api_key, :ai_model attr_accessor :error_reporter attr_accessor :notification_handler + attr_accessor :onboarding_banner + attr_accessor :agent_auth_instructions + attr_accessor :agent_curl_prefix def initialize @authenticate = nil @@ -12,6 +15,34 @@ def initialize @ai_model = "gpt-4o" @error_reporter = ->(exception, context) { Rails.error.report(exception, context: context) } @notification_handler = nil + @onboarding_banner = 'Want to upload Agentic plans? Give your agent these instructions.' + @agent_curl_prefix = 'curl -s -H "Authorization: Bearer $TOKEN"' + @agent_auth_instructions = <<~MARKDOWN + ## Authentication + + Credentials are stored at `~/.config/coplan/credentials.json`: + + ```json + { + "base_url": "BASE_URL", + "token": "your-token-here" + } + ``` + + On first use: + + 1. Read `~/.config/coplan/credentials.json` to get `token` and `base_url`. + 2. If the file does not exist, tell the user: "Go to **Settings → API Tokens** in the CoPlan web UI to create a token." Ask for the token and base URL, then save to `~/.config/coplan/credentials.json` with `chmod 600`. + 3. If any API call returns 401, the token is invalid or revoked. Prompt the user to create a new token in Settings and update the credentials file. + + Use the values from the credentials file in all API calls below. + + All requests use `Authorization: Bearer $TOKEN` header. + MARKDOWN + end + + def show_api_tokens? + @api_authenticate.nil? end end end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 2706b70..e873bbd 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -23,6 +23,8 @@ + + def sign_in_as(coplan_user) coplan_user.update!(email: "#{coplan_user.external_id}@test.example.com") unless coplan_user.email.present? post sign_in_path, params: { email: coplan_user.email } diff --git a/spec/requests/plans_spec.rb b/spec/requests/plans_spec.rb index 0b642dd..ae778bc 100644 --- a/spec/requests/plans_spec.rb +++ b/spec/requests/plans_spec.rb @@ -109,4 +109,36 @@ get plan_path(brainstorm_plan) expect(response).to have_http_status(:ok) end + + describe "onboarding banner" do + it "shows banner when user has no plans" do + sign_in_as(bob) + get plans_path + expect(response.body).to include("onboarding-banner") + end + + it "hides banner when user has created a plan" do + plan # alice has a plan + get plans_path + expect(response.body).not_to include("onboarding-banner") + end + + it "hides banner when onboarding_banner config is nil" do + sign_in_as(bob) + original = CoPlan.configuration.onboarding_banner + CoPlan.configuration.onboarding_banner = nil + get plans_path + expect(response.body).not_to include("onboarding-banner") + CoPlan.configuration.onboarding_banner = original + end + + it "displays custom banner text from configuration" do + sign_in_as(bob) + original = CoPlan.configuration.onboarding_banner + CoPlan.configuration.onboarding_banner = "Custom onboarding message" + get plans_path + expect(response.body).to include("Custom onboarding message") + CoPlan.configuration.onboarding_banner = original + end + end end