applyr 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.
applyr/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """applyr — CLI job application tracker designed for AI coding agents."""
2
+
3
+ __version__ = "0.1.0"
applyr/cli.py ADDED
@@ -0,0 +1,245 @@
1
+ """CLI entry point — argparse setup, command routing, error handling."""
2
+
3
+ import sys
4
+
5
+ from applyr import __version__
6
+ from applyr.commands import (
7
+ cmd_add,
8
+ cmd_delete,
9
+ cmd_export,
10
+ cmd_followups,
11
+ cmd_gaps,
12
+ cmd_init,
13
+ cmd_list,
14
+ cmd_pipeline,
15
+ cmd_search,
16
+ cmd_show,
17
+ cmd_stats,
18
+ cmd_summary,
19
+ cmd_trends,
20
+ cmd_update,
21
+ )
22
+ from applyr.cv import cmd_cv_generate, cmd_cv_pdf
23
+ from applyr.db import init_db, VALID_STATUSES
24
+
25
+ USAGE = f"""\
26
+ applyr v{__version__} — CLI job application tracker for AI coding agents
27
+
28
+ Usage: applyr <command> [options]
29
+
30
+ Commands:
31
+ init Set up ~/.applyr/ (config, database, templates)
32
+ add '<json>' Register a new job offer
33
+ list [--status S] [--sort F] List offers (default: last 50)
34
+ pipeline [--min-score N] View offers grouped by status
35
+ show <id> Show full offer details
36
+ update <id> <status> [opts] Update offer status
37
+ delete <id> Delete an offer
38
+ search <keyword> [--status S] Search by company/title/notes/tech_stack
39
+ stats Conversion funnel and metrics
40
+ gaps [--limit N] Skill gap analysis
41
+ followups Pending/overdue follow-ups
42
+ trends [--period week|month] Application trends over time
43
+ summary [--json] Weekly summary (LLM-optimized)
44
+ export [--format csv|json] Export all data
45
+ version Show version
46
+ help Show this help
47
+
48
+ Statuses: {' | '.join(VALID_STATUSES)}
49
+
50
+ Docs: https://github.com/DeibyGS/applyr
51
+ """
52
+
53
+
54
+ def _safe_int(value: str, label: str = "ID") -> int | None:
55
+ """Parse string to int, print error and return None on failure."""
56
+ try:
57
+ return int(value)
58
+ except ValueError:
59
+ print(f"Error: {label} must be an integer, got: {value}")
60
+ return None
61
+
62
+
63
+ def _get_flag(args: list[str], flag: str) -> str | None:
64
+ """Get the value after a flag like --status, or None if not present."""
65
+ if flag not in args:
66
+ return None
67
+ idx = args.index(flag)
68
+ if idx + 1 >= len(args):
69
+ print(f"Error: {flag} requires a value")
70
+ return None
71
+ return args[idx + 1]
72
+
73
+
74
+ def _has_flag(args: list[str], flag: str) -> bool:
75
+ """Check if a flag is present."""
76
+ return flag in args
77
+
78
+
79
+ def main():
80
+ args = sys.argv[1:]
81
+
82
+ if not args or args[0] in ("help", "--help", "-h"):
83
+ print(USAGE)
84
+ return
85
+
86
+ cmd = args[0]
87
+
88
+ if cmd in ("version", "--version", "-v"):
89
+ print(f"applyr v{__version__}")
90
+ return
91
+
92
+ # Commands that need DB initialized
93
+ if cmd != "init":
94
+ init_db()
95
+
96
+ if cmd == "init":
97
+ cmd_init()
98
+
99
+ elif cmd == "add":
100
+ if len(args) < 2:
101
+ print("Usage: applyr add '<json>'")
102
+ print(" Required: title")
103
+ print(" Optional: company, summary, date_received, date_applied,")
104
+ print(" compatibility_pct, status, canal, work_mode,")
105
+ print(" location, salary_min, salary_max, seniority_level,")
106
+ print(" role_category, tech_stack, cover_letter, job_url,")
107
+ print(" contact_name, contact_role, topics, notes")
108
+ return
109
+ cmd_add(args[1])
110
+
111
+ elif cmd == "list":
112
+ status = _get_flag(args, "--status")
113
+ sort_by = _get_flag(args, "--sort") or "date_applied"
114
+ limit = None
115
+ if _has_flag(args, "--all"):
116
+ limit = -1
117
+ elif _get_flag(args, "--limit") is not None:
118
+ limit = _safe_int(_get_flag(args, "--limit"), "--limit")
119
+ if limit is None:
120
+ return
121
+ cmd_list(status_filter=status, sort_by=sort_by, limit=limit)
122
+
123
+ elif cmd == "pipeline":
124
+ min_score = 0
125
+ raw = _get_flag(args, "--min-score")
126
+ if raw is not None:
127
+ min_score = _safe_int(raw, "--min-score")
128
+ if min_score is None:
129
+ return
130
+ cmd_pipeline(min_score=min_score)
131
+
132
+ elif cmd == "show":
133
+ if len(args) < 2:
134
+ print("Usage: applyr show <id>")
135
+ return
136
+ offer_id = _safe_int(args[1])
137
+ if offer_id is not None:
138
+ cmd_show(offer_id)
139
+
140
+ elif cmd == "update":
141
+ if len(args) < 3:
142
+ print("Usage: applyr update <id> <status> [--notes '...'] [--canal '...']")
143
+ print(f" Statuses: {', '.join(VALID_STATUSES)}")
144
+ return
145
+ offer_id = _safe_int(args[1])
146
+ if offer_id is None:
147
+ return
148
+ status = args[2]
149
+ notes = _get_flag(args, "--notes")
150
+ canal = _get_flag(args, "--canal")
151
+ cmd_update(offer_id, status, notes, canal)
152
+
153
+ elif cmd == "delete":
154
+ if len(args) < 2:
155
+ print("Usage: applyr delete <id>")
156
+ return
157
+ offer_id = _safe_int(args[1])
158
+ if offer_id is not None:
159
+ cmd_delete(offer_id)
160
+
161
+ elif cmd == "search":
162
+ if len(args) < 2:
163
+ print("Usage: applyr search <keyword> [--status S]")
164
+ return
165
+ status = _get_flag(args, "--status")
166
+ # Collect keyword (everything between cmd and flags)
167
+ keyword_parts = []
168
+ i = 1
169
+ while i < len(args):
170
+ if args[i].startswith("--"):
171
+ i += 2 # skip flag and its value
172
+ continue
173
+ keyword_parts.append(args[i])
174
+ i += 1
175
+ keyword = " ".join(keyword_parts)
176
+ cmd_search(keyword, status_filter=status)
177
+
178
+ elif cmd == "stats":
179
+ cmd_stats()
180
+
181
+ elif cmd == "gaps":
182
+ limit = 10
183
+ raw = _get_flag(args, "--limit")
184
+ if raw is not None:
185
+ limit = _safe_int(raw, "--limit")
186
+ if limit is None:
187
+ return
188
+ cmd_gaps(limit=limit)
189
+
190
+ elif cmd == "followups":
191
+ cmd_followups()
192
+
193
+ elif cmd == "trends":
194
+ period = _get_flag(args, "--period") or "week"
195
+ if period not in ("week", "month"):
196
+ print(f"Error: period must be 'week' or 'month', got: {period}")
197
+ return
198
+ cmd_trends(period=period)
199
+
200
+ elif cmd == "summary":
201
+ as_json = _has_flag(args, "--json")
202
+ cmd_summary(as_json=as_json)
203
+
204
+ elif cmd == "export":
205
+ fmt = _get_flag(args, "--format") or "csv"
206
+ if fmt not in ("csv", "json"):
207
+ print(f"Error: format must be 'csv' or 'json', got: {fmt}")
208
+ return
209
+ filepath = _get_flag(args, "--file")
210
+ cmd_export(fmt=fmt, filepath=filepath)
211
+
212
+ elif cmd == "cv":
213
+ if len(args) < 2:
214
+ print("Usage:")
215
+ print(" applyr cv generate <id> [--template ats] Generate CV for offer")
216
+ print(" applyr cv pdf <html-file> [--output f.pdf] HTML to PDF via Chrome")
217
+ return
218
+ subcmd = args[1]
219
+ if subcmd == "generate":
220
+ if len(args) < 3:
221
+ print("Usage: applyr cv generate <id> [--template ats]")
222
+ return
223
+ offer_id = _safe_int(args[2])
224
+ if offer_id is None:
225
+ return
226
+ template = _get_flag(args, "--template") or "ats"
227
+ cmd_cv_generate(offer_id, template=template)
228
+ elif subcmd == "pdf":
229
+ if len(args) < 3:
230
+ print("Usage: applyr cv pdf <html-file> [--output file.pdf]")
231
+ return
232
+ html_file = args[2]
233
+ output = _get_flag(args, "--output")
234
+ cmd_cv_pdf(html_file, output=output)
235
+ else:
236
+ print(f"Unknown cv subcommand: '{subcmd}'")
237
+ print(" Available: generate, pdf")
238
+
239
+ else:
240
+ print(f"Unknown command: '{cmd}'")
241
+ print(f"Run 'applyr help' to see available commands.")
242
+
243
+
244
+ if __name__ == "__main__":
245
+ main()