codexapi 0.6.6__tar.gz → 0.6.7__tar.gz

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 (25) hide show
  1. {codexapi-0.6.6/src/codexapi.egg-info → codexapi-0.6.7}/PKG-INFO +6 -1
  2. {codexapi-0.6.6 → codexapi-0.6.7}/README.md +5 -0
  3. {codexapi-0.6.6 → codexapi-0.6.7}/pyproject.toml +1 -1
  4. {codexapi-0.6.6 → codexapi-0.6.7}/src/codexapi/__init__.py +1 -1
  5. {codexapi-0.6.6 → codexapi-0.6.7}/src/codexapi/cli.py +18 -0
  6. {codexapi-0.6.6 → codexapi-0.6.7}/src/codexapi/gh_integration.py +31 -1
  7. {codexapi-0.6.6 → codexapi-0.6.7/src/codexapi.egg-info}/PKG-INFO +6 -1
  8. {codexapi-0.6.6 → codexapi-0.6.7}/LICENSE +0 -0
  9. {codexapi-0.6.6 → codexapi-0.6.7}/setup.cfg +0 -0
  10. {codexapi-0.6.6 → codexapi-0.6.7}/src/codexapi/__main__.py +0 -0
  11. {codexapi-0.6.6 → codexapi-0.6.7}/src/codexapi/agent.py +0 -0
  12. {codexapi-0.6.6 → codexapi-0.6.7}/src/codexapi/foreach.py +0 -0
  13. {codexapi-0.6.6 → codexapi-0.6.7}/src/codexapi/pushover.py +0 -0
  14. {codexapi-0.6.6 → codexapi-0.6.7}/src/codexapi/ralph.py +0 -0
  15. {codexapi-0.6.6 → codexapi-0.6.7}/src/codexapi/rate_limits.py +0 -0
  16. {codexapi-0.6.6 → codexapi-0.6.7}/src/codexapi/science.py +0 -0
  17. {codexapi-0.6.6 → codexapi-0.6.7}/src/codexapi/task.py +0 -0
  18. {codexapi-0.6.6 → codexapi-0.6.7}/src/codexapi/taskfile.py +0 -0
  19. {codexapi-0.6.6 → codexapi-0.6.7}/src/codexapi/watch.py +0 -0
  20. {codexapi-0.6.6 → codexapi-0.6.7}/src/codexapi/welfare.py +0 -0
  21. {codexapi-0.6.6 → codexapi-0.6.7}/src/codexapi.egg-info/SOURCES.txt +0 -0
  22. {codexapi-0.6.6 → codexapi-0.6.7}/src/codexapi.egg-info/dependency_links.txt +0 -0
  23. {codexapi-0.6.6 → codexapi-0.6.7}/src/codexapi.egg-info/entry_points.txt +0 -0
  24. {codexapi-0.6.6 → codexapi-0.6.7}/src/codexapi.egg-info/requires.txt +0 -0
  25. {codexapi-0.6.6 → codexapi-0.6.7}/src/codexapi.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: codexapi
3
- Version: 0.6.6
3
+ Version: 0.6.7
4
4
  Summary: Minimal Python API for running the Codex CLI.
5
5
  License: MIT
6
6
  Keywords: codex,agent,cli,openai
@@ -94,6 +94,11 @@ Take tasks from a GitHub Project (requires `gh-task`):
94
94
  ```bash
95
95
  codexapi task -p owner/projects/3 -n "Your Name" -s Ready task_a.yaml task_b.yaml
96
96
  ```
97
+ Filter project issues by title before taking them:
98
+
99
+ ```bash
100
+ codexapi task -p owner/projects/3 -n "Your Name" --only-matching "/n300/" task_a.yaml task_b.yaml
101
+ ```
97
102
  Reset owned tasks on a GitHub Project back to Ready:
98
103
 
99
104
  ```bash
@@ -79,6 +79,11 @@ Take tasks from a GitHub Project (requires `gh-task`):
79
79
  ```bash
80
80
  codexapi task -p owner/projects/3 -n "Your Name" -s Ready task_a.yaml task_b.yaml
81
81
  ```
82
+ Filter project issues by title before taking them:
83
+
84
+ ```bash
85
+ codexapi task -p owner/projects/3 -n "Your Name" --only-matching "/n300/" task_a.yaml task_b.yaml
86
+ ```
82
87
  Reset owned tasks on a GitHub Project back to Ready:
83
88
 
84
89
  ```bash
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "codexapi"
7
- version = "0.6.6"
7
+ version = "0.6.7"
8
8
  description = "Minimal Python API for running the Codex CLI."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -27,4 +27,4 @@ __all__ = [
27
27
  "task_result",
28
28
  "watch",
29
29
  ]
30
- __version__ = "0.6.6"
30
+ __version__ = "0.6.7"
@@ -1106,6 +1106,13 @@ def main(argv=None):
1106
1106
  "--name",
1107
1107
  help="Owner label name for gh-task when using --project.",
1108
1108
  )
