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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +4 -0
- data/LICENSE +21 -0
- data/README.md +492 -0
- data/exe/suth +19 -0
- data/lib/superthread/cli/accounts.rb +240 -0
- data/lib/superthread/cli/activity.rb +210 -0
- data/lib/superthread/cli/base.rb +355 -0
- data/lib/superthread/cli/boards.rb +131 -0
- data/lib/superthread/cli/cards.rb +530 -0
- data/lib/superthread/cli/checklists.rb +223 -0
- data/lib/superthread/cli/comments.rb +86 -0
- data/lib/superthread/cli/completion.rb +306 -0
- data/lib/superthread/cli/concerns/board_resolvable.rb +70 -0
- data/lib/superthread/cli/concerns/confirmable.rb +55 -0
- data/lib/superthread/cli/concerns/date_parsable.rb +196 -0
- data/lib/superthread/cli/concerns/list_resolvable.rb +53 -0
- data/lib/superthread/cli/concerns/space_resolvable.rb +52 -0
- data/lib/superthread/cli/concerns/sprint_resolvable.rb +55 -0
- data/lib/superthread/cli/concerns/tag_resolvable.rb +49 -0
- data/lib/superthread/cli/concerns/user_resolvable.rb +52 -0
- data/lib/superthread/cli/concerns/workspace_resolvable.rb +83 -0
- data/lib/superthread/cli/config.rb +129 -0
- data/lib/superthread/cli/formatter.rb +388 -0
- data/lib/superthread/cli/lists.rb +85 -0
- data/lib/superthread/cli/main.rb +121 -0
- data/lib/superthread/cli/members.rb +19 -0
- data/lib/superthread/cli/notes.rb +64 -0
- data/lib/superthread/cli/pages.rb +128 -0
- data/lib/superthread/cli/projects.rb +124 -0
- data/lib/superthread/cli/replies.rb +94 -0
- data/lib/superthread/cli/search.rb +34 -0
- data/lib/superthread/cli/setup.rb +253 -0
- data/lib/superthread/cli/spaces.rb +141 -0
- data/lib/superthread/cli/sprints.rb +32 -0
- data/lib/superthread/cli/tags.rb +86 -0
- data/lib/superthread/cli/ui/gum_prompt.rb +58 -0
- data/lib/superthread/cli/ui/plain_prompt.rb +73 -0
- data/lib/superthread/cli/ui.rb +263 -0
- data/lib/superthread/cli/workspaces.rb +105 -0
- data/lib/superthread/cli.rb +12 -0
- data/lib/superthread/client.rb +207 -0
- data/lib/superthread/configuration.rb +354 -0
- data/lib/superthread/connection.rb +57 -0
- data/lib/superthread/error.rb +164 -0
- data/lib/superthread/mention_formatter.rb +96 -0
- data/lib/superthread/model.rb +178 -0
- data/lib/superthread/models/board.rb +59 -0
- data/lib/superthread/models/card.rb +321 -0
- data/lib/superthread/models/checklist.rb +91 -0
- data/lib/superthread/models/checklist_item.rb +69 -0
- data/lib/superthread/models/comment.rb +71 -0
- data/lib/superthread/models/concerns/archivable.rb +32 -0
- data/lib/superthread/models/concerns/presentable.rb +113 -0
- data/lib/superthread/models/concerns/timestampable.rb +91 -0
- data/lib/superthread/models/list.rb +67 -0
- data/lib/superthread/models/member.rb +40 -0
- data/lib/superthread/models/note.rb +56 -0
- data/lib/superthread/models/page.rb +70 -0
- data/lib/superthread/models/project.rb +83 -0
- data/lib/superthread/models/space.rb +71 -0
- data/lib/superthread/models/sprint.rb +53 -0
- data/lib/superthread/models/tag.rb +52 -0
- data/lib/superthread/models/team.rb +68 -0
- data/lib/superthread/models/user.rb +76 -0
- data/lib/superthread/models.rb +12 -0
- data/lib/superthread/object.rb +285 -0
- data/lib/superthread/objects/collection.rb +179 -0
- data/lib/superthread/resources/base.rb +204 -0
- data/lib/superthread/resources/boards.rb +150 -0
- data/lib/superthread/resources/cards.rb +363 -0
- data/lib/superthread/resources/comments.rb +163 -0
- data/lib/superthread/resources/notes.rb +61 -0
- data/lib/superthread/resources/pages.rb +110 -0
- data/lib/superthread/resources/projects.rb +117 -0
- data/lib/superthread/resources/search.rb +46 -0
- data/lib/superthread/resources/spaces.rb +104 -0
- data/lib/superthread/resources/sprints.rb +37 -0
- data/lib/superthread/resources/tags.rb +52 -0
- data/lib/superthread/resources/users.rb +29 -0
- data/lib/superthread/version.rb +6 -0
- data/lib/superthread/version_checker.rb +174 -0
- data/lib/superthread.rb +30 -0
- metadata +259 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Superthread
|
|
4
|
+
module Resources
|
|
5
|
+
# API resource for page (document) operations.
|
|
6
|
+
#
|
|
7
|
+
# Provides methods for creating, updating, and managing pages
|
|
8
|
+
# (documents, wikis) via the Superthread API.
|
|
9
|
+
class Pages < Base
|
|
10
|
+
# Lists all pages in a workspace.
|
|
11
|
+
#
|
|
12
|
+
# @param workspace_id [String] the workspace identifier
|
|
13
|
+
# @param space_id [String, nil] optional space identifier to filter by
|
|
14
|
+
# @param archived [Boolean, nil] when true, includes archived pages
|
|
15
|
+
# @param updated_recently [Boolean, nil] when true, returns only recently updated pages
|
|
16
|
+
# @return [Superthread::Objects::Collection<Superthread::Models::Page>] the pages in the workspace
|
|
17
|
+
def list(workspace_id, space_id: nil, archived: nil, updated_recently: nil)
|
|
18
|
+
ws = safe_id("workspace_id", workspace_id)
|
|
19
|
+
params = compact_params(project_id: space_id, archived: archived, updated_recently: updated_recently)
|
|
20
|
+
get_collection("/#{ws}/pages", params: params,
|
|
21
|
+
item_class: Models::Page, items_key: :pages)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Gets a specific page.
|
|
25
|
+
#
|
|
26
|
+
# @param workspace_id [String] the workspace identifier
|
|
27
|
+
# @param page_id [String] the page identifier
|
|
28
|
+
# @return [Superthread::Models::Page] the page with all attributes
|
|
29
|
+
def find(workspace_id, page_id)
|
|
30
|
+
ws = safe_id("workspace_id", workspace_id)
|
|
31
|
+
page = safe_id("page_id", page_id)
|
|
32
|
+
get_object("/#{ws}/pages/#{page}",
|
|
33
|
+
object_class: Models::Page, unwrap_key: :page)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Creates a new page in a space.
|
|
37
|
+
#
|
|
38
|
+
# @param workspace_id [String] the workspace identifier
|
|
39
|
+
# @param space_id [String] the space identifier
|
|
40
|
+
# @param params [Hash{Symbol => Object}] page parameters
|
|
41
|
+
# @option params [String] :title the page title
|
|
42
|
+
# @option params [String] :content the page content as HTML
|
|
43
|
+
# @option params [Hash{Symbol => Object}] :schema the content schema for structured content
|
|
44
|
+
# @option params [String] :parent_page_id the parent page identifier for nesting
|
|
45
|
+
# @return [Superthread::Models::Page] the created page
|
|
46
|
+
def create(workspace_id, space_id:, **params)
|
|
47
|
+
ws = safe_id("workspace_id", workspace_id)
|
|
48
|
+
body = compact_params(project_id: space_id, **params)
|
|
49
|
+
post_object("/#{ws}/pages", body: body,
|
|
50
|
+
object_class: Models::Page, unwrap_key: :page)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Updates a page's attributes.
|
|
54
|
+
#
|
|
55
|
+
# @param workspace_id [String] the workspace identifier
|
|
56
|
+
# @param page_id [String] the page identifier
|
|
57
|
+
# @param params [Hash{Symbol => Object}] the attributes to update
|
|
58
|
+
# @option params [String] :title the new page title
|
|
59
|
+
# @option params [String] :content the new page content as HTML
|
|
60
|
+
# @option params [Boolean] :archived whether the page is archived
|
|
61
|
+
# @option params [String] :parent_page_id the new parent page identifier
|
|
62
|
+
# @return [Superthread::Models::Page] the updated page
|
|
63
|
+
def update(workspace_id, page_id, **params)
|
|
64
|
+
ws = safe_id("workspace_id", workspace_id)
|
|
65
|
+
page = safe_id("page_id", page_id)
|
|
66
|
+
patch_object("/#{ws}/pages/#{page}", body: compact_params(**params),
|
|
67
|
+
object_class: Models::Page, unwrap_key: :page)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Duplicates a page to a destination space.
|
|
71
|
+
#
|
|
72
|
+
# @param workspace_id [String] the workspace identifier
|
|
73
|
+
# @param page_id [String] the page identifier to duplicate
|
|
74
|
+
# @param space_id [String] the destination space identifier
|
|
75
|
+
# @param params [Hash{Symbol => Object}] optional duplication parameters
|
|
76
|
+
# @option params [String] :title the title for the duplicated page (defaults to original)
|
|
77
|
+
# @option params [String] :parent_page_id the parent page identifier in the destination
|
|
78
|
+
# @option params [Integer] :position the position index among sibling pages
|
|
79
|
+
# @return [Superthread::Models::Page] the duplicated page
|
|
80
|
+
def duplicate(workspace_id, page_id, space_id:, **params)
|
|
81
|
+
ws = safe_id("workspace_id", workspace_id)
|
|
82
|
+
page = safe_id("page_id", page_id)
|
|
83
|
+
body = compact_params(project_id: space_id, **params)
|
|
84
|
+
post_object("/#{ws}/pages/#{page}/copy", body: body,
|
|
85
|
+
object_class: Models::Page, unwrap_key: :page)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Archives a page (soft delete).
|
|
89
|
+
#
|
|
90
|
+
# @param workspace_id [String] the workspace identifier
|
|
91
|
+
# @param page_id [String] the page identifier to archive
|
|
92
|
+
# @return [Superthread::Models::Page] the archived page
|
|
93
|
+
def archive(workspace_id, page_id)
|
|
94
|
+
update(workspace_id, page_id, archived: true)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Deletes a page permanently.
|
|
98
|
+
#
|
|
99
|
+
# @param workspace_id [String] the workspace identifier
|
|
100
|
+
# @param page_id [String] the page identifier to delete
|
|
101
|
+
# @return [Superthread::Object] a response object with success: true
|
|
102
|
+
def destroy(workspace_id, page_id)
|
|
103
|
+
ws = safe_id("workspace_id", workspace_id)
|
|
104
|
+
page = safe_id("page_id", page_id)
|
|
105
|
+
http_delete("/#{ws}/pages/#{page}")
|
|
106
|
+
success_response
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Superthread
|
|
4
|
+
module Resources
|
|
5
|
+
# API resource for project (epic/roadmap item) operations.
|
|
6
|
+
#
|
|
7
|
+
# Provides methods for creating, updating, and managing projects
|
|
8
|
+
# (epics) and their linked cards via the Superthread API.
|
|
9
|
+
class Projects < Base
|
|
10
|
+
# Lists all roadmap projects (epics) in a workspace.
|
|
11
|
+
#
|
|
12
|
+
# @param workspace_id [String] the workspace identifier
|
|
13
|
+
# @return [Superthread::Objects::Collection<Superthread::Models::Project>] the projects in the workspace
|
|
14
|
+
# @note The API returns epics nested within lists (status columns).
|
|
15
|
+
# This method flattens them into a single collection.
|
|
16
|
+
def list(workspace_id)
|
|
17
|
+
ws = safe_id("workspace_id", workspace_id)
|
|
18
|
+
data = http_get("/#{ws}/epics")
|
|
19
|
+
|
|
20
|
+
# Epics are nested inside each list - flatten them
|
|
21
|
+
epics = (data[:lists] || []).flat_map { |list| list[:epics] || [] }
|
|
22
|
+
|
|
23
|
+
Superthread::Objects::Collection.from_response(
|
|
24
|
+
{epics: epics},
|
|
25
|
+
key: :epics,
|
|
26
|
+
item_class: Models::Project
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Gets a specific project.
|
|
31
|
+
#
|
|
32
|
+
# @param workspace_id [String] the workspace identifier
|
|
33
|
+
# @param project_id [String] the project identifier (maps to epic_id in API)
|
|
34
|
+
# @return [Superthread::Models::Project] the project with all attributes
|
|
35
|
+
def find(workspace_id, project_id)
|
|
36
|
+
ws = safe_id("workspace_id", workspace_id)
|
|
37
|
+
proj = safe_id("project_id", project_id)
|
|
38
|
+
get_object("/#{ws}/epics/#{proj}",
|
|
39
|
+
object_class: Models::Project, unwrap_key: :epic)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Creates a new project (epic) on the roadmap.
|
|
43
|
+
#
|
|
44
|
+
# @param workspace_id [String] the workspace identifier
|
|
45
|
+
# @param title [String] the project title
|
|
46
|
+
# @param list_id [String] the list identifier (status column) for the project
|
|
47
|
+
# @param params [Hash{Symbol => Object}] optional project parameters
|
|
48
|
+
# @option params [String] :content the project description content
|
|
49
|
+
# @option params [Integer] :start_date the start date as Unix timestamp
|
|
50
|
+
# @option params [Integer] :due_date the due date as Unix timestamp
|
|
51
|
+
# @return [Superthread::Models::Project] the created project
|
|
52
|
+
def create(workspace_id, title:, list_id:, **params)
|
|
53
|
+
ws = safe_id("workspace_id", workspace_id)
|
|
54
|
+
body = compact_params(title: title, list_id: list_id, **params)
|
|
55
|
+
post_object("/#{ws}/epics", body: body,
|
|
56
|
+
object_class: Models::Project, unwrap_key: :epic)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Updates a project's attributes.
|
|
60
|
+
#
|
|
61
|
+
# @param workspace_id [String] the workspace identifier
|
|
62
|
+
# @param project_id [String] the project identifier
|
|
63
|
+
# @param params [Hash{Symbol => Object}] the attributes to update
|
|
64
|
+
# @option params [String] :title the new project title
|
|
65
|
+
# @option params [String] :content the new project description
|
|
66
|
+
# @option params [String] :list_id the new list identifier (status column)
|
|
67
|
+
# @option params [Integer] :start_date the new start date as Unix timestamp
|
|
68
|
+
# @option params [Integer] :due_date the new due date as Unix timestamp
|
|
69
|
+
# @return [Superthread::Models::Project] the updated project
|
|
70
|
+
def update(workspace_id, project_id, **params)
|
|
71
|
+
ws = safe_id("workspace_id", workspace_id)
|
|
72
|
+
proj = safe_id("project_id", project_id)
|
|
73
|
+
patch_object("/#{ws}/epics/#{proj}", body: compact_params(**params),
|
|
74
|
+
object_class: Models::Project, unwrap_key: :epic)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Deletes a project permanently.
|
|
78
|
+
#
|
|
79
|
+
# @param workspace_id [String] the workspace identifier
|
|
80
|
+
# @param project_id [String] the project identifier to delete
|
|
81
|
+
# @return [Superthread::Object] a response object with success: true
|
|
82
|
+
def destroy(workspace_id, project_id)
|
|
83
|
+
ws = safe_id("workspace_id", workspace_id)
|
|
84
|
+
proj = safe_id("project_id", project_id)
|
|
85
|
+
http_delete("/#{ws}/epics/#{proj}")
|
|
86
|
+
success_response
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Links a card to a project (epic).
|
|
90
|
+
#
|
|
91
|
+
# @param workspace_id [String] the workspace identifier
|
|
92
|
+
# @param project_id [String] the project identifier
|
|
93
|
+
# @param card_id [String] the card identifier to link
|
|
94
|
+
# @return [Superthread::Object] the link result
|
|
95
|
+
def add_card(workspace_id, project_id, card_id)
|
|
96
|
+
ws = safe_id("workspace_id", workspace_id)
|
|
97
|
+
proj = safe_id("project_id", project_id)
|
|
98
|
+
card = safe_id("card_id", card_id)
|
|
99
|
+
post_object("/#{ws}/epics/#{proj}/cards/#{card}")
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Removes a card from a project (epic).
|
|
103
|
+
#
|
|
104
|
+
# @param workspace_id [String] the workspace identifier
|
|
105
|
+
# @param project_id [String] the project identifier
|
|
106
|
+
# @param card_id [String] the card identifier to remove
|
|
107
|
+
# @return [Superthread::Object] a response object with success: true
|
|
108
|
+
def remove_card(workspace_id, project_id, card_id)
|
|
109
|
+
ws = safe_id("workspace_id", workspace_id)
|
|
110
|
+
proj = safe_id("project_id", project_id)
|
|
111
|
+
card = safe_id("card_id", card_id)
|
|
112
|
+
http_delete("/#{ws}/epics/#{proj}/cards/#{card}")
|
|
113
|
+
success_response
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Superthread
|
|
4
|
+
module Resources
|
|
5
|
+
# API resource for search operations.
|
|
6
|
+
#
|
|
7
|
+
# Provides methods for searching across workspace entities
|
|
8
|
+
# (cards, pages, boards, etc.) via the Superthread API.
|
|
9
|
+
class Search < Base
|
|
10
|
+
# Searches across workspace entities.
|
|
11
|
+
#
|
|
12
|
+
# @param workspace_id [String] the workspace identifier
|
|
13
|
+
# @param query [String] the search query string
|
|
14
|
+
# @param params [Hash{Symbol => Object}] optional search parameters
|
|
15
|
+
# @option params [String] :field the field to search (title, content)
|
|
16
|
+
# @option params [Array<String>] :types entity types to include (board, card, page, etc.)
|
|
17
|
+
# @option params [Array<String>] :statuses status values to filter by
|
|
18
|
+
# @option params [String] :space_id the space identifier to filter by
|
|
19
|
+
# @option params [Boolean] :archived when true, includes archived entities
|
|
20
|
+
# @option params [Boolean] :grouped when true, groups results by type (default: false)
|
|
21
|
+
# @option params [String] :cursor the pagination cursor for next page
|
|
22
|
+
# @return [Superthread::Objects::Collection] the search results
|
|
23
|
+
def query(workspace_id, query:, **params)
|
|
24
|
+
ws = safe_id("workspace_id", workspace_id)
|
|
25
|
+
# Default grouped to false so results come in a flat array
|
|
26
|
+
# Use || instead of fetch because CLI may pass grouped: nil explicitly
|
|
27
|
+
grouped = params[:grouped].nil? ? false : params[:grouped]
|
|
28
|
+
search_params = compact_params(
|
|
29
|
+
query: query,
|
|
30
|
+
project_id: params[:space_id],
|
|
31
|
+
grouped: grouped,
|
|
32
|
+
**params.except(:space_id, :grouped)
|
|
33
|
+
)
|
|
34
|
+
response = http_get("/#{ws}/search", params: search_params)
|
|
35
|
+
|
|
36
|
+
# Unwrap results - each item is {"card": {...}} or {"board": {...}}, etc.
|
|
37
|
+
results = (response[:results] || []).map do |item|
|
|
38
|
+
result_type, data = item.first
|
|
39
|
+
data.merge(result_type: result_type.to_s)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
Objects::Collection.from_response(results)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Superthread
|
|
4
|
+
module Resources
|
|
5
|
+
# API resource for space operations.
|
|
6
|
+
#
|
|
7
|
+
# Provides methods for creating, updating, and managing spaces
|
|
8
|
+
# and their members via the Superthread API.
|
|
9
|
+
class Spaces < Base
|
|
10
|
+
# Lists all spaces in a workspace.
|
|
11
|
+
#
|
|
12
|
+
# @param workspace_id [String] the workspace identifier
|
|
13
|
+
# @return [Superthread::Objects::Collection<Superthread::Models::Space>] the spaces in the workspace
|
|
14
|
+
def list(workspace_id)
|
|
15
|
+
ws = safe_id("workspace_id", workspace_id)
|
|
16
|
+
get_collection("/#{ws}/projects",
|
|
17
|
+
item_class: Models::Space, items_key: :projects)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Gets a specific space.
|
|
21
|
+
#
|
|
22
|
+
# @param workspace_id [String] the workspace identifier
|
|
23
|
+
# @param space_id [String] the space identifier (maps to project_id in API)
|
|
24
|
+
# @return [Superthread::Models::Space] the space with all attributes
|
|
25
|
+
def find(workspace_id, space_id)
|
|
26
|
+
ws = safe_id("workspace_id", workspace_id)
|
|
27
|
+
space = safe_id("space_id", space_id)
|
|
28
|
+
get_object("/#{ws}/projects/#{space}",
|
|
29
|
+
object_class: Models::Space, unwrap_key: :project)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Creates a new space in the workspace.
|
|
33
|
+
#
|
|
34
|
+
# @param workspace_id [String] the workspace identifier
|
|
35
|
+
# @param title [String] the space title
|
|
36
|
+
# @param params [Hash{Symbol => Object}] optional space parameters
|
|
37
|
+
# @option params [String] :description the space description
|
|
38
|
+
# @option params [String] :icon the space icon identifier
|
|
39
|
+
# @return [Superthread::Models::Space] the created space
|
|
40
|
+
def create(workspace_id, title:, **params)
|
|
41
|
+
ws = safe_id("workspace_id", workspace_id)
|
|
42
|
+
body = compact_params(title: title, **params)
|
|
43
|
+
post_object("/#{ws}/projects", body: body,
|
|
44
|
+
object_class: Models::Space, unwrap_key: :project)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Updates a space's attributes.
|
|
48
|
+
#
|
|
49
|
+
# @param workspace_id [String] the workspace identifier
|
|
50
|
+
# @param space_id [String] the space identifier
|
|
51
|
+
# @param params [Hash{Symbol => Object}] the attributes to update
|
|
52
|
+
# @option params [String] :title the new space title
|
|
53
|
+
# @option params [String] :description the new space description
|
|
54
|
+
# @option params [String] :icon the new space icon identifier
|
|
55
|
+
# @return [Superthread::Models::Space] the updated space
|
|
56
|
+
def update(workspace_id, space_id, **params)
|
|
57
|
+
ws = safe_id("workspace_id", workspace_id)
|
|
58
|
+
space = safe_id("space_id", space_id)
|
|
59
|
+
patch_object("/#{ws}/projects/#{space}", body: compact_params(**params),
|
|
60
|
+
object_class: Models::Space, unwrap_key: :project)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Deletes a space permanently.
|
|
64
|
+
#
|
|
65
|
+
# @param workspace_id [String] the workspace identifier
|
|
66
|
+
# @param space_id [String] the space identifier to delete
|
|
67
|
+
# @return [Superthread::Object] a response object with success: true
|
|
68
|
+
def destroy(workspace_id, space_id)
|
|
69
|
+
ws = safe_id("workspace_id", workspace_id)
|
|
70
|
+
space = safe_id("space_id", space_id)
|
|
71
|
+
http_delete("/#{ws}/projects/#{space}")
|
|
72
|
+
success_response
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Adds a member to a space.
|
|
76
|
+
#
|
|
77
|
+
# @param workspace_id [String] the workspace identifier
|
|
78
|
+
# @param space_id [String] the space identifier
|
|
79
|
+
# @param user_id [String] the user identifier to add as a member
|
|
80
|
+
# @param role [String] the member role (admin, member, viewer)
|
|
81
|
+
# @return [Superthread::Object] the membership result
|
|
82
|
+
def add_member(workspace_id, space_id, user_id:, role: "member")
|
|
83
|
+
ws = safe_id("workspace_id", workspace_id)
|
|
84
|
+
space = safe_id("space_id", space_id)
|
|
85
|
+
body = {members: [{user_id: user_id, role: role}]}
|
|
86
|
+
post_object("/#{ws}/projects/#{space}/members", body: body)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Removes a member from a space.
|
|
90
|
+
#
|
|
91
|
+
# @param workspace_id [String] the workspace identifier
|
|
92
|
+
# @param space_id [String] the space identifier
|
|
93
|
+
# @param user_id [String] the user identifier to remove
|
|
94
|
+
# @return [Superthread::Object] a response object with success: true
|
|
95
|
+
def remove_member(workspace_id, space_id, user_id)
|
|
96
|
+
ws = safe_id("workspace_id", workspace_id)
|
|
97
|
+
space = safe_id("space_id", space_id)
|
|
98
|
+
user = safe_id("user_id", user_id)
|
|
99
|
+
http_delete("/#{ws}/projects/#{space}/members/#{user}")
|
|
100
|
+
success_response
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Superthread
|
|
4
|
+
module Resources
|
|
5
|
+
# API resource for sprint operations.
|
|
6
|
+
#
|
|
7
|
+
# Provides methods for listing and retrieving sprints
|
|
8
|
+
# via the Superthread API.
|
|
9
|
+
class Sprints < Base
|
|
10
|
+
# Lists all sprints in a space.
|
|
11
|
+
#
|
|
12
|
+
# @param workspace_id [String] the workspace identifier
|
|
13
|
+
# @param space_id [String] the space identifier to list sprints from
|
|
14
|
+
# @return [Superthread::Objects::Collection<Superthread::Models::Sprint>] the sprints in the space
|
|
15
|
+
def list(workspace_id, space_id:)
|
|
16
|
+
ws = safe_id("workspace_id", workspace_id)
|
|
17
|
+
params = compact_params(project_id: space_id)
|
|
18
|
+
get_collection("/#{ws}/sprints", params: params,
|
|
19
|
+
item_class: Models::Sprint, items_key: :sprints)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Gets a specific sprint with its available lists.
|
|
23
|
+
#
|
|
24
|
+
# @param workspace_id [String] the workspace identifier
|
|
25
|
+
# @param sprint_id [String] the sprint identifier
|
|
26
|
+
# @param space_id [String] the space identifier (required by the API)
|
|
27
|
+
# @return [Superthread::Models::Sprint] the sprint with available lists
|
|
28
|
+
def find(workspace_id, sprint_id, space_id:)
|
|
29
|
+
ws = safe_id("workspace_id", workspace_id)
|
|
30
|
+
sprint = safe_id("sprint_id", sprint_id)
|
|
31
|
+
params = compact_params(project_id: space_id)
|
|
32
|
+
get_object("/#{ws}/sprints/#{sprint}", params: params,
|
|
33
|
+
object_class: Models::Sprint, unwrap_key: :sprint)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Superthread
|
|
4
|
+
module Resources
|
|
5
|
+
# API resource for tag operations.
|
|
6
|
+
#
|
|
7
|
+
# Provides methods for creating, updating, and deleting tags
|
|
8
|
+
# via the Superthread API.
|
|
9
|
+
class Tags < Base
|
|
10
|
+
# Creates a new tag in the workspace.
|
|
11
|
+
#
|
|
12
|
+
# @param workspace_id [String] the workspace identifier
|
|
13
|
+
# @param name [String] the tag name
|
|
14
|
+
# @param color [String] the tag color as hex string (e.g., "#FF5733")
|
|
15
|
+
# @param space_id [String, nil] optional space identifier to scope the tag
|
|
16
|
+
# @return [Superthread::Models::Tag] the created tag
|
|
17
|
+
def create(workspace_id, name:, color:, space_id: nil)
|
|
18
|
+
ws = safe_id("workspace_id", workspace_id)
|
|
19
|
+
body = compact_params(name: name, color: color, project_id: space_id)
|
|
20
|
+
post_object("/#{ws}/tags", body: body,
|
|
21
|
+
object_class: Models::Tag, unwrap_key: :tag)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Updates a tag's attributes.
|
|
25
|
+
#
|
|
26
|
+
# @param workspace_id [String] the workspace identifier
|
|
27
|
+
# @param tag_id [String] the tag identifier
|
|
28
|
+
# @param params [Hash{Symbol => Object}] the attributes to update
|
|
29
|
+
# @option params [String] :name the new tag name
|
|
30
|
+
# @option params [String] :color the new tag color as hex string
|
|
31
|
+
# @return [Superthread::Models::Tag] the updated tag
|
|
32
|
+
def update(workspace_id, tag_id, **params)
|
|
33
|
+
ws = safe_id("workspace_id", workspace_id)
|
|
34
|
+
tag = safe_id("tag_id", tag_id)
|
|
35
|
+
patch_object("/#{ws}/tags/#{tag}", body: compact_params(**params),
|
|
36
|
+
object_class: Models::Tag, unwrap_key: :tag)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Deletes a tag permanently.
|
|
40
|
+
#
|
|
41
|
+
# @param workspace_id [String] the workspace identifier
|
|
42
|
+
# @param tag_id [String] the tag identifier to delete
|
|
43
|
+
# @return [Superthread::Object] a response object with success: true
|
|
44
|
+
def destroy(workspace_id, tag_id)
|
|
45
|
+
ws = safe_id("workspace_id", workspace_id)
|
|
46
|
+
tag = safe_id("tag_id", tag_id)
|
|
47
|
+
http_delete("/#{ws}/tags/#{tag}")
|
|
48
|
+
success_response
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Superthread
|
|
4
|
+
module Resources
|
|
5
|
+
# API resource for user operations.
|
|
6
|
+
#
|
|
7
|
+
# Provides methods for retrieving user information and
|
|
8
|
+
# listing workspace members via the Superthread API.
|
|
9
|
+
class Users < Base
|
|
10
|
+
# Gets the current authenticated user's account information.
|
|
11
|
+
#
|
|
12
|
+
# @return [Superthread::Models::User] the current user's profile
|
|
13
|
+
def me
|
|
14
|
+
get_object("/users/me", object_class: Models::User, unwrap_key: :user)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Gets workspace members.
|
|
18
|
+
#
|
|
19
|
+
# @param workspace_id [String] the workspace identifier
|
|
20
|
+
# @return [Superthread::Objects::Collection<Superthread::Models::User>] the workspace members
|
|
21
|
+
# @note The API uses /teams/:id/members but we use workspace terminology.
|
|
22
|
+
def members(workspace_id)
|
|
23
|
+
ws = safe_id("workspace_id", workspace_id)
|
|
24
|
+
get_collection("/teams/#{ws}/members",
|
|
25
|
+
item_class: Models::User, items_key: :members)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "faraday"
|
|
4
|
+
require "json"
|
|
5
|
+
require "fileutils"
|
|
6
|
+
|
|
7
|
+
module Superthread
|
|
8
|
+
# Checks for newer versions of the Superthread CLI.
|
|
9
|
+
#
|
|
10
|
+
# Uses the GitHub Releases API to find the latest version and caches
|
|
11
|
+
# the result to avoid repeated network requests. Designed to never
|
|
12
|
+
# interrupt or slow down normal CLI usage.
|
|
13
|
+
#
|
|
14
|
+
# @example Check for updates
|
|
15
|
+
# Superthread::VersionChecker.check_if_stale!
|
|
16
|
+
# if (notice = Superthread::VersionChecker.update_notice)
|
|
17
|
+
# $stderr.puts notice
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# @example Force check (bypass cache TTL)
|
|
21
|
+
# latest = Superthread::VersionChecker.check!
|
|
22
|
+
module VersionChecker
|
|
23
|
+
# GitHub API endpoint for the latest release.
|
|
24
|
+
RELEASES_URL = "https://api.github.com/repos/steveclarke/superthread/releases/latest"
|
|
25
|
+
|
|
26
|
+
# How long to cache the version check result (24 hours).
|
|
27
|
+
CACHE_TTL = 86_400
|
|
28
|
+
|
|
29
|
+
# HTTP connect timeout in seconds.
|
|
30
|
+
CONNECT_TIMEOUT = 3
|
|
31
|
+
|
|
32
|
+
# HTTP read timeout in seconds.
|
|
33
|
+
READ_TIMEOUT = 5
|
|
34
|
+
|
|
35
|
+
module_function
|
|
36
|
+
|
|
37
|
+
# Returns a formatted update notice if a newer version is available.
|
|
38
|
+
#
|
|
39
|
+
# Reads from cache only — does not make network requests.
|
|
40
|
+
# Returns nil if no update is available, cache is missing, or
|
|
41
|
+
# update checks are disabled.
|
|
42
|
+
#
|
|
43
|
+
# @return [String, nil] update message or nil
|
|
44
|
+
def update_notice
|
|
45
|
+
return nil if disabled?
|
|
46
|
+
|
|
47
|
+
cached = read_cache
|
|
48
|
+
return nil unless cached
|
|
49
|
+
|
|
50
|
+
latest = cached[:latest_version]
|
|
51
|
+
return nil unless latest
|
|
52
|
+
return nil unless newer?(latest)
|
|
53
|
+
|
|
54
|
+
current = Superthread::VERSION
|
|
55
|
+
"\n" \
|
|
56
|
+
" Update available: #{current} \u2192 #{latest}\n" \
|
|
57
|
+
" Run: brew upgrade superthread\n"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Fetches the latest version from GitHub and updates the cache.
|
|
61
|
+
#
|
|
62
|
+
# Always makes a network request regardless of cache freshness.
|
|
63
|
+
# Returns nil silently on any error (network, parse, etc.).
|
|
64
|
+
#
|
|
65
|
+
# @return [String, nil] the latest version string, or nil on failure
|
|
66
|
+
def check!
|
|
67
|
+
return nil if disabled?
|
|
68
|
+
|
|
69
|
+
version = fetch_latest_version
|
|
70
|
+
write_cache(version) if version
|
|
71
|
+
version
|
|
72
|
+
rescue
|
|
73
|
+
nil
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Fetches the latest version only if the cache is stale.
|
|
77
|
+
#
|
|
78
|
+
# If the cache is fresh (less than {CACHE_TTL} old), does nothing.
|
|
79
|
+
# This is the method to call on every CLI invocation — it's
|
|
80
|
+
# effectively free when the cache is warm.
|
|
81
|
+
#
|
|
82
|
+
# @return [String, nil] the latest version string, or nil
|
|
83
|
+
def check_if_stale!
|
|
84
|
+
return nil if disabled?
|
|
85
|
+
|
|
86
|
+
cached = read_cache
|
|
87
|
+
if cached && (Time.now.to_i - cached[:checked_at].to_i) < CACHE_TTL
|
|
88
|
+
return cached[:latest_version]
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
check!
|
|
92
|
+
rescue
|
|
93
|
+
nil
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Whether version checking is disabled via environment variable.
|
|
97
|
+
#
|
|
98
|
+
# @return [Boolean] true if SUPERTHREAD_NO_UPDATE_CHECK is set
|
|
99
|
+
def disabled?
|
|
100
|
+
ENV.key?("SUPERTHREAD_NO_UPDATE_CHECK")
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Compares a version string against the current version.
|
|
104
|
+
#
|
|
105
|
+
# @param version [String] the version to compare
|
|
106
|
+
# @return [Boolean] true if the given version is newer
|
|
107
|
+
def newer?(version)
|
|
108
|
+
Gem::Version.new(version) > Gem::Version.new(Superthread::VERSION)
|
|
109
|
+
rescue ArgumentError
|
|
110
|
+
false
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Returns the path to the version check cache file.
|
|
114
|
+
#
|
|
115
|
+
# @return [String] absolute path to version_check.json
|
|
116
|
+
def cache_path
|
|
117
|
+
File.join(
|
|
118
|
+
ENV.fetch("XDG_STATE_HOME", File.expand_path("~/.local/state")),
|
|
119
|
+
"superthread",
|
|
120
|
+
"version_check.json"
|
|
121
|
+
)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Reads the cached version check result.
|
|
125
|
+
#
|
|
126
|
+
# @return [Hash{Symbol => Object}, nil] cached data or nil
|
|
127
|
+
def read_cache
|
|
128
|
+
return nil unless File.exist?(cache_path)
|
|
129
|
+
|
|
130
|
+
data = JSON.parse(File.read(cache_path), symbolize_names: true)
|
|
131
|
+
data if data.is_a?(Hash)
|
|
132
|
+
rescue JSON::ParserError, Errno::ENOENT
|
|
133
|
+
nil
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Writes a version check result to the cache file.
|
|
137
|
+
#
|
|
138
|
+
# @param version [String] the latest version string
|
|
139
|
+
# @return [void]
|
|
140
|
+
def write_cache(version)
|
|
141
|
+
FileUtils.mkdir_p(File.dirname(cache_path))
|
|
142
|
+
File.write(cache_path, JSON.generate(
|
|
143
|
+
latest_version: version,
|
|
144
|
+
checked_at: Time.now.to_i
|
|
145
|
+
))
|
|
146
|
+
rescue Errno::EACCES, Errno::ENOSPC
|
|
147
|
+
nil
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Fetches the latest version tag from GitHub Releases API.
|
|
151
|
+
#
|
|
152
|
+
# @return [String, nil] version string (without leading "v"), or nil
|
|
153
|
+
def fetch_latest_version
|
|
154
|
+
conn = Faraday.new(url: RELEASES_URL) do |f|
|
|
155
|
+
f.headers["Accept"] = "application/vnd.github+json"
|
|
156
|
+
f.options.timeout = READ_TIMEOUT
|
|
157
|
+
f.options.open_timeout = CONNECT_TIMEOUT
|
|
158
|
+
f.adapter Faraday.default_adapter
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
response = conn.get
|
|
162
|
+
return nil unless response.status == 200
|
|
163
|
+
|
|
164
|
+
data = JSON.parse(response.body, symbolize_names: true)
|
|
165
|
+
tag = data[:tag_name]
|
|
166
|
+
return nil unless tag
|
|
167
|
+
|
|
168
|
+
# Strip leading "v" (e.g., "v0.5.7" -> "0.5.7")
|
|
169
|
+
tag.sub(/\Av/, "")
|
|
170
|
+
rescue Faraday::Error, JSON::ParserError
|
|
171
|
+
nil
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|