contexthop 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.
context_hop.py ADDED
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ ContextHop - Carry an AI chat to any other model without starting over.
4
+
5
+ Usage:
6
+ python context_hop.py sample_chat.txt
7
+ python context_hop.py sample_chat.txt -o HANDOFF.md
8
+ python context_hop.py sample_chat.txt --goal "Build a resume website"
9
+
10
+ Input: a plain .txt / .md chat transcript. Works best if lines start with
11
+ "User:" / "You:" / "Assistant:" / "ChatGPT:" / "Claude:" / "Gemini:"
12
+ If no labels are found, the whole file is treated as raw transcript.
13
+
14
+ Output: a HANDOFF.md file containing
15
+ 1. A ready-to-paste starter prompt for the NEW model
16
+ 2. A structured context section (goal, transcript cleaned up)
17
+ 3. The full original transcript (so nothing is lost)
18
+
19
+ No API key. No internet. 100% local.
20
+ """
21
+ import argparse
22
+ import re
23
+ import sys
24
+ from datetime import date
25
+ from pathlib import Path
26
+
27
+ SPEAKERS = r"(User|You|Human|Assistant|ChatGPT|Claude|Gemini|DeepSeek|Perplexity|AI)"
28
+ LINE_RE = re.compile(rf"^\s*{SPEAKERS}\s*:\s*", re.IGNORECASE)
29
+
30
+
31
+ def parse_transcript(text: str):
32
+ """Split a transcript into (speaker, message) turns."""
33
+ turns = []
34
+ current_speaker, current_lines = None, []
35
+ for line in text.splitlines():
36
+ m = LINE_RE.match(line)
37
+ if m:
38
+ if current_speaker is not None:
39
+ turns.append((current_speaker, "\n".join(current_lines).strip()))
40
+ current_speaker = m.group(1).title()
41
+ current_lines = [line[m.end():]]
42
+ else:
43
+ if current_speaker is None:
44
+ current_speaker, current_lines = "User", []
45
+ current_lines.append(line)
46
+ if current_speaker is not None and current_lines:
47
+ turns.append((current_speaker, "\n".join(current_lines).strip()))
48
+ return [(s, msg) for s, msg in turns if msg]
49
+
50
+
51
+ def first_user_goal(turns):
52
+ for speaker, msg in turns:
53
+ if speaker.lower() in ("user", "you", "human"):
54
+ first_line = msg.strip().splitlines()[0].strip()
55
+ return (first_line[:180] + "...") if len(first_line) > 180 else first_line
56
+ return "Not detected - edit this line in HANDOFF.md"
57
+
58
+
59
+ def build_handoff(turns, source_name: str, goal_override: str | None):
60
+ goal = goal_override or first_user_goal(turns)
61
+ today = date.today().isoformat()
62
+ clean = "\n\n".join(f"**{s}:** {msg}" for s, msg in turns)
63
+
64
+ return f"""# ContextHop Handoff
65
+ *Created {today} from `{source_name}` - {len(turns)} messages*
66
+
67
+ ---
68
+
69
+ ## 1. Paste this first in the NEW model
70
+
71
+ > You are continuing a conversation that started on another AI model.
72
+ > Read the context below carefully. Do NOT restart the task and do NOT
73
+ > re-ask questions that are already answered below.
74
+ > First reply with: (1) a 3-line summary of where we are, (2) what you
75
+ > think the next step is. Then wait for my instruction.
76
+
77
+ ## 2. Context
78
+
79
+ **Original goal:** {goal}
80
+
81
+ **Where we left off:** [EDIT ME - 1 line: what was just done / what broke]
82
+
83
+ **Decisions already made:** [EDIT ME - or delete if none]
84
+
85
+ **Next step I want:** [EDIT ME - what the new model should do first]
86
+
87
+ ## 3. Conversation so far
88
+
89
+ {clean}
90
+
91
+ ---
92
+ *Made with ContextHop - https://github.com/aryancodes-dev/contexthop*
93
+ """
94
+
95
+
96
+ def main():
97
+ ap = argparse.ArgumentParser(description="Turn an AI chat transcript into a portable HANDOFF.md")
98
+ ap.add_argument("input", help="Transcript file (.txt or .md)")
99
+ ap.add_argument("-o", "--output", default="HANDOFF.md", help="Output file (default: HANDOFF.md)")
100
+ ap.add_argument("--goal", default=None, help="Override the detected goal line")
101
+ args = ap.parse_args()
102
+
103
+ src = Path(args.input)
104
+ if not src.exists():
105
+ sys.exit(f"File not found: {src}")
106
+
107
+ turns = parse_transcript(src.read_text(encoding="utf-8", errors="ignore"))
108
+ if not turns:
109
+ sys.exit("No messages found in that file.")
110
+
111
+ out = Path(args.output)
112
+ out.write_text(build_handoff(turns, src.name, args.goal), encoding="utf-8")
113
+ print(f"Done: {len(turns)} messages -> {out} ({out.stat().st_size} bytes)")
114
+ print("Next: open HANDOFF.md, fill the 3 [EDIT ME] lines, paste/upload it in the new model.")
115
+
116
+
117
+ if __name__ == "__main__":
118
+ main()
@@ -0,0 +1,77 @@
1
+ Metadata-Version: 2.4
2
+ Name: contexthop
3
+ Version: 0.1.0
4
+ Summary: Carry an AI chat to any other model without starting over — portable HANDOFF.md generator for ChatGPT, Claude & Gemini. No API key, no server.
5
+ Author: Aryan Kumar Shah
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/aryancodes-dev/contexthop
8
+ Project-URL: Repository, https://github.com/aryancodes-dev/contexthop
9
+ Keywords: ai,chatgpt,claude,gemini,llm,context,productivity,developer-tools
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Topic :: Utilities
13
+ Requires-Python: >=3.10
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Dynamic: license-file
17
+
18
+ # ContextHop
19
+
20
+ **Hit your limit on Claude? Continue the same work on ChatGPT, Gemini, or any other model - without explaining everything from the beginning.**
21
+
22
+ ContextHop turns a finished (or interrupted) AI conversation into one portable `HANDOFF.md` file: a starter prompt for the new model + a clean, structured copy of your context. Paste it, and the new model picks up where the old one stopped.
23
+
24
+ No API key. No account. No server. 100% local and free.
25
+
26
+ ## The problem
27
+
28
+ You spend an hour building something with one AI. Then: *"You've reached your limit."*
29
+ You open another model and waste 20 minutes re-explaining the goal, the decisions, the code, and what already failed.
30
+
31
+ Your context should travel with **you**, not stay locked inside one app.
32
+
33
+ ## Two ways to use it
34
+
35
+ ### Way 1 - The Magic Prompt (zero install, works on mobile)
36
+
37
+ Before your limit runs out, paste the prompt in [`prompts/magic_handoff_prompt.md`](prompts/magic_handoff_prompt.md) as your last message on the old model. It writes its own handoff summary. Paste that summary into the new model. Done.
38
+
39
+ ### Way 2 - The Python tool
40
+
41
+ ```bash
42
+ git clone https://github.com/aryancodes-dev/contexthop.git
43
+ cd contexthop
44
+ python context_hop.py your_chat.txt
45
+ # creates HANDOFF.md - fill the 3 [EDIT ME] lines, then paste/upload it in the new model
46
+ ```
47
+
48
+ Works with transcripts exported or copied from ChatGPT, Claude, Gemini, DeepSeek and Perplexity (lines starting with `User:` / `Claude:` / etc.).
49
+
50
+ Try the example:
51
+
52
+ ```bash
53
+ python context_hop.py examples/sample_chat.txt
54
+ ```
55
+
56
+ ## Why not just copy-paste the whole chat?
57
+
58
+ | | Full copy-paste | ContextHop handoff |
59
+ |---|---|---|
60
+ | Tokens on new model | 20,000+ | ~500-1,500 (Magic Prompt) |
61
+ | New model understands state | Has to dig through chat | Goal / decisions / next step up top |
62
+ | Works when limit already hit | Yes, but messy | Yes - transcript mode |
63
+
64
+ ## Roadmap
65
+
66
+ - [x] v0.1 - Python CLI + Magic Prompt
67
+ - [ ] v0.2 - Auto-detect ChatGPT / Claude JSON exports
68
+ - [ ] v0.3 - Simple web page version (paste chat -> download HANDOFF.md)
69
+ - [ ] v0.4 - Browser extension (one-click export)
70
+
71
+ ## Contributing
72
+
73
+ Issues and PRs welcome. Especially: export parsers for new platforms, and translations of the Magic Prompt.
74
+
75
+ ## License
76
+
77
+ MIT - see [LICENSE](LICENSE).
@@ -0,0 +1,7 @@
1
+ context_hop.py,sha256=OWNFezSwZ1b6cqN5qcb_0hCVU7bmkikJBmoRoI1R8FM,4129
2
+ contexthop-0.1.0.dist-info/licenses/LICENSE,sha256=DFcOAMEFO4h--sDOcdzv0PAecGnGzBKJEwL-FyfbNko,1073
3
+ contexthop-0.1.0.dist-info/METADATA,sha256=IFt40vq3TnTJzIl4n8yca-MhXccMy5tdE95LxJevU1g,2979
4
+ contexthop-0.1.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
5
+ contexthop-0.1.0.dist-info/entry_points.txt,sha256=RjrENKXT-Ap71hEtfWa90_ZENOQfhF_7jw_4WLNajQc,48
6
+ contexthop-0.1.0.dist-info/top_level.txt,sha256=93KkM4El4bvVV0dmy3lijpRx2lmDgYZ8SuBjDmcq69I,12
7
+ contexthop-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ contexthop = context_hop:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Aryan Kumar Shah
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ context_hop