1109
+ task_parser.add_argument(
1110
+ "--only-matching",
1111
+ help=(
1112
+ "When using --project, only take issues whose title matches this regex. "
1113
+ "Useful for filtering tasks by hardware encoded in the issue title/path."
1114
+ ),
1115
+ )
1109
1116
  task_parser.add_argument(
1110
1117
  "task_args",
1111
1118
  nargs="*",
@@ -1419,6 +1426,11 @@ def main(argv=None):
1419
1426
  raise SystemExit("--name is required with --project.")
1420
1427
  if not args.task_args:
1421
1428
  raise SystemExit("task --project requires one or more task files.")
1429
+ if args.only_matching is not None:
1430
+ try:
1431
+ re.compile(args.only_matching)
1432
+ except re.error as exc:
1433
+ raise SystemExit(f"--only-matching regex is invalid: {exc}") from None
1422
1434
  from .gh_integration import GhTaskRunner, project_url
1423
1435
  from gh_task.errors import TakeError
1424
1436
 
@@ -1430,6 +1442,7 @@ def main(argv=None):
1430
1442
  args.name,
1431
1443
  args.task_args,
1432
1444
  args.status,
1445
+ args.only_matching,
1433
1446
  args.cwd,
1434
1447
  args.yolo,
1435
1448
  args.flags,
@@ -1457,6 +1470,7 @@ def main(argv=None):
1457
1470
  args.name,
1458
1471
  args.task_args,
1459
1472
  args.status,
1473
+ args.only_matching,
1460
1474
  args.cwd,
1461
1475
  args.yolo,
1462
1476
  args.flags,
@@ -1482,6 +1496,8 @@ def main(argv=None):
1482
1496
  raise SystemExit(
1483
1497
  "task -f --item requires {{item}} in the task file."
1484
1498
  )
1499
+ if args.only_matching is not None:
1500
+ raise SystemExit("--only-matching is only supported with --project.")
1485
1501
  if args.check is not None:
1486
1502
  raise SystemExit("--check is not allowed with -f.")
1487
1503
  if args.max_iterations is not None:
@@ -1553,6 +1569,8 @@ def main(argv=None):
1553
1569
  raise SystemExit("--loop is only supported with -p.")
1554
1570
  if args.item is not None:
1555
1571
  raise SystemExit("--item is only supported with -f.")
1572
+ if args.only_matching is not None:
1573
+ raise SystemExit("--only-matching is only supported with --project.")
1556
1574
  if args.max_iterations is None:
1557
1575
  args.max_iterations = DEFAULT_MAX_ITERATIONS
1558
1576
  if args.max_iterations < 0:
@@ -5,6 +5,7 @@ from pathlib import Path
5
5
 
6
6
  from tqdm import tqdm
7
7
 
8
+ from gh_task.errors import TakeError
8
9
  from gh_task.project import Project, UPDATE_STATUS_MUTATION
9
10
 
10
11
  from .taskfile import TaskFile
@@ -138,6 +139,34 @@ def _match_task_file(issue, task_map):
138
139
  return matches[0][1]
139
140
 
140
141
 
142
+ def _take_matching_issue(project, status, only_matching):
143
+ """Take the first available issue whose title matches only_matching.
144
+
145
+ only_matching is a regular expression. When unset/empty, this behaves like
146
+ Project.take(status=...).
147
+ """
148
+ if not only_matching:
149
+ return project.take(status=status, return_issue=True)
150
+ try:
151
+ pattern = re.compile(only_matching)
152
+ except re.error as exc:
153
+ raise ValueError(f"Invalid only-matching regex {only_matching!r}: {exc}") from exc
154
+ status_name = project._resolve_status_name(status)
155
+ # Filter by title before fetching labels so we don't spam GitHub REST calls for
156
+ # obviously unsupported issues.
157
+ for issue in project._list_items():
158
+ if (issue.status or "").lower() != status_name.lower():
159
+ continue
160
+ title = issue.title or ""
161
+ if not pattern.search(title):
162
+ continue
163
+ if not project._issue_matches_label(issue):
164
+ continue
165
+ if project._try_take(issue, wait_seconds=1.0, strict=False):
166
+ return issue
167
+ raise TakeError(f"No available issues to take in status '{status_name}' matching {only_matching!r}")
168
+
169
+
141
170
  def _strip_progress_section(body):
142
171
  if not body:
143
172
  return ""
@@ -275,13 +304,14 @@ class GhTaskRunner:
275
304
  name,
276
305
  task_files,
277
306
  status="Ready",
307
+ only_matching=None,
278
308
  cwd=None,
279
309
  yolo=True,
280
310
  flags=None,
281
311
  ):
282
312
  task_map = _task_file_map(task_files)
283
313
  self.project = Project(project, name, has_label=list(task_map))
284
- self.issue = self.project.take(status=status, return_issue=True)
314
+ self.issue = _take_matching_issue(self.project, status, only_matching)
285
315
  self.issue = self.project.get_issue(self.issue)
286
316
  try:
287
317
  task_path = _match_task_file(self.issue, task_map)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: codexapi
3
- Version: 0.6.6
3
+ Version: 0.6.7
4
4
  Summary: Minimal Python API for running the Codex CLI.
5
5
  License: MIT
6
6
  Keywords: codex,agent,cli,openai
@@ -94,6 +94,11 @@ Take tasks from a GitHub Project (requires `gh-task`):
94
94
  ```bash
95
95
  codexapi task -p owner/projects/3 -n "Your Name" -s Ready task_a.yaml task_b.yaml
96
96
  ```
97
+ Filter project issues by title before taking them:
98
+
99
+ ```bash
100
+ codexapi task -p owner/projects/3 -n "Your Name" --only-matching "/n300/" task_a.yaml task_b.yaml
101
+ ```
97
102
  Reset owned tasks on a GitHub Project back to Ready:
98
103
 
99
104
  ```bash
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes