cli-web-hackernews 0.1.0__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.
@@ -0,0 +1,91 @@
1
+ # cli-web-hackernews
2
+
3
+ CLI for browsing and interacting with Hacker News — top stories, search, comments, user profiles, plus auth-enabled actions (upvote, submit, comment, favorite).
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ cd hackernews/agent-harness
9
+ pip install -e .
10
+ ```
11
+
12
+ ## Usage
13
+
14
+ ### Browse (no auth required)
15
+
16
+ ```bash
17
+ # Browse stories
18
+ cli-web-hackernews stories top # Front page (top 30)
19
+ cli-web-hackernews stories new -n 10 # Newest 10 stories
20
+ cli-web-hackernews stories best # Best stories (all time)
21
+ cli-web-hackernews stories ask # Ask HN
22
+ cli-web-hackernews stories show # Show HN
23
+ cli-web-hackernews stories jobs # Job listings
24
+
25
+ # View a story with comments
26
+ cli-web-hackernews stories view 47530330
27
+ cli-web-hackernews stories view 47530330 -n 5 --json
28
+
29
+ # Search
30
+ cli-web-hackernews search stories "claude code"
31
+ cli-web-hackernews search comments "react hooks" --sort-date -n 5
32
+
33
+ # User profiles
34
+ cli-web-hackernews user view dang
35
+ cli-web-hackernews user view pg --json
36
+ ```
37
+
38
+ ### Auth-Enabled Actions
39
+
40
+ ```bash
41
+ # Login
42
+ cli-web-hackernews auth login # Username/password prompt
43
+ cli-web-hackernews auth login-browser # Login via browser window
44
+ cli-web-hackernews auth status # Check login status
45
+ cli-web-hackernews auth logout # Remove credentials
46
+
47
+ # Interact (requires login)
48
+ cli-web-hackernews upvote 47530330 # Upvote a story
49
+ cli-web-hackernews submit -t "My Title" -u "https://example.com" # Submit link
50
+ cli-web-hackernews submit -t "Ask HN: Question?" --text "Details" # Ask HN
51
+ cli-web-hackernews comment 47530330 "Great article!" # Comment
52
+ cli-web-hackernews favorite 47530330 # Save to favorites
53
+ cli-web-hackernews hide 47530330 # Hide from feed
54
+
55
+ # View your activity
56
+ cli-web-hackernews user favorites # Your favorites
57
+ cli-web-hackernews user submissions # Your submissions
58
+ cli-web-hackernews user threads # Replies to your comments
59
+ cli-web-hackernews user submissions dang # Someone else's submissions
60
+ ```
61
+
62
+ ### JSON output
63
+
64
+ Every command supports `--json` for structured output:
65
+
66
+ ```bash
67
+ cli-web-hackernews stories top -n 5 --json
68
+ cli-web-hackernews upvote 47530330 --json
69
+ cli-web-hackernews --json # Propagates to all commands in REPL mode
70
+ ```
71
+
72
+ ### REPL mode
73
+
74
+ Run without arguments to enter interactive mode:
75
+
76
+ ```bash
77
+ cli-web-hackernews
78
+ ```
79
+
80
+ ## API Sources
81
+
82
+ - **Firebase API**: `hacker-news.firebaseio.com/v0/` — stories, items, users (public)
83
+ - **Algolia API**: `hn.algolia.com/api/v1/` — full-text search (public)
84
+ - **HN Web**: `news.ycombinator.com` — auth actions (upvote, submit, comment, favorite, hide)
85
+
86
+ ## Testing
87
+
88
+ ```bash
89
+ cd hackernews/agent-harness
90
+ python -m pytest cli_web/hackernews/tests/ -v -s
91
+ ```
File without changes
@@ -0,0 +1,6 @@
1
+ """Allow running as: python -m cli_web.hackernews"""
2
+
3
+ from cli_web.hackernews.hackernews_cli import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
File without changes
@@ -0,0 +1,105 @@
1
+ """Actions command group — upvote, submit, comment, favorite, hide (auth required)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import click
6
+ from cli_web.hackernews.core import auth
7
+ from cli_web.hackernews.core.client import HackerNewsClient
8
+ from cli_web.hackernews.utils.helpers import handle_errors, print_json, resolve_json_mode
9
+
10
+
11
+ def _auth_client() -> HackerNewsClient:
12
+ """Create an authenticated client."""
13
+ cookie = auth.get_user_cookie()
14
+ return HackerNewsClient(user_cookie=cookie)
15
+
16
+
17
+ @click.command("upvote")
18
+ @click.argument("item_id", type=int)
19
+ @click.option("--json", "json_mode", is_flag=True, help="Output as JSON.")
20
+ @click.pass_context
21
+ def upvote_cmd(ctx, item_id, json_mode):
22
+ """Upvote a story or comment by ID. (Requires auth)"""
23
+ json_mode = resolve_json_mode(json_mode)
24
+ with handle_errors(json_mode=json_mode):
25
+ client = _auth_client()
26
+ result = client.upvote(item_id)
27
+ if json_mode:
28
+ print_json(result)
29
+ else:
30
+ click.echo(f"Upvoted item {item_id}")
31
+
32
+
33
+ @click.command("submit")
34
+ @click.option("--title", "-t", required=True, help="Story title.")
35
+ @click.option("--url", "-u", default=None, help="URL to submit (leave blank for Ask HN).")
36
+ @click.option("--text", default=None, help="Text body (for Ask HN or to add context).")
37
+ @click.option("--json", "json_mode", is_flag=True, help="Output as JSON.")
38
+ @click.pass_context
39
+ def submit_cmd(ctx, title, url, text, json_mode):
40
+ """Submit a new story to Hacker News. (Requires auth)
41
+
42
+ Use --url for link submissions, or leave blank for Ask HN (text only).
43
+ """
44
+ json_mode = resolve_json_mode(json_mode)
45
+ with handle_errors(json_mode=json_mode):
46
+ client = _auth_client()
47
+ result = client.submit_story(title=title, url=url, text=text)
48
+ if json_mode:
49
+ print_json(result)
50
+ else:
51
+ kind = "link" if url else "Ask HN"
52
+ click.echo(f"Submitted {kind}: {title}")
53
+
54
+
55
+ @click.command("comment")
56
+ @click.argument("parent_id", type=int)
57
+ @click.argument("text")
58
+ @click.option("--json", "json_mode", is_flag=True, help="Output as JSON.")
59
+ @click.pass_context
60
+ def comment_cmd(ctx, parent_id, text, json_mode):
61
+ """Post a comment on a story or reply to a comment. (Requires auth)
62
+
63
+ PARENT_ID is the story or comment ID to reply to.
64
+ TEXT is the comment body.
65
+ """
66
+ json_mode = resolve_json_mode(json_mode)
67
+ with handle_errors(json_mode=json_mode):
68
+ client = _auth_client()
69
+ result = client.post_comment(parent_id=parent_id, text=text)
70
+ if json_mode:
71
+ print_json(result)
72
+ else:
73
+ click.echo(f"Comment posted on item {parent_id}")
74
+
75
+
76
+ @click.command("favorite")
77
+ @click.argument("item_id", type=int)
78
+ @click.option("--json", "json_mode", is_flag=True, help="Output as JSON.")
79
+ @click.pass_context
80
+ def favorite_cmd(ctx, item_id, json_mode):
81
+ """Favorite (save) a story. (Requires auth)"""
82
+ json_mode = resolve_json_mode(json_mode)
83
+ with handle_errors(json_mode=json_mode):
84
+ client = _auth_client()
85
+ result = client.favorite(item_id)
86
+ if json_mode:
87
+ print_json(result)
88
+ else:
89
+ click.echo(f"Favorited item {item_id}")
90
+
91
+
92
+ @click.command("hide")
93
+ @click.argument("item_id", type=int)
94
+ @click.option("--json", "json_mode", is_flag=True, help="Output as JSON.")
95
+ @click.pass_context
96
+ def hide_cmd(ctx, item_id, json_mode):
97
+ """Hide a story from your feed. (Requires auth)"""
98
+ json_mode = resolve_json_mode(json_mode)
99
+ with handle_errors(json_mode=json_mode):
100
+ client = _auth_client()
101
+ result = client.hide(item_id)
102
+ if json_mode:
103
+ print_json(result)
104
+ else:
105
+ click.echo(f"Hidden item {item_id}")
@@ -0,0 +1,80 @@
1
+ """Auth command group — login, status, logout."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import click
6
+ from cli_web.hackernews.core import auth
7
+ from cli_web.hackernews.utils.helpers import handle_errors, print_json, resolve_json_mode
8
+
9
+
10
+ @click.group("auth")
11
+ def auth_group():
12
+ """Manage Hacker News authentication."""
13
+
14
+
15
+ @auth_group.command("login")
16
+ @click.option("--username", "-u", prompt="HN username", help="Your Hacker News username.")
17
+ @click.option("--password", "-p", prompt=True, hide_input=True, help="Your Hacker News password.")
18
+ @click.option("--json", "json_mode", is_flag=True, help="Output as JSON.")
19
+ @click.pass_context
20
+ def auth_login(ctx, username, password, json_mode):
21
+ """Login to Hacker News with username and password."""
22
+ json_mode = resolve_json_mode(json_mode)
23
+ with handle_errors(json_mode=json_mode):
24
+ result = auth.login_with_password(username, password)
25
+ if json_mode:
26
+ print_json({"success": True, "username": result["username"]})
27
+ else:
28
+ click.echo(f"Logged in as {result['username']}")
29
+
30
+
31
+ @auth_group.command("login-browser")
32
+ @click.option("--json", "json_mode", is_flag=True, help="Output as JSON.")
33
+ @click.pass_context
34
+ def auth_login_browser(ctx, json_mode):
35
+ """Login to Hacker News via browser (opens a browser window)."""
36
+ json_mode = resolve_json_mode(json_mode)
37
+ with handle_errors(json_mode=json_mode):
38
+ result = auth.login_browser()
39
+ if json_mode:
40
+ print_json({"success": True, "username": result["username"]})
41
+ else:
42
+ click.echo(f"Logged in as {result['username']}")
43
+
44
+
45
+ @auth_group.command("status")
46
+ @click.option("--json", "json_mode", is_flag=True, help="Output as JSON.")
47
+ @click.pass_context
48
+ def auth_status(ctx, json_mode):
49
+ """Check authentication status."""
50
+ json_mode = resolve_json_mode(json_mode)
51
+ with handle_errors(json_mode=json_mode):
52
+ if not auth.is_logged_in():
53
+ if json_mode:
54
+ print_json({"logged_in": False})
55
+ else:
56
+ click.echo("Not logged in. Run: cli-web-hackernews auth login")
57
+ return
58
+
59
+ result = auth.validate_auth()
60
+ if json_mode:
61
+ print_json(
62
+ {"logged_in": True, "username": result["username"], "valid": result["valid"]}
63
+ )
64
+ else:
65
+ click.echo(f"Logged in as: {result['username']}")
66
+ click.echo(f"Cookie valid: {'yes' if result['valid'] else 'no'}")
67
+
68
+
69
+ @auth_group.command("logout")
70
+ @click.option("--json", "json_mode", is_flag=True, help="Output as JSON.")
71
+ @click.pass_context
72
+ def auth_logout(ctx, json_mode):
73
+ """Remove stored authentication credentials."""
74
+ json_mode = resolve_json_mode(json_mode)
75
+ with handle_errors(json_mode=json_mode):
76
+ auth.logout()
77
+ if json_mode:
78
+ print_json({"success": True, "message": "Logged out"})
79
+ else:
80
+ click.echo("Logged out successfully.")
@@ -0,0 +1,69 @@
1
+ """Search command — search HN via Algolia API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import click
6
+ from cli_web.hackernews.core.client import HackerNewsClient
7
+ from cli_web.hackernews.utils.helpers import handle_errors, resolve_json_mode
8
+ from cli_web.hackernews.utils.output import print_json, print_search_results_table
9
+
10
+
11
+ @click.group("search")
12
+ def search_group():
13
+ """Search Hacker News stories and comments."""
14
+
15
+
16
+ @search_group.command("stories")
17
+ @click.argument("query")
18
+ @click.option("-n", "--limit", default=20, show_default=True, help="Number of results.")
19
+ @click.option("--page", default=0, help="Page number (0-indexed).")
20
+ @click.option("--sort-date", is_flag=True, help="Sort by date instead of relevance.")
21
+ @click.option("--json", "json_mode", is_flag=True, help="Output as JSON.")
22
+ @click.pass_context
23
+ def search_stories(ctx, query, limit, page, sort_date, json_mode):
24
+ """Search for stories by keyword."""
25
+ json_mode = resolve_json_mode(json_mode)
26
+ with handle_errors(json_mode=json_mode):
27
+ client = HackerNewsClient()
28
+ results = client.search(
29
+ query=query,
30
+ tags="story",
31
+ sort_by_date=sort_date,
32
+ hits_per_page=limit,
33
+ page=page,
34
+ )
35
+ if json_mode:
36
+ print_json([r.to_dict() for r in results])
37
+ else:
38
+ sort_label = "by date" if sort_date else "by relevance"
39
+ click.echo(f"\nSearch: '{query}' ({sort_label})\n")
40
+ print_search_results_table(results)
41
+ click.echo(f"\n{len(results)} results\n")
42
+
43
+
44
+ @search_group.command("comments")
45
+ @click.argument("query")
46
+ @click.option("-n", "--limit", default=20, show_default=True, help="Number of results.")
47
+ @click.option("--page", default=0, help="Page number (0-indexed).")
48
+ @click.option("--sort-date", is_flag=True, help="Sort by date instead of relevance.")
49
+ @click.option("--json", "json_mode", is_flag=True, help="Output as JSON.")
50
+ @click.pass_context
51
+ def search_comments(ctx, query, limit, page, sort_date, json_mode):
52
+ """Search for comments by keyword."""
53
+ json_mode = resolve_json_mode(json_mode)
54
+ with handle_errors(json_mode=json_mode):
55
+ client = HackerNewsClient()
56
+ results = client.search(
57
+ query=query,
58
+ tags="comment",
59
+ sort_by_date=sort_date,
60
+ hits_per_page=limit,
61
+ page=page,
62
+ )
63
+ if json_mode:
64
+ print_json([r.to_dict() for r in results])
65
+ else:
66
+ sort_label = "by date" if sort_date else "by relevance"
67
+ click.echo(f"\nSearch comments: '{query}' ({sort_label})\n")
68
+ print_search_results_table(results)
69
+ click.echo(f"\n{len(results)} results\n")
@@ -0,0 +1,160 @@
1
+ """Stories command group — browse HN feeds (top, new, best, ask, show, job)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import click
6
+ from cli_web.hackernews.core.client import HackerNewsClient
7
+ from cli_web.hackernews.utils.helpers import handle_errors, resolve_json_mode
8
+ from cli_web.hackernews.utils.output import (
9
+ print_comments_list,
10
+ print_json,
11
+ print_stories_table,
12
+ )
13
+
14
+
15
+ @click.group("stories")
16
+ def stories_group():
17
+ """Browse Hacker News stories."""
18
+
19
+
20
+ @stories_group.command("top")
21
+ @click.option("-n", "--limit", default=30, show_default=True, help="Number of stories to show.")
22
+ @click.option("--json", "json_mode", is_flag=True, help="Output as JSON.")
23
+ @click.pass_context
24
+ def stories_top(ctx, limit, json_mode):
25
+ """Show top stories from the front page."""
26
+ json_mode = resolve_json_mode(json_mode)
27
+ with handle_errors(json_mode=json_mode):
28
+ client = HackerNewsClient()
29
+ stories = client.get_stories("top", limit=limit)
30
+ if json_mode:
31
+ print_json([s.to_dict() for s in stories])
32
+ else:
33
+ click.echo("\nTop Stories\n")
34
+ print_stories_table(stories)
35
+ click.echo(f"\n{len(stories)} stories\n")
36
+
37
+
38
+ @stories_group.command("new")
39
+ @click.option("-n", "--limit", default=30, show_default=True, help="Number of stories to show.")
40
+ @click.option("--json", "json_mode", is_flag=True, help="Output as JSON.")
41
+ @click.pass_context
42
+ def stories_new(ctx, limit, json_mode):
43
+ """Show newest stories."""
44
+ json_mode = resolve_json_mode(json_mode)
45
+ with handle_errors(json_mode=json_mode):
46
+ client = HackerNewsClient()
47
+ stories = client.get_stories("new", limit=limit)
48
+ if json_mode:
49
+ print_json([s.to_dict() for s in stories])
50
+ else:
51
+ click.echo("\nNew Stories\n")
52
+ print_stories_table(stories)
53
+ click.echo(f"\n{len(stories)} stories\n")
54
+
55
+
56
+ @stories_group.command("best")
57
+ @click.option("-n", "--limit", default=30, show_default=True, help="Number of stories to show.")
58
+ @click.option("--json", "json_mode", is_flag=True, help="Output as JSON.")
59
+ @click.pass_context
60
+ def stories_best(ctx, limit, json_mode):
61
+ """Show best stories (all time)."""
62
+ json_mode = resolve_json_mode(json_mode)
63
+ with handle_errors(json_mode=json_mode):
64
+ client = HackerNewsClient()
65
+ stories = client.get_stories("best", limit=limit)
66
+ if json_mode:
67
+ print_json([s.to_dict() for s in stories])
68
+ else:
69
+ click.echo("\nBest Stories\n")
70
+ print_stories_table(stories)
71
+ click.echo(f"\n{len(stories)} stories\n")
72
+
73
+
74
+ @stories_group.command("ask")
75
+ @click.option("-n", "--limit", default=30, show_default=True, help="Number of stories to show.")
76
+ @click.option("--json", "json_mode", is_flag=True, help="Output as JSON.")
77
+ @click.pass_context
78
+ def stories_ask(ctx, limit, json_mode):
79
+ """Show Ask HN stories."""
80
+ json_mode = resolve_json_mode(json_mode)
81
+ with handle_errors(json_mode=json_mode):
82
+ client = HackerNewsClient()
83
+ stories = client.get_stories("ask", limit=limit)
84
+ if json_mode:
85
+ print_json([s.to_dict() for s in stories])
86
+ else:
87
+ click.echo("\nAsk HN\n")
88
+ print_stories_table(stories)
89
+ click.echo(f"\n{len(stories)} stories\n")
90
+
91
+
92
+ @stories_group.command("show")
93
+ @click.option("-n", "--limit", default=30, show_default=True, help="Number of stories to show.")
94
+ @click.option("--json", "json_mode", is_flag=True, help="Output as JSON.")
95
+ @click.pass_context
96
+ def stories_show(ctx, limit, json_mode):
97
+ """Show Show HN stories."""
98
+ json_mode = resolve_json_mode(json_mode)
99
+ with handle_errors(json_mode=json_mode):
100
+ client = HackerNewsClient()
101
+ stories = client.get_stories("show", limit=limit)
102
+ if json_mode:
103
+ print_json([s.to_dict() for s in stories])
104
+ else:
105
+ click.echo("\nShow HN\n")
106
+ print_stories_table(stories)
107
+ click.echo(f"\n{len(stories)} stories\n")
108
+
109
+
110
+ @stories_group.command("jobs")
111
+ @click.option("-n", "--limit", default=30, show_default=True, help="Number of stories to show.")
112
+ @click.option("--json", "json_mode", is_flag=True, help="Output as JSON.")
113
+ @click.pass_context
114
+ def stories_jobs(ctx, limit, json_mode):
115
+ """Show job stories."""
116
+ json_mode = resolve_json_mode(json_mode)
117
+ with handle_errors(json_mode=json_mode):
118
+ client = HackerNewsClient()
119
+ stories = client.get_stories("job", limit=limit)
120
+ if json_mode:
121
+ print_json([s.to_dict() for s in stories])
122
+ else:
123
+ click.echo("\nJobs\n")
124
+ print_stories_table(stories)
125
+ click.echo(f"\n{len(stories)} stories\n")
126
+
127
+
128
+ @stories_group.command("view")
129
+ @click.argument("story_id", type=int)
130
+ @click.option("--comments/--no-comments", default=True, help="Show comments.")
131
+ @click.option("-n", "--limit", default=10, show_default=True, help="Number of comments to show.")
132
+ @click.option("--json", "json_mode", is_flag=True, help="Output as JSON.")
133
+ @click.pass_context
134
+ def stories_view(ctx, story_id, comments, limit, json_mode):
135
+ """View a story and its comments by ID."""
136
+ json_mode = resolve_json_mode(json_mode)
137
+ with handle_errors(json_mode=json_mode):
138
+ client = HackerNewsClient()
139
+ story = client.get_story(story_id)
140
+
141
+ if json_mode:
142
+ data = story.to_dict()
143
+ if comments:
144
+ cmts = client.get_comments(story_id, limit=limit)
145
+ data["comments"] = [c.to_dict() for c in cmts]
146
+ print_json(data)
147
+ else:
148
+ click.echo(f"\n {story.title}")
149
+ if story.url:
150
+ click.echo(f" {story.url}")
151
+ click.echo(
152
+ f" {story.score} points by {story.by} | {story.age} | {story.descendants} comments"
153
+ )
154
+ click.echo()
155
+
156
+ if comments:
157
+ cmts = client.get_comments(story_id, limit=limit)
158
+ if cmts:
159
+ click.echo(" Comments:\n")
160
+ print_comments_list(cmts)
@@ -0,0 +1,112 @@
1
+ """User command — view HN user profiles, favorites, and submissions."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import click
6
+ from cli_web.hackernews.core import auth
7
+ from cli_web.hackernews.core.client import HackerNewsClient
8
+ from cli_web.hackernews.utils.helpers import handle_errors, resolve_json_mode
9
+ from cli_web.hackernews.utils.output import (
10
+ print_comments_list,
11
+ print_json,
12
+ print_stories_table,
13
+ print_user_profile,
14
+ )
15
+
16
+
17
+ @click.group("user")
18
+ def user_group():
19
+ """View Hacker News user profiles."""
20
+
21
+
22
+ @user_group.command("view")
23
+ @click.argument("username")
24
+ @click.option("--json", "json_mode", is_flag=True, help="Output as JSON.")
25
+ @click.pass_context
26
+ def user_view(ctx, username, json_mode):
27
+ """View a user's profile by username."""
28
+ json_mode = resolve_json_mode(json_mode)
29
+ with handle_errors(json_mode=json_mode):
30
+ client = HackerNewsClient()
31
+ user = client.get_user(username)
32
+ if json_mode:
33
+ print_json(user.to_dict())
34
+ else:
35
+ click.echo(f"\nUser Profile: {username}\n")
36
+ print_user_profile(user)
37
+ click.echo()
38
+
39
+
40
+ @user_group.command("favorites")
41
+ @click.argument("username", required=False)
42
+ @click.option("-n", "--limit", default=30, show_default=True, help="Number of items to show.")
43
+ @click.option("--json", "json_mode", is_flag=True, help="Output as JSON.")
44
+ @click.pass_context
45
+ def user_favorites(ctx, username, limit, json_mode):
46
+ """View a user's favorite stories. (Requires auth)
47
+
48
+ If USERNAME is omitted, shows your own favorites.
49
+ """
50
+ json_mode = resolve_json_mode(json_mode)
51
+ with handle_errors(json_mode=json_mode):
52
+ if not username:
53
+ username = auth.get_username()
54
+ cookie = auth.get_user_cookie()
55
+ client = HackerNewsClient(user_cookie=cookie)
56
+ stories = client.get_favorites(username, limit=limit)
57
+ if json_mode:
58
+ print_json([s.to_dict() for s in stories])
59
+ else:
60
+ click.echo(f"\n{username}'s Favorites\n")
61
+ print_stories_table(stories)
62
+ click.echo(f"\n{len(stories)} stories\n")
63
+
64
+
65
+ @user_group.command("submissions")
66
+ @click.argument("username", required=False)
67
+ @click.option("-n", "--limit", default=30, show_default=True, help="Number of items to show.")
68
+ @click.option("--json", "json_mode", is_flag=True, help="Output as JSON.")
69
+ @click.pass_context
70
+ def user_submissions(ctx, username, limit, json_mode):
71
+ """View a user's submitted stories. (Requires auth)
72
+
73
+ If USERNAME is omitted, shows your own submissions.
74
+ """
75
+ json_mode = resolve_json_mode(json_mode)
76
+ with handle_errors(json_mode=json_mode):
77
+ if not username:
78
+ username = auth.get_username()
79
+ cookie = auth.get_user_cookie()
80
+ client = HackerNewsClient(user_cookie=cookie)
81
+ stories = client.get_submissions(username, limit=limit)
82
+ if json_mode:
83
+ print_json([s.to_dict() for s in stories])
84
+ else:
85
+ click.echo(f"\n{username}'s Submissions\n")
86
+ print_stories_table(stories)
87
+ click.echo(f"\n{len(stories)} stories\n")
88
+
89
+
90
+ @user_group.command("threads")
91
+ @click.argument("username", required=False)
92
+ @click.option("-n", "--limit", default=20, show_default=True, help="Number of comments to show.")
93
+ @click.option("--json", "json_mode", is_flag=True, help="Output as JSON.")
94
+ @click.pass_context
95
+ def user_threads(ctx, username, limit, json_mode):
96
+ """View comment replies to a user (your threads). (Requires auth)
97
+
98
+ If USERNAME is omitted, shows replies to your own comments.
99
+ """
100
+ json_mode = resolve_json_mode(json_mode)
101
+ with handle_errors(json_mode=json_mode):
102
+ if not username:
103
+ username = auth.get_username()
104
+ cookie = auth.get_user_cookie()
105
+ client = HackerNewsClient(user_cookie=cookie)
106
+ comments = client.get_threads(username, limit=limit)
107
+ if json_mode:
108
+ print_json([c.to_dict() for c in comments])
109
+ else:
110
+ click.echo(f"\n{username}'s Threads (replies)\n")
111
+ print_comments_list(comments)
112
+ click.echo(f"\n{len(comments)} comments\n")
File without changes