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,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
|