Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ gh-llm issue view 77924 --repo PaddlePaddle/Paddle --expand minimized,details
gh-llm issue view 77924 --repo PaddlePaddle/Paddle --show meta,description
```

When `--show` does not include `timeline` (for example `--show meta`, `--show summary`, or `--show actions`), both `pr view` and `issue view` stay on the lightweight metadata path and skip timeline bootstrap.

`--expand` values:

- PR: `resolved`, `minimized`, `details`, `all`
Expand Down
2 changes: 2 additions & 0 deletions skills/github-conversation/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ gh-llm issue view <issue> --repo <owner/repo>
gh-llm issue timeline-expand <page> --issue <issue> --repo <owner/repo>
```

For lightweight inspection, prefer non-timeline `--show` combinations such as `--show meta`, `--show summary`, or `--show actions`; `gh-llm` keeps those paths on metadata-only loading unless `timeline` is explicitly requested.

### Write simple updates

```bash
Expand Down
78 changes: 49 additions & 29 deletions src/gh_llm/commands/issue.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,15 @@
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any

from gh_llm.commands.options import raise_unknown_option_value, resolve_file_or_inline_text
from gh_llm.commands.options import (
add_body_input_arguments,
maybe_resolve_subject,
raise_unknown_option_value,
resolve_file_or_inline_text,
resolve_subject,
)
from gh_llm.github_api import GitHubClient
from gh_llm.pager import DEFAULT_PAGE_SIZE, TimelinePager
from gh_llm.pager import DEFAULT_PAGE_SIZE, TimelinePager, build_context_from_meta
from gh_llm.render import (
render_comment_node_detail,
render_description,
Expand Down Expand Up @@ -86,12 +92,11 @@ def register_issue_parser(subparsers: Any) -> None:

comment_edit_parser = issue_subparsers.add_parser("comment-edit", help="edit one issue comment by node id")
comment_edit_parser.add_argument("comment_id", help="comment id, e.g. IC_xxx")
comment_edit_body_group = comment_edit_parser.add_mutually_exclusive_group(required=True)
comment_edit_body_group.add_argument("--body", help="new comment body")
comment_edit_body_group.add_argument(
"-F",
"--body-file",
help="read new comment body from file (use `-` to read from standard input)",
add_body_input_arguments(
comment_edit_parser,
required=True,
body_help="new comment body",
file_help="read new comment body from file (use `-` to read from standard input)",
)
comment_edit_parser.add_argument("--issue", help="Issue number/url")
comment_edit_parser.add_argument("--repo", help="repository in OWNER/REPO format")
Expand All @@ -111,14 +116,20 @@ def cmd_issue_view(args: Any) -> int:
client = GitHubClient()
pager = TimelinePager(client)

meta = client.resolve_issue(selector=args.issue, repo=args.repo)
context, first_page, last_page = pager.build_initial(
meta,
page_size=page_size,
show_minimized_details=expand.minimized,
show_details_blocks=expand.details,
)
shown_pages: set[int] = {1}
meta = _resolve_issue_meta(client=client, args=args)
context = build_context_from_meta(meta=meta, page_size=page_size)
first_page: TimelinePage | None = None
last_page: TimelinePage | None = None
shown_pages: set[int] = set()

if show.timeline:
context, first_page, last_page = pager.build_initial(
meta,
page_size=page_size,
show_minimized_details=expand.minimized,
show_details_blocks=expand.details,
)
shown_pages.add(1)

wrote_output = False

Expand All @@ -137,6 +148,7 @@ def print_block(lines: list[str]) -> None:
if show.description:
print_block(render_description(context))
if show.timeline:
assert first_page is not None
print_block(["## Timeline"])
print_block(render_page(1, context, first_page))

Expand Down Expand Up @@ -232,10 +244,7 @@ def cmd_issue_details_expand(args: Any) -> int:

def cmd_issue_comment_edit(args: Any) -> int:
client = GitHubClient()
if args.repo is not None and args.issue is None:
raise RuntimeError("`--issue` is required when `--repo` is provided")
if args.issue is not None:
client.resolve_issue(selector=args.issue, repo=args.repo)
_resolve_optional_issue(client=client, args=args)
updated_comment_id = client.edit_comment(
comment_id=str(args.comment_id),
body=resolve_file_or_inline_text(args, text_attr="body", file_attr="body_file"),
Expand All @@ -247,26 +256,37 @@ def cmd_issue_comment_edit(args: Any) -> int:

def cmd_issue_comment_expand(args: Any) -> int:
client = GitHubClient()
if args.repo is not None and args.issue is None:
raise RuntimeError("`--issue` is required when `--repo` is provided")
if args.issue is not None:
client.resolve_issue(selector=args.issue, repo=args.repo)
_resolve_optional_issue(client=client, args=args)
node = client.fetch_comment_node(str(args.comment_id))
for line in render_comment_node_detail(str(args.comment_id), node):
print(line)
return 0


def _resolve_issue_meta(*, client: GitHubClient, args: Any) -> PullRequestMeta:
return resolve_subject(
selector=getattr(args, "issue", None),
repo=getattr(args, "repo", None),
selector_flag="--issue",
resolver=client.resolve_issue,
)


def _resolve_optional_issue(*, client: GitHubClient, args: Any) -> PullRequestMeta | None:
return maybe_resolve_subject(
selector=getattr(args, "issue", None),
repo=getattr(args, "repo", None),
selector_flag="--issue",
resolver=client.resolve_issue,
)


def _resolve_context_and_meta(
*, client: GitHubClient, pager: TimelinePager, args: Any
) -> tuple[TimelineContext, PullRequestMeta]:
selector = getattr(args, "issue", None)
repo = getattr(args, "repo", None)
if repo is not None and selector is None:
raise RuntimeError("`--issue` is required when `--repo` is provided")
page_size = getattr(args, "page_size", None)
effective_page_size = DEFAULT_PAGE_SIZE if page_size is None else int(page_size)
meta = client.resolve_issue(selector=selector, repo=repo)
meta = _resolve_issue_meta(client=client, args=args)
context, _, _ = pager.build_initial(meta=meta, page_size=effective_page_size)
return context, meta

Expand Down
54 changes: 53 additions & 1 deletion src/gh_llm/commands/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,30 @@
import sys
from difflib import get_close_matches
from pathlib import Path
from typing import Any, NoReturn
from typing import TYPE_CHECKING, Any, NoReturn

if TYPE_CHECKING:
from collections.abc import Callable


def add_body_input_arguments(
parser: Any,
*,
required: bool,
body_help: str,
file_help: str,
default: str | None = None,
) -> None:
group = parser.add_mutually_exclusive_group(required=required)
body_kwargs: dict[str, object] = {"help": body_help}
if default is not None:
body_kwargs["default"] = default
group.add_argument("--body", **body_kwargs)
group.add_argument(
"-F",
"--body-file",
help=file_help,
)


def read_text_from_path_or_stdin(path: str) -> str:
Expand All @@ -28,6 +51,35 @@ def resolve_file_or_inline_text(
return str(text)


def resolve_subject[T](
*,
selector: str | None,
repo: str | None,
selector_flag: str,
resolver: Callable[[str | None, str | None], T],
) -> T:
_validate_selector_for_repo(selector=selector, repo=repo, selector_flag=selector_flag)
return resolver(selector, repo)


def maybe_resolve_subject[T](
*,
selector: str | None,
repo: str | None,
selector_flag: str,
resolver: Callable[[str | None, str | None], T],
) -> T | None:
_validate_selector_for_repo(selector=selector, repo=repo, selector_flag=selector_flag)
if selector is None:
return None
return resolver(selector, repo)


def _validate_selector_for_repo(*, selector: str | None, repo: str | None, selector_flag: str) -> None:
if repo is not None and selector is None:
raise RuntimeError(f"`{selector_flag}` is required when `--repo` is provided")


def raise_unknown_option_value(
*,
flag: str,
Expand Down
Loading
Loading