superthread 0.7.2

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 (84) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +4 -0
  3. data/LICENSE +21 -0
  4. data/README.md +492 -0
  5. data/exe/suth +19 -0
  6. data/lib/superthread/cli/accounts.rb +240 -0
  7. data/lib/superthread/cli/activity.rb +210 -0
  8. data/lib/superthread/cli/base.rb +355 -0
  9. data/lib/superthread/cli/boards.rb +131 -0
  10. data/lib/superthread/cli/cards.rb +530 -0
  11. data/lib/superthread/cli/checklists.rb +223 -0
  12. data/lib/superthread/cli/comments.rb +86 -0
  13. data/lib/superthread/cli/completion.rb +306 -0
  14. data/lib/superthread/cli/concerns/board_resolvable.rb +70 -0
  15. data/lib/superthread/cli/concerns/confirmable.rb +55 -0
  16. data/lib/superthread/cli/concerns/date_parsable.rb +196 -0
  17. data/lib/superthread/cli/concerns/list_resolvable.rb +53 -0
  18. data/lib/superthread/cli/concerns/space_resolvable.rb +52 -0
  19. data/lib/superthread/cli/concerns/sprint_resolvable.rb +55 -0
  20. data/lib/superthread/cli/concerns/tag_resolvable.rb +49 -0
  21. data/lib/superthread/cli/concerns/user_resolvable.rb +52 -0
  22. data/lib/superthread/cli/concerns/workspace_resolvable.rb +83 -0
  23. data/lib/superthread/cli/config.rb +129 -0
  24. data/lib/superthread/cli/formatter.rb +388 -0
  25. data/lib/superthread/cli/lists.rb +85 -0
  26. data/lib/superthread/cli/main.rb +121 -0
  27. data/lib/superthread/cli/members.rb +19 -0
  28. data/lib/superthread/cli/notes.rb +64 -0
  29. data/lib/superthread/cli/pages.rb +128 -0
  30. data/lib/superthread/cli/projects.rb +124 -0
  31. data/lib/superthread/cli/replies.rb +94 -0
  32. data/lib/superthread/cli/search.rb +34 -0
  33. data/lib/superthread/cli/setup.rb +253 -0
  34. data/lib/superthread/cli/spaces.rb +141 -0
  35. data/lib/superthread/cli/sprints.rb +32 -0
  36. data/lib/superthread/cli/tags.rb +86 -0
  37. data/lib/superthread/cli/ui/gum_prompt.rb +58 -0
  38. data/lib/superthread/cli/ui/plain_prompt.rb +73 -0
  39. data/lib/superthread/cli/ui.rb +263 -0
  40. data/lib/superthread/cli/workspaces.rb +105 -0
  41. data/lib/superthread/cli.rb +12 -0
  42. data/lib/superthread/client.rb +207 -0
  43. data/lib/superthread/configuration.rb +354 -0
  44. data/lib/superthread/connection.rb +57 -0
  45. data/lib/superthread/error.rb +164 -0
  46. data/lib/superthread/mention_formatter.rb +96 -0
  47. data/lib/superthread/model.rb +178 -0
  48. data/lib/superthread/models/board.rb +59 -0
  49. data/lib/superthread/models/card.rb +321 -0
  50. data/lib/superthread/models/checklist.rb +91 -0
  51. data/lib/superthread/models/checklist_item.rb +69 -0
  52. data/lib/superthread/models/comment.rb +71 -0
  53. data/lib/superthread/models/concerns/archivable.rb +32 -0
  54. data/lib/superthread/models/concerns/presentable.rb +113 -0
  55. data/lib/superthread/models/concerns/timestampable.rb +91 -0
  56. data/lib/superthread/models/list.rb +67 -0
  57. data/lib/superthread/models/member.rb +40 -0
  58. data/lib/superthread/models/note.rb +56 -0
  59. data/lib/superthread/models/page.rb +70 -0
  60. data/lib/superthread/models/project.rb +83 -0
  61. data/lib/superthread/models/space.rb +71 -0
  62. data/lib/superthread/models/sprint.rb +53 -0
  63. data/lib/superthread/models/tag.rb +52 -0
  64. data/lib/superthread/models/team.rb +68 -0
  65. data/lib/superthread/models/user.rb +76 -0
  66. data/lib/superthread/models.rb +12 -0
  67. data/lib/superthread/object.rb +285 -0
  68. data/lib/superthread/objects/collection.rb +179 -0
  69. data/lib/superthread/resources/base.rb +204 -0
  70. data/lib/superthread/resources/boards.rb +150 -0
  71. data/lib/superthread/resources/cards.rb +363 -0
  72. data/lib/superthread/resources/comments.rb +163 -0
  73. data/lib/superthread/resources/notes.rb +61 -0
  74. data/lib/superthread/resources/pages.rb +110 -0
  75. data/lib/superthread/resources/projects.rb +117 -0
  76. data/lib/superthread/resources/search.rb +46 -0
  77. data/lib/superthread/resources/spaces.rb +104 -0
  78. data/lib/superthread/resources/sprints.rb +37 -0
  79. data/lib/superthread/resources/tags.rb +52 -0
  80. data/lib/superthread/resources/users.rb +29 -0
  81. data/lib/superthread/version.rb +6 -0
  82. data/lib/superthread/version_checker.rb +174 -0
  83. data/lib/superthread.rb +30 -0
  84. metadata +259 -0
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Superthread
4
+ module Cli
5
+ # Main entry point for the Superthread CLI.
6
+ # Registers all subcommands and provides top-level commands like version and setup.
7
+ class Main < Base
8
+ map %w[--version -V] => :version
9
+
10
+ desc "version", "Show version and check for updates"
11
+ # Displays the current version and checks for updates.
12
+ #
13
+ # @return [void]
14
+ def version
15
+ puts "superthread #{Superthread::VERSION}"
16
+
17
+ latest = Superthread::VersionChecker.check!
18
+ if latest && Superthread::VersionChecker.newer?(latest)
19
+ say ""
20
+ say "Update available: #{Superthread::VERSION} \u2192 #{latest}", :yellow
21
+ say "Run: brew upgrade superthread", :yellow
22
+ end
23
+ rescue
24
+ nil
25
+ end
26
+
27
+ desc "setup", "Interactive setup wizard"
28
+ # Runs the interactive setup wizard to configure accounts and workspaces.
29
+ #
30
+ # @return [void]
31
+ def setup
32
+ Superthread::Cli::Setup.execute
33
+ end
34
+
35
+ desc "config SUBCOMMAND", "Manage configuration"
36
+ subcommand "config", Superthread::Cli::Config
37
+
38
+ desc "me", "Show current user account info"
39
+ # Displays information about the currently authenticated user.
40
+ #
41
+ # @return [void]
42
+ def me
43
+ handle_error do
44
+ user = client.users.me
45
+
46
+ if json_output?
47
+ output_item user
48
+ else
49
+ # Show basic user info
50
+ output_item user, fields: %i[display_name email time_created], labels: {
51
+ time_created: "Time Created"
52
+ }
53
+
54
+ # Show workspaces with roles
55
+ if user.teams&.any?
56
+ say ""
57
+ say "Workspaces:", :cyan
58
+ user.teams.each do |team|
59
+ say " #{team.team_name} (#{team.id}) - #{team.role}"
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+ desc "accounts SUBCOMMAND", "Manage accounts"
67
+ subcommand "accounts", Superthread::Cli::Accounts
68
+
69
+ desc "workspaces SUBCOMMAND", "List and select workspaces"
70
+ subcommand "workspaces", Superthread::Cli::Workspaces
71
+
72
+ desc "members SUBCOMMAND", "Workspace member commands"
73
+ subcommand "members", Superthread::Cli::Members
74
+
75
+ desc "cards SUBCOMMAND", "Card management commands"
76
+ subcommand "cards", Superthread::Cli::Cards
77
+
78
+ desc "boards SUBCOMMAND", "Board and list management commands"
79
+ subcommand "boards", Superthread::Cli::Boards
80
+
81
+ desc "projects SUBCOMMAND", "Roadmap project (epic) commands"
82
+ subcommand "projects", Superthread::Cli::Projects
83
+
84
+ desc "spaces SUBCOMMAND", "Space management commands"
85
+ subcommand "spaces", Superthread::Cli::Spaces
86
+
87
+ desc "comments SUBCOMMAND", "Comment management commands"
88
+ subcommand "comments", Superthread::Cli::Comments
89
+
90
+ desc "replies SUBCOMMAND", "Comment reply commands"
91
+ subcommand "replies", Superthread::Cli::Replies
92
+
93
+ desc "lists SUBCOMMAND", "Board list (column) commands"
94
+ subcommand "lists", Superthread::Cli::Lists
95
+
96
+ desc "checklists SUBCOMMAND", "Card checklist commands"
97
+ subcommand "checklists", Superthread::Cli::Checklists
98
+
99
+ desc "pages SUBCOMMAND", "Page/documentation commands"
100
+ subcommand "pages", Superthread::Cli::Pages
101
+
102
+ desc "notes SUBCOMMAND", "Meeting notes commands"
103
+ subcommand "notes", Superthread::Cli::Notes
104
+
105
+ desc "sprints SUBCOMMAND", "Sprint commands"
106
+ subcommand "sprints", Superthread::Cli::Sprints
107
+
108
+ desc "search QUERY", "Search across workspace"
109
+ subcommand "search", Superthread::Cli::Search
110
+
111
+ desc "tags SUBCOMMAND", "Tag management commands"
112
+ subcommand "tags", Superthread::Cli::Tags
113
+
114
+ desc "activity", "Show recent activity across workspace"
115
+ subcommand "activity", Superthread::Cli::Activity
116
+
117
+ desc "completion SHELL", "Generate shell completion scripts"
118
+ subcommand "completion", Superthread::Cli::Completion
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Superthread
4
+ module Cli
5
+ # CLI commands for workspace member operations.
6
+ class Members < Base
7
+ desc "list", "List workspace members"
8
+ # Lists all members of the current workspace.
9
+ #
10
+ # @return [void]
11
+ def list
12
+ handle_error do
13
+ members = client.users.members(workspace_id)
14
+ output_list members, columns: %i[id display_name email role], headers: {id: "USER_ID"}
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Superthread
4
+ module Cli
5
+ # CLI commands for managing Superthread notes.
6
+ #
7
+ # Notes are standalone content items that can contain transcripts
8
+ # and user annotations. They can be made public for sharing.
9
+ class Notes < Base
10
+ desc "list", "List all notes"
11
+ # Lists all notes in the current workspace.
12
+ #
13
+ # @return [void]
14
+ def list
15
+ notes = client.notes.list(workspace_id)
16
+ output_list notes, columns: %i[id title time_created], headers: {id: "NOTE_ID"}
17
+ end
18
+
19
+ desc "get NOTE", "Get note details"
20
+ # Retrieves and displays details for a specific note.
21
+ #
22
+ # @param note_id [String] numeric ID of the note to retrieve
23
+ # @return [void]
24
+ def get(note_id)
25
+ handle_error do
26
+ note = with_not_found("Note not found: '#{note_id}'. Use 'suth notes list' to see available notes.") do
27
+ client.notes.find(workspace_id, note_id)
28
+ end
29
+ output_item note, fields: %i[id title content time_created time_updated], labels: {id: "Note ID"}
30
+ end
31
+ end
32
+
33
+ desc "create", "Create a new note"
34
+ option :title, type: :string, required: true, desc: "Note title"
35
+ option :transcript, type: :string, desc: "Transcript content"
36
+ option :user_notes, type: :string, desc: "User notes"
37
+ option :is_public, type: :boolean, desc: "Make note public"
38
+ # Creates a new note in the current workspace.
39
+ #
40
+ # @return [void]
41
+ def create
42
+ note = client.notes.create(workspace_id, **symbolized_options(:title, :transcript, :user_notes, :is_public))
43
+ output_item note, labels: {id: "Note ID"}
44
+ end
45
+
46
+ desc "delete NOTE", "Delete a note"
47
+ # Deletes a note after confirmation.
48
+ #
49
+ # @param note_ref [String] note identifier (ID or name)
50
+ # @return [void]
51
+ def delete(note_ref)
52
+ handle_error do
53
+ note = with_not_found("Note not found: '#{note_ref}'. Use 'suth notes list' to see available notes.") do
54
+ client.notes.find(workspace_id, note_ref)
55
+ end
56
+ confirming("Delete note '#{note.title}' (#{note.id})?") do
57
+ client.notes.destroy(workspace_id, note.id)
58
+ output_success "Note '#{note.title}' deleted"
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Superthread
4
+ module Cli
5
+ # CLI commands for managing Superthread pages (documents).
6
+ #
7
+ # Pages are rich-text documents that can be organized within spaces
8
+ # and nested under parent pages to form a hierarchy.
9
+ class Pages < Base
10
+ desc "list", "List all pages"
11
+ option :space, type: :string, aliases: "-s", desc: "Space to filter by (ID or name)"
12
+ option :include_archived, type: :boolean, desc: "Include archived pages"
13
+ option :updated_recently, type: :boolean, desc: "Filter by recently updated pages"
14
+ # Lists all pages in the current workspace with optional filters.
15
+ #
16
+ # @return [void]
17
+ def list
18
+ opts = symbolized_options(:updated_recently)
19
+ opts[:archived] = options[:include_archived] if options[:include_archived]
20
+ opts[:space_id] = space_id if options[:space]
21
+ pages = client.pages.list(workspace_id, **opts)
22
+ output_list pages, columns: %i[id title], headers: {id: "PAGE_ID"}
23
+ end
24
+
25
+ desc "get PAGE", "Get page details"
26
+ # Retrieves and displays details for a specific page.
27
+ #
28
+ # @param page_id [String] numeric ID or URL slug of the page to retrieve
29
+ # @return [void]
30
+ def get(page_id)
31
+ handle_error do
32
+ page = with_not_found("Page not found: '#{page_id}'. Use 'suth pages list' to see available pages.") do
33
+ client.pages.find(workspace_id, page_id)
34
+ end
35
+ output_item page, fields: %i[id title time_created time_updated], labels: {id: "Page ID"}
36
+ end
37
+ end
38
+
39
+ desc "create", "Create a new page"
40
+ option :space, type: :string, required: true, aliases: "-s", desc: "Space to create page in (ID or name)"
41
+ option :title, type: :string, desc: "Page title"
42
+ option :content, type: :string, desc: "Page content"
43
+ option :parent_page, type: :string, desc: "Parent page ID"
44
+ option :is_public, type: :boolean, desc: "Make page public"
45
+ # Creates a new page in a space.
46
+ #
47
+ # @return [void]
48
+ def create
49
+ opts = symbolized_options(:title, :content, :is_public)
50
+ opts[:space_id] = space_id
51
+ opts[:parent_page_id] = options[:parent_page] if options[:parent_page]
52
+ page = client.pages.create(workspace_id, **opts)
53
+ output_item page, labels: {id: "Page ID"}
54
+ end
55
+
56
+ desc "update PAGE", "Update a page"
57
+ option :title, type: :string, desc: "New title"
58
+ option :is_public, type: :boolean, desc: "Public visibility"
59
+ option :parent_page, type: :string, desc: "Parent page ID"
60
+ option :archived, type: :boolean, desc: "Archive/unarchive"
61
+ # Updates an existing page's properties.
62
+ #
63
+ # @param page_id [String] numeric ID or URL slug of the page to retrieve
64
+ # @return [void]
65
+ def update(page_id)
66
+ handle_error do
67
+ opts = symbolized_options(:title, :is_public, :archived)
68
+ opts[:parent_page_id] = options[:parent_page] if options[:parent_page]
69
+ page = with_not_found("Page not found: '#{page_id}'. Use 'suth pages list' to see available pages.") do
70
+ client.pages.update(workspace_id, page_id, **opts)
71
+ end
72
+ output_item page, labels: {id: "Page ID"}
73
+ end
74
+ end
75
+
76
+ desc "duplicate PAGE", "Duplicate a page"
77
+ option :space, type: :string, required: true, aliases: "-s", desc: "Destination space (ID or name)"
78
+ option :title, type: :string, desc: "Title for the duplicated page"
79
+ option :parent_page, type: :string, desc: "Parent page ID"
80
+ # Creates a copy of an existing page in a specified space.
81
+ #
82
+ # @param page_id [String] numeric ID of the source page to duplicate
83
+ # @return [void]
84
+ def duplicate(page_id)
85
+ handle_error do
86
+ opts = symbolized_options(:title)
87
+ opts[:space_id] = space_id
88
+ opts[:parent_page_id] = options[:parent_page] if options[:parent_page]
89
+ page = with_not_found("Page not found: '#{page_id}'. Use 'suth pages list' to see available pages.") do
90
+ client.pages.duplicate(workspace_id, page_id, **opts)
91
+ end
92
+ output_item page, labels: {id: "Page ID"}
93
+ end
94
+ end
95
+
96
+ desc "archive PAGE", "Archive a page"
97
+ # Archives a page, hiding it from default views.
98
+ #
99
+ # @param page_id [String] numeric ID or URL slug of the page to retrieve
100
+ # @return [void]
101
+ def archive(page_id)
102
+ handle_error do
103
+ page = with_not_found("Page not found: '#{page_id}'. Use 'suth pages list' to see available pages.") do
104
+ client.pages.archive(workspace_id, page_id)
105
+ end
106
+ output_item page, labels: {id: "Page ID"}
107
+ end
108
+ end
109
+
110
+ desc "delete PAGE", "Delete a page permanently"
111
+ # Permanently deletes a page after confirmation.
112
+ #
113
+ # @param page_ref [String] page identifier (ID or name)
114
+ # @return [void]
115
+ def delete(page_ref)
116
+ handle_error do
117
+ page = with_not_found("Page not found: '#{page_ref}'. Use 'suth pages list' to see available pages.") do
118
+ client.pages.find(workspace_id, page_ref)
119
+ end
120
+ confirming("Delete page '#{page.title}' (#{page.id})?") do
121
+ client.pages.destroy(workspace_id, page.id)
122
+ output_success "Page '#{page.title}' deleted"
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Superthread
4
+ module Cli
5
+ # CLI commands for managing Superthread projects (epics/roadmap items).
6
+ #
7
+ # Projects are high-level items on the roadmap that can contain multiple
8
+ # cards. They have start/due dates, owners, and can be organized on boards.
9
+ class Projects < Base
10
+ # Kebab-case aliases for commands
11
+ map "add-card" => :add_card,
12
+ "remove-card" => :remove_card
13
+ desc "list", "List all roadmap projects"
14
+ # Lists all projects in the current workspace.
15
+ #
16
+ # @return [void]
17
+ def list
18
+ projects = client.projects.list(workspace_id)
19
+ output_list projects, columns: %i[id title status], headers: {id: "PROJECT_ID"}
20
+ end
21
+
22
+ desc "get PROJECT", "Get project details"
23
+ # Retrieves and displays details for a specific project.
24
+ #
25
+ # @param project_id [String] numeric ID or short code of the project/epic
26
+ # @return [void]
27
+ def get(project_id)
28
+ handle_error do
29
+ project = with_not_found("Project not found: '#{project_id}'. Use 'suth projects list' to see available projects.") do
30
+ client.projects.find(workspace_id, project_id)
31
+ end
32
+ output_item project, fields: %i[id title status start_date due_date time_created time_updated],
33
+ labels: {id: "Project ID"}
34
+ end
35
+ end
36
+
37
+ desc "create", "Create a new project"
38
+ option :title, type: :string, required: true, desc: "Project title"
39
+ option :list, type: :string, required: true, aliases: "-l", desc: "Destination list (ID or name, requires --board)"
40
+ option :board, type: :string, aliases: "-b", desc: "Board (helps resolve list name)"
41
+ option :space, type: :string, aliases: "-s", desc: "Space (helps resolve board name)"
42
+ option :content, type: :string, desc: "Project description"
43
+ option :start_date, type: :numeric, desc: "Start date (Unix timestamp)"
44
+ option :due_date, type: :numeric, desc: "Due date (Unix timestamp)"
45
+ option :owner, type: :string, aliases: "-o", desc: "Owner (user ID, name, or email)"
46
+ option :priority, type: :numeric, desc: "Priority level"
47
+ # Creates a new project on a board list.
48
+ #
49
+ # @return [void]
50
+ def create
51
+ opts = symbolized_options(:title, :content, :start_date, :due_date, :priority)
52
+ opts[:list_id] = resolve_list(options[:list])
53
+ opts[:owner_id] = resolve_user(options[:owner]) if options[:owner]
54
+ project = client.projects.create(workspace_id, **opts)
55
+ output_item project, labels: {id: "Project ID"}
56
+ end
57
+
58
+ desc "update PROJECT", "Update a project"
59
+ option :title, type: :string, desc: "New title"
60
+ option :list, type: :string, aliases: "-l", desc: "Destination list (ID or name, requires --board)"
61
+ option :board, type: :string, aliases: "-b", desc: "Board (helps resolve list name)"
62
+ option :space, type: :string, aliases: "-s", desc: "Space (helps resolve board name)"
63
+ option :owner, type: :string, aliases: "-o", desc: "New owner (user ID, name, or email)"
64
+ option :start_date, type: :numeric, desc: "Start date"
65
+ option :due_date, type: :numeric, desc: "Due date"
66
+ option :priority, type: :numeric, desc: "Priority"
67
+ option :archived, type: :boolean, desc: "Archive/unarchive"
68
+ # Updates an existing project's properties.
69
+ #
70
+ # @param project_id [String] numeric ID or short code of the project/epic
71
+ # @return [void]
72
+ def update(project_id)
73
+ handle_error do
74
+ opts = symbolized_options(:title, :start_date, :due_date, :priority, :archived)
75
+ opts[:list_id] = resolve_list(options[:list]) if options[:list]
76
+ opts[:owner_id] = resolve_user(options[:owner]) if options[:owner]
77
+ project = with_not_found("Project not found: '#{project_id}'. Use 'suth projects list' to see available projects.") do
78
+ client.projects.update(workspace_id, project_id, **opts)
79
+ end
80
+ output_item project, labels: {id: "Project ID"}
81
+ end
82
+ end
83
+
84
+ desc "delete PROJECT", "Delete a project"
85
+ # Deletes a project after confirmation.
86
+ #
87
+ # @param project_ref [String] project identifier (ID or name)
88
+ # @return [void]
89
+ def delete(project_ref)
90
+ handle_error do
91
+ project = with_not_found("Project not found: '#{project_ref}'. Use 'suth projects list' to see available projects.") do
92
+ client.projects.find(workspace_id, project_ref)
93
+ end
94
+ confirming("Delete project '#{project.title}' (#{project.id})?") do
95
+ client.projects.destroy(workspace_id, project.id)
96
+ output_success "Project '#{project.title}' deleted"
97
+ end
98
+ end
99
+ end
100
+
101
+ desc "add_card PROJECT_ID CARD_ID", "Link a card to a project"
102
+ # Links an existing card to a project.
103
+ #
104
+ # @param project_id [String] numeric ID or short code of the project/epic
105
+ # @param card_id [String] unique identifier of the card to link
106
+ # @return [void]
107
+ def add_card(project_id, card_id)
108
+ client.projects.add_card(workspace_id, project_id, card_id)
109
+ output_success "Linked card #{card_id} to project #{project_id}"
110
+ end
111
+
112
+ desc "remove_card PROJECT_ID CARD_ID", "Remove a card from a project"
113
+ # Removes a card's association from a project.
114
+ #
115
+ # @param project_id [String] numeric ID or short code of the project/epic
116
+ # @param card_id [String] unique identifier of the card to unlink
117
+ # @return [void]
118
+ def remove_card(project_id, card_id)
119
+ client.projects.remove_card(workspace_id, project_id, card_id)
120
+ output_success "Removed card #{card_id} from project #{project_id}"
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Superthread
4
+ module Cli
5
+ # CLI commands for managing comment replies.
6
+ #
7
+ # Replies are threaded responses to comments on cards and pages.
8
+ # This class provides commands to list, create, update, and delete replies.
9
+ class Replies < Base
10
+ desc "list", "List all replies to a comment"
11
+ option :comment, type: :string, required: true, desc: "Parent comment ID"
12
+ # List all replies to a specified comment.
13
+ #
14
+ # @return [void]
15
+ def list
16
+ handle_error do
17
+ replies = client.comments.replies(workspace_id, options[:comment])
18
+ output_list replies, columns: %i[id content user_id time_created], headers: {id: "REPLY_ID"}
19
+ end
20
+ end
21
+
22
+ desc "get REPLY_ID", "Get reply details"
23
+ option :comment, type: :string, required: true, desc: "Parent comment ID"
24
+ # Display detailed information about a specific reply.
25
+ #
26
+ # @param reply_id [String] the unique identifier of the reply
27
+ # @return [void]
28
+ def get(reply_id)
29
+ handle_error do
30
+ begin
31
+ reply = client.comments.find_reply(workspace_id, options[:comment], reply_id)
32
+ rescue Superthread::NotFoundError
33
+ raise Thor::Error, "Reply not found: '#{reply_id}' on comment '#{options[:comment]}'."
34
+ end
35
+ output_item reply, fields: %i[id content user_id card_id time_created time_updated], labels: {
36
+ id: "Reply ID",
37
+ user_id: "User ID",
38
+ card_id: "Card ID",
39
+ time_created: "Time Created",
40
+ time_updated: "Time Updated"
41
+ }
42
+ end
43
+ end
44
+
45
+ desc "create", "Reply to a comment"
46
+ option :comment, type: :string, required: true, desc: "Parent comment ID"
47
+ option :content, type: :string, required: true, desc: "Reply content"
48
+ # Add a threaded reply to an existing comment.
49
+ #
50
+ # @return [void]
51
+ def create
52
+ handle_error do
53
+ reply = client.comments.reply(workspace_id, options[:comment], content: options[:content])
54
+ output_item reply, labels: {id: "Reply ID"}
55
+ end
56
+ end
57
+
58
+ desc "update REPLY_ID", "Update a reply"
59
+ option :comment, type: :string, required: true, desc: "Parent comment ID"
60
+ option :content, type: :string, desc: "New content"
61
+ option :status, type: :string, enum: %w[resolved open orphaned], desc: "Status"
62
+ # Update an existing reply's content or status.
63
+ #
64
+ # @param reply_id [String] the unique identifier of the reply
65
+ # @return [void]
66
+ def update(reply_id)
67
+ handle_error do
68
+ reply = client.comments.update_reply(
69
+ workspace_id, options[:comment], reply_id,
70
+ **symbolized_options(:content, :status)
71
+ )
72
+ output_item reply, labels: {id: "Reply ID"}
73
+ end
74
+ end
75
+
76
+ desc "delete REPLY_ID", "Delete a reply"
77
+ option :comment, type: :string, required: true, desc: "Parent comment ID"
78
+ # Permanently delete a reply after confirmation.
79
+ #
80
+ # @param reply_id [String] the unique identifier of the reply
81
+ # @return [void]
82
+ def delete(reply_id)
83
+ handle_error do
84
+ reply = client.comments.find_reply(workspace_id, options[:comment], reply_id)
85
+ preview = Formatter.truncate(Formatter.strip_html(reply.content), 50)
86
+ confirming("Delete reply '#{preview}' (#{reply_id})?") do
87
+ client.comments.delete_reply(workspace_id, options[:comment], reply_id)
88
+ output_success "Reply #{reply_id} deleted"
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Superthread
4
+ module Cli
5
+ # CLI commands for search operations.
6
+ class Search < Base
7
+ desc "query SEARCH_TERM", "Search across workspace"
8
+ option :field, type: :string, enum: %w[title content], desc: "Field to search in"
9
+ option :types, type: :string, desc: "Entity types to search (comma-separated: board,card,page,project,epic,note)"
10
+ option :space, type: :string, aliases: "-s", desc: "Space to filter by (ID or name)"
11
+ option :include_archived, type: :boolean, desc: "Include archived items"
12
+ option :grouped, type: :boolean, desc: "Group results by type"
13
+ # Searches across all entities in the workspace.
14
+ #
15
+ # @param search_term [String] the text to search for
16
+ # @return [void]
17
+ def query(search_term)
18
+ handle_error do
19
+ types = options[:types]&.split(",")&.map(&:strip)
20
+ results = client.search.query(
21
+ workspace_id,
22
+ query: search_term,
23
+ field: options[:field],
24
+ types: types,
25
+ space_id: space_id,
26
+ archived: options[:include_archived],
27
+ grouped: options[:grouped]
28
+ )
29
+ output_list results, columns: %i[result_type id title], headers: {id: "ID"}
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end