1bcoder 0.1.13__tar.gz → 0.1.14__tar.gz
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.
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/1bcoder.egg-info/PKG-INFO +1 -1
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/1bcoder.egg-info/SOURCES.txt +3 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/PKG-INFO +1 -1
- 1bcoder-0.1.14/_bcoder_data/flows/deobfuscate.py +234 -0
- 1bcoder-0.1.14/_bcoder_data/flows/external_help.py +357 -0
- 1bcoder-0.1.14/_bcoder_data/flows/obfuscate.py +268 -0
- 1bcoder-0.1.14/_bcoder_data/proc/action-required.py +65 -0
- 1bcoder-0.1.14/_bcoder_data/proc/add-save.py +40 -0
- 1bcoder-0.1.14/_bcoder_data/proc/assist.py +72 -0
- 1bcoder-0.1.14/_bcoder_data/proc/collect-files.py +60 -0
- 1bcoder-0.1.14/_bcoder_data/proc/ctx_cut.py +37 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/proc/extract-code.py +27 -9
- 1bcoder-0.1.14/_bcoder_data/proc/extract-files.py +55 -0
- 1bcoder-0.1.14/_bcoder_data/proc/extract-list.py +70 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/proc/grounding-check.py +28 -4
- 1bcoder-0.1.14/_bcoder_data/proc/md.py +43 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/proc/mdx.py +32 -2
- 1bcoder-0.1.14/_bcoder_data/proc/pattern-gate.py +49 -0
- 1bcoder-0.1.14/_bcoder_data/proc/rude_words.py +54 -0
- 1bcoder-0.1.14/_bcoder_data/proc/scan-save.py +38 -0
- 1bcoder-0.1.14/_bcoder_data/proc/secret_check.py +47 -0
- 1bcoder-0.1.14/_bcoder_data/proc/sql_readonly_guard.py +68 -0
- 1bcoder-0.1.14/_bcoder_data/proc/tempctx-cut.py +38 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/chat.py +40 -7
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/pyproject.toml +1 -1
- 1bcoder-0.1.13/_bcoder_data/proc/action-required.py +0 -42
- 1bcoder-0.1.13/_bcoder_data/proc/add-save.py +0 -21
- 1bcoder-0.1.13/_bcoder_data/proc/assist.py +0 -48
- 1bcoder-0.1.13/_bcoder_data/proc/collect-files.py +0 -42
- 1bcoder-0.1.13/_bcoder_data/proc/ctx_cut.py +0 -19
- 1bcoder-0.1.13/_bcoder_data/proc/extract-files.py +0 -31
- 1bcoder-0.1.13/_bcoder_data/proc/extract-list.py +0 -51
- 1bcoder-0.1.13/_bcoder_data/proc/md.py +0 -23
- 1bcoder-0.1.13/_bcoder_data/proc/pattern-gate.py +0 -28
- 1bcoder-0.1.13/_bcoder_data/proc/rude_words.py +0 -34
- 1bcoder-0.1.13/_bcoder_data/proc/scan-save.py +0 -19
- 1bcoder-0.1.13/_bcoder_data/proc/secret_check.py +0 -29
- 1bcoder-0.1.13/_bcoder_data/proc/sql_readonly_guard.py +0 -40
- 1bcoder-0.1.13/_bcoder_data/proc/tempctx-cut.py +0 -19
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/1bcoder.egg-info/dependency_links.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/1bcoder.egg-info/entry_points.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/1bcoder.egg-info/requires.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/1bcoder.egg-info/top_level.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/LICENSE +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/README.md +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/__init__.py +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/agents/advance.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/agents/ask.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/agents/compact.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/agents/concepts.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/agents/fill.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/agents/planning.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/agents/scan.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/agents/sqlite.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/agents/websearch.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/aliases.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/doc/FLOWS.md +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/doc/MCP.md +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/doc/OLLAMA_SERVER_PARAM.md +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/doc/PARAM.md +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/doc/PROC.md +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/doc/TRANSLATE.md +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/flows/__pycache__/commit_message.cpython-311.pyc +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/flows/__pycache__/webcrawl.cpython-311.pyc +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/flows/commit_message.py +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/flows/deepagent.py +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/flows/grounding.py +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/flows/py_error_trace.py +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/flows/simargl_files.py +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/flows/visual_search.py +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/flows/webask.py +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/flows/webcrawl.py +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/map.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/proc/regexp-extract.py +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/profiles.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/prompts/analysis.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/prompts/sumarise.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/prompts.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/AddFunction.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/AskProject.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/CheckRequirements.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/DockerMySQL.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/DockerNginx.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/DockerPython.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/DockerStack.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/DuckDuckGoInstant.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/EnvTemplate.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/Explain.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/ExploreProjectStructure.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/GitIgnorePython.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/MySQLDump.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/NewScript.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/PipFreeze.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/PyPI.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/Refactor.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/RunAndFix.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/SQLiteSchema.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/Translate.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/WikiPage.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/WikiSearch.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/auto-bkup.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/edit-control.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/parallel_call.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/personal/content/create-regular-content.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/personal/content/plan.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/personal/test/collect-data-from-test-environment.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/plan.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/remote/create-content-on-remote-server.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/set_ctx.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/simargl-cli_index_files.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/simargl-cli_index_units.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/simargl-cli_search.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/team-map-worker.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/team-search-worker.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/team-summarize.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/team-tree-worker.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/test.txt +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/teams/code-analysis.yaml +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/map_index.py +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/map_query.py +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/setup.cfg +0 -0
- {1bcoder-0.1.13 → 1bcoder-0.1.14}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: 1bcoder
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.14
|
|
4
4
|
Summary: AI coding assistant agent for 1B–7B local models (Ollama, LMStudio, llama.cpp). Terminal REPL with file editing, project map, agents, scripts, and parallel multi-model queries.
|
|
5
5
|
Project-URL: Homepage, https://github.com/szholobetsky/1bcoder
|
|
6
6
|
Project-URL: Repository, https://github.com/szholobetsky/1bcoder
|
|
@@ -32,7 +32,10 @@ _bcoder_data/doc/PROC.md
|
|
|
32
32
|
_bcoder_data/doc/TRANSLATE.md
|
|
33
33
|
_bcoder_data/flows/commit_message.py
|
|
34
34
|
_bcoder_data/flows/deepagent.py
|
|
35
|
+
_bcoder_data/flows/deobfuscate.py
|
|
36
|
+
_bcoder_data/flows/external_help.py
|
|
35
37
|
_bcoder_data/flows/grounding.py
|
|
38
|
+
_bcoder_data/flows/obfuscate.py
|
|
36
39
|
_bcoder_data/flows/py_error_trace.py
|
|
37
40
|
_bcoder_data/flows/simargl_files.py
|
|
38
41
|
_bcoder_data/flows/visual_search.py
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: 1bcoder
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.14
|
|
4
4
|
Summary: AI coding assistant agent for 1B–7B local models (Ollama, LMStudio, llama.cpp). Terminal REPL with file editing, project map, agents, scripts, and parallel multi-model queries.
|
|
5
5
|
Project-URL: Homepage, https://github.com/szholobetsky/1bcoder
|
|
6
6
|
Project-URL: Repository, https://github.com/szholobetsky/1bcoder
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"""Restore obfuscated text back to original business terms using a glossary.
|
|
2
|
+
|
|
3
|
+
Reverses what /flow obfuscate did — maps neutral terms back to real ones.
|
|
4
|
+
Use this after getting a response from a cloud LLM to restore original terminology.
|
|
5
|
+
Safe for any text size: text is passed as a Python string, never expanded inline.
|
|
6
|
+
|
|
7
|
+
── Usage ──────────────────────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
/flow deobfuscate --glossary <name>
|
|
10
|
+
Decode $ (last output — paste the cloud LLM response as a message first).
|
|
11
|
+
|
|
12
|
+
/flow deobfuscate --var <varname> --glossary <name>
|
|
13
|
+
Decode a named session variable.
|
|
14
|
+
|
|
15
|
+
/flow deobfuscate --glossary <name> -> clear_solution
|
|
16
|
+
Capture decoded result into a variable.
|
|
17
|
+
|
|
18
|
+
/flow deobfuscate --glossary <name> --profile <name>
|
|
19
|
+
Use a specific 1bcoder profile (model) for the LLM call.
|
|
20
|
+
Same model as used for obfuscation gives best results.
|
|
21
|
+
|
|
22
|
+
/flow deobfuscate --glossary <name> --force
|
|
23
|
+
Skip LLM — replace every occurrence by direct string substitution (reversed).
|
|
24
|
+
Useful when the cloud LLM preserved terms exactly and no paraphrasing occurred.
|
|
25
|
+
|
|
26
|
+
── Typical workflow ───────────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
Step 1 — obfuscate and send to cloud:
|
|
29
|
+
> describe the task -> task_text
|
|
30
|
+
> /flow obfuscate --var task_text --glossary myproject
|
|
31
|
+
[copy obfuscated text → paste in ChatGPT / Claude / Gemini → get answer]
|
|
32
|
+
|
|
33
|
+
Step 2 — paste response and decode:
|
|
34
|
+
> <paste cloud LLM response here as a plain message> -> cloud_answer
|
|
35
|
+
> /flow deobfuscate --var cloud_answer --glossary myproject -> clear_solution
|
|
36
|
+
> /var get clear_solution
|
|
37
|
+
|
|
38
|
+
Shortcut (using $ = last output):
|
|
39
|
+
> <paste cloud LLM response>
|
|
40
|
+
> /flow deobfuscate --glossary myproject
|
|
41
|
+
|
|
42
|
+
── Notes ──────────────────────────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
- Decoding runs in an isolated context — current conversation is not affected.
|
|
45
|
+
- The glossary is reversed automatically (obfuscated → real).
|
|
46
|
+
- If the cloud LLM slightly changed the obfuscated terms (e.g. "vessels" instead
|
|
47
|
+
of "vessel"), the LLM decoder will still recover them correctly — unlike simple
|
|
48
|
+
string replacement which would miss variations.
|
|
49
|
+
- Use /flow obfuscate --glossary-new <name> to create a new glossary template.
|
|
50
|
+
- For the full guided workflow, use /flow external_help instead.
|
|
51
|
+
"""
|
|
52
|
+
from __future__ import annotations
|
|
53
|
+
import re as _re
|
|
54
|
+
import os as _os
|
|
55
|
+
|
|
56
|
+
# ── reuse helpers from obfuscate ──────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
def _force_replace(text: str, glossary: dict[str, str]) -> str:
|
|
59
|
+
"""Case-preserving direct substitution — no LLM, no context awareness."""
|
|
60
|
+
for real, neutral in glossary.items():
|
|
61
|
+
if not real or not neutral:
|
|
62
|
+
continue
|
|
63
|
+
def _make_rep(n: str):
|
|
64
|
+
def _rep(m: "_re.Match") -> str:
|
|
65
|
+
f = m.group(0)
|
|
66
|
+
if f[0].isupper():
|
|
67
|
+
return n[0].upper() + n[1:]
|
|
68
|
+
return n[0].lower() + n[1:]
|
|
69
|
+
return _rep
|
|
70
|
+
text = _re.sub(_re.escape(real), _make_rep(neutral), text, flags=_re.IGNORECASE)
|
|
71
|
+
return text
|
|
72
|
+
|
|
73
|
+
def _find_glossary(name: str) -> str | None:
|
|
74
|
+
if _os.sep in name or "/" in name:
|
|
75
|
+
return name if _os.path.exists(name) else None
|
|
76
|
+
candidates = [
|
|
77
|
+
_os.path.join(".1bcoder", "glossaries", f"{name}.yaml"),
|
|
78
|
+
_os.path.join(_os.path.expanduser("~"), ".1bcoder", "glossaries", f"{name}.yaml"),
|
|
79
|
+
]
|
|
80
|
+
for p in candidates:
|
|
81
|
+
if _os.path.exists(p):
|
|
82
|
+
return p
|
|
83
|
+
return None
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _load_glossary(name: str) -> dict[str, str]:
|
|
87
|
+
path = _find_glossary(name)
|
|
88
|
+
if not path:
|
|
89
|
+
return {}
|
|
90
|
+
try:
|
|
91
|
+
import yaml as _yaml
|
|
92
|
+
with open(path, encoding="utf-8") as f:
|
|
93
|
+
data = _yaml.safe_load(f)
|
|
94
|
+
return {str(k): str(v) for k, v in (data or {}).items()}
|
|
95
|
+
except ImportError:
|
|
96
|
+
pass
|
|
97
|
+
result = {}
|
|
98
|
+
with open(path, encoding="utf-8") as f:
|
|
99
|
+
for line in f:
|
|
100
|
+
line = line.strip()
|
|
101
|
+
if not line or line.startswith("#"):
|
|
102
|
+
continue
|
|
103
|
+
if ":" in line:
|
|
104
|
+
k, _, v = line.partition(":")
|
|
105
|
+
k, v = k.strip().strip('"\''), v.strip().strip('"\'')
|
|
106
|
+
if k and v:
|
|
107
|
+
result[k] = v
|
|
108
|
+
return result
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _load_profile_first(profile_name: str) -> tuple[str | None, str | None]:
|
|
112
|
+
for pfile in [
|
|
113
|
+
_os.path.join(".1bcoder", "profiles.txt"),
|
|
114
|
+
_os.path.join(_os.path.expanduser("~"), ".1bcoder", "profiles.txt"),
|
|
115
|
+
]:
|
|
116
|
+
if not _os.path.exists(pfile):
|
|
117
|
+
continue
|
|
118
|
+
with open(pfile, encoding="utf-8") as f:
|
|
119
|
+
content = f.read()
|
|
120
|
+
m = _re.search(
|
|
121
|
+
rf'^{_re.escape(profile_name)}:\s*\n((?:[ \t]+\S.*\n?)+)',
|
|
122
|
+
content, _re.MULTILINE
|
|
123
|
+
)
|
|
124
|
+
if not m:
|
|
125
|
+
continue
|
|
126
|
+
for line in m.group(1).splitlines():
|
|
127
|
+
line = line.strip()
|
|
128
|
+
if line and not line.startswith("#"):
|
|
129
|
+
parts = line.split("|")
|
|
130
|
+
if len(parts) >= 2:
|
|
131
|
+
return parts[0].strip(), parts[1].strip()
|
|
132
|
+
return None, None
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _deobfuscate_prompt(text: str, glossary: dict[str, str]) -> str:
|
|
136
|
+
# reverse: obfuscated → real
|
|
137
|
+
pairs = "\n".join(f" {v} → {k}" for k, v in glossary.items())
|
|
138
|
+
return (
|
|
139
|
+
"Your task is to restore the following text by replacing neutral/obfuscated terms "
|
|
140
|
+
"back to their original business terminology according to the glossary provided. "
|
|
141
|
+
"Keep all technical meaning intact. Handle plurals and grammatical forms naturally. "
|
|
142
|
+
"Do not add explanations or commentary. Output only the restored text.\n\n"
|
|
143
|
+
f"Glossary (obfuscated → original):\n{pairs}\n\n"
|
|
144
|
+
f"Text to restore:\n{text}"
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
# ── entry point ────────────────────────────────────────────────────────────────
|
|
149
|
+
|
|
150
|
+
def run(chat, args: str):
|
|
151
|
+
var_m = _re.search(r'--var\s+(\w+)', args)
|
|
152
|
+
glossary_m = _re.search(r'--glossary\s+(\S+)', args)
|
|
153
|
+
profile_m = _re.search(r'--profile\s+(\S+)', args)
|
|
154
|
+
force = "--force" in args
|
|
155
|
+
|
|
156
|
+
if not glossary_m:
|
|
157
|
+
print(__doc__)
|
|
158
|
+
return
|
|
159
|
+
|
|
160
|
+
gname = glossary_m.group(1)
|
|
161
|
+
glossary = _load_glossary(gname)
|
|
162
|
+
|
|
163
|
+
if not glossary:
|
|
164
|
+
gpath = _find_glossary(gname)
|
|
165
|
+
if not gpath:
|
|
166
|
+
print(f"[deobfuscate] glossary '{gname}' not found.")
|
|
167
|
+
print(f" Create it: /flow obfuscate --glossary-new {gname}")
|
|
168
|
+
else:
|
|
169
|
+
print(f"[deobfuscate] glossary '{gname}' is empty: {gpath}")
|
|
170
|
+
return
|
|
171
|
+
|
|
172
|
+
# ── get text ──
|
|
173
|
+
if var_m:
|
|
174
|
+
text = chat._vars.get(var_m.group(1), "")
|
|
175
|
+
if not text:
|
|
176
|
+
print(f"[deobfuscate] variable '{var_m.group(1)}' is empty or not set")
|
|
177
|
+
print( " Paste the cloud LLM response as a message, capture it:")
|
|
178
|
+
print( " > <paste response> -> cloud_answer")
|
|
179
|
+
print(f" > /flow deobfuscate --var cloud_answer --glossary {gname}")
|
|
180
|
+
return
|
|
181
|
+
else:
|
|
182
|
+
text = chat._last_output
|
|
183
|
+
if not text:
|
|
184
|
+
print("[deobfuscate] nothing to decode — paste the cloud LLM response first:")
|
|
185
|
+
print(" > <paste response here>")
|
|
186
|
+
print(f" > /flow deobfuscate --glossary {gname}")
|
|
187
|
+
return
|
|
188
|
+
|
|
189
|
+
# ── force mode: direct string substitution, reversed glossary ──
|
|
190
|
+
if force:
|
|
191
|
+
rev_glossary = {v: k for k, v in glossary.items()}
|
|
192
|
+
print(f"[deobfuscate] FORCE mode — {len(rev_glossary)} terms, direct substitution, text: {len(text)} chars")
|
|
193
|
+
chat._sep("DECODED")
|
|
194
|
+
reply = _force_replace(text, rev_glossary)
|
|
195
|
+
print(reply)
|
|
196
|
+
chat.last_reply = reply
|
|
197
|
+
chat._last_output = reply
|
|
198
|
+
print(f"\n[deobfuscate] done (force) — original terminology restored")
|
|
199
|
+
return
|
|
200
|
+
|
|
201
|
+
# ── switch profile if requested ──
|
|
202
|
+
orig_model = getattr(chat, "_model", None)
|
|
203
|
+
orig_host = getattr(chat, "_host", None)
|
|
204
|
+
switched = False
|
|
205
|
+
|
|
206
|
+
if profile_m:
|
|
207
|
+
phost, pmodel = _load_profile_first(profile_m.group(1))
|
|
208
|
+
if phost and pmodel:
|
|
209
|
+
chat._host = phost
|
|
210
|
+
chat._model = pmodel
|
|
211
|
+
switched = True
|
|
212
|
+
print(f"[deobfuscate] using profile '{profile_m.group(1)}': {pmodel}")
|
|
213
|
+
else:
|
|
214
|
+
print(f"[deobfuscate] profile '{profile_m.group(1)}' not found — using current model")
|
|
215
|
+
|
|
216
|
+
# ── run LLM in isolated context ──
|
|
217
|
+
prompt = _deobfuscate_prompt(text, glossary)
|
|
218
|
+
temp_msgs = [
|
|
219
|
+
{"role": "system", "content": "You are a precise text rewriter. Follow glossary instructions exactly."},
|
|
220
|
+
{"role": "user", "content": prompt},
|
|
221
|
+
]
|
|
222
|
+
|
|
223
|
+
print(f"[deobfuscate] glossary: {gname} ({len(glossary)} terms, reversed) text: {len(text)} chars")
|
|
224
|
+
chat._sep("DECODED")
|
|
225
|
+
reply = chat._stream_chat(temp_msgs)
|
|
226
|
+
|
|
227
|
+
if switched:
|
|
228
|
+
chat._model = orig_model
|
|
229
|
+
chat._host = orig_host
|
|
230
|
+
|
|
231
|
+
if reply:
|
|
232
|
+
chat.last_reply = reply
|
|
233
|
+
chat._last_output = reply
|
|
234
|
+
print(f"\n[deobfuscate] done — original terminology restored")
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
"""Ask a cloud LLM for help without revealing sensitive business terms.
|
|
2
|
+
|
|
3
|
+
Guides you through the full privacy-safe workflow:
|
|
4
|
+
1. Obfuscate your text (local model replaces real terms with neutral ones)
|
|
5
|
+
2. You manually copy the result to any cloud LLM (ChatGPT, Claude, Gemini, etc.)
|
|
6
|
+
3. You paste the cloud response back
|
|
7
|
+
4. Deobfuscate the response (local model restores real terminology)
|
|
8
|
+
|
|
9
|
+
No cloud credentials required. No LiteLLM. Works with any service — even manual
|
|
10
|
+
web browser usage. The cloud provider never sees your real business terms.
|
|
11
|
+
|
|
12
|
+
── Usage ──────────────────────────────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
/flow external_help --glossary <name>
|
|
15
|
+
Obfuscate $ (last LLM output) and print step-by-step instructions.
|
|
16
|
+
|
|
17
|
+
/flow external_help --var <varname> --glossary <name>
|
|
18
|
+
Obfuscate a named session variable.
|
|
19
|
+
|
|
20
|
+
/flow external_help --glossary <name> --profile <name>
|
|
21
|
+
Use a specific local model profile for obfuscation.
|
|
22
|
+
Recommended for best quality: qwen3:4b, nemotron-mini, phi4-mini.
|
|
23
|
+
|
|
24
|
+
/flow external_help --glossary <name> --cloud-profile <name>
|
|
25
|
+
Full automatic pipeline: obfuscate → send to cloud LLM → deobfuscate.
|
|
26
|
+
Use when you have a cloud LLM configured as a 1bcoder profile.
|
|
27
|
+
|
|
28
|
+
/flow external_help --glossary <name> --force
|
|
29
|
+
Use direct string substitution for obfuscation instead of LLM.
|
|
30
|
+
Guarantees every occurrence is replaced, including camelCase identifiers.
|
|
31
|
+
Combine with --cloud-profile for fully automatic pipeline with force obfuscation.
|
|
32
|
+
|
|
33
|
+
/flow external_help --glossary <name> --decode --var cloud_answer
|
|
34
|
+
Deobfuscate a cloud response (shortcut for /flow deobfuscate).
|
|
35
|
+
|
|
36
|
+
── Full example session (automatic — cloud profile configured) ────────────────
|
|
37
|
+
|
|
38
|
+
> describe the linear optimisation problem -> task_text
|
|
39
|
+
> /flow external_help --var task_text --glossary tanker \
|
|
40
|
+
--profile local_nemotron --cloud-profile claude_api
|
|
41
|
+
|
|
42
|
+
[obfuscates locally → sends to cloud → deobfuscates locally]
|
|
43
|
+
→ clear solution with real terminology
|
|
44
|
+
|
|
45
|
+
── Full example session (manual — no cloud profile) ──────────────────────────
|
|
46
|
+
|
|
47
|
+
> describe the linear optimisation problem -> task_text
|
|
48
|
+
> /flow external_help --var task_text --glossary tanker --profile local_nemotron
|
|
49
|
+
|
|
50
|
+
── OBFUSCATED ────────────────────────────────
|
|
51
|
+
Optimize vessel routing for liquid cargo delivery to loading terminals...
|
|
52
|
+
── END ───────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
══ NEXT STEPS ════════════════════════════════
|
|
55
|
+
1. Copy the obfuscated text above
|
|
56
|
+
2. Paste it into ChatGPT / Claude.ai / Gemini (any cloud LLM)
|
|
57
|
+
3. Get the answer from the cloud LLM
|
|
58
|
+
4. Come back here and paste the answer as a plain message:
|
|
59
|
+
> <paste cloud response here> -> cloud_answer
|
|
60
|
+
5. Then decode it:
|
|
61
|
+
> /flow deobfuscate --var cloud_answer --glossary tanker
|
|
62
|
+
|
|
63
|
+
── Glossary management ────────────────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
Create a new glossary template:
|
|
66
|
+
/flow obfuscate --glossary-new <name>
|
|
67
|
+
|
|
68
|
+
Glossary location:
|
|
69
|
+
.1bcoder/glossaries/<name>.yaml ← project-local
|
|
70
|
+
~/.1bcoder/glossaries/<name>.yaml ← global (shared across projects)
|
|
71
|
+
|
|
72
|
+
Glossary format:
|
|
73
|
+
tanker: vessel
|
|
74
|
+
oil: liquid cargo
|
|
75
|
+
port: loading terminal
|
|
76
|
+
pipeline: distribution network
|
|
77
|
+
|
|
78
|
+
── Why this approach works ────────────────────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
Cloud LLMs solve the STRUCTURE of your problem, not the CONTENT.
|
|
81
|
+
A linear optimisation problem about "vessels and liquid cargo" has
|
|
82
|
+
the same mathematical structure as one about "tankers and oil".
|
|
83
|
+
The cloud model returns a structurally correct solution — your local
|
|
84
|
+
model restores the real terminology. Neither side sees the full picture.
|
|
85
|
+
|
|
86
|
+
── Notes ──────────────────────────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
- Small models (< 3B) may fail to follow glossary instructions correctly.
|
|
89
|
+
Use --profile to specify a 4B+ model for better results.
|
|
90
|
+
- Use /flow obfuscate and /flow deobfuscate for a more granular workflow
|
|
91
|
+
where you control each step separately.
|
|
92
|
+
- Save this workflow as a script: /script save external_help
|
|
93
|
+
"""
|
|
94
|
+
from __future__ import annotations
|
|
95
|
+
import re as _re
|
|
96
|
+
import os as _os
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# ── helpers (same as obfuscate/deobfuscate) ────────────────────────────────────
|
|
100
|
+
|
|
101
|
+
def _find_glossary(name: str) -> str | None:
|
|
102
|
+
if _os.sep in name or "/" in name:
|
|
103
|
+
return name if _os.path.exists(name) else None
|
|
104
|
+
for p in [
|
|
105
|
+
_os.path.join(".1bcoder", "glossaries", f"{name}.yaml"),
|
|
106
|
+
_os.path.join(_os.path.expanduser("~"), ".1bcoder", "glossaries", f"{name}.yaml"),
|
|
107
|
+
]:
|
|
108
|
+
if _os.path.exists(p):
|
|
109
|
+
return p
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _load_glossary(name: str) -> dict[str, str]:
|
|
114
|
+
path = _find_glossary(name)
|
|
115
|
+
if not path:
|
|
116
|
+
return {}
|
|
117
|
+
try:
|
|
118
|
+
import yaml as _yaml
|
|
119
|
+
with open(path, encoding="utf-8") as f:
|
|
120
|
+
data = _yaml.safe_load(f)
|
|
121
|
+
return {str(k): str(v) for k, v in (data or {}).items()}
|
|
122
|
+
except ImportError:
|
|
123
|
+
pass
|
|
124
|
+
result = {}
|
|
125
|
+
with open(path, encoding="utf-8") as f:
|
|
126
|
+
for line in f:
|
|
127
|
+
line = line.strip()
|
|
128
|
+
if not line or line.startswith("#"):
|
|
129
|
+
continue
|
|
130
|
+
if ":" in line:
|
|
131
|
+
k, _, v = line.partition(":")
|
|
132
|
+
k, v = k.strip().strip('"\''), v.strip().strip('"\'')
|
|
133
|
+
if k and v:
|
|
134
|
+
result[k] = v
|
|
135
|
+
return result
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _load_profile_first(profile_name: str) -> tuple[str | None, str | None]:
|
|
139
|
+
for pfile in [
|
|
140
|
+
_os.path.join(".1bcoder", "profiles.txt"),
|
|
141
|
+
_os.path.join(_os.path.expanduser("~"), ".1bcoder", "profiles.txt"),
|
|
142
|
+
]:
|
|
143
|
+
if not _os.path.exists(pfile):
|
|
144
|
+
continue
|
|
145
|
+
with open(pfile, encoding="utf-8") as f:
|
|
146
|
+
content = f.read()
|
|
147
|
+
m = _re.search(
|
|
148
|
+
rf'^{_re.escape(profile_name)}:\s*\n((?:[ \t]+\S.*\n?)+)',
|
|
149
|
+
content, _re.MULTILINE
|
|
150
|
+
)
|
|
151
|
+
if not m:
|
|
152
|
+
continue
|
|
153
|
+
for line in m.group(1).splitlines():
|
|
154
|
+
line = line.strip()
|
|
155
|
+
if line and not line.startswith("#"):
|
|
156
|
+
parts = line.split("|")
|
|
157
|
+
if len(parts) >= 2:
|
|
158
|
+
return parts[0].strip(), parts[1].strip()
|
|
159
|
+
return None, None
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _run_llm(chat, system_prompt: str, user_prompt: str,
|
|
163
|
+
profile_name: str | None) -> str | None:
|
|
164
|
+
orig_model = getattr(chat, "_model", None)
|
|
165
|
+
orig_host = getattr(chat, "_host", None)
|
|
166
|
+
switched = False
|
|
167
|
+
|
|
168
|
+
if profile_name:
|
|
169
|
+
phost, pmodel = _load_profile_first(profile_name)
|
|
170
|
+
if phost and pmodel:
|
|
171
|
+
chat._host = phost
|
|
172
|
+
chat._model = pmodel
|
|
173
|
+
switched = True
|
|
174
|
+
print(f"[external_help] profile '{profile_name}': {pmodel}")
|
|
175
|
+
else:
|
|
176
|
+
print(f"[external_help] profile '{profile_name}' not found — using current model")
|
|
177
|
+
|
|
178
|
+
temp_msgs = [
|
|
179
|
+
{"role": "system", "content": system_prompt},
|
|
180
|
+
{"role": "user", "content": user_prompt},
|
|
181
|
+
]
|
|
182
|
+
reply = chat._stream_chat(temp_msgs)
|
|
183
|
+
|
|
184
|
+
if switched:
|
|
185
|
+
chat._model = orig_model
|
|
186
|
+
chat._host = orig_host
|
|
187
|
+
|
|
188
|
+
return reply
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
# ── entry point ────────────────────────────────────────────────────────────────
|
|
192
|
+
|
|
193
|
+
def _force_replace(text: str, glossary: dict[str, str]) -> str:
|
|
194
|
+
"""Case-preserving direct substitution — no LLM, no context awareness."""
|
|
195
|
+
for real, neutral in glossary.items():
|
|
196
|
+
if not real or not neutral:
|
|
197
|
+
continue
|
|
198
|
+
def _make_rep(n: str):
|
|
199
|
+
def _rep(m: "_re.Match") -> str:
|
|
200
|
+
f = m.group(0)
|
|
201
|
+
if f[0].isupper():
|
|
202
|
+
return n[0].upper() + n[1:]
|
|
203
|
+
return n[0].lower() + n[1:]
|
|
204
|
+
return _rep
|
|
205
|
+
text = _re.sub(_re.escape(real), _make_rep(neutral), text, flags=_re.IGNORECASE)
|
|
206
|
+
return text
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def run(chat, args: str):
|
|
210
|
+
var_m = _re.search(r'--var\s+(\w+)', args)
|
|
211
|
+
glossary_m = _re.search(r'--glossary\s+(\S+)', args)
|
|
212
|
+
profile_m = _re.search(r'--profile\s+(\S+)', args)
|
|
213
|
+
cloud_m = _re.search(r'--cloud-profile\s+(\S+)', args)
|
|
214
|
+
decode = "--decode" in args
|
|
215
|
+
force = "--force" in args
|
|
216
|
+
|
|
217
|
+
if not glossary_m:
|
|
218
|
+
print(__doc__)
|
|
219
|
+
return
|
|
220
|
+
|
|
221
|
+
gname = glossary_m.group(1)
|
|
222
|
+
glossary = _load_glossary(gname)
|
|
223
|
+
local_profile = profile_m.group(1) if profile_m else None
|
|
224
|
+
cloud_profile = cloud_m.group(1) if cloud_m else None
|
|
225
|
+
|
|
226
|
+
if not glossary:
|
|
227
|
+
gpath = _find_glossary(gname)
|
|
228
|
+
if not gpath:
|
|
229
|
+
print(f"[external_help] glossary '{gname}' not found.")
|
|
230
|
+
print(f" Create it: /flow obfuscate --glossary-new {gname}")
|
|
231
|
+
else:
|
|
232
|
+
print(f"[external_help] glossary '{gname}' is empty: {gpath}")
|
|
233
|
+
return
|
|
234
|
+
|
|
235
|
+
# ── get text ──
|
|
236
|
+
if var_m:
|
|
237
|
+
text = chat._vars.get(var_m.group(1), "")
|
|
238
|
+
if not text:
|
|
239
|
+
print(f"[external_help] variable '{var_m.group(1)}' is not set")
|
|
240
|
+
print(" Use /var get to list available variables")
|
|
241
|
+
return
|
|
242
|
+
else:
|
|
243
|
+
text = chat._last_output
|
|
244
|
+
if not text:
|
|
245
|
+
print("[external_help] no text to process — use --var <name> or run after an LLM response")
|
|
246
|
+
return
|
|
247
|
+
|
|
248
|
+
# ── decode mode (shortcut for deobfuscate) ──
|
|
249
|
+
profile = local_profile # used in decode/obfuscate single-step calls
|
|
250
|
+
if decode:
|
|
251
|
+
pairs = "\n".join(f" {v} → {k}" for k, v in glossary.items())
|
|
252
|
+
prompt = (
|
|
253
|
+
"Restore the following text by replacing neutral terms back to their "
|
|
254
|
+
"original business terminology according to the glossary. "
|
|
255
|
+
"Handle plurals and grammar naturally. Output only the restored text.\n\n"
|
|
256
|
+
f"Glossary (obfuscated → original):\n{pairs}\n\nText:\n{text}"
|
|
257
|
+
)
|
|
258
|
+
print(f"[external_help] decoding with glossary '{gname}' ({len(glossary)} terms)")
|
|
259
|
+
chat._sep("DECODED")
|
|
260
|
+
reply = _run_llm(
|
|
261
|
+
chat,
|
|
262
|
+
"You are a precise text rewriter. Follow glossary instructions exactly.",
|
|
263
|
+
prompt, profile
|
|
264
|
+
)
|
|
265
|
+
if reply:
|
|
266
|
+
chat.last_reply = reply
|
|
267
|
+
chat._last_output = reply
|
|
268
|
+
print(f"\n[external_help] done — terminology restored")
|
|
269
|
+
return
|
|
270
|
+
|
|
271
|
+
# ── step 1: obfuscate (force = direct replacement, otherwise local LLM) ──
|
|
272
|
+
if force:
|
|
273
|
+
print(f"[external_help] step 1/3 — obfuscating ({len(glossary)} terms, FORCE/direct)")
|
|
274
|
+
chat._sep("OBFUSCATED")
|
|
275
|
+
obf_text = _force_replace(text, glossary)
|
|
276
|
+
print(obf_text)
|
|
277
|
+
else:
|
|
278
|
+
pairs = "\n".join(f" {k} → {v}" for k, v in glossary.items())
|
|
279
|
+
obf_prompt = (
|
|
280
|
+
"Rewrite the following text by replacing specific terms with neutral equivalents "
|
|
281
|
+
"according to the glossary. Keep all technical meaning intact. Handle plurals and "
|
|
282
|
+
"grammar naturally. Do not add explanations. Output only the rewritten text.\n\n"
|
|
283
|
+
f"Glossary:\n{pairs}\n\nText:\n{text}"
|
|
284
|
+
)
|
|
285
|
+
print(f"[external_help] step 1/3 — obfuscating ({len(glossary)} terms, local model)")
|
|
286
|
+
chat._sep("OBFUSCATED")
|
|
287
|
+
obf_text = _run_llm(
|
|
288
|
+
chat,
|
|
289
|
+
"You are a precise text rewriter. Follow glossary instructions exactly.",
|
|
290
|
+
obf_prompt, local_profile
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
if not obf_text:
|
|
294
|
+
return
|
|
295
|
+
|
|
296
|
+
# ── step 2: cloud LLM ──
|
|
297
|
+
if cloud_profile:
|
|
298
|
+
# automatic: send to cloud profile
|
|
299
|
+
phost, pmodel = _load_profile_first(cloud_profile)
|
|
300
|
+
if not phost:
|
|
301
|
+
print(f"[external_help] cloud profile '{cloud_profile}' not found in profiles.txt")
|
|
302
|
+
print(f" Available profiles: /parallel profile list")
|
|
303
|
+
print(f" Falling back to manual workflow ↓")
|
|
304
|
+
cloud_profile = None # fall through to manual
|
|
305
|
+
else:
|
|
306
|
+
print(f"\n[external_help] step 2/3 — asking cloud LLM ({pmodel})")
|
|
307
|
+
chat._sep("CLOUD RESPONSE")
|
|
308
|
+
cloud_reply = _run_llm(chat, "", obf_text, cloud_profile)
|
|
309
|
+
|
|
310
|
+
if not cloud_reply:
|
|
311
|
+
print("[external_help] cloud LLM returned no response")
|
|
312
|
+
return
|
|
313
|
+
|
|
314
|
+
# ── step 3: deobfuscate (local) ──
|
|
315
|
+
rev_pairs = "\n".join(f" {v} → {k}" for k, v in glossary.items())
|
|
316
|
+
deobf_prompt = (
|
|
317
|
+
"Restore the following text by replacing neutral terms back to their "
|
|
318
|
+
"original business terminology according to the glossary. "
|
|
319
|
+
"Handle plurals and grammar naturally. Output only the restored text.\n\n"
|
|
320
|
+
f"Glossary (obfuscated → original):\n{rev_pairs}\n\nText:\n{cloud_reply}"
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
print(f"\n[external_help] step 3/3 — deobfuscating (local model)")
|
|
324
|
+
chat._sep("DECODED")
|
|
325
|
+
final = _run_llm(
|
|
326
|
+
chat,
|
|
327
|
+
"You are a precise text rewriter. Follow glossary instructions exactly.",
|
|
328
|
+
deobf_prompt, local_profile
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
if final:
|
|
332
|
+
chat.last_reply = final
|
|
333
|
+
chat._last_output = final
|
|
334
|
+
print(f"\n[external_help] done — full pipeline complete")
|
|
335
|
+
return
|
|
336
|
+
|
|
337
|
+
# ── manual workflow (no cloud profile) ──
|
|
338
|
+
chat.last_reply = obf_text
|
|
339
|
+
chat._last_output = obf_text
|
|
340
|
+
|
|
341
|
+
sep = "═" * 54
|
|
342
|
+
print(f"\n{sep}")
|
|
343
|
+
print(" NEXT STEPS (manual cloud step)")
|
|
344
|
+
print(sep)
|
|
345
|
+
print(" 1. Copy the obfuscated text above")
|
|
346
|
+
print(" 2. Paste it into any cloud LLM:")
|
|
347
|
+
print(" ChatGPT → chat.openai.com")
|
|
348
|
+
print(" Claude → claude.ai")
|
|
349
|
+
print(" Gemini → gemini.google.com")
|
|
350
|
+
print(" 3. Get the answer from the cloud LLM")
|
|
351
|
+
print(" 4. Come back here — paste the answer as a plain message:")
|
|
352
|
+
print(" > <paste cloud response here> -> cloud_answer")
|
|
353
|
+
print(" 5. Decode the response:")
|
|
354
|
+
print(f" > /flow deobfuscate --var cloud_answer --glossary {gname}")
|
|
355
|
+
print(sep)
|
|
356
|
+
print(f"\n Tip: if you have a cloud profile, use --cloud-profile <name> next time")
|
|
357
|
+
print(f" for a fully automatic pipeline without manual copy-paste")
|