tisit-cli 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.
tisit_cli/display.py ADDED
@@ -0,0 +1,582 @@
1
+ """Rich output helpers for terminal display."""
2
+ from rich.console import Console
3
+ from rich.markdown import Markdown
4
+ from rich.panel import Panel
5
+ from rich.table import Table
6
+
7
+ console = Console()
8
+
9
+
10
+ def print_success(message: str):
11
+ console.print(f"[green]{message}[/green]")
12
+
13
+
14
+ def print_error(message: str):
15
+ console.print(f"[red]{message}[/red]")
16
+
17
+
18
+ def print_info(message: str):
19
+ console.print(f"[blue]{message}[/blue]")
20
+
21
+
22
+ def print_note_table(notes: list, meta: dict | None = None):
23
+ table = Table(title="Notes")
24
+ table.add_column("ID", style="cyan", justify="right")
25
+ table.add_column("Term", style="bold")
26
+ table.add_column("Context")
27
+ table.add_column("Category")
28
+ table.add_column("Status")
29
+ table.add_column("Created")
30
+
31
+ for n in notes:
32
+ created = (n.get("created_at") or "")[:10]
33
+ table.add_row(
34
+ str(n.get("id", "")),
35
+ n.get("term", ""),
36
+ n.get("context", ""),
37
+ n.get("category", ""),
38
+ n.get("completion_status", ""),
39
+ created,
40
+ )
41
+
42
+ console.print(table)
43
+ if meta:
44
+ console.print(
45
+ f" Page {meta['page']}/{meta['pages']} "
46
+ f"({meta['total']} total)"
47
+ )
48
+
49
+
50
+ def print_note_detail(note: dict):
51
+ term = note.get("term", "Unknown")
52
+ context = note.get("context", "")
53
+ header = f"[bold]{term}[/bold] ({context})"
54
+
55
+ lines = [header, ""]
56
+
57
+ if note.get("category"):
58
+ lines.append(f"Category: {note['category']}")
59
+ domains = [note.get(d) for d in
60
+ ("primary_domain", "secondary_domain", "tertiary_domain")
61
+ if note.get(d)]
62
+ if domains:
63
+ lines.append(f"Domains: {' > '.join(domains)}")
64
+ if note.get("completion_status"):
65
+ lines.append(f"Status: {note['completion_status']}")
66
+ if note.get("model_used"):
67
+ lines.append(f"Model: {note['model_used']} ({note.get('provider', '')})")
68
+ lines.append(f"ID: {note.get('id', '')} Created: {(note.get('created_at') or '')[:10]}")
69
+ lines.append("")
70
+
71
+ console.print(Panel("\n".join(lines), title="Note Detail", border_style="cyan"))
72
+
73
+ if note.get("description"):
74
+ console.print(Panel(Markdown(note["description"]), title="Description"))
75
+
76
+ if note.get("considerations"):
77
+ console.print(Panel(Markdown(note["considerations"]), title="Considerations"))
78
+
79
+ if note.get("examples"):
80
+ for ex in note["examples"]:
81
+ console.print(f" [dim]Example:[/dim] {ex.get('example_text', '')}")
82
+
83
+ if note.get("related_terms"):
84
+ terms = [rt["term_text"] for rt in note["related_terms"]]
85
+ console.print(f" [dim]Related:[/dim] {', '.join(terms)}")
86
+
87
+ if note.get("references"):
88
+ for ref in note["references"]:
89
+ console.print(f" [dim]Ref:[/dim] {ref.get('reference_url', '')}")
90
+
91
+ if note.get("question_answers"):
92
+ console.print()
93
+ for qa in note["question_answers"]:
94
+ console.print(f" [bold]Q:[/bold] {qa.get('question', '')}")
95
+ console.print(f" [dim]A:[/dim] {qa.get('answer', '')}")
96
+ console.print()
97
+
98
+
99
+ def print_paper_table(papers: list, meta: dict | None = None):
100
+ table = Table(title="Papers")
101
+ table.add_column("ID", style="cyan", justify="right")
102
+ table.add_column("Title", style="bold")
103
+ table.add_column("Authors")
104
+ table.add_column("Status")
105
+ table.add_column("Uploaded")
106
+
107
+ for p in papers:
108
+ uploaded = (p.get("uploaded_at") or "")[:10]
109
+ title = p.get("title") or "(untitled)"
110
+ if len(title) > 50:
111
+ title = title[:47] + "..."
112
+ table.add_row(
113
+ str(p.get("id", "")),
114
+ title,
115
+ p.get("authors", "") or "",
116
+ p.get("status", ""),
117
+ uploaded,
118
+ )
119
+
120
+ console.print(table)
121
+ if meta:
122
+ console.print(
123
+ f" Page {meta['page']}/{meta['pages']} "
124
+ f"({meta['total']} total)"
125
+ )
126
+
127
+
128
+ def print_paper_detail(paper: dict):
129
+ title = paper.get("title") or "(untitled)"
130
+ authors = paper.get("authors") or "Unknown"
131
+ header = f"[bold]{title}[/bold]\nby {authors}"
132
+
133
+ lines = [header, ""]
134
+
135
+ if paper.get("url"):
136
+ lines.append(f"URL: {paper['url']}")
137
+ if paper.get("arxiv_id"):
138
+ lines.append(f"arXiv: {paper['arxiv_id']}")
139
+ lines.append(f"Status: {paper.get('status', '')}")
140
+ lines.append(f"ID: {paper.get('id', '')} Uploaded: {(paper.get('uploaded_at') or '')[:10]}")
141
+ lines.append("")
142
+
143
+ console.print(Panel("\n".join(lines), title="Paper Detail", border_style="cyan"))
144
+
145
+ if paper.get("abstract"):
146
+ console.print(Panel(Markdown(paper["abstract"]), title="Abstract"))
147
+
148
+ if paper.get("simple_summary"):
149
+ console.print(Panel(Markdown(paper["simple_summary"]), title="Summary"))
150
+
151
+ if paper.get("key_findings"):
152
+ console.print(Panel(Markdown(paper["key_findings"]), title="Key Findings"))
153
+
154
+ if paper.get("problem_solution_impact"):
155
+ console.print(Panel(Markdown(paper["problem_solution_impact"]), title="Problem / Solution / Impact"))
156
+
157
+
158
+ def print_article_table(articles: list, meta: dict | None = None):
159
+ table = Table(title="Articles")
160
+ table.add_column("ID", style="cyan", justify="right")
161
+ table.add_column("Title", style="bold")
162
+ table.add_column("Domain")
163
+ table.add_column("Credibility")
164
+ table.add_column("Status")
165
+ table.add_column("Fetched")
166
+
167
+ for a in articles:
168
+ fetched = (a.get("fetched_at") or "")[:10]
169
+ title = a.get("title") or "(untitled)"
170
+ if len(title) > 45:
171
+ title = title[:42] + "..."
172
+ table.add_row(
173
+ str(a.get("id", "")),
174
+ title,
175
+ a.get("domain", "") or "",
176
+ a.get("credibility_score", "") or "",
177
+ a.get("status", ""),
178
+ fetched,
179
+ )
180
+
181
+ console.print(table)
182
+ if meta:
183
+ console.print(
184
+ f" Page {meta['page']}/{meta['pages']} "
185
+ f"({meta['total']} total)"
186
+ )
187
+
188
+
189
+ def print_article_detail(article: dict):
190
+ title = article.get("title") or "(untitled)"
191
+ author = article.get("author") or "Unknown"
192
+ header = f"[bold]{title}[/bold]\nby {author}"
193
+
194
+ lines = [header, ""]
195
+
196
+ if article.get("url"):
197
+ lines.append(f"URL: {article['url']}")
198
+ if article.get("domain"):
199
+ lines.append(f"Domain: {article['domain']}")
200
+ if article.get("credibility_score"):
201
+ lines.append(f"Credibility: {article['credibility_score']} ({article.get('bs_issues_count', 0)} issues)")
202
+ lines.append(f"Status: {article.get('status', '')}")
203
+ lines.append(f"ID: {article.get('id', '')} Fetched: {(article.get('fetched_at') or '')[:10]}")
204
+ lines.append("")
205
+
206
+ console.print(Panel("\n".join(lines), title="Article Detail", border_style="cyan"))
207
+
208
+ if article.get("excerpt"):
209
+ console.print(Panel(article["excerpt"], title="Excerpt"))
210
+
211
+ if article.get("simple_summary"):
212
+ console.print(Panel(Markdown(article["simple_summary"]), title="Summary"))
213
+
214
+ if article.get("key_findings"):
215
+ console.print(Panel(Markdown(article["key_findings"]), title="Key Findings"))
216
+
217
+ if article.get("problem_solution_impact"):
218
+ console.print(Panel(Markdown(article["problem_solution_impact"]), title="Problem / Solution / Impact"))
219
+
220
+
221
+ def print_video_table(videos: list, meta: dict | None = None):
222
+ table = Table(title="YouTube Videos")
223
+ table.add_column("ID", style="cyan", justify="right")
224
+ table.add_column("Title", style="bold")
225
+ table.add_column("Channel")
226
+ table.add_column("Status")
227
+ table.add_column("Created")
228
+
229
+ for v in videos:
230
+ created = (v.get("created_at") or "")[:10]
231
+ title = v.get("title") or "(untitled)"
232
+ if len(title) > 45:
233
+ title = title[:42] + "..."
234
+ table.add_row(
235
+ str(v.get("id", "")),
236
+ title,
237
+ v.get("channel_name", "") or "",
238
+ v.get("status", ""),
239
+ created,
240
+ )
241
+
242
+ console.print(table)
243
+ if meta:
244
+ console.print(
245
+ f" Page {meta['page']}/{meta['pages']} "
246
+ f"({meta['total']} total)"
247
+ )
248
+
249
+
250
+ def print_video_detail(video: dict):
251
+ title = video.get("title") or "(untitled)"
252
+ channel = video.get("channel_name") or "Unknown"
253
+ header = f"[bold]{title}[/bold]\nChannel: {channel}"
254
+
255
+ lines = [header, ""]
256
+
257
+ if video.get("video_url"):
258
+ lines.append(f"URL: {video['video_url']}")
259
+ dur = video.get("duration_seconds")
260
+ if dur:
261
+ lines.append(f"Duration: {dur // 60}m {dur % 60}s")
262
+ if video.get("view_count"):
263
+ lines.append(f"Views: {video['view_count']:,}")
264
+ if video.get("has_transcript"):
265
+ lines.append("Transcript: available")
266
+ if video.get("inferred_context"):
267
+ lines.append(f"Context: {video['inferred_context']}")
268
+ lines.append(f"Status: {video.get('status', '')}")
269
+ lines.append(f"ID: {video.get('id', '')} Created: {(video.get('created_at') or '')[:10]}")
270
+ lines.append("")
271
+
272
+ console.print(Panel("\n".join(lines), title="Video Detail", border_style="cyan"))
273
+
274
+ if video.get("summary"):
275
+ console.print(Panel(Markdown(video["summary"]), title="Summary"))
276
+
277
+ if video.get("key_points"):
278
+ points = "\n".join(f"- {p}" for p in video["key_points"])
279
+ console.print(Panel(Markdown(points), title="Key Points"))
280
+
281
+
282
+ def print_tweet_table(tweets: list, meta: dict | None = None):
283
+ table = Table(title="Tweets")
284
+ table.add_column("ID", style="cyan", justify="right")
285
+ table.add_column("Author", style="bold")
286
+ table.add_column("Content")
287
+ table.add_column("Thread")
288
+ table.add_column("Status")
289
+ table.add_column("Created")
290
+
291
+ for t in tweets:
292
+ created = (t.get("created_at") or "")[:10]
293
+ content = t.get("content") or ""
294
+ if len(content) > 50:
295
+ content = content[:47] + "..."
296
+ thread = "Yes" if t.get("is_thread") else ""
297
+ table.add_row(
298
+ str(t.get("id", "")),
299
+ t.get("author_handle", "") or "",
300
+ content,
301
+ thread,
302
+ t.get("status", ""),
303
+ created,
304
+ )
305
+
306
+ console.print(table)
307
+ if meta:
308
+ console.print(
309
+ f" Page {meta['page']}/{meta['pages']} "
310
+ f"({meta['total']} total)"
311
+ )
312
+
313
+
314
+ def print_tweet_detail(tweet: dict):
315
+ author = tweet.get("author_handle") or "Unknown"
316
+ name = tweet.get("author_name") or ""
317
+ header = f"[bold]@{author}[/bold]"
318
+ if name:
319
+ header += f" ({name})"
320
+
321
+ lines = [header, ""]
322
+ if tweet.get("tweet_url"):
323
+ lines.append(f"URL: {tweet['tweet_url']}")
324
+ if tweet.get("is_thread"):
325
+ lines.append(f"Thread: {tweet.get('thread_count', 1)} tweets")
326
+ if tweet.get("credibility_score"):
327
+ lines.append(f"Credibility: {tweet['credibility_score']}")
328
+ if tweet.get("author_verified"):
329
+ lines.append("Verified: Yes")
330
+ lines.append(f"Status: {tweet.get('status', '')}")
331
+ lines.append(f"ID: {tweet.get('id', '')} Created: {(tweet.get('created_at') or '')[:10]}")
332
+ lines.append("")
333
+
334
+ console.print(Panel("\n".join(lines), title="Tweet Detail", border_style="cyan"))
335
+
336
+ content = tweet.get("thread_content") or tweet.get("content")
337
+ if content:
338
+ console.print(Panel(content, title="Content"))
339
+
340
+ if tweet.get("summary"):
341
+ console.print(Panel(Markdown(tweet["summary"]), title="Summary"))
342
+
343
+ if tweet.get("key_points"):
344
+ points = "\n".join(f"- {p}" for p in tweet["key_points"])
345
+ console.print(Panel(Markdown(points), title="Key Points"))
346
+
347
+
348
+ def print_book_table(books: list, meta: dict | None = None):
349
+ table = Table(title="Books")
350
+ table.add_column("ID", style="cyan", justify="right")
351
+ table.add_column("Title", style="bold")
352
+ table.add_column("Authors")
353
+ table.add_column("Status")
354
+ table.add_column("Added")
355
+
356
+ for b in books:
357
+ created = (b.get("created_at") or "")[:10]
358
+ title = b.get("title") or "(untitled)"
359
+ if len(title) > 40:
360
+ title = title[:37] + "..."
361
+ authors = ", ".join(b.get("authors") or []) if b.get("authors") else ""
362
+ table.add_row(
363
+ str(b.get("id", "")),
364
+ title,
365
+ authors,
366
+ b.get("status", ""),
367
+ created,
368
+ )
369
+
370
+ console.print(table)
371
+ if meta:
372
+ console.print(
373
+ f" Page {meta['page']}/{meta['pages']} "
374
+ f"({meta['total']} total)"
375
+ )
376
+
377
+
378
+ def print_book_detail(book: dict):
379
+ title = book.get("title") or "(untitled)"
380
+ authors = ", ".join(book.get("authors") or []) if book.get("authors") else "Unknown"
381
+ header = f"[bold]{title}[/bold]\nby {authors}"
382
+
383
+ lines = [header, ""]
384
+
385
+ if book.get("publisher"):
386
+ lines.append(f"Publisher: {book['publisher']}")
387
+ if book.get("published_date"):
388
+ lines.append(f"Published: {book['published_date']}")
389
+ if book.get("isbn_13"):
390
+ lines.append(f"ISBN: {book['isbn_13']}")
391
+ if book.get("page_count"):
392
+ lines.append(f"Pages: {book['page_count']}")
393
+ if book.get("categories"):
394
+ lines.append(f"Categories: {', '.join(book['categories'])}")
395
+ if book.get("inferred_context"):
396
+ lines.append(f"Context: {book['inferred_context']}")
397
+ lines.append(f"Status: {book.get('status', '')}")
398
+ lines.append(f"ID: {book.get('id', '')} Added: {(book.get('created_at') or '')[:10]}")
399
+ lines.append("")
400
+
401
+ console.print(Panel("\n".join(lines), title="Book Detail", border_style="cyan"))
402
+
403
+ if book.get("description"):
404
+ console.print(Panel(Markdown(book["description"]), title="Description"))
405
+
406
+ if book.get("simple_summary"):
407
+ console.print(Panel(Markdown(book["simple_summary"]), title="Summary"))
408
+
409
+ if book.get("key_findings"):
410
+ console.print(Panel(Markdown(book["key_findings"]), title="Key Findings"))
411
+
412
+
413
+ def print_podcast_table(podcasts: list, meta: dict | None = None):
414
+ table = Table(title="Podcast Episodes")
415
+ table.add_column("ID", style="cyan", justify="right")
416
+ table.add_column("Title", style="bold")
417
+ table.add_column("Podcast")
418
+ table.add_column("Status")
419
+ table.add_column("Created")
420
+
421
+ for p in podcasts:
422
+ created = (p.get("created_at") or "")[:10]
423
+ title = p.get("title") or "(untitled)"
424
+ if len(title) > 40:
425
+ title = title[:37] + "..."
426
+ table.add_row(
427
+ str(p.get("id", "")),
428
+ title,
429
+ p.get("podcast_name", "") or "",
430
+ p.get("status", ""),
431
+ created,
432
+ )
433
+
434
+ console.print(table)
435
+ if meta:
436
+ console.print(
437
+ f" Page {meta['page']}/{meta['pages']} "
438
+ f"({meta['total']} total)"
439
+ )
440
+
441
+
442
+ def print_podcast_detail(podcast: dict):
443
+ title = podcast.get("title") or "(untitled)"
444
+ show = podcast.get("podcast_name") or "Unknown"
445
+ header = f"[bold]{title}[/bold]\nPodcast: {show}"
446
+
447
+ lines = [header, ""]
448
+
449
+ if podcast.get("podcast_author"):
450
+ lines.append(f"Host: {podcast['podcast_author']}")
451
+ if podcast.get("episode_url"):
452
+ lines.append(f"URL: {podcast['episode_url']}")
453
+ dur = podcast.get("duration_seconds")
454
+ if dur:
455
+ lines.append(f"Duration: {dur // 60}m {dur % 60}s")
456
+ if podcast.get("has_transcript"):
457
+ lines.append("Transcript: available")
458
+ if podcast.get("inferred_context"):
459
+ lines.append(f"Context: {podcast['inferred_context']}")
460
+ lines.append(f"Status: {podcast.get('status', '')}")
461
+ lines.append(f"ID: {podcast.get('id', '')} Created: {(podcast.get('created_at') or '')[:10]}")
462
+ lines.append("")
463
+
464
+ console.print(Panel("\n".join(lines), title="Podcast Detail", border_style="cyan"))
465
+
466
+ if podcast.get("description"):
467
+ console.print(Panel(podcast["description"], title="Description"))
468
+
469
+ if podcast.get("summary"):
470
+ console.print(Panel(Markdown(podcast["summary"]), title="Summary"))
471
+
472
+ if podcast.get("key_points"):
473
+ points = "\n".join(f"- {p}" for p in podcast["key_points"])
474
+ console.print(Panel(Markdown(points), title="Key Points"))
475
+
476
+
477
+ def print_patent_table(patents: list, meta: dict | None = None):
478
+ table = Table(title="Patents")
479
+ table.add_column("ID", style="cyan", justify="right")
480
+ table.add_column("Number", style="bold")
481
+ table.add_column("Title")
482
+ table.add_column("Potential")
483
+ table.add_column("Status")
484
+ table.add_column("Fetched")
485
+
486
+ for p in patents:
487
+ fetched = (p.get("fetched_at") or "")[:10]
488
+ title = p.get("title") or "(untitled)"
489
+ if len(title) > 40:
490
+ title = title[:37] + "..."
491
+ table.add_row(
492
+ str(p.get("id", "")),
493
+ p.get("patent_number", "") or "",
494
+ title,
495
+ p.get("commercial_potential", "") or "",
496
+ p.get("status", ""),
497
+ fetched,
498
+ )
499
+
500
+ console.print(table)
501
+ if meta:
502
+ console.print(
503
+ f" Page {meta['page']}/{meta['pages']} "
504
+ f"({meta['total']} total)"
505
+ )
506
+
507
+
508
+ def print_patent_detail(patent: dict):
509
+ title = patent.get("title") or "(untitled)"
510
+ number = patent.get("patent_number") or ""
511
+ header = f"[bold]{title}[/bold]"
512
+ if number:
513
+ header += f"\nPatent: {number}"
514
+
515
+ lines = [header, ""]
516
+
517
+ if patent.get("url"):
518
+ lines.append(f"URL: {patent['url']}")
519
+ if patent.get("inventors"):
520
+ names = [i.get("name", "") for i in patent["inventors"]]
521
+ lines.append(f"Inventors: {', '.join(names)}")
522
+ if patent.get("assignees"):
523
+ names = [a.get("name", "") for a in patent["assignees"]]
524
+ lines.append(f"Assignees: {', '.join(names)}")
525
+ if patent.get("commercial_potential"):
526
+ lines.append(f"Commercial Potential: {patent['commercial_potential']}")
527
+ lines.append(f"Status: {patent.get('status', '')}")
528
+ lines.append(f"ID: {patent.get('id', '')} Fetched: {(patent.get('fetched_at') or '')[:10]}")
529
+ lines.append("")
530
+
531
+ console.print(Panel("\n".join(lines), title="Patent Detail", border_style="cyan"))
532
+
533
+ if patent.get("abstract"):
534
+ console.print(Panel(Markdown(patent["abstract"]), title="Abstract"))
535
+
536
+ if patent.get("simple_summary"):
537
+ console.print(Panel(Markdown(patent["simple_summary"]), title="Summary"))
538
+
539
+ if patent.get("key_findings"):
540
+ console.print(Panel(Markdown(patent["key_findings"]), title="Key Innovations"))
541
+
542
+ if patent.get("claim_analysis"):
543
+ console.print(Panel(Markdown(patent["claim_analysis"]), title="Claim Analysis"))
544
+
545
+
546
+ def print_search_results(results: list, meta: dict | None = None):
547
+ table = Table(title="Search Results")
548
+ table.add_column("Type", style="cyan")
549
+ table.add_column("ID", justify="right")
550
+ table.add_column("Title", style="bold")
551
+ table.add_column("Snippet")
552
+ table.add_column("Score", justify="right")
553
+
554
+ for r in results:
555
+ snippet = r.get("snippet", "")
556
+ if len(snippet) > 80:
557
+ snippet = snippet[:77] + "..."
558
+ table.add_row(
559
+ r.get("type", ""),
560
+ str(r.get("id", "")),
561
+ r.get("title", ""),
562
+ snippet,
563
+ f"{r.get('similarity', 0):.3f}",
564
+ )
565
+
566
+ console.print(table)
567
+ if meta:
568
+ console.print(f" Query: \"{meta.get('query', '')}\" ({meta.get('count', 0)} results)")
569
+
570
+
571
+ def print_queued(data: dict):
572
+ console.print(Panel(
573
+ f"[yellow]Request queued[/yellow]\n\n"
574
+ f" Term: {data.get('term', '')}\n"
575
+ f" Context: {data.get('context', '')}\n"
576
+ f" Request ID: {data.get('request_id', '')}\n"
577
+ f" Task ID: {data.get('celery_task_id', '')}\n"
578
+ f" Status: {data.get('status', 'queued')}\n\n"
579
+ f"[dim]Note creation is async. Check with: tisit note list[/dim]",
580
+ title="Note Queued",
581
+ border_style="yellow",
582
+ ))
@@ -0,0 +1,29 @@
1
+ """Custom exception hierarchy for the TISIT CLI."""
2
+
3
+
4
+ class TisitCLIError(Exception):
5
+ """Base exception for all CLI errors."""
6
+
7
+
8
+ class AuthenticationError(TisitCLIError):
9
+ """Raised when authentication fails (401/403)."""
10
+
11
+
12
+ class APIError(TisitCLIError):
13
+ """Raised on non-2xx API responses."""
14
+
15
+ def __init__(self, message, status_code=None, detail=None):
16
+ self.status_code = status_code
17
+ self.detail = detail
18
+ super().__init__(message)
19
+
20
+
21
+ class NotFoundError(APIError):
22
+ """Raised when a resource is not found (404)."""
23
+
24
+ def __init__(self, message="Resource not found"):
25
+ super().__init__(message, status_code=404)
26
+
27
+
28
+ class ConfigError(TisitCLIError):
29
+ """Raised on configuration problems."""
tisit_cli/main.py ADDED
@@ -0,0 +1,68 @@
1
+ """TISIT CLI entry point."""
2
+ import typer
3
+
4
+ from . import __version__
5
+ from .commands.auth_commands import login, logout, whoami
6
+ from .commands.note_commands import note_app
7
+ from .commands.paper_commands import paper_app
8
+ from .commands.article_commands import article_app
9
+ from .commands.video_commands import video_app
10
+ from .commands.tweet_commands import tweet_app
11
+ from .commands.book_commands import book_app
12
+ from .commands.podcast_commands import podcast_app
13
+ from .commands.patent_commands import patent_app
14
+ from .commands.chat_commands import chat_app
15
+ from .commands.radar_commands import radar_app
16
+ from .commands.graph_commands import graph_app
17
+ from .commands.focus_commands import focus_app
18
+ from .commands.browse_commands import browse
19
+ from .commands.status_commands import status
20
+ from .commands.search_commands import search
21
+
22
+ app = typer.Typer(
23
+ name="tisit",
24
+ help="TISIT CLI - Learn anything, explained intelligently.",
25
+ no_args_is_help=True,
26
+ )
27
+
28
+
29
+ def version_callback(value: bool):
30
+ if value:
31
+ typer.echo(f"tisit-cli {__version__}")
32
+ raise typer.Exit()
33
+
34
+
35
+ @app.callback()
36
+ def main(
37
+ version: bool = typer.Option(
38
+ None, "--version", "-v",
39
+ callback=version_callback, is_eager=True,
40
+ help="Show version and exit.",
41
+ ),
42
+ ):
43
+ """TISIT CLI - terminal-native client for the TISIT learning platform."""
44
+
45
+
46
+ # Auth commands at top level
47
+ app.command()(login)
48
+ app.command()(logout)
49
+ app.command()(whoami)
50
+
51
+ # Sub-command groups
52
+ app.add_typer(note_app, name="note")
53
+ app.add_typer(paper_app, name="paper")
54
+ app.add_typer(article_app, name="article")
55
+ app.add_typer(video_app, name="video")
56
+ app.add_typer(tweet_app, name="tweet")
57
+ app.add_typer(book_app, name="book")
58
+ app.add_typer(podcast_app, name="podcast")
59
+ app.add_typer(patent_app, name="patent")
60
+ app.add_typer(chat_app, name="chat")
61
+ app.add_typer(radar_app, name="radar")
62
+ app.add_typer(graph_app, name="graph")
63
+ app.add_typer(focus_app, name="focus")
64
+
65
+ # Top-level commands
66
+ app.command()(search)
67
+ app.command()(browse)
68
+ app.command()(status)