messy2json 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.
messy2json/__init__.py ADDED
@@ -0,0 +1 @@
1
+ # messy2json
messy2json/main.py ADDED
@@ -0,0 +1,370 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ messy2json
4
+ ----------
5
+ Global CLI tool. Paste any messy text — email, meeting notes, voice-to-text —
6
+ and get back strict JSON: {summary, action_items, deadline}
7
+
8
+ Usage (after pip install .):
9
+ messy2json # interactive loop
10
+ messy2json -f notes.txt # read from file
11
+ cat notes.txt | messy2json # pipe input
12
+ messy2json -f notes.txt -o out.json # save result to file
13
+
14
+ API key is safely stored in the platform-appropriate system config directory.
15
+ Override anytime by setting the GROQ_API_KEY environment variable.
16
+ """
17
+
18
+ import argparse
19
+ import json
20
+ import os
21
+ import pathlib
22
+ import sys
23
+ import time
24
+
25
+ from groq import Groq
26
+ from rich.console import Console
27
+ from rich.panel import Panel
28
+ from rich.syntax import Syntax
29
+
30
+ try:
31
+ import pyperclip
32
+ CLIPBOARD_AVAILABLE = True
33
+ except ImportError:
34
+ CLIPBOARD_AVAILABLE = False
35
+
36
+ console = Console()
37
+
38
+ # ── config paths (Cross-Platform Compliant) ──────────────────────────────────
39
+ if os.name == "nt": # Windows
40
+ CONFIG_DIR = pathlib.Path(os.environ.get("APPDATA", pathlib.Path.home())) / "messy2json"
41
+ else: # macOS / Linux
42
+ CONFIG_DIR = pathlib.Path.home() / ".config" / "messy2json"
43
+
44
+ CONFIG_FILE = CONFIG_DIR / "config.json"
45
+
46
+ DEFAULT_MODEL = "llama-3.3-70b-versatile"
47
+ MAX_ATTEMPTS = 3
48
+ REQUIRED_KEYS = {"summary", "action_items", "deadline"}
49
+
50
+ SYSTEM_PROMPT = """You are a strict information-extraction engine.
51
+
52
+ You will be given raw, possibly messy text: an email, meeting notes, or a rambling
53
+ voice-to-text transcript. It may contain typos, filler words, run-on sentences, or
54
+ no clear structure at all.
55
+
56
+ Extract exactly this JSON object and return NOTHING else - no markdown fences, no
57
+ commentary, no preamble, no explanation:
58
+
59
+ {
60
+ "summary": "one to three sentence plain-English summary of what the text is about",
61
+ "action_items": ["short actionable task", "another task"],
62
+ "deadline": "the deadline mentioned in the text (in its own words), or null if none is mentioned"
63
+ }
64
+
65
+ Rules:
66
+ - Output ONLY that JSON object.
67
+ - "action_items" is always a JSON array of strings. Use [] if there are none.
68
+ - "deadline" is always a JSON string or JSON null. Never omit the key, never use "N/A" or "".
69
+ - Never add any key other than summary, action_items, deadline.
70
+ - Even if the text is garbled, very short, or nearly empty, still return the full
71
+ JSON object with your best-effort summary and empty/null values where nothing
72
+ was found. Do not refuse and do not error out.
73
+ """
74
+
75
+
76
+ # ── config manager ────────────────────────────────────────────────────────────
77
+
78
+ def _load_config() -> dict:
79
+ try:
80
+ return json.loads(CONFIG_FILE.read_text())
81
+ except Exception:
82
+ return {}
83
+
84
+
85
+ def _save_config(data: dict):
86
+ CONFIG_DIR.mkdir(parents=True, exist_ok=True)
87
+ CONFIG_FILE.write_text(json.dumps(data, indent=2))
88
+
89
+
90
+ def _first_time_setup() -> str:
91
+ console.print()
92
+ console.print(
93
+ Panel(
94
+ "No API key found.\n\n"
95
+ "Get a free key at [bold cyan]https://console.groq.com[/bold cyan]\n"
96
+ "[dim](no credit card needed — sign up and click API Keys)[/dim]\n\n"
97
+ "Your key will be securely saved to your local system configuration\n"
98
+ "directory and never asked again.",
99
+ title="[bold yellow]⚙ First Time Setup[/bold yellow]",
100
+ border_style="yellow",
101
+ expand=False,
102
+ )
103
+ )
104
+
105
+ while True:
106
+ try:
107
+ key = input("\n Enter your Groq API key: ").strip()
108
+ except KeyboardInterrupt:
109
+ console.print("\n[dim]Setup cancelled.[/dim]")
110
+ sys.exit(0)
111
+
112
+ if not key:
113
+ console.print("[bold red]✗[/bold red] No key entered. Please try again or press Ctrl+C to exit.")
114
+ continue
115
+
116
+ # Live authentication check over the network before committing to disk
117
+ with console.status("[bold yellow]Validating API key with Groq...[/bold yellow]", spinner="dots"):
118
+ try:
119
+ test_client = Groq(api_key=key)
120
+ test_client.chat.completions.create(
121
+ model=DEFAULT_MODEL,
122
+ messages=[{"role": "user", "content": "ping"}],
123
+ max_tokens=1,
124
+ )
125
+ break # Validation passed! Exit the authentication loop
126
+ except Exception as e:
127
+ console.print(f"\n[bold red]✗[/bold red] Authentication failed! The API key is invalid.")
128
+ console.print(f"[dim]Groq API Error: {e}[/dim]")
129
+ console.print("[bold yellow] Please double-check your key and try again.[/bold yellow]\n")
130
+
131
+ _save_config({"api_key": key, "model": DEFAULT_MODEL})
132
+ console.print("[bold green]✓[/bold green] Key verified and saved successfully!\n")
133
+ return key
134
+
135
+
136
+ def get_api_key() -> str:
137
+ """Priority: env var → config file → first-time setup prompt."""
138
+ key = os.environ.get("GROQ_API_KEY", "").strip()
139
+ if key:
140
+ return key
141
+
142
+ key = _load_config().get("api_key", "").strip()
143
+ if key:
144
+ return key
145
+
146
+ return _first_time_setup()
147
+
148
+
149
+ def get_model() -> str:
150
+ """Read model from config file, fall back to default."""
151
+ env_model = os.environ.get("GROQ_MODEL", "").strip()
152
+ if env_model:
153
+ return env_model
154
+ return _load_config().get("model", DEFAULT_MODEL)
155
+
156
+
157
+ # ── core extraction ───────────────────────────────────────────────────────────
158
+
159
+ def _safe_fallback(reason: str) -> dict:
160
+ return {
161
+ "summary": f"Could not process input: {reason}",
162
+ "action_items": [],
163
+ "deadline": None,
164
+ }
165
+
166
+
167
+ def _validate(data) -> tuple[bool, str]:
168
+ if not isinstance(data, dict):
169
+ return False, "top-level JSON is not an object"
170
+ missing = REQUIRED_KEYS - data.keys()
171
+ if missing:
172
+ return False, f"missing keys: {sorted(missing)}"
173
+ if not isinstance(data["summary"], str):
174
+ return False, "'summary' must be a string"
175
+ if not isinstance(data["action_items"], list) or not all(
176
+ isinstance(x, str) for x in data["action_items"]
177
+ ):
178
+ return False, "'action_items' must be a list of strings"
179
+ if data["deadline"] is not None and not isinstance(data["deadline"], str):
180
+ return False, "'deadline' must be a string or null"
181
+ return True, ""
182
+
183
+
184
+ def extract_json(raw_text: str, client: Groq, model: str) -> dict:
185
+ if not raw_text or not raw_text.strip():
186
+ return _safe_fallback("empty input")
187
+
188
+ messages = [
189
+ {"role": "system", "content": SYSTEM_PROMPT},
190
+ {"role": "user", "content": raw_text},
191
+ ]
192
+
193
+ last_error = "unknown error"
194
+ for attempt in range(1, MAX_ATTEMPTS + 1):
195
+ try:
196
+ response = client.chat.completions.create(
197
+ model=model,
198
+ messages=messages,
199
+ response_format={"type": "json_object"},
200
+ temperature=0.2,
201
+ max_tokens=1024,
202
+ )
203
+ content = response.choices[0].message.content
204
+
205
+ try:
206
+ data = json.loads(content)
207
+ except json.JSONDecodeError as e:
208
+ last_error = f"invalid JSON syntax ({e})"
209
+ messages.append({"role": "assistant", "content": content})
210
+ messages.append({
211
+ "role": "user",
212
+ "content": (
213
+ f"That was not valid JSON ({e}). Reply again with ONLY the "
214
+ "corrected JSON object, no other text."
215
+ ),
216
+ })
217
+ continue
218
+
219
+ ok, err = _validate(data)
220
+ if ok:
221
+ return {k: data[k] for k in REQUIRED_KEYS}
222
+
223
+ last_error = f"schema mismatch ({err})"
224
+ messages.append({"role": "assistant", "content": content})
225
+ messages.append({
226
+ "role": "user",
227
+ "content": (
228
+ f"That JSON didn't match the required schema ({err}). Reply again "
229
+ "with ONLY the corrected JSON object, no other text."
230
+ ),
231
+ })
232
+
233
+ except Exception as e:
234
+ last_error = str(e)
235
+ if attempt < MAX_ATTEMPTS:
236
+ time.sleep(1.5 * attempt)
237
+
238
+ return _safe_fallback(last_error)
239
+
240
+
241
+ # ── UI helpers ────────────────────────────────────────────────────────────────
242
+
243
+ def show_banner(model: str):
244
+ console.print()
245
+ console.print(
246
+ Panel(
247
+ "[bold]messy2json[/bold]\n"
248
+ "Paste any messy text — email, meeting notes, voice-to-text — "
249
+ "and get back structured JSON.\n\n"
250
+ f"[dim]Model :[/dim] [yellow]{model}[/yellow]\n"
251
+ "[dim]Output :[/dim] [cyan]summary[/cyan] [cyan]action_items[/cyan] [cyan]deadline[/cyan]\n"
252
+ "[dim]GitHub :[/dim] [bold link=https://github.com/hamzatahir06/Messy2JSON]https://github.com/hamzatahir06/Messy2JSON[/bold link]",
253
+ title="[bold green]🤖 Agent Ready[/bold green]",
254
+ border_style="green",
255
+ expand=False,
256
+ )
257
+ )
258
+ console.print(
259
+ " [dim]HOW TO USE[/dim]\n"
260
+ " • Type [bold cyan]/do[/bold cyan] on a new line and press Enter to extract JSON\n"
261
+ " • Press [bold red]Ctrl+C[/bold red] at any time to exit\n"
262
+ )
263
+
264
+ def _read_one_input() -> str:
265
+ console.print("[dim]─────────────────────────────────────[/dim]")
266
+ console.print("[dim]Paste your text below (or Ctrl+C to stop):[/dim]\n")
267
+ lines = []
268
+ while True:
269
+ try:
270
+ line = input()
271
+ except KeyboardInterrupt:
272
+ console.print("\n\n[bold green]Agent signing off.[/bold green] Goodbye!\n")
273
+ sys.exit(0)
274
+
275
+ if line.strip() == "/do":
276
+ break
277
+ lines.append(line)
278
+ return "\n".join(lines)
279
+
280
+
281
+ def _print_result(output_str: str, is_error: bool, output_file: str | None):
282
+ console.print()
283
+ if is_error:
284
+ console.print("[bold red]✗ Something went wrong[/bold red]")
285
+ else:
286
+ console.print("[bold green]✓ Extracted JSON[/bold green]")
287
+
288
+ console.print(Syntax(output_str, "json", theme="ansi_dark", word_wrap=True))
289
+
290
+ if CLIPBOARD_AVAILABLE and not is_error:
291
+ try:
292
+ pyperclip.copy(output_str)
293
+ console.print("[dim]✓ Copied to clipboard[/dim]")
294
+ except Exception:
295
+ console.print("[dim]⚠ Clipboard unavailable on this system[/dim]")
296
+
297
+ if output_file:
298
+ try:
299
+ with open(output_file, "w", encoding="utf-8") as f:
300
+ f.write(output_str)
301
+ console.print(f"[dim]✓ Saved to {output_file}[/dim]")
302
+ except Exception as e:
303
+ console.print(f"[bold red]✗ Failed to save output file:[/bold red] {e}")
304
+
305
+
306
+ def _execute_processing_pipeline(raw_text: str, client: Groq, model: str, output_file: str | None, status_msg: str):
307
+ """Centralized extraction engine to prevent code repetition."""
308
+ with console.status(status_msg, spinner="dots"):
309
+ result = extract_json(raw_text, client, model=model)
310
+
311
+ output_str = json.dumps(result, indent=2, ensure_ascii=False)
312
+ is_error = result["summary"].startswith("Could not process input:")
313
+ _print_result(output_str, is_error, output_file)
314
+
315
+
316
+ # ── entry point ───────────────────────────────────────────────────────────────
317
+
318
+ def main():
319
+ parser = argparse.ArgumentParser(
320
+ prog="messy2json",
321
+ description="Turn messy text into strict JSON.",
322
+ )
323
+ parser.add_argument("-f", "--file", help="read from a file instead of interactive mode")
324
+ parser.add_argument("-o", "--output", help="also save the JSON result to this file")
325
+ parser.add_argument("--model", help=f"override Groq model (default: {DEFAULT_MODEL})")
326
+ args = parser.parse_args()
327
+
328
+ api_key = get_api_key()
329
+ model = args.model or get_model()
330
+ client = Groq(api_key=api_key)
331
+
332
+ # ── non-interactive mode (Files / Streams) ────────────────────────────────
333
+ if args.file or not sys.stdin.isatty():
334
+ if args.file:
335
+ try:
336
+ raw_text = open(args.file, encoding="utf-8").read()
337
+ except FileNotFoundError:
338
+ console.print(f"[bold red]✗ Error:[/bold red] The file '{args.file}' could not be found.")
339
+ sys.exit(1)
340
+ except Exception as e:
341
+ console.print(f"[bold red]✗ Error reading file:[/bold red] {e}")
342
+ sys.exit(1)
343
+ else:
344
+ raw_text = sys.stdin.read()
345
+
346
+ _execute_processing_pipeline(
347
+ raw_text, client, model, args.output, "[bold yellow]Processing input data...[/bold yellow]"
348
+ )
349
+ return
350
+
351
+ # ── interactive loop mode ─────────────────────────────────────────────────
352
+ show_banner(model)
353
+ try:
354
+ while True:
355
+ raw_text = _read_one_input()
356
+ if not raw_text.strip():
357
+ console.print("[dim]⚠ No text entered — paste something and type /do[/dim]\n")
358
+ continue
359
+
360
+ _execute_processing_pipeline(
361
+ raw_text, client, model, args.output, "[bold yellow] Processing...[/bold yellow]"
362
+ )
363
+ console.print()
364
+
365
+ except KeyboardInterrupt:
366
+ console.print("\n\n[bold green]Agent signing off.[/bold green] Goodbye!\n")
367
+
368
+
369
+ if __name__ == "__main__":
370
+ main()
@@ -0,0 +1,214 @@
1
+ Metadata-Version: 2.4
2
+ Name: messy2json
3
+ Version: 0.1.0
4
+ Summary: Turns messy text (emails, notes, voice-to-text) into structured JSON using Groq LLM.
5
+ Project-URL: Homepage, https://github.com/hamzatahir06/Messy2JSON
6
+ Project-URL: Repository, https://github.com/hamzatahir06/Messy2JSON
7
+ Project-URL: Bug Tracker, https://github.com/hamzatahir06/Messy2JSON/issues
8
+ Requires-Python: >=3.12
9
+ Description-Content-Type: text/markdown
10
+ Requires-Dist: groq>=1.5.0
11
+ Requires-Dist: rich>=13.7.0
12
+ Requires-Dist: pyperclip>=1.8.2
13
+ Requires-Dist: python-dotenv>=1.0.0
14
+
15
+ # 🤖 Messy2JSON
16
+
17
+ > **Transform messy, unstructured text into clean, structured JSON using Groq LLMs.**
18
+
19
+ Messy2JSON is a lightweight, cross-platform Python CLI that converts meeting notes, emails, chat logs, and voice-to-text transcripts into structured JSON containing a summary, action items, and deadlines. Built with reliability in mind, it validates AI responses, automatically repairs malformed outputs, and provides a seamless terminal experience.
20
+
21
+ ---
22
+
23
+ ## ✨ Features
24
+
25
+ - 🤖 AI-powered information extraction using **Groq LLMs**
26
+ - 🔒 One-time API key setup with live validation
27
+ - 🧠 Automatic JSON self-repair using an AI reflection loop
28
+ - ✅ Strict schema validation for reliable output
29
+ - 📋 Automatic clipboard copy after successful extraction
30
+ - 📂 Read input from files, interactive mode, or Unix pipes
31
+ - 💾 Save structured JSON directly to a file
32
+ - 💻 Cross-platform support (Windows, macOS, Linux)
33
+ - ⚡ Fast, clean terminal interface powered by Rich
34
+
35
+ ---
36
+
37
+ ## 📦 Output Format
38
+
39
+ Every input is converted into the following JSON structure:
40
+
41
+ ```json
42
+ {
43
+ "summary": "A concise summary of the input.",
44
+ "action_items": [
45
+ "Action item 1",
46
+ "Action item 2"
47
+ ],
48
+ "deadline": "Mentioned deadline or null"
49
+ }
50
+ ```
51
+
52
+ ---
53
+
54
+ ## 🚀 Installation
55
+
56
+ ### Clone the repository
57
+
58
+ ```bash
59
+ git clone https://github.com/hamzatahir06/Messy2JSON.git
60
+ cd Messy2JSON
61
+ ```
62
+
63
+ ### Create a virtual environment
64
+
65
+ **Linux / macOS**
66
+
67
+ ```bash
68
+ python -m venv venv
69
+ source venv/bin/activate
70
+ ```
71
+
72
+ **Windows**
73
+
74
+ ```powershell
75
+ python -m venv venv
76
+ .\venv\Scripts\activate
77
+ ```
78
+
79
+ ### Install the package
80
+
81
+ ```bash
82
+ pip install -e .
83
+ ```
84
+
85
+ > **PyPI support is coming soon.**
86
+
87
+ ```bash
88
+ pip install messy2json
89
+ ```
90
+
91
+ ---
92
+
93
+ ## ⚡ Quick Start
94
+
95
+ ### Interactive Mode
96
+
97
+ ```bash
98
+ messy2json
99
+ ```
100
+
101
+ Paste your text, then type:
102
+
103
+ ```text
104
+ /do
105
+ ```
106
+
107
+ to generate structured JSON.
108
+
109
+ ---
110
+
111
+ ### Read from a File
112
+
113
+ ```bash
114
+ messy2json -f meeting_notes.txt
115
+ ```
116
+
117
+ ---
118
+
119
+ ### Read from Standard Input
120
+
121
+ ```bash
122
+ cat notes.txt | messy2json
123
+ ```
124
+
125
+ ---
126
+
127
+ ### Save Output to a File
128
+
129
+ ```bash
130
+ messy2json -f notes.txt -o output.json
131
+ ```
132
+
133
+ ---
134
+
135
+ ### Use a Different Model
136
+
137
+ ```bash
138
+ messy2json --model llama-3.3-70b-versatile
139
+ ```
140
+
141
+ ---
142
+
143
+ ## ⚙️ Configuration
144
+
145
+ During the first run, Messy2JSON securely prompts for your Groq API key, validates it online, and stores it locally using your operating system's standard configuration directory.
146
+
147
+ You can also provide configuration through environment variables.
148
+
149
+ | Variable | Description |
150
+ |----------|-------------|
151
+ | `GROQ_API_KEY` | Your Groq API key |
152
+ | `GROQ_MODEL` | Override the default model |
153
+
154
+ ---
155
+
156
+ ## 📁 Project Structure
157
+
158
+ ```text
159
+ Messy2JSON/
160
+
161
+ ├── messy2json/
162
+ │ ├── __init__.py
163
+ │ └── main.py
164
+
165
+ ├── pyproject.toml
166
+ ├── README.md
167
+ └── LICENSE
168
+ ```
169
+
170
+ ---
171
+
172
+ ## 🛠 Built With
173
+
174
+ - **Python 3.12+**
175
+ - **Groq Python SDK**
176
+ - **Rich**
177
+ - **Pyperclip**
178
+
179
+ ---
180
+
181
+ ## 💡 Why Messy2JSON?
182
+
183
+ Unlike basic AI wrappers, Messy2JSON is designed to produce **consistent and dependable JSON**.
184
+
185
+ It includes:
186
+
187
+ - Schema validation
188
+ - Automatic retry and self-correction
189
+ - Live API key verification
190
+ - Cross-platform configuration management
191
+ - Clipboard integration
192
+ - Interactive and non-interactive workflows
193
+
194
+ The goal is simple: **give developers structured output they can immediately use in automation pipelines, scripts, or applications.**
195
+
196
+ ---
197
+
198
+ ## 📄 License
199
+
200
+ This project is licensed under the **MIT License**.
201
+
202
+ See the [LICENSE](LICENSE) file for details.
203
+
204
+ ---
205
+
206
+ ## ⭐ Support the Project
207
+
208
+ If you find Messy2JSON useful, consider giving the repository a ⭐ on GitHub.
209
+
210
+ It helps others discover the project and motivates future development.
211
+
212
+ ---
213
+
214
+ **Made with ❤️ by [Hamza Tahir](https://github.com/hamzatahir06)**
@@ -0,0 +1,7 @@
1
+ messy2json/__init__.py,sha256=GWYiwxhH6tSINqIHCRLuJKqGcWOfIsXRgXMlsMZZ-1g,12
2
+ messy2json/main.py,sha256=zdbUn_-mzDZxrkiwCQm3T-V7rBo-FsTci5Ml0VLuvGA,14135
3
+ messy2json-0.1.0.dist-info/METADATA,sha256=QtQCBFgqzTwBlrOIiEtFb-JNMiSvY7ARSOXbytDjIoA,4490
4
+ messy2json-0.1.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
5
+ messy2json-0.1.0.dist-info/entry_points.txt,sha256=2sfg1V15EFSv9rc9QDsrnzxeIcO-vI7X4hViVYV1qcs,52
6
+ messy2json-0.1.0.dist-info/top_level.txt,sha256=YNtQEHOcJlm_muLpTMR8H9sr49zFF9Sy955clWEXel8,11
7
+ messy2json-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ messy2json = messy2json.main:main
@@ -0,0 +1 @@
1
+ messy2json