agentr 0.1.7__py3-none-any.whl → 0.1.8__py3-none-any.whl
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.
- agentr/application.py +9 -8
- agentr/applications/github/app.py +168 -257
- agentr/applications/google_calendar/app.py +349 -408
- agentr/applications/reddit/app.py +288 -8
- agentr/integration.py +79 -3
- agentr/integrations/README.md +25 -0
- agentr/integrations/__init__.py +5 -0
- agentr/integrations/agentr.py +87 -0
- agentr/integrations/api_key.py +16 -0
- agentr/integrations/base.py +60 -0
- agentr/server.py +15 -0
- agentr/test.py +3 -28
- agentr/utils/openapi.py +113 -25
- {agentr-0.1.7.dist-info → agentr-0.1.8.dist-info}/METADATA +1 -1
- agentr-0.1.8.dist-info/RECORD +30 -0
- agentr-0.1.7.dist-info/RECORD +0 -25
- {agentr-0.1.7.dist-info → agentr-0.1.8.dist-info}/WHEEL +0 -0
- {agentr-0.1.7.dist-info → agentr-0.1.8.dist-info}/entry_points.txt +0 -0
- {agentr-0.1.7.dist-info → agentr-0.1.8.dist-info}/licenses/LICENSE +0 -0
agentr/application.py
CHANGED
@@ -35,8 +35,8 @@ class APIApplication(Application):
|
|
35
35
|
response.raise_for_status()
|
36
36
|
return response
|
37
37
|
except NotAuthorizedError as e:
|
38
|
-
logger.warning(f"
|
39
|
-
|
38
|
+
logger.warning(f"Authorization needed: {e.message}")
|
39
|
+
raise e
|
40
40
|
except Exception as e:
|
41
41
|
logger.error(f"Error getting {url}: {e}")
|
42
42
|
raise e
|
@@ -49,8 +49,8 @@ class APIApplication(Application):
|
|
49
49
|
response.raise_for_status()
|
50
50
|
return response
|
51
51
|
except NotAuthorizedError as e:
|
52
|
-
logger.warning(f"
|
53
|
-
|
52
|
+
logger.warning(f"Authorization needed: {e.message}")
|
53
|
+
raise e
|
54
54
|
except httpx.HTTPStatusError as e:
|
55
55
|
if e.response.status_code == 429:
|
56
56
|
return e.response.text or "Rate limit exceeded. Please try again later."
|
@@ -67,8 +67,8 @@ class APIApplication(Application):
|
|
67
67
|
response.raise_for_status()
|
68
68
|
return response
|
69
69
|
except NotAuthorizedError as e:
|
70
|
-
logger.warning(f"
|
71
|
-
|
70
|
+
logger.warning(f"Authorization needed: {e.message}")
|
71
|
+
raise e
|
72
72
|
except Exception as e:
|
73
73
|
logger.error(f"Error posting {url}: {e}")
|
74
74
|
raise e
|
@@ -80,10 +80,11 @@ class APIApplication(Application):
|
|
80
80
|
response.raise_for_status()
|
81
81
|
return response
|
82
82
|
except NotAuthorizedError as e:
|
83
|
-
logger.warning(f"
|
84
|
-
|
83
|
+
logger.warning(f"Authorization needed: {e.message}")
|
84
|
+
raise e
|
85
85
|
except Exception as e:
|
86
86
|
logger.error(f"Error posting {url}: {e}")
|
87
|
+
raise e
|
87
88
|
|
88
89
|
def validate(self):
|
89
90
|
pass
|
@@ -1,7 +1,6 @@
|
|
1
1
|
from agentr.integration import Integration
|
2
2
|
from agentr.application import APIApplication
|
3
3
|
from loguru import logger
|
4
|
-
from agentr.exceptions import NotAuthorizedError
|
5
4
|
|
6
5
|
class GithubApp(APIApplication):
|
7
6
|
def __init__(self, integration: Integration) -> None:
|
@@ -12,10 +11,6 @@ class GithubApp(APIApplication):
|
|
12
11
|
if not self.integration:
|
13
12
|
raise ValueError("Integration not configured")
|
14
13
|
credentials = self.integration.get_credentials()
|
15
|
-
if not credentials:
|
16
|
-
logger.warning("No credentials found")
|
17
|
-
action = self.integration.authorize()
|
18
|
-
raise NotAuthorizedError(action)
|
19
14
|
if "headers" in credentials:
|
20
15
|
return credentials["headers"]
|
21
16
|
return {
|
@@ -55,35 +50,24 @@ class GithubApp(APIApplication):
|
|
55
50
|
Returns:
|
56
51
|
A formatted list of recent commits
|
57
52
|
"""
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
53
|
+
repo_full_name = repo_full_name.strip()
|
54
|
+
url = f"{self.base_api_url}/{repo_full_name}/commits"
|
55
|
+
response = self._get(url)
|
56
|
+
response.raise_for_status()
|
57
|
+
|
58
|
+
commits = response.json()
|
59
|
+
if not commits:
|
60
|
+
return f"No commits found for repository {repo_full_name}"
|
61
|
+
|
62
|
+
result = f"Recent commits for {repo_full_name}:\n\n"
|
63
|
+
for commit in commits[:12]: # Limit to 12 commits
|
64
|
+
sha = commit.get("sha", "")[:7]
|
65
|
+
message = commit.get("commit", {}).get("message", "").split('\n')[0]
|
66
|
+
author = commit.get("commit", {}).get("author", {}).get("name", "Unknown")
|
62
67
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
return f"No commits found for repository {repo_full_name}"
|
67
|
-
|
68
|
-
result = f"Recent commits for {repo_full_name}:\n\n"
|
69
|
-
for commit in commits[:12]: # Limit to 12 commits
|
70
|
-
sha = commit.get("sha", "")[:7]
|
71
|
-
message = commit.get("commit", {}).get("message", "").split('\n')[0]
|
72
|
-
author = commit.get("commit", {}).get("author", {}).get("name", "Unknown")
|
73
|
-
|
74
|
-
result += f"- {sha}: {message} (by {author})\n"
|
75
|
-
|
76
|
-
return result
|
77
|
-
elif response.status_code == 404:
|
78
|
-
return f"Repository {repo_full_name} not found"
|
79
|
-
else:
|
80
|
-
logger.error(response.text)
|
81
|
-
return f"Error retrieving commits: {response.status_code} - {response.text}"
|
82
|
-
except NotAuthorizedError as e:
|
83
|
-
return e.message
|
84
|
-
except Exception as e:
|
85
|
-
logger.error(e)
|
86
|
-
return f"Error retrieving commits: {str(e)}"
|
68
|
+
result += f"- {sha}: {message} (by {author})\n"
|
69
|
+
|
70
|
+
return result
|
87
71
|
|
88
72
|
def list_branches(self, repo_full_name: str) -> str:
|
89
73
|
"""List branches for a GitHub repository
|
@@ -94,32 +78,21 @@ class GithubApp(APIApplication):
|
|
94
78
|
Returns:
|
95
79
|
A formatted list of branches
|
96
80
|
"""
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
return result
|
113
|
-
elif response.status_code == 404:
|
114
|
-
return f"Repository {repo_full_name} not found"
|
115
|
-
else:
|
116
|
-
logger.error(response.text)
|
117
|
-
return f"Error retrieving branches: {response.status_code} - {response.text}"
|
118
|
-
except NotAuthorizedError as e:
|
119
|
-
return e.message
|
120
|
-
except Exception as e:
|
121
|
-
logger.error(e)
|
122
|
-
return f"Error retrieving branches: {str(e)}"
|
81
|
+
repo_full_name = repo_full_name.strip()
|
82
|
+
url = f"{self.base_api_url}/{repo_full_name}/branches"
|
83
|
+
response = self._get(url)
|
84
|
+
response.raise_for_status()
|
85
|
+
|
86
|
+
branches = response.json()
|
87
|
+
if not branches:
|
88
|
+
return f"No branches found for repository {repo_full_name}"
|
89
|
+
|
90
|
+
result = f"Branches for {repo_full_name}:\n\n"
|
91
|
+
for branch in branches:
|
92
|
+
branch_name = branch.get("name", "Unknown")
|
93
|
+
result += f"- {branch_name}\n"
|
94
|
+
|
95
|
+
return result
|
123
96
|
|
124
97
|
def list_pull_requests(self, repo_full_name: str, state: str = "open") -> str:
|
125
98
|
"""List pull requests for a GitHub repository
|
@@ -131,37 +104,26 @@ class GithubApp(APIApplication):
|
|
131
104
|
Returns:
|
132
105
|
A formatted list of pull requests
|
133
106
|
"""
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
107
|
+
repo_full_name = repo_full_name.strip()
|
108
|
+
url = f"{self.base_api_url}/{repo_full_name}/pulls"
|
109
|
+
params = {"state": state}
|
110
|
+
response = self._get(url, params=params)
|
111
|
+
response.raise_for_status()
|
112
|
+
|
113
|
+
pull_requests = response.json()
|
114
|
+
if not pull_requests:
|
115
|
+
return f"No pull requests found for repository {repo_full_name} with state '{state}'"
|
116
|
+
|
117
|
+
result = f"Pull requests for {repo_full_name} (State: {state}):\n\n"
|
118
|
+
for pr in pull_requests:
|
119
|
+
pr_title = pr.get("title", "No Title")
|
120
|
+
pr_number = pr.get("number", "Unknown")
|
121
|
+
pr_state = pr.get("state", "Unknown")
|
122
|
+
pr_user = pr.get("user", {}).get("login", "Unknown")
|
139
123
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
return f"No pull requests found for repository {repo_full_name} with state '{state}'"
|
144
|
-
|
145
|
-
result = f"Pull requests for {repo_full_name} (State: {state}):\n\n"
|
146
|
-
for pr in pull_requests:
|
147
|
-
pr_title = pr.get("title", "No Title")
|
148
|
-
pr_number = pr.get("number", "Unknown")
|
149
|
-
pr_state = pr.get("state", "Unknown")
|
150
|
-
pr_user = pr.get("user", {}).get("login", "Unknown")
|
151
|
-
|
152
|
-
result += f"- PR #{pr_number}: {pr_title} (by {pr_user}, Status: {pr_state})\n"
|
153
|
-
|
154
|
-
return result
|
155
|
-
elif response.status_code == 404:
|
156
|
-
return f"Repository {repo_full_name} not found"
|
157
|
-
else:
|
158
|
-
logger.error(response.text)
|
159
|
-
return f"Error retrieving pull requests: {response.status_code} - {response.text}"
|
160
|
-
except NotAuthorizedError as e:
|
161
|
-
return e.message
|
162
|
-
except Exception as e:
|
163
|
-
logger.error(e)
|
164
|
-
return f"Error retrieving pull requests: {str(e)}"
|
124
|
+
result += f"- PR #{pr_number}: {pr_title} (by {pr_user}, Status: {pr_state})\n"
|
125
|
+
|
126
|
+
return result
|
165
127
|
|
166
128
|
def list_issues(self, repo_full_name: str, per_page: int = 30, page: int = 1) -> str:
|
167
129
|
"""List issues for a GitHub repository
|
@@ -174,40 +136,29 @@ class GithubApp(APIApplication):
|
|
174
136
|
Returns:
|
175
137
|
A formatted list of issues
|
176
138
|
"""
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
139
|
+
repo_full_name = repo_full_name.strip()
|
140
|
+
url = f"{self.base_api_url}/{repo_full_name}/issues/events"
|
141
|
+
params = {
|
142
|
+
"per_page": per_page,
|
143
|
+
"page": page
|
144
|
+
}
|
145
|
+
response = self._get(url, params=params)
|
146
|
+
response.raise_for_status()
|
147
|
+
|
148
|
+
issues = response.json()
|
149
|
+
if not issues:
|
150
|
+
return f"No issues found for repository {repo_full_name}"
|
151
|
+
|
152
|
+
result = f"Issues for {repo_full_name} (Page {page}):\n\n"
|
153
|
+
for issue in issues:
|
154
|
+
issue_title = issue.get("issue", {}).get("title", "No Title")
|
155
|
+
issue_number = issue.get("issue", {}).get("number", "Unknown")
|
156
|
+
issue_state = issue.get("issue", {}).get("state", "Unknown")
|
157
|
+
issue_user = issue.get("issue", {}).get("user", {}).get("login", "Unknown")
|
185
158
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
return f"No issues found for repository {repo_full_name}"
|
190
|
-
|
191
|
-
result = f"Issues for {repo_full_name} (Page {page}):\n\n"
|
192
|
-
for issue in issues:
|
193
|
-
issue_title = issue.get("issue", {}).get("title", "No Title")
|
194
|
-
issue_number = issue.get("issue", {}).get("number", "Unknown")
|
195
|
-
issue_state = issue.get("issue", {}).get("state", "Unknown")
|
196
|
-
issue_user = issue.get("issue", {}).get("user", {}).get("login", "Unknown")
|
197
|
-
|
198
|
-
result += f"- Issue #{issue_number}: {issue_title} (by {issue_user}, Status: {issue_state})\n"
|
199
|
-
|
200
|
-
return result
|
201
|
-
elif response.status_code == 404:
|
202
|
-
return f"Repository {repo_full_name} not found"
|
203
|
-
else:
|
204
|
-
logger.error(response.text)
|
205
|
-
return f"Error retrieving issues: {response.status_code} - {response.text}"
|
206
|
-
except NotAuthorizedError as e:
|
207
|
-
return e.message
|
208
|
-
except Exception as e:
|
209
|
-
logger.error(e)
|
210
|
-
return f"Error retrieving issues: {str(e)}"
|
159
|
+
result += f"- Issue #{issue_number}: {issue_title} (by {issue_user}, Status: {issue_state})\n"
|
160
|
+
|
161
|
+
return result
|
211
162
|
|
212
163
|
def get_pull_request(self, repo_full_name: str, pull_number: int) -> str:
|
213
164
|
"""Get a specific pull request for a GitHub repository
|
@@ -219,37 +170,26 @@ class GithubApp(APIApplication):
|
|
219
170
|
Returns:
|
220
171
|
A formatted string with pull request details
|
221
172
|
"""
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
return result
|
243
|
-
elif response.status_code == 404:
|
244
|
-
return f"Pull request #{pull_number} not found in repository {repo_full_name}"
|
245
|
-
else:
|
246
|
-
logger.error(response.text)
|
247
|
-
return f"Error retrieving pull request: {response.status_code} - {response.text}"
|
248
|
-
except NotAuthorizedError as e:
|
249
|
-
return e.message
|
250
|
-
except Exception as e:
|
251
|
-
logger.error(e)
|
252
|
-
return f"Error retrieving pull request: {str(e)}"
|
173
|
+
repo_full_name = repo_full_name.strip()
|
174
|
+
url = f"{self.base_api_url}/{repo_full_name}/pulls/{pull_number}"
|
175
|
+
response = self._get(url)
|
176
|
+
response.raise_for_status()
|
177
|
+
|
178
|
+
pr = response.json()
|
179
|
+
pr_title = pr.get("title", "No Title")
|
180
|
+
pr_number = pr.get("number", "Unknown")
|
181
|
+
pr_state = pr.get("state", "Unknown")
|
182
|
+
pr_user = pr.get("user", {}).get("login", "Unknown")
|
183
|
+
pr_body = pr.get("body", "No description provided.")
|
184
|
+
|
185
|
+
result = (
|
186
|
+
f"Pull Request #{pr_number}: {pr_title}\n"
|
187
|
+
f"Created by: {pr_user}\n"
|
188
|
+
f"Status: {pr_state}\n"
|
189
|
+
f"Description: {pr_body}\n"
|
190
|
+
)
|
191
|
+
|
192
|
+
return result
|
253
193
|
|
254
194
|
def create_pull_request(self, repo_full_name: str, title: str, head: str, base: str, body: str = "",
|
255
195
|
maintainer_can_modify: bool = True, draft: bool = False) -> str:
|
@@ -267,38 +207,29 @@ class GithubApp(APIApplication):
|
|
267
207
|
Returns:
|
268
208
|
A confirmation message with the new pull request details
|
269
209
|
"""
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
f"URL: {pr_url}"
|
294
|
-
else:
|
295
|
-
logger.error(response.text)
|
296
|
-
return f"Error creating pull request: {response.status_code} - {response.text}"
|
297
|
-
except NotAuthorizedError as e:
|
298
|
-
return e.message
|
299
|
-
except Exception as e:
|
300
|
-
logger.error(e)
|
301
|
-
return f"Error creating pull request: {str(e)}"
|
210
|
+
repo_full_name = repo_full_name.strip()
|
211
|
+
url = f"{self.base_api_url}/{repo_full_name}/pulls"
|
212
|
+
|
213
|
+
pull_request_data = {
|
214
|
+
"title": title,
|
215
|
+
"head": head,
|
216
|
+
"base": base,
|
217
|
+
"body": body,
|
218
|
+
"maintainer_can_modify": maintainer_can_modify,
|
219
|
+
"draft": draft
|
220
|
+
}
|
221
|
+
|
222
|
+
response = self._post(url, pull_request_data)
|
223
|
+
response.raise_for_status()
|
224
|
+
|
225
|
+
pr = response.json()
|
226
|
+
pr_number = pr.get("number", "Unknown")
|
227
|
+
pr_url = pr.get("html_url", "")
|
228
|
+
|
229
|
+
return f"Successfully created pull request #{pr_number}:\n" \
|
230
|
+
f"Title: {title}\n" \
|
231
|
+
f"From: {head} → To: {base}\n" \
|
232
|
+
f"URL: {pr_url}"
|
302
233
|
|
303
234
|
def create_issue(self, repo_full_name: str, title: str, body: str = "", labels = None) -> str:
|
304
235
|
"""Create a new issue in a GitHub repository
|
@@ -315,40 +246,31 @@ class GithubApp(APIApplication):
|
|
315
246
|
Returns:
|
316
247
|
A confirmation message with the new issue details
|
317
248
|
"""
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
if labels:
|
328
|
-
|
329
|
-
|
330
|
-
issue_data["labels"] = labels_list
|
331
|
-
else:
|
332
|
-
issue_data["labels"] = labels
|
333
|
-
|
334
|
-
response = self._post(url, issue_data)
|
335
|
-
|
336
|
-
if response.status_code in [200, 201]:
|
337
|
-
issue = response.json()
|
338
|
-
issue_number = issue.get("number", "Unknown")
|
339
|
-
issue_url = issue.get("html_url", "")
|
340
|
-
|
341
|
-
return f"Successfully created issue #{issue_number}:\n" \
|
342
|
-
f"Title: {title}\n" \
|
343
|
-
f"URL: {issue_url}"
|
249
|
+
repo_full_name = repo_full_name.strip()
|
250
|
+
url = f"{self.base_api_url}/{repo_full_name}/issues"
|
251
|
+
|
252
|
+
issue_data = {
|
253
|
+
"title": title,
|
254
|
+
"body": body
|
255
|
+
}
|
256
|
+
|
257
|
+
if labels:
|
258
|
+
if isinstance(labels, str):
|
259
|
+
labels_list = [label.strip() for label in labels.split(",") if label.strip()]
|
260
|
+
issue_data["labels"] = labels_list
|
344
261
|
else:
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
262
|
+
issue_data["labels"] = labels
|
263
|
+
|
264
|
+
response = self._post(url, issue_data)
|
265
|
+
response.raise_for_status()
|
266
|
+
|
267
|
+
issue = response.json()
|
268
|
+
issue_number = issue.get("number", "Unknown")
|
269
|
+
issue_url = issue.get("html_url", "")
|
270
|
+
|
271
|
+
return f"Successfully created issue #{issue_number}:\n" \
|
272
|
+
f"Title: {title}\n" \
|
273
|
+
f"URL: {issue_url}"
|
352
274
|
|
353
275
|
def list_repo_activities(self, repo_full_name: str, direction: str = "desc", per_page: int = 30) -> str:
|
354
276
|
"""List activities for a GitHub repository
|
@@ -361,46 +283,35 @@ class GithubApp(APIApplication):
|
|
361
283
|
Returns:
|
362
284
|
A formatted list of repository activities
|
363
285
|
"""
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
286
|
+
repo_full_name = repo_full_name.strip()
|
287
|
+
url = f"{self.base_api_url}/{repo_full_name}/activity"
|
288
|
+
|
289
|
+
# Build query parameters
|
290
|
+
params = {
|
291
|
+
"direction": direction,
|
292
|
+
"per_page": per_page
|
293
|
+
}
|
294
|
+
|
295
|
+
response = self._get(url, params=params)
|
296
|
+
response.raise_for_status()
|
297
|
+
|
298
|
+
activities = response.json()
|
299
|
+
if not activities:
|
300
|
+
return f"No activities found for repository {repo_full_name}"
|
301
|
+
|
302
|
+
result = f"Repository activities for {repo_full_name}:\n\n"
|
303
|
+
|
304
|
+
for activity in activities:
|
305
|
+
# Extract common fields
|
306
|
+
timestamp = activity.get("timestamp", "Unknown time")
|
307
|
+
actor_name = "Unknown user"
|
308
|
+
if "actor" in activity and activity["actor"]:
|
309
|
+
actor_name = activity["actor"].get("login", "Unknown user")
|
375
310
|
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
result = f"Repository activities for {repo_full_name}:\n\n"
|
382
|
-
|
383
|
-
for activity in activities:
|
384
|
-
# Extract common fields
|
385
|
-
timestamp = activity.get("timestamp", "Unknown time")
|
386
|
-
actor_name = "Unknown user"
|
387
|
-
if "actor" in activity and activity["actor"]:
|
388
|
-
actor_name = activity["actor"].get("login", "Unknown user")
|
389
|
-
|
390
|
-
# Create a simple description of the activity
|
391
|
-
result += f"- {actor_name} performed an activity at {timestamp}\n"
|
392
|
-
|
393
|
-
return result
|
394
|
-
elif response.status_code == 404:
|
395
|
-
return f"Repository {repo_full_name} not found"
|
396
|
-
else:
|
397
|
-
logger.error(response.text)
|
398
|
-
return f"Error retrieving activities: {response.status_code} - {response.text}"
|
399
|
-
except NotAuthorizedError as e:
|
400
|
-
return e.message
|
401
|
-
except Exception as e:
|
402
|
-
logger.error(e)
|
403
|
-
return f"Error retrieving activities: {str(e)}"
|
311
|
+
# Create a simple description of the activity
|
312
|
+
result += f"- {actor_name} performed an activity at {timestamp}\n"
|
313
|
+
|
314
|
+
return result
|
404
315
|
|
405
316
|
def list_tools(self):
|
406
317
|
return [self.star_repository, self.list_commits, self.list_branches,
|