Skip to content

I gotta have more tools#127

Open
marcelklehr wants to merge 16 commits intomainfrom
feat/moartools
Open

I gotta have more tools#127
marcelklehr wants to merge 16 commits intomainfrom
feat/moartools

Conversation

@marcelklehr
Copy link
Member

@marcelklehr marcelklehr commented Mar 11, 2026

@janepie maybe we can share reviewing and testing these together?

Reviewed and tested works:

  • bookmarks: Auth doesn't work, likely due to how the app handles auth
  • tasks: Works
  • circles: Works, although deleting members and circles doesn't seem to work for me on stable33
  • cookbook: Mostly works. Updating recipes doesn't work, though
  • deck
  • files
  • forms
  • mail: broken, list_mail_folders uses a nonexistent route and everything else depends on that
  • music
  • news
  • notes
  • passwords
  • polls
  • sharing

Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR significantly expands the available “all_tools” integrations by adding new tool modules for multiple Nextcloud apps (e.g., Shares, Polls, Photos, Passwords, Notes, News, Music, Forms, Bookmarks, Circles, Cookbook) and extending existing tool sets (Files, Deck, Calendar, Mail).

Changes:

  • Add many new tool modules exposing Nextcloud app APIs via @tool functions (safe/dangerous).
  • Extend existing tools for Files (WebDAV ops + tags), Deck (card operations), Calendar (task operations), and Mail (folder/message helpers).
  • Introduce new per-app availability gating via is_available for most new modules.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
ex_app/lib/all_tools/shares.py New sharing + groups tools (list/create/update/delete shares, group info)
ex_app/lib/all_tools/polls.py New Polls tools (list, details, create/options/votes, close/reopen)
ex_app/lib/all_tools/photos.py New Photos tools (albums CRUD, album membership changes, date search)
ex_app/lib/all_tools/passwords.py New Passwords tools (folders, search/list, CRUD incl. secret retrieval)
ex_app/lib/all_tools/notes.py New Notes tools (list/get/create/update/delete + client-side search)
ex_app/lib/all_tools/news.py New News/RSS tools (feeds/folders/items + mark read/unread)
ex_app/lib/all_tools/music.py New Music tools (library browsing/search + playlist CRUD)
ex_app/lib/all_tools/forms.py New Forms tools (list/details/create/questions/responses/settings)
ex_app/lib/all_tools/bookmarks.py New Bookmarks tools (list/search + bookmark/folder/tag management)
ex_app/lib/all_tools/circles.py New Circles/Teams tools (CRUD circles + membership + share to circle)
ex_app/lib/all_tools/cookbook.py New Cookbook tools (recipes CRUD + categories + search)
ex_app/lib/all_tools/files.py Adds WebDAV file ops + file tagging tools
ex_app/lib/all_tools/deck.py Expands Deck tooling for card lifecycle, labels, assignments, comments
ex_app/lib/all_tools/calendar.py Adds task list/complete/update/delete tools (CalDAV todos)
ex_app/lib/all_tools/mail.py Adds folder listing, message listing/search, move/delete helpers

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +47 to +53
async def share_with_group(path: str, share_with: str, permissions: int = 19):
"""
Share a file or folder with a group
:param path: the path of the file or folder to share
:param share_with: the group name to share with
:param permissions: permissions bitmask - 1=read, 2=update, 4=create, 8=delete, 16=share. Default is 19 (read+update+delete+share)
:return: the created share
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same permissions doc issue as share_with_user: the docstring states permissions=19 includes delete, but it does not. Please align the default value and the explanation of the bitmask.

Copilot uses AI. Check for mistakes.
:param is_required: whether the question is required
:param options: list of options for multiple choice, dropdown, etc. (required for multiple/dropdown types)
:return: the created question
"""
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add_question_to_form's docstring states options is required for multiple-choice/dropdown question types, but the implementation doesn't validate this (it will create the question and silently skip option creation when options is None/empty). Consider validating inputs and raising a clear error when question_type requires options but none are provided, to avoid creating invalid/unusable questions.

Suggested change
"""
"""
# Validate that options are provided for question types that require them
if question_type in ['multiple', 'multiple_unique', 'dropdown'] and not options:
raise ValueError(
"Parameter 'options' must be a non-empty list when 'question_type' is one of "
"'multiple', 'multiple_unique', or 'dropdown'."
)

