better-notion 2.2.0__py3-none-any.whl → 2.3.2__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.
@@ -769,6 +769,235 @@ class TaskManager:
769
769
  """
770
770
  return await self.find_ready(version_id)
771
771
 
772
+ async def search(
773
+ self,
774
+ query: str,
775
+ status: str | None = None,
776
+ priority: str | None = None,
777
+ assignee: str | None = None,
778
+ project_id: str | None = None,
779
+ version_id: str | None = None,
780
+ limit: int = 50,
781
+ ) -> list[dict]:
782
+ """Search tasks by text query with optional filters.
783
+
784
+ Args:
785
+ query: Text search query
786
+ status: Optional status filter
787
+ priority: Optional priority filter
788
+ assignee: Optional assignee filter
789
+ project_id: Optional project ID filter
790
+ version_id: Optional version ID filter
791
+ limit: Maximum results to return
792
+
793
+ Returns:
794
+ List of search result dictionaries
795
+ """
796
+ from better_notion.plugins.official.agents_sdk.search import TextSearcher
797
+
798
+ database_id = self._get_database_id("Tasks")
799
+ if not database_id:
800
+ return []
801
+
802
+ # Build filters
803
+ filters: list[dict[str, Any]] = []
804
+
805
+ if status:
806
+ filters.append({"property": "Status", "select": {"equals": status}})
807
+
808
+ if priority:
809
+ filters.append({"property": "Priority", "select": {"equals": priority}})
810
+
811
+ if assignee:
812
+ filters.append({"property": "Assignee", "select": {"equals": assignee}})
813
+
814
+ # Combine filters
815
+ filter_dict = None
816
+ if filters:
817
+ if len(filters) == 1:
818
+ filter_dict = filters[0]
819
+ else:
820
+ filter_dict = {"and": filters}
821
+
822
+ searcher = TextSearcher(self._client)
823
+ results = await searcher.search_tasks(query, database_id, filter_dict, limit)
824
+
825
+ # Filter by project/version if specified (post-filter due to relation filtering)
826
+ if project_id or version_id:
827
+ filtered_results = []
828
+ for result in results:
829
+ task = result.entity
830
+ # Filter by version
831
+ if version_id:
832
+ task_version = getattr(task, "version_id", None)
833
+ if task_version != version_id:
834
+ continue
835
+
836
+ # Filter by project
837
+ if project_id:
838
+ version = await task.version() if hasattr(task, "version") else None
839
+ if version:
840
+ project = await version.project() if hasattr(version, "project") else None
841
+ if project and project.id != project_id:
842
+ continue
843
+
844
+ filtered_results.append(result)
845
+
846
+ results = filtered_results
847
+
848
+ return [r.to_dict() for r in results]
849
+
850
+ async def pick(
851
+ self,
852
+ skills: list[str] | None = None,
853
+ max_priority: str | None = None,
854
+ exclude_patterns: list[str] | None = None,
855
+ count: int = 5,
856
+ project_id: str | None = None,
857
+ version_id: str | None = None,
858
+ ) -> list[dict]:
859
+ """Pick the best tasks for an agent to work on.
860
+
861
+ Args:
862
+ skills: List of agent skills (e.g., ["python", "database"])
863
+ max_priority: Maximum priority to consider
864
+ exclude_patterns: Regex patterns to exclude
865
+ count: Maximum number of recommendations
866
+ project_id: Optional project filter
867
+ version_id: Optional version filter
868
+
869
+ Returns:
870
+ List of task recommendation dictionaries
871
+ """
872
+ from better_notion.plugins.official.agents_sdk.agent import TaskSelector
873
+
874
+ selector = TaskSelector(self._client)
875
+ recommendations = await selector.pick_best_tasks(
876
+ skills=skills,
877
+ max_priority=max_priority,
878
+ exclude_patterns=exclude_patterns,
879
+ count=count,
880
+ project_id=project_id,
881
+ version_id=version_id,
882
+ )
883
+
884
+ return [r.to_dict() for r in recommendations]
885
+
886
+ async def suggest(
887
+ self,
888
+ unassigned: bool = False,
889
+ priority: list[str] | None = None,
890
+ count: int = 5,
891
+ ) -> list[dict]:
892
+ """Suggest tasks based on criteria.
893
+
894
+ Args:
895
+ unassigned: Only suggest unassigned tasks
896
+ priority: List of priority levels to include
897
+ count: Maximum number of suggestions
898
+
899
+ Returns:
900
+ List of task dictionaries
901
+ """
902
+ # Get candidate tasks
903
+ filters = []
904
+
905
+ if unassigned:
906
+ filters.append({"property": "Assignee", "select": {"is_empty": True}})
907
+
908
+ if priority:
909
+ or_filter = {"or": [{"property": "Priority", "select": {"equals": p}} for p in priority]}
910
+ filters.append(or_filter)
911
+
912
+ # Query with filters
913
+ database_id = self._get_database_id("Tasks")
914
+ if not database_id:
915
+ return []
916
+
917
+ query_obj: dict[str, Any] = {}
918
+ if filters:
919
+ if len(filters) == 1:
920
+ query_obj["filter"] = filters[0]
921
+ else:
922
+ query_obj["filter"] = {"and": filters}
923
+
924
+ from better_notion.plugins.official.agents_sdk.models import Task
925
+
926
+ response = await self._client._api._request(
927
+ "POST",
928
+ f"/databases/{database_id}/query",
929
+ json=query_obj,
930
+ )
931
+
932
+ tasks = [Task(self._client, page_data) for page_data in response.get("results", [])]
933
+
934
+ # Sort by priority (Critical > High > Medium > Low)
935
+ priority_order = {"Critical": 0, "High": 1, "Medium": 2, "Low": 3}
936
+ tasks.sort(key=lambda t: priority_order.get(getattr(t, "priority", "Low"), 3))
937
+
938
+ # Get top N and convert to dicts
939
+ suggestions = []
940
+ for task in tasks[:count]:
941
+ suggestions.append({
942
+ "id": task.id,
943
+ "title": task.title,
944
+ "priority": getattr(task, "priority", None),
945
+ "status": getattr(task, "status", None),
946
+ "assignee": getattr(task, "assignee", None),
947
+ "type": getattr(task, "task_type", None),
948
+ })
949
+
950
+ return suggestions
951
+
952
+ async def claim(self, task_id: str) -> dict:
953
+ """Claim a task (set status to In Progress).
954
+
955
+ Args:
956
+ task_id: Task ID to claim
957
+
958
+ Returns:
959
+ Dict with task_id and new status
960
+ """
961
+ from better_notion.plugins.official.agents_sdk.models import Task
962
+
963
+ task = await Task.get(task_id, client=self._client)
964
+ await task.update(status="In Progress")
965
+
966
+ return {
967
+ "task_id": task_id,
968
+ "status": "In Progress",
969
+ }
970
+
971
+ async def start(self, task_id: str) -> dict:
972
+ """Start a task (alias for claim).
973
+
974
+ Args:
975
+ task_id: Task ID to start
976
+
977
+ Returns:
978
+ Dict with task_id and new status
979
+ """
980
+ return await self.claim(task_id)
981
+
982
+ async def complete(self, task_id: str) -> dict:
983
+ """Complete a task (set status to Completed).
984
+
985
+ Args:
986
+ task_id: Task ID to complete
987
+
988
+ Returns:
989
+ Dict with task_id and new status
990
+ """
991
+ from better_notion.plugins.official.agents_sdk.models import Task
992
+
993
+ task = await Task.get(task_id, client=self._client)
994
+ await task.update(status="Completed")
995
+
996
+ return {
997
+ "task_id": task_id,
998
+ "status": "Completed",
999
+ }
1000
+
772
1001
  def _get_database_id(self, name: str) -> str | None:
773
1002
  """Get database ID from workspace config."""
774
1003
  return getattr(self._client, "_workspace_config", {}).get(name)
@@ -890,6 +1119,57 @@ class IdeaManager:
890
1119
  ideas = await self.list(status="Accepted")
891
1120
  return [idea for idea in ideas if not idea.related_task_id]
892
1121
 
1122
+ async def search(
1123
+ self,
1124
+ query: str,
1125
+ status: str | None = None,
1126
+ category: str | None = None,
1127
+ project_id: str | None = None,
1128
+ limit: int = 50,
1129
+ ) -> list[dict]:
1130
+ """Search ideas by text query with optional filters.
1131
+
1132
+ Args:
1133
+ query: Text search query
1134
+ status: Optional status filter
1135
+ category: Optional category filter
1136
+ project_id: Optional project ID filter
1137
+ limit: Maximum results to return
1138
+
1139
+ Returns:
1140
+ List of search result dictionaries
1141
+ """
1142
+ from better_notion.plugins.official.agents_sdk.search import TextSearcher
1143
+
1144
+ database_id = self._get_database_id("Ideas")
1145
+ if not database_id:
1146
+ return []
1147
+
1148
+ # Build filters
1149
+ filters: list[dict[str, Any]] = []
1150
+
1151
+ if status:
1152
+ filters.append({"property": "Status", "select": {"equals": status}})
1153
+
1154
+ if category:
1155
+ filters.append({"property": "Category", "select": {"equals": category}})
1156
+
1157
+ if project_id:
1158
+ filters.append({"property": "Project", "relation": {"contains": project_id}})
1159
+
1160
+ # Combine filters
1161
+ filter_dict = None
1162
+ if filters:
1163
+ if len(filters) == 1:
1164
+ filter_dict = filters[0]
1165
+ else:
1166
+ filter_dict = {"and": filters}
1167
+
1168
+ searcher = TextSearcher(self._client)
1169
+ results = await searcher.search_ideas(query, database_id, filter_dict, limit)
1170
+
1171
+ return [r.to_dict() for r in results]
1172
+
893
1173
  def _get_database_id(self, name: str) -> str | None:
894
1174
  """Get database ID from workspace config."""
895
1175
  return getattr(self._client, "_workspace_config", {}).get(name)
@@ -1105,6 +1385,62 @@ class WorkIssueManager:
1105
1385
  for page_data in response.get("results", [])
1106
1386
  ]
1107
1387
 
1388
+ async def search(
1389
+ self,
1390
+ query: str,
1391
+ status: str | None = None,
1392
+ type_: str | None = None,
1393
+ severity: str | None = None,
1394
+ project_id: str | None = None,
1395
+ limit: int = 50,
1396
+ ) -> list[dict]:
1397
+ """Search work issues by text query with optional filters.
1398
+
1399
+ Args:
1400
+ query: Text search query
1401
+ status: Optional status filter
1402
+ type_: Optional type filter
1403
+ severity: Optional severity filter
1404
+ project_id: Optional project ID filter
1405
+ limit: Maximum results to return
1406
+
1407
+ Returns:
1408
+ List of search result dictionaries
1409
+ """
1410
+ from better_notion.plugins.official.agents_sdk.search import TextSearcher
1411
+
1412
+ database_id = self._get_database_id("Work_issues")
1413
+ if not database_id:
1414
+ return []
1415
+
1416
+ # Build filters
1417
+ filters: list[dict[str, Any]] = []
1418
+
1419
+ if status:
1420
+ filters.append({"property": "Status", "select": {"equals": status}})
1421
+
1422
+ if type_:
1423
+ filters.append({"property": "Type", "select": {"equals": type_}})
1424
+
1425
+ if severity:
1426
+ filters.append({"property": "Severity", "select": {"equals": severity}})
1427
+
1428
+ if project_id:
1429
+ filters.append({"property": "Project", "relation": {"contains": project_id}})
1430
+
1431
+ # Combine filters
1432
+ filter_dict = None
1433
+ if filters:
1434
+ if len(filters) == 1:
1435
+ filter_dict = filters[0]
1436
+ else:
1437
+ filter_dict = {"and": filters}
1438
+
1439
+ searcher = TextSearcher(self._client)
1440
+ results = await searcher.search_work_issues(query, database_id, filter_dict, limit)
1441
+
1442
+ return [r.to_dict() for r in results]
1443
+
1108
1444
  def _get_database_id(self, name: str) -> str | None:
1109
1445
  """Get database ID from workspace config."""
1110
1446
  return getattr(self._client, "_workspace_config", {}).get(name)
@@ -1198,6 +1534,79 @@ class IncidentManager:
1198
1534
 
1199
1535
  return incidents
1200
1536
 
1537
+ async def search(
1538
+ self,
1539
+ query: str,
1540
+ status: str | None = None,
1541
+ severity: str | None = None,
1542
+ incident_type: str | None = None,
1543
+ project_id: str | None = None,
1544
+ limit: int = 50,
1545
+ ) -> list[dict]:
1546
+ """Search incidents by text query with optional filters.
1547
+
1548
+ Args:
1549
+ query: Text search query
1550
+ status: Optional status filter
1551
+ severity: Optional severity filter
1552
+ incident_type: Optional incident type filter
1553
+ project_id: Optional project ID filter
1554
+ limit: Maximum results to return
1555
+
1556
+ Returns:
1557
+ List of search result dictionaries
1558
+ """
1559
+ from better_notion.plugins.official.agents_sdk.search import TextSearcher
1560
+
1561
+ database_id = self._get_database_id("Incidents")
1562
+ if not database_id:
1563
+ return []
1564
+
1565
+ # Build filters
1566
+ filters: list[dict[str, Any]] = []
1567
+
1568
+ if status:
1569
+ filters.append({"property": "Status", "select": {"equals": status}})
1570
+
1571
+ if severity:
1572
+ filters.append({"property": "Severity", "select": {"equals": severity}})
1573
+
1574
+ if incident_type:
1575
+ filters.append({"property": "Type", "select": {"equals": incident_type}})
1576
+
1577
+ if project_id:
1578
+ filters.append({"property": "Project", "relation": {"contains": project_id}})
1579
+
1580
+ # Combine filters
1581
+ filter_dict = None
1582
+ if filters:
1583
+ if len(filters) == 1:
1584
+ filter_dict = filters[0]
1585
+ else:
1586
+ filter_dict = {"and": filters}
1587
+
1588
+ searcher = TextSearcher(self._client)
1589
+ results = await searcher.search_incidents(query, database_id, filter_dict, limit)
1590
+
1591
+ return [r.to_dict() for r in results]
1592
+
1593
+ async def triage(self, incident_id: str) -> dict:
1594
+ """Triage and classify an incident.
1595
+
1596
+ Args:
1597
+ incident_id: Incident ID to triage
1598
+
1599
+ Returns:
1600
+ Dictionary with triage results including severity, type, and suggested assignment
1601
+ """
1602
+ from better_notion.plugins.official.agents_sdk.agent import IncidentTriager
1603
+ from better_notion.plugins.official.agents_sdk.models import Incident
1604
+
1605
+ incident = await Incident.get(incident_id, client=self._client)
1606
+ triager = IncidentTriager(self._client)
1607
+
1608
+ return await triager.triage_incident(incident)
1609
+
1201
1610
  async def find_sla_violations(self) -> list:
1202
1611
  """
1203
1612
  Find all incidents that violated SLA.