From efb964d4dae0a79a3188c0c3ed96c70a8fd637b1 Mon Sep 17 00:00:00 2001 From: Steve Clarke Date: Fri, 27 Mar 2026 09:26:33 -0230 Subject: [PATCH 1/4] fix: resolve 'me' to current user in all commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously only `cards assigned me` worked — other commands like `cards assign`, `cards unassign`, `spaces add-member`, `--owner`, and `activity --user` passed 'me' through literally, causing 403s. Fixes #77 --- .../cli/concerns/user_resolvable.rb | 5 ++ skills/superthread/SKILL.md | 7 +- spec/integration/cli/activity_spec.rb | 3 + .../cli/concerns/user_resolvable_spec.rb | 83 +++++++++++++++++++ 4 files changed, 95 insertions(+), 3 deletions(-) create mode 100644 spec/superthread/cli/concerns/user_resolvable_spec.rb diff --git a/lib/superthread/cli/concerns/user_resolvable.rb b/lib/superthread/cli/concerns/user_resolvable.rb index 07fb852..e482aa4 100644 --- a/lib/superthread/cli/concerns/user_resolvable.rb +++ b/lib/superthread/cli/concerns/user_resolvable.rb @@ -21,6 +21,11 @@ module UserResolvable # @raise [Thor::Error] if name/email is provided but not found def resolve_user(ref) return ref if ref.nil? + + if ref.downcase == "me" + return client.users.me.user_identifier + end + return ref if looks_like_id?(ref) user = find_user_by_name(ref) diff --git a/skills/superthread/SKILL.md b/skills/superthread/SKILL.md index a35d98f..fed1ac0 100644 --- a/skills/superthread/SKILL.md +++ b/skills/superthread/SKILL.md @@ -22,7 +22,7 @@ suth search query "term" # Search everything --json # Structured output (for scripts/agents) -y / --yes # Skip confirmations -s SPACE # Resolve ambiguous names (boards, lists, sprints) -me # Use as user reference: suth cards assigned me +me # Current user in any user argument (assigned, assign, --owner, --user, etc.) ``` Most commands accept **names or IDs** for spaces, boards, lists, sprints, users, and tags. @@ -198,8 +198,9 @@ suth cards duplicate CARD --project ID -b BOARD -l LIST # Required: --project, --board/-b, --list/-l # Options: --title, --space/-s -# Members -suth cards assign CARD USERS # Comma-separated +# Members (USERS = IDs, names, emails, or 'me'; comma-separated) +suth cards assign CARD USERS +suth cards assign CARD me # Assign yourself suth cards unassign CARD USERS # Relationships diff --git a/spec/integration/cli/activity_spec.rb b/spec/integration/cli/activity_spec.rb index a88f5ba..f52406d 100644 --- a/spec/integration/cli/activity_spec.rb +++ b/spec/integration/cli/activity_spec.rb @@ -100,6 +100,7 @@ context "with completed cards" do before do + stub_api_get("users/me", response: ApiFixtures::Users::ME) stub_api_post("test_workspace/views/preview", response: { cards: [ { @@ -127,6 +128,7 @@ context "with no activity" do before do + stub_api_get("users/me", response: ApiFixtures::Users::ME) stub_api_post("test_workspace/views/preview", response: { cards: [ { @@ -151,6 +153,7 @@ describe "activity with date filters" do before do + stub_api_get("users/me", response: ApiFixtures::Users::ME) stub_api_post("test_workspace/views/preview", response: { cards: [ { diff --git a/spec/superthread/cli/concerns/user_resolvable_spec.rb b/spec/superthread/cli/concerns/user_resolvable_spec.rb new file mode 100644 index 0000000..469c660 --- /dev/null +++ b/spec/superthread/cli/concerns/user_resolvable_spec.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe Superthread::Cli::Concerns::UserResolvable do + let(:test_class) do + Class.new do + include Superthread::Cli::Concerns::UserResolvable + + attr_accessor :client + + def looks_like_id?(value) + value.match?(/\A[a-zA-Z0-9_-]+\z/) && value.length <= 36 + end + + def workspace_id + "test_workspace" + end + + public :resolve_user + end + end + + let(:client) { instance_double(Superthread::Client) } + let(:users_resource) { instance_double(Superthread::Resources::Users) } + let(:instance) do + obj = test_class.new + obj.client = client + obj + end + + let(:current_user) do + Superthread::Models::User.new(id: "u123abc", display_name: "Test User", email: "test@example.com") + end + + let(:members) do + [ + Superthread::Models::User.new(user_id: "u123abc", display_name: "Test User", email: "test@example.com"), + Superthread::Models::User.new(user_id: "u456def", display_name: "Another User", email: "another@example.com") + ] + end + + before do + allow(client).to receive(:users).and_return(users_resource) + allow(users_resource).to receive(:members).with("test_workspace").and_return(members) + end + + describe "#resolve_user" do + it "returns nil for nil input" do + expect(instance.resolve_user(nil)).to be_nil + end + + it "returns ID directly when ref looks like an ID" do + expect(instance.resolve_user("u123abc")).to eq("u123abc") + end + + it "resolves display name to user_identifier" do + expect(instance.resolve_user("Another User")).to eq("u456def") + end + + it "raises Thor::Error when name not found" do + expect { instance.resolve_user("Nobody Here") }.to raise_error(Thor::Error, /User not found/) + end + + context "when ref is 'me'" do + before do + allow(users_resource).to receive(:me).and_return(current_user) + end + + it "resolves 'me' to the current user's identifier" do + expect(instance.resolve_user("me")).to eq("u123abc") + end + + it "resolves 'Me' case-insensitively" do + expect(instance.resolve_user("Me")).to eq("u123abc") + end + + it "resolves 'ME' case-insensitively" do + expect(instance.resolve_user("ME")).to eq("u123abc") + end + end + end +end From 8621196d88db9cdcef37e6ef80be1092a09b8d59 Mon Sep 17 00:00:00 2001 From: Steve Clarke Date: Fri, 27 Mar 2026 09:33:03 -0230 Subject: [PATCH 2/4] refactor: cache 'me' resolution and use guard clause style --- lib/superthread/cli/concerns/user_resolvable.rb | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/superthread/cli/concerns/user_resolvable.rb b/lib/superthread/cli/concerns/user_resolvable.rb index e482aa4..e740a46 100644 --- a/lib/superthread/cli/concerns/user_resolvable.rb +++ b/lib/superthread/cli/concerns/user_resolvable.rb @@ -22,9 +22,7 @@ module UserResolvable def resolve_user(ref) return ref if ref.nil? - if ref.downcase == "me" - return client.users.me.user_identifier - end + return resolve_me if ref.downcase == "me" return ref if looks_like_id?(ref) @@ -34,6 +32,13 @@ def resolve_user(ref) raise Thor::Error, "User not found: '#{ref}'. Use 'suth members list' to see available users." end + # Resolve 'me' to the current user's identifier (cached). + # + # @return [String] the current user's identifier + def resolve_me + @me_cache ||= client.users.me.user_identifier + end + # Get cached workspace members list. # # @return [Array] all workspace members From 2cb64048cc0e36ffa99f417c505ee0c5fc484312 Mon Sep 17 00:00:00 2001 From: Steve Clarke Date: Fri, 27 Mar 2026 09:43:11 -0230 Subject: [PATCH 3/4] docs: document 'me' as valid user arg across all skill commands --- skills/superthread/SKILL.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/skills/superthread/SKILL.md b/skills/superthread/SKILL.md index fed1ac0..1eb8a55 100644 --- a/skills/superthread/SKILL.md +++ b/skills/superthread/SKILL.md @@ -128,7 +128,7 @@ suth spaces create --title "Name" suth spaces update SPACE --title "New Name" # Options: --description, --icon NAME, --icon-color "#HEX" suth spaces delete SPACE -suth spaces add_member SPACE USERS [--role ROLE] # Comma-separated users +suth spaces add_member SPACE USERS [--role ROLE] # IDs, names, emails, or 'me' suth spaces remove_member SPACE USERS ``` @@ -171,7 +171,7 @@ suth lists delete LIST suth cards list -b BOARD suth cards list --sprint SPRINT -s SPACE # Options: --list, --include-archived, --since DATE, --updated-since DATE, -s SPACE -suth cards assigned USER +suth cards assigned USER # USER = ID, name, email, or 'me' suth cards assigned me # Options: --board, --space, --project, --include-archived, # --since DATE, --updated-since DATE @@ -187,7 +187,7 @@ suth cards get CARD suth cards create --title "Task" -l LIST -b BOARD # Options: --content HTML, --project ID, --parent-card ID, --epic ID, # --sprint SPRINT, -s SPACE, --start-date TIMESTAMP, - # --due-date TIMESTAMP, --priority N, --owner/-o USER + # --due-date TIMESTAMP, --priority N, --owner/-o USER (or 'me') suth cards update CARD # Options: --title, --content HTML, --list LIST, --board BOARD, --sprint SPRINT, # -s SPACE, --position N, --priority N, --epic ID, --archived/--no-archived @@ -219,9 +219,9 @@ suth projects list suth projects get PROJECT suth projects create --title "Q1" -l LIST [-b BOARD] # Options: --content, --start-date TIMESTAMP, --due-date TIMESTAMP, - # --priority N, --owner/-o USER, -s SPACE + # --priority N, --owner/-o USER (or 'me'), -s SPACE suth projects update PROJECT --title "New" - # Options: --list/-l, --board/-b, --space/-s, --owner/-o USER, + # Options: --list/-l, --board/-b, --space/-s, --owner/-o USER (or 'me'), # --start-date, --due-date, --priority, --archived suth projects delete PROJECT suth projects add_card PROJECT CARD @@ -322,7 +322,7 @@ suth search query TERM ```bash suth activity # Recent activity (default: today) # Runs `show` subcommand by default - # Options: --since DATE, --user USER, --board/-b BOARD, --space/-s SPACE + # Options: --since DATE, --user USER (or 'me'), --board/-b BOARD, --space/-s SPACE ``` ### Config & Completion @@ -359,7 +359,7 @@ suth completion fish | `--list` | `-l` | List (ID or name) | | `--card` | `-c` | Card ID | | `--related` | `-r` | Related card ID | -| `--owner` | `-o` | Owner (user ID, name, or email) | +| `--owner` | `-o` | Owner (user ID, name, email, or 'me') | | `--yes` | `-y` | Skip confirmations | Priority levels: 1=Urgent, 2=High, 3=Medium, 4=Low From c0cbe57ecd53adaa4bf15e294cb2a5a7b6e7b86d Mon Sep 17 00:00:00 2001 From: Steve Clarke Date: Fri, 27 Mar 2026 09:46:18 -0230 Subject: [PATCH 4/4] docs: add 'me' to --owner help text and README tip --- README.md | 2 +- lib/superthread/cli/cards.rb | 2 +- lib/superthread/cli/projects.rb | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a8c5081..15312f9 100644 --- a/README.md +++ b/README.md @@ -325,7 +325,7 @@ Common options have short forms: > - Most commands accept **names or IDs** for spaces, boards, lists, sprints, users, and tags > - Use `-s SPACE` to help when board or list names are unclear > - Use `--json` for scripted output: `suth cards assigned me --json` -> - Use `me` as a shortcut: `suth cards assigned me` +> - Use `me` in any user argument: `suth cards assigned me`, `suth cards assign CARD me`, `--owner me` > - Priority levels: 1=Urgent, 2=High, 3=Medium, 4=Low ### Mentions diff --git a/lib/superthread/cli/cards.rb b/lib/superthread/cli/cards.rb index 0024000..f5696d7 100644 --- a/lib/superthread/cli/cards.rb +++ b/lib/superthread/cli/cards.rb @@ -179,7 +179,7 @@ def get(card_id) option :priority, type: :numeric, desc: "Priority level (1=low, 4=urgent)" option :parent_card, type: :string, desc: "Parent card ID" option :epic, type: :string, desc: "Epic ID" - option :owner, type: :string, aliases: "-o", desc: "Owner (user ID, name, or email)" + option :owner, type: :string, aliases: "-o", desc: "Owner (user ID, name, email, or 'me')" # Create a new card on a board or sprint. # # @return [void] diff --git a/lib/superthread/cli/projects.rb b/lib/superthread/cli/projects.rb index e7f2409..276ba8f 100644 --- a/lib/superthread/cli/projects.rb +++ b/lib/superthread/cli/projects.rb @@ -42,7 +42,7 @@ def get(project_id) option :content, type: :string, desc: "Project description" option :start_date, type: :numeric, desc: "Start date (Unix timestamp)" option :due_date, type: :numeric, desc: "Due date (Unix timestamp)" - option :owner, type: :string, aliases: "-o", desc: "Owner (user ID, name, or email)" + option :owner, type: :string, aliases: "-o", desc: "Owner (user ID, name, email, or 'me')" option :priority, type: :numeric, desc: "Priority level" # Creates a new project on a board list. # @@ -60,7 +60,7 @@ def create option :list, type: :string, aliases: "-l", desc: "Destination list (ID or name, requires --board)" option :board, type: :string, aliases: "-b", desc: "Board (helps resolve list name)" option :space, type: :string, aliases: "-s", desc: "Space (helps resolve board name)" - option :owner, type: :string, aliases: "-o", desc: "New owner (user ID, name, or email)" + option :owner, type: :string, aliases: "-o", desc: "New owner (user ID, name, email, or 'me')" option :start_date, type: :numeric, desc: "Start date" option :due_date, type: :numeric, desc: "Due date" option :priority, type: :numeric, desc: "Priority"