Copilot uses AI. Check for mistakes.
Comment on lines +31 to +51
response = await nc._session._create_adapter(True).request('GET', f"{nc.app_cfg.endpoint}/index.php/apps/passwords/api/1.0/password/list", headers={
"Content-Type": "application/json",
})
all_passwords = response.json()

# Filter passwords by search term in label or url
matching = [
{
'id': p.get('id'),
'label': p.get('label'),
'username': p.get('username'),
'url': p.get('url'),
'folder': p.get('folder'),
'tags': p.get('tags', [])
}
for p in all_passwords
if search_term.lower() in p.get('label', '').lower()
or search_term.lower() in p.get('url', '').lower()
]

return matching
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

search_passwords fetches the full password list from the server and then filters locally. This can become slow and memory-heavy for users with many entries. If the Passwords API supports server-side filtering/search or pagination, prefer that; otherwise consider adding an optional limit (and/or folder filter) and stop filtering after collecting enough matches.

Copilot uses AI. Check for mistakes.
Comment on lines +84 to +95
async def search_notes(search_term: str):
"""
Search for notes containing a specific term
:param search_term: the text to search for in note titles and content
:return: list of matching notes
"""
all_notes = await nc.ocs('GET', '/ocs/v2.php/apps/notes/api/v1/notes')
matching_notes = [
note for note in all_notes
if search_term.lower() in note.get('title', '').lower()
or search_term.lower() in note.get('content', '').lower()
]
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

search_notes loads all notes and filters client-side, which can be expensive for large note collections. If the Notes API provides a search endpoint or supports query parameters/pagination, prefer that; otherwise consider adding an optional limit and returning early once enough matches are collected.

Suggested change
async def search_notes(search_term: str):
"""
Search for notes containing a specific term
:param search_term: the text to search for in note titles and content
:return: list of matching notes
"""
all_notes = await nc.ocs('GET', '/ocs/v2.php/apps/notes/api/v1/notes')
matching_notes = [
note for note in all_notes
if search_term.lower() in note.get('title', '').lower()
or search_term.lower() in note.get('content', '').lower()
]
async def search_notes(search_term: str, limit: Optional[int] = None):
"""
Search for notes containing a specific term
:param search_term: the text to search for in note titles and content
:param limit: optional maximum number of matching notes to return
:return: list of matching notes
"""
all_notes = await nc.ocs('GET', '/ocs/v2.php/apps/notes/api/v1/notes')
matching_notes = []
lowered_term = search_term.lower()
for note in all_notes:
title = note.get('title', '')
content = note.get('content', '')
if lowered_term in title.lower() or lowered_term in content.lower():
matching_notes.append(note)
if limit is not None and len(matching_notes) >= limit:
break

Copilot uses AI. Check for mistakes.
Comment on lines +44 to +56
params = {
'batchSize': limit,
'type': 3, # 3 = all items
'getRead': 'false'
}
if feed_id:
params['id'] = feed_id

response = await nc._session._create_adapter(True).request('GET', f"{nc.app_cfg.endpoint}/index.php/apps/news/api/v1-3/items", headers={
"Content-Type": "application/json",
}, params=params)
return response.json()

Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_unread_articles largely duplicates the logic in get_articles (same endpoint/params structure, only differing by unread filter). To reduce duplication and keep behavior consistent, consider removing get_unread_articles and using get_articles(unread_only=True) (or implement get_unread_articles as a thin wrapper around get_articles).

Suggested change
params = {
'batchSize': limit,
'type': 3, # 3 = all items
'getRead': 'false'
}
if feed_id:
params['id'] = feed_id
response = await nc._session._create_adapter(True).request('GET', f"{nc.app_cfg.endpoint}/index.php/apps/news/api/v1-3/items", headers={
"Content-Type": "application/json",
}, params=params)
return response.json()
return await get_articles(limit=limit, feed_id=feed_id, unread_only=True)

Copilot uses AI. Check for mistakes.
Comment on lines +30 to +36
async def share_with_user(path: str, share_with: str, permissions: int = 19):
"""
Share a file or folder with a user
:param path: the path of the file or folder to share
:param share_with: the user id to share with
:param permissions: permissions bitmask - 1=read, 2=update, 4=create, 8=delete, 16=share. Default is 19 (read+update+delete+share)
:return: the created share
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docstring says default permissions=19 represents "read+update+delete+share", but 19 does not include the delete bit (8). Either update the default to include delete (e.g., 27) or fix the docstring to match the actual default permissions.

Copilot uses AI. Check for mistakes.
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants