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.
Files changed (122) hide show
  1. {1bcoder-0.1.13 → 1bcoder-0.1.14}/1bcoder.egg-info/PKG-INFO +1 -1
  2. {1bcoder-0.1.13 → 1bcoder-0.1.14}/1bcoder.egg-info/SOURCES.txt +3 -0
  3. {1bcoder-0.1.13 → 1bcoder-0.1.14}/PKG-INFO +1 -1
  4. 1bcoder-0.1.14/_bcoder_data/flows/deobfuscate.py +234 -0
  5. 1bcoder-0.1.14/_bcoder_data/flows/external_help.py +357 -0
  6. 1bcoder-0.1.14/_bcoder_data/flows/obfuscate.py +268 -0
  7. 1bcoder-0.1.14/_bcoder_data/proc/action-required.py +65 -0
  8. 1bcoder-0.1.14/_bcoder_data/proc/add-save.py +40 -0
  9. 1bcoder-0.1.14/_bcoder_data/proc/assist.py +72 -0
  10. 1bcoder-0.1.14/_bcoder_data/proc/collect-files.py +60 -0
  11. 1bcoder-0.1.14/_bcoder_data/proc/ctx_cut.py +37 -0
  12. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/proc/extract-code.py +27 -9
  13. 1bcoder-0.1.14/_bcoder_data/proc/extract-files.py +55 -0
  14. 1bcoder-0.1.14/_bcoder_data/proc/extract-list.py +70 -0
  15. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/proc/grounding-check.py +28 -4
  16. 1bcoder-0.1.14/_bcoder_data/proc/md.py +43 -0
  17. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/proc/mdx.py +32 -2
  18. 1bcoder-0.1.14/_bcoder_data/proc/pattern-gate.py +49 -0
  19. 1bcoder-0.1.14/_bcoder_data/proc/rude_words.py +54 -0
  20. 1bcoder-0.1.14/_bcoder_data/proc/scan-save.py +38 -0
  21. 1bcoder-0.1.14/_bcoder_data/proc/secret_check.py +47 -0
  22. 1bcoder-0.1.14/_bcoder_data/proc/sql_readonly_guard.py +68 -0
  23. 1bcoder-0.1.14/_bcoder_data/proc/tempctx-cut.py +38 -0
  24. {1bcoder-0.1.13 → 1bcoder-0.1.14}/chat.py +40 -7
  25. {1bcoder-0.1.13 → 1bcoder-0.1.14}/pyproject.toml +1 -1
  26. 1bcoder-0.1.13/_bcoder_data/proc/action-required.py +0 -42
  27. 1bcoder-0.1.13/_bcoder_data/proc/add-save.py +0 -21
  28. 1bcoder-0.1.13/_bcoder_data/proc/assist.py +0 -48
  29. 1bcoder-0.1.13/_bcoder_data/proc/collect-files.py +0 -42
  30. 1bcoder-0.1.13/_bcoder_data/proc/ctx_cut.py +0 -19
  31. 1bcoder-0.1.13/_bcoder_data/proc/extract-files.py +0 -31
  32. 1bcoder-0.1.13/_bcoder_data/proc/extract-list.py +0 -51
  33. 1bcoder-0.1.13/_bcoder_data/proc/md.py +0 -23
  34. 1bcoder-0.1.13/_bcoder_data/proc/pattern-gate.py +0 -28
  35. 1bcoder-0.1.13/_bcoder_data/proc/rude_words.py +0 -34
  36. 1bcoder-0.1.13/_bcoder_data/proc/scan-save.py +0 -19
  37. 1bcoder-0.1.13/_bcoder_data/proc/secret_check.py +0 -29
  38. 1bcoder-0.1.13/_bcoder_data/proc/sql_readonly_guard.py +0 -40
  39. 1bcoder-0.1.13/_bcoder_data/proc/tempctx-cut.py +0 -19
  40. {1bcoder-0.1.13 → 1bcoder-0.1.14}/1bcoder.egg-info/dependency_links.txt +0 -0
  41. {1bcoder-0.1.13 → 1bcoder-0.1.14}/1bcoder.egg-info/entry_points.txt +0 -0
  42. {1bcoder-0.1.13 → 1bcoder-0.1.14}/1bcoder.egg-info/requires.txt +0 -0
  43. {1bcoder-0.1.13 → 1bcoder-0.1.14}/1bcoder.egg-info/top_level.txt +0 -0
  44. {1bcoder-0.1.13 → 1bcoder-0.1.14}/LICENSE +0 -0
  45. {1bcoder-0.1.13 → 1bcoder-0.1.14}/README.md +0 -0
  46. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/__init__.py +0 -0
  47. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/agents/advance.txt +0 -0
  48. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/agents/ask.txt +0 -0
  49. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/agents/compact.txt +0 -0
  50. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/agents/concepts.txt +0 -0
  51. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/agents/fill.txt +0 -0
  52. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/agents/planning.txt +0 -0
  53. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/agents/scan.txt +0 -0
  54. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/agents/sqlite.txt +0 -0
  55. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/agents/websearch.txt +0 -0
  56. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/aliases.txt +0 -0
  57. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/doc/FLOWS.md +0 -0
  58. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/doc/MCP.md +0 -0
  59. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/doc/OLLAMA_SERVER_PARAM.md +0 -0
  60. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/doc/PARAM.md +0 -0
  61. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/doc/PROC.md +0 -0
  62. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/doc/TRANSLATE.md +0 -0
  63. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/flows/__pycache__/commit_message.cpython-311.pyc +0 -0
  64. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/flows/__pycache__/webcrawl.cpython-311.pyc +0 -0
  65. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/flows/commit_message.py +0 -0
  66. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/flows/deepagent.py +0 -0
  67. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/flows/grounding.py +0 -0
  68. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/flows/py_error_trace.py +0 -0
  69. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/flows/simargl_files.py +0 -0
  70. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/flows/visual_search.py +0 -0
  71. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/flows/webask.py +0 -0
  72. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/flows/webcrawl.py +0 -0
  73. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/map.txt +0 -0
  74. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/proc/regexp-extract.py +0 -0
  75. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/profiles.txt +0 -0
  76. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/prompts/analysis.txt +0 -0
  77. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/prompts/sumarise.txt +0 -0
  78. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/prompts.txt +0 -0
  79. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/AddFunction.txt +0 -0
  80. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/AskProject.txt +0 -0
  81. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/CheckRequirements.txt +0 -0
  82. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/DockerMySQL.txt +0 -0
  83. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/DockerNginx.txt +0 -0
  84. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/DockerPython.txt +0 -0
  85. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/DockerStack.txt +0 -0
  86. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/DuckDuckGoInstant.txt +0 -0
  87. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/EnvTemplate.txt +0 -0
  88. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/Explain.txt +0 -0
  89. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/ExploreProjectStructure.txt +0 -0
  90. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/GitIgnorePython.txt +0 -0
  91. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/MySQLDump.txt +0 -0
  92. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/NewScript.txt +0 -0
  93. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/PipFreeze.txt +0 -0
  94. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/PyPI.txt +0 -0
  95. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/Refactor.txt +0 -0
  96. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/RunAndFix.txt +0 -0
  97. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/SQLiteSchema.txt +0 -0
  98. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/Translate.txt +0 -0
  99. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/WikiPage.txt +0 -0
  100. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/WikiSearch.txt +0 -0
  101. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/auto-bkup.txt +0 -0
  102. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/edit-control.txt +0 -0
  103. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/parallel_call.txt +0 -0
  104. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/personal/content/create-regular-content.txt +0 -0
  105. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/personal/content/plan.txt +0 -0
  106. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/personal/test/collect-data-from-test-environment.txt +0 -0
  107. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/plan.txt +0 -0
  108. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/remote/create-content-on-remote-server.txt +0 -0
  109. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/set_ctx.txt +0 -0
  110. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/simargl-cli_index_files.txt +0 -0
  111. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/simargl-cli_index_units.txt +0 -0
  112. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/simargl-cli_search.txt +0 -0
  113. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/team-map-worker.txt +0 -0
  114. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/team-search-worker.txt +0 -0
  115. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/team-summarize.txt +0 -0
  116. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/team-tree-worker.txt +0 -0
  117. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/scripts/test.txt +0 -0
  118. {1bcoder-0.1.13 → 1bcoder-0.1.14}/_bcoder_data/teams/code-analysis.yaml +0 -0
  119. {1bcoder-0.1.13 → 1bcoder-0.1.14}/map_index.py +0 -0
  120. {1bcoder-0.1.13 → 1bcoder-0.1.14}/map_query.py +0 -0
  121. {1bcoder-0.1.13 → 1bcoder-0.1.14}/setup.cfg +0 -0
  122. {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.13
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.13
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")