l101 0.1.0__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.
l101-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,48 @@
1
+ Metadata-Version: 2.4
2
+ Name: l101
3
+ Version: 0.1.0
4
+ Summary: Logic101 CLI - Submit and test your problem sets
5
+ Author: Dhriti Krishna
6
+ License-Expression: MIT
7
+ Classifier: Intended Audience :: Education
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Topic :: Education
10
+ Classifier: Topic :: Utilities
11
+ Requires-Python: >=3.9
12
+ Description-Content-Type: text/markdown
13
+ Requires-Dist: logic101-coursekit>=0.1.0
14
+ Requires-Dist: typer>=0.12.0
15
+ Requires-Dist: rich>=13.0
16
+ Requires-Dist: pyyaml>=6.0
17
+ Requires-Dist: requests>=2.28
18
+ Requires-Dist: pygithub>=2.0
19
+ Provides-Extra: dev
20
+ Requires-Dist: pytest; extra == "dev"
21
+ Requires-Dist: pytest-mock; extra == "dev"
22
+ Requires-Dist: ruff; extra == "dev"
23
+ Requires-Dist: mypy; extra == "dev"
24
+
25
+ # l101
26
+
27
+ Logic101 CLI — Submit and test your problem sets.
28
+
29
+ ## Installation
30
+
31
+ ```bash
32
+ pip install l101
33
+ ```
34
+
35
+ ## Commands
36
+
37
+ | Command | What it does |
38
+ |---------|-------------|
39
+ | `l101 login` | Authenticate with GitHub (one-time) |
40
+ | `l101 pull pset0` | Download starter code |
41
+ | `l101 test namaste.py` | Run visible tests locally |
42
+ | `l101 submit pset0` | Submit for grading (max 5 attempts) |
43
+ | `l101 status` | Check submission history and attempts |
44
+ | `l101 logout` | Log out and remove stored credentials |
45
+
46
+ ## License
47
+
48
+ MIT
l101-0.1.0/README.md ADDED
@@ -0,0 +1,24 @@
1
+ # l101
2
+
3
+ Logic101 CLI — Submit and test your problem sets.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install l101
9
+ ```
10
+
11
+ ## Commands
12
+
13
+ | Command | What it does |
14
+ |---------|-------------|
15
+ | `l101 login` | Authenticate with GitHub (one-time) |
16
+ | `l101 pull pset0` | Download starter code |
17
+ | `l101 test namaste.py` | Run visible tests locally |
18
+ | `l101 submit pset0` | Submit for grading (max 5 attempts) |
19
+ | `l101 status` | Check submission history and attempts |
20
+ | `l101 logout` | Log out and remove stored credentials |
21
+
22
+ ## License
23
+
24
+ MIT
@@ -0,0 +1,44 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "setuptools-scm>=8.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "l101"
7
+ version = "0.1.0"
8
+ description = "Logic101 CLI - Submit and test your problem sets"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.9"
12
+ authors = [
13
+ {name = "Dhriti Krishna"}
14
+ ]
15
+ classifiers = [
16
+ "Intended Audience :: Education",
17
+ "Programming Language :: Python :: 3",
18
+ "Topic :: Education",
19
+ "Topic :: Utilities",
20
+ ]
21
+ dependencies = [
22
+ "logic101-coursekit>=0.1.0",
23
+ "typer>=0.12.0",
24
+ "rich>=13.0",
25
+ "pyyaml>=6.0",
26
+ "requests>=2.28",
27
+ "pygithub>=2.0",
28
+ ]
29
+
30
+ [project.optional-dependencies]
31
+ dev = ["pytest", "pytest-mock", "ruff", "mypy"]
32
+
33
+ [project.scripts]
34
+ l101 = "l101.cli:app"
35
+
36
+ [tool.setuptools.packages.find]
37
+ where = ["src"]
38
+
39
+ [tool.ruff]
40
+ target-version = "py39"
41
+ line-length = 100
42
+
43
+ [tool.ruff.lint]
44
+ select = ["E", "F", "W", "I"]
l101-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ """l101 - Logic101 CLI for submitting and testing problem sets."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,6 @@
1
+ """Entry point for python -m l101."""
2
+
3
+ from l101.cli import app
4
+
5
+ if __name__ == "__main__":
6
+ app()
@@ -0,0 +1,420 @@
1
+ """l101 CLI - Logic101 command-line interface."""
2
+
3
+ from pathlib import Path
4
+ from typing import Optional
5
+
6
+ import typer
7
+ from rich.console import Console
8
+ from rich.panel import Panel
9
+ from rich.table import Table
10
+
11
+ from coursekit import (
12
+ DEFAULT_GRADING_SERVER,
13
+ DEFAULT_ORG,
14
+ auth as ck_auth,
15
+ config as ck_config,
16
+ github as ck_github,
17
+ grading_client as ck_client,
18
+ rate_limit as ck_rate,
19
+ submission as ck_submission,
20
+ test_runner as ck_test,
21
+ codespace as ck_codespace,
22
+ )
23
+ from l101 import __version__
24
+ from l101.course import (
25
+ COURSE_NAME,
26
+ GITHUB_ORG,
27
+ GRADING_SERVER_URL,
28
+ OAUTH_CLIENT_ID,
29
+ PSETS,
30
+ )
31
+
32
+ app = typer.Typer(
33
+ name="l101",
34
+ help="Logic101 CLI - Submit and test your problem sets.",
35
+ rich_markup_mode="rich",
36
+ no_args_is_help=True,
37
+ )
38
+ console = Console()
39
+
40
+ BANNER = r"""
41
+ [bold cyan] _ _____ ___ [/]
42
+ [bold cyan] | | | ___|/ _ \ [/]
43
+ [bold cyan] | | ___ ___| |__ / /_\ \ [/]
44
+ [bold cyan] | | / _ \/ _ \ __|| _ | [/]
45
+ [bold cyan] | |___| __/ __/ |___| | | | [/]
46
+ [bold cyan] \_____/\___|\___\_____\_| |_/ [/]
47
+ [bold cyan] [/]
48
+ [dim] Logic101 - Computational Thinking through Python[/]
49
+ """
50
+
51
+
52
+ def _print_welcome():
53
+ console.print(BANNER)
54
+
55
+
56
+ @app.callback(invoke_without_command=True)
57
+ def main(ctx: typer.Context):
58
+ """Logic101 CLI - Submit and test your problem sets."""
59
+ if ctx.invoked_subcommand is None:
60
+ _print_welcome()
61
+ console.print("\n[bold]Commands:[/bold]")
62
+ console.print(" [bold]l101 login[/bold] Authenticate with GitHub")
63
+ console.print(" [bold]l101 pull pset0[/bold] Download starter code")
64
+ console.print(" [bold]l101 test file.py[/bold] Run visible tests locally")
65
+ console.print(" [bold]l101 submit pset0[/bold] Submit for grading")
66
+ console.print(" [bold]l101 status[/bold] Check submission history")
67
+ console.print("\n[dim]Run [bold]l101 COMMAND --help[/bold] for more information on a command.[/dim]")
68
+
69
+
70
+ @app.command()
71
+ def login():
72
+ """Authenticate with GitHub. Required before using other commands."""
73
+ try:
74
+ token, username = ck_auth.ensure_authenticated(OAUTH_CLIENT_ID)
75
+ console.print(Panel(
76
+ f"[bold green]Successfully authenticated![/bold green]\n\n"
77
+ f"Username: {username}\n"
78
+ f"Token saved.",
79
+ title="Login Successful",
80
+ ))
81
+
82
+ try:
83
+ repo_name = ck_github.ensure_student_repo(GITHUB_ORG)
84
+ console.print(f"[dim]Private repo ready: {repo_name}[/dim]")
85
+ except Exception as e:
86
+ console.print(f"[yellow]Could not create repo: {e}[/yellow]")
87
+ console.print("[dim]You can still use l101, but submit may not work until the repo is created.[/dim]")
88
+
89
+ except ck_auth.AuthError as e:
90
+ console.print(Panel(
91
+ f"[red]Authentication failed[/red]\n\n"
92
+ f"{e}\n\n"
93
+ f"Make sure you have a GitHub account and try again.",
94
+ title="Login Failed",
95
+ style="red",
96
+ ))
97
+ raise typer.Exit(1)
98
+ except Exception as e:
99
+ console.print(Panel(
100
+ f"[red]Something went wrong[/red]\n\n"
101
+ f"{e}\n\n"
102
+ f"If this keeps happening, ask for help.",
103
+ title="Error",
104
+ style="red",
105
+ ))
106
+ raise typer.Exit(1)
107
+
108
+
109
+ @app.command()
110
+ def pull(
111
+ pset: str = typer.Argument(..., help="Problem set name (e.g., pset0)"),
112
+ ):
113
+ """Pull/clone a problem set into your local workspace."""
114
+ try:
115
+ token = ck_auth.get_token()
116
+ username = ck_auth.get_username()
117
+ if not token or not username:
118
+ console.print("[red]Not authenticated. Run [bold]l101 login[/bold] first.[/red]")
119
+ raise typer.Exit(1)
120
+
121
+ console.print(f"[bold]Pulling {pset}...[/bold]")
122
+
123
+ work_dir = Path.home() / COURSE_NAME
124
+ result_path = ck_submission.pull_pset(
125
+ pset=pset,
126
+ course=COURSE_NAME,
127
+ org=GITHUB_ORG,
128
+ dest=work_dir,
129
+ )
130
+
131
+ console.print(Panel(
132
+ f"[bold green]{pset} pulled successfully![/bold green]\n\n"
133
+ f"Working directory: {result_path}\n\n"
134
+ f"Next steps:\n"
135
+ f" cd {result_path}\n"
136
+ f" # Edit your files\n"
137
+ f" l101 test <file>.py\n"
138
+ f" l101 submit {pset}",
139
+ title="Pull Complete",
140
+ ))
141
+
142
+ except ck_github.GitHubError as e:
143
+ console.print(f"[red]GitHub error: {e}[/red]")
144
+ raise typer.Exit(1)
145
+ except Exception as e:
146
+ console.print(f"[red]Error: {e}[/red]")
147
+ raise typer.Exit(1)
148
+
149
+
150
+ @app.command()
151
+ def test(
152
+ file: str = typer.Argument(..., help="File to test (e.g., game.py)"),
153
+ ):
154
+ """Run visible tests on a file locally."""
155
+ try:
156
+ config = ck_config.load_pset_config()
157
+ results = ck_test.run_visible_tests(config, file)
158
+
159
+ if not results:
160
+ console.print("[yellow]No visible tests found in .l101.yml[/yellow]")
161
+ console.print("[dim]Hidden tests will run on submit.[/dim]")
162
+ return
163
+
164
+ ck_test.format_test_results(results, file)
165
+
166
+ except ck_config.ConfigError as e:
167
+ console.print(Panel(
168
+ f"[red]Configuration error[/red]\n\n"
169
+ f"Are you in the right directory? Run [bold]l101 pull pset0[/bold] first, "
170
+ f"then [bold]cd ~/logic101/pset0[/bold].",
171
+ title="Config Error",
172
+ style="red",
173
+ ))
174
+ raise typer.Exit(1)
175
+ except Exception as e:
176
+ console.print(f"[red]Error: {e}[/red]")
177
+ console.print("[dim]If you need help, ask for assistance.[/dim]")
178
+ raise typer.Exit(1)
179
+
180
+
181
+ @app.command()
182
+ def test(
183
+ file: str = typer.Argument(..., help="File to test (e.g., namaste.py)"),
184
+ ):
185
+ """Submit a problem set for grading."""
186
+ try:
187
+ token = ck_auth.get_token()
188
+ username = ck_auth.get_username()
189
+ if not token or not username:
190
+ console.print("[red]Not authenticated. Run [bold]l101 login[/bold] first.[/red]")
191
+ raise typer.Exit(1)
192
+
193
+ client = ck_client.GradingClient(base_url=GRADING_SERVER_URL, token=token)
194
+
195
+ console.print(f"[bold]Checking rate limit for {pset}...[/bold]")
196
+ try:
197
+ rate_info = client.check_rate_limit(pset=pset, course=COURSE_NAME)
198
+ remaining = rate_info.get("remaining", "?")
199
+ max_attempts = rate_info.get("max_attempts", "?")
200
+ console.print(f" Attempts: {rate_info.get('attempts_made', 0)}/{max_attempts} "
201
+ f"(remaining: {remaining})")
202
+ except ck_client.RateLimitError as e:
203
+ console.print(Panel(
204
+ f"[red]Rate limit exceeded![/red]\n\n"
205
+ f"{e.message}",
206
+ title="Rate Limited",
207
+ style="red",
208
+ ))
209
+ raise typer.Exit(1)
210
+
211
+ if not typer.confirm(f"\nSubmit {pset}?"):
212
+ console.print("[dim]Submission cancelled.[/dim]")
213
+ raise typer.Exit(0)
214
+
215
+ console.print("[bold]Submitting...[/bold]")
216
+
217
+ with console.status("Submitting to grading server..."):
218
+ result = ck_submission.submit_pset(
219
+ pset=pset,
220
+ course=COURSE_NAME,
221
+ org=GITHUB_ORG,
222
+ grading_server=GRADING_SERVER_URL,
223
+ )
224
+
225
+ submission_id = result.get("submission_id")
226
+ status = result.get("status", "unknown")
227
+
228
+ if status == "complete":
229
+ _display_results(result, pset)
230
+ elif status in ("queued", "grading"):
231
+ console.print(f"[bold]Submission {submission_id} is {status}. Waiting for results...[/bold]")
232
+ with console.status("Grading in progress..."):
233
+ results = client.poll_results(submission_id)
234
+ _display_results(results, pset)
235
+ elif status == "error":
236
+ console.print(Panel(
237
+ f"[red]Grading failed![/red]\n\n{result.get('message', 'Unknown error')}",
238
+ title="Submission Error",
239
+ style="red",
240
+ ))
241
+ raise typer.Exit(1)
242
+ else:
243
+ console.print(f"[yellow]Submission status: {status}[/yellow]")
244
+ console.print(f"Submission ID: {submission_id}")
245
+
246
+ except ck_config.ConfigError as e:
247
+ console.print(Panel(
248
+ f"[red]Configuration error[/red]\n\n"
249
+ f"Are you in the right directory? Run [bold]l101 pull pset0[/bold] first, "
250
+ f"then [bold]cd ~/logic101/pset0[/bold].",
251
+ title="Config Error",
252
+ style="red",
253
+ ))
254
+ raise typer.Exit(1)
255
+ except Exception as e:
256
+ console.print(f"[red]Error running tests: {e}[/red]")
257
+ console.print("[dim]Make sure your Python file runs without errors before testing.[/dim]")
258
+ raise typer.Exit(1)
259
+ except ck_client.RateLimitError as e:
260
+ console.print(Panel(
261
+ f"[red]Rate limit exceeded![/red]\n\n{e.message}",
262
+ title="Rate Limited",
263
+ style="red",
264
+ ))
265
+ raise typer.Exit(1)
266
+ except Exception as e:
267
+ console.print(f"[red]Error: {e}[/red]")
268
+ raise typer.Exit(1)
269
+
270
+
271
+ @app.command()
272
+ def status(
273
+ pset: Optional[str] = typer.Argument(None, help="Problem set name (optional)"),
274
+ ):
275
+ """Show submission history and remaining attempts."""
276
+ try:
277
+ token = ck_auth.get_token()
278
+ if not token:
279
+ console.print("[red]Not authenticated. Run [bold]l101 login[/bold] first.[/red]")
280
+ raise typer.Exit(1)
281
+
282
+ client = ck_client.GradingClient(base_url=GRADING_SERVER_URL, token=token)
283
+
284
+ if pset:
285
+ rate_info = client.check_rate_limit(pset=pset, course=COURSE_NAME)
286
+ remaining = rate_info.get("remaining", "?")
287
+ max_attempts = rate_info.get("max_attempts", "?")
288
+ attempts = rate_info.get("attempts_made", 0)
289
+
290
+ console.print(Panel(
291
+ f"Problem set: [bold]{pset}[/bold]\n"
292
+ f"Attempts: {attempts}/{max_attempts} "
293
+ f"({remaining} remaining)",
294
+ title="Rate Limit Status",
295
+ ))
296
+
297
+ status_data = client.get_status(pset=pset, course=COURSE_NAME)
298
+ submissions = status_data.get("submissions", [])
299
+
300
+ if submissions:
301
+ table = Table(title=f"Submissions for {pset}", show_lines=True)
302
+ table.add_column("#", style="dim", width=3)
303
+ table.add_column("Submitted", width=20)
304
+ table.add_column("Score", style="bold")
305
+ table.add_column("Status")
306
+
307
+ best_score = 0
308
+ best_max = 0
309
+ for i, sub in enumerate(submissions, 1):
310
+ score = sub.get("score", 0) or 0
311
+ max_score = sub.get("max_score", 0) or 0
312
+ if score > best_score:
313
+ best_score = score
314
+ best_max = max_score
315
+ status = sub.get("status", "?")
316
+ score_str = f"{score}/{max_score}" if max_score else str(score)
317
+ table.add_row(
318
+ str(i),
319
+ sub.get("submitted_at", "?")[:19] if sub.get("submitted_at") else "?",
320
+ score_str,
321
+ status,
322
+ )
323
+ console.print(table)
324
+ console.print(f"\n[bold]Best score: {best_score}/{best_max}[/bold]")
325
+ else:
326
+ console.print("[dim]No submissions yet. Run [bold]l101 submit pset0[/bold] to submit your work.[/dim]")
327
+ else:
328
+ console.print("[bold]Status for all problem sets:[/bold]\n")
329
+ for p in PSETS:
330
+ try:
331
+ rate_info = client.check_rate_limit(pset=p, course=COURSE_NAME)
332
+ remaining = rate_info.get("remaining", "?")
333
+ max_attempts = rate_info.get("max_attempts", "?")
334
+ attempts = rate_info.get("attempts_made", 0)
335
+ console.print(
336
+ f" [bold]{p}[/bold]: {attempts}/{max_attempts} "
337
+ f"attempts ({remaining} remaining)"
338
+ )
339
+
340
+ status_data = client.get_status(pset=p, course=COURSE_NAME)
341
+ submissions = status_data.get("submissions", [])
342
+ if submissions:
343
+ best = max((s.get("score", 0) or 0) for s in submissions)
344
+ max_s = max((s.get("max_score", 0) or 0) for s in submissions)
345
+ console.print(f" Best score: {best}/{max_s}")
346
+ except Exception:
347
+ console.print(f" [bold]{p}[/bold]: [yellow]Could not fetch status[/yellow]")
348
+
349
+ except ck_client.RateLimitError as e:
350
+ console.print(Panel(
351
+ f"[red]Rate limit exceeded![/red]\n\n{e.message}",
352
+ title="Rate Limited",
353
+ style="red",
354
+ ))
355
+ raise typer.Exit(1)
356
+ except Exception as e:
357
+ console.print(f"[red]Error: {e}[/red]")
358
+ raise typer.Exit(1)
359
+
360
+
361
+ @app.command()
362
+ def logout():
363
+ """Log out and remove stored credentials."""
364
+ ck_auth.logout()
365
+ console.print("[bold green]Logged out successfully.[/bold green]")
366
+
367
+
368
+ @app.command()
369
+ def codespace(
370
+ pset: str = typer.Argument(..., help="Problem set name (e.g., pset0)"),
371
+ ):
372
+ """Get a one-click Codespace URL for a problem set."""
373
+ url = ck_codespace.get_codespace_url(GITHUB_ORG, pset)
374
+ console.print(f"[bold]Codespace URL for {pset}:[/bold]")
375
+ console.print(f"[blue]{url}[/blue]")
376
+ console.print("\n[dim]Click this link to open a Codespace in your browser.[/dim]")
377
+ console.print("[dim]The environment will be auto-configured with l101.[/dim]")
378
+
379
+
380
+ def _display_results(results: dict, pset: str):
381
+ """Display grading results in a formatted table with per-problem grouping."""
382
+ test_results = results.get("results", [])
383
+ score = results.get("score", 0)
384
+ max_score = results.get("max_score", 0)
385
+
386
+ if not test_results:
387
+ console.print(f"[bold]Score: {score}/{max_score}[/bold]")
388
+ return
389
+
390
+ last_problem = None
391
+ for test in test_results:
392
+ name = test.get("name", "Unnamed")
393
+ problem = name.split(":")[0].strip() if ":" in name else None
394
+
395
+ if problem and problem != last_problem:
396
+ last_problem = problem
397
+ console.print(f"\n[bold]{problem}[/bold]")
398
+
399
+ status_str = "[green]:check_mark: PASS[/green]" if test.get("passed", False) else "[red]:cross_mark: FAIL[/red]"
400
+ message = test.get("message", "")
401
+ if message and not test.get("passed", False):
402
+ console.print(f" {status_str} {name} [dim]({message})[/dim]")
403
+ else:
404
+ console.print(f" {status_str} {name}")
405
+
406
+ console.print(f"\n[bold]Score: {score}/{max_score}[/bold]")
407
+
408
+ if score == max_score:
409
+ console.print("[bold green]All tests passed! Full marks![/bold green]")
410
+ else:
411
+ remaining = results.get("rate_limit", {})
412
+ attempts_left = remaining.get("remaining")
413
+ if attempts_left is not None:
414
+ console.print(f"[yellow]You have {attempts_left} submission attempts remaining. Fix the issues and resubmit.[/yellow]")
415
+ else:
416
+ console.print("[yellow]You can submit again to improve your score.[/yellow]")
417
+
418
+
419
+ if __name__ == "__main__":
420
+ app()
@@ -0,0 +1,13 @@
1
+ """Logic101 course-specific configuration."""
2
+
3
+ from coursekit import DEFAULT_GRADING_SERVER, DEFAULT_ORG
4
+
5
+ COURSE_NAME = "logic101"
6
+ GITHUB_ORG = DEFAULT_ORG
7
+
8
+ OAUTH_CLIENT_ID = "Ov23liXbdbGsB6k1yuDM"
9
+ OAUTH_CLIENT_SECRET = "ce26ff62054e4ae674777fde096a210b2a4e1c8e"
10
+
11
+ GRADING_SERVER_URL = DEFAULT_GRADING_SERVER
12
+
13
+ PSETS = ["pset0"]
@@ -0,0 +1,48 @@
1
+ Metadata-Version: 2.4
2
+ Name: l101
3
+ Version: 0.1.0
4
+ Summary: Logic101 CLI - Submit and test your problem sets
5
+ Author: Dhriti Krishna
6
+ License-Expression: MIT
7
+ Classifier: Intended Audience :: Education
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Topic :: Education
10
+ Classifier: Topic :: Utilities
11
+ Requires-Python: >=3.9
12
+ Description-Content-Type: text/markdown
13
+ Requires-Dist: logic101-coursekit>=0.1.0
14
+ Requires-Dist: typer>=0.12.0
15
+ Requires-Dist: rich>=13.0
16
+ Requires-Dist: pyyaml>=6.0
17
+ Requires-Dist: requests>=2.28
18
+ Requires-Dist: pygithub>=2.0
19
+ Provides-Extra: dev
20
+ Requires-Dist: pytest; extra == "dev"
21
+ Requires-Dist: pytest-mock; extra == "dev"
22
+ Requires-Dist: ruff; extra == "dev"
23
+ Requires-Dist: mypy; extra == "dev"
24
+
25
+ # l101
26
+
27
+ Logic101 CLI — Submit and test your problem sets.
28
+
29
+ ## Installation
30
+
31
+ ```bash
32
+ pip install l101
33
+ ```
34
+
35
+ ## Commands
36
+
37
+ | Command | What it does |
38
+ |---------|-------------|
39
+ | `l101 login` | Authenticate with GitHub (one-time) |
40
+ | `l101 pull pset0` | Download starter code |
41
+ | `l101 test namaste.py` | Run visible tests locally |
42
+ | `l101 submit pset0` | Submit for grading (max 5 attempts) |
43
+ | `l101 status` | Check submission history and attempts |
44
+ | `l101 logout` | Log out and remove stored credentials |
45
+
46
+ ## License
47
+
48
+ MIT
@@ -0,0 +1,12 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/l101/__init__.py
4
+ src/l101/__main__.py
5
+ src/l101/cli.py
6
+ src/l101/course.py
7
+ src/l101.egg-info/PKG-INFO
8
+ src/l101.egg-info/SOURCES.txt
9
+ src/l101.egg-info/dependency_links.txt
10
+ src/l101.egg-info/entry_points.txt
11
+ src/l101.egg-info/requires.txt
12
+ src/l101.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ l101 = l101.cli:app
@@ -0,0 +1,12 @@
1
+ logic101-coursekit>=0.1.0
2
+ typer>=0.12.0
3
+ rich>=13.0
4
+ pyyaml>=6.0
5
+ requests>=2.28
6
+ pygithub>=2.0
7
+
8
+ [dev]
9
+ pytest
10
+ pytest-mock
11
+ ruff
12
+ mypy
@@ -0,0 +1 @@
1
+ l101