myra-termux 0.1.0__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.
@@ -0,0 +1,56 @@
1
+ Metadata-Version: 2.4
2
+ Name: myra-termux
3
+ Version: 0.1.0
4
+ Summary: MYRA AI assistant for Termux (Android): chat, voice, 148+ languages and phone control via Gemini
5
+ License: Proprietary
6
+ Keywords: termux,android,assistant,gemini,voice,myra
7
+ Classifier: Environment :: Console
8
+ Classifier: Operating System :: Android
9
+ Classifier: Programming Language :: Python :: 3
10
+ Requires-Python: >=3.8
11
+ Description-Content-Type: text/markdown
12
+
13
+ # MYRA for Termux
14
+
15
+ MYRA AI assistant for your Android phone, running inside [Termux](https://termux.dev). Powered by Google Gemini.
16
+ Chat or talk in **148+ languages**, and let MYRA control your phone (torch, battery, SMS, calls, apps, volume, brightness, clipboard, location).
17
+
18
+ ## Install
19
+
20
+ ```
21
+ pkg update && pkg install python termux-api
22
+ pip install myra-termux
23
+ myra-termux --setup
24
+ myra-termux
25
+ ```
26
+
27
+ Also install the **Termux:API** app (F-Droid) for phone control and voice.
28
+
29
+ Get a free Gemini API key at https://aistudio.google.com/apikey
30
+
31
+ ## Update
32
+
33
+ ```
34
+ pip install --upgrade myra-termux
35
+ ```
36
+
37
+ ## Usage
38
+
39
+ | Command | What it does |
40
+ |---|---|
41
+ | `myra-termux` | Chat mode |
42
+ | `myra-termux --voice` | Speak and listen (Termux:API) |
43
+ | `myra-termux --language Tamil` | Set language (any of 148+; `auto` = Hinglish) |
44
+ | `myra-termux --languages` | List all languages |
45
+ | `myra-termux --name Rahul` | Set your name |
46
+ | `myra-termux --ask "battery kitni hai?"` | One-shot question |
47
+
48
+ You can also say it naturally: "speak Spanish", "tamil me bolo", "mera naam Rahul hai", "torch on karo", "mummy ko SMS bhejo".
49
+
50
+ SMS and calls always ask for your confirmation first.
51
+
52
+ ## Notes
53
+
54
+ - Your API key is stored in `~/.myra_termux/config.json` (user-only permissions). Env var `GEMINI_API_KEY` also works.
55
+ - Voice quality varies by language; big languages (Hindi, English, Spanish, Japanese, Tamil...) are best.
56
+ - This package is pure Python with no dependencies.
@@ -0,0 +1,44 @@
1
+ # MYRA for Termux
2
+
3
+ MYRA AI assistant for your Android phone, running inside [Termux](https://termux.dev). Powered by Google Gemini.
4
+ Chat or talk in **148+ languages**, and let MYRA control your phone (torch, battery, SMS, calls, apps, volume, brightness, clipboard, location).
5
+
6
+ ## Install
7
+
8
+ ```
9
+ pkg update && pkg install python termux-api
10
+ pip install myra-termux
11
+ myra-termux --setup
12
+ myra-termux
13
+ ```
14
+
15
+ Also install the **Termux:API** app (F-Droid) for phone control and voice.
16
+
17
+ Get a free Gemini API key at https://aistudio.google.com/apikey
18
+
19
+ ## Update
20
+
21
+ ```
22
+ pip install --upgrade myra-termux
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ | Command | What it does |
28
+ |---|---|
29
+ | `myra-termux` | Chat mode |
30
+ | `myra-termux --voice` | Speak and listen (Termux:API) |
31
+ | `myra-termux --language Tamil` | Set language (any of 148+; `auto` = Hinglish) |
32
+ | `myra-termux --languages` | List all languages |
33
+ | `myra-termux --name Rahul` | Set your name |
34
+ | `myra-termux --ask "battery kitni hai?"` | One-shot question |
35
+
36
+ You can also say it naturally: "speak Spanish", "tamil me bolo", "mera naam Rahul hai", "torch on karo", "mummy ko SMS bhejo".
37
+
38
+ SMS and calls always ask for your confirmation first.
39
+
40
+ ## Notes
41
+
42
+ - Your API key is stored in `~/.myra_termux/config.json` (user-only permissions). Env var `GEMINI_API_KEY` also works.
43
+ - Voice quality varies by language; big languages (Hindi, English, Spanish, Japanese, Tamil...) are best.
44
+ - This package is pure Python with no dependencies.
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
@@ -0,0 +1,4 @@
1
+ import sys
2
+ from .cli import main
3
+
4
+ sys.exit(main())
@@ -0,0 +1,145 @@
1
+ # -*- coding: utf-8 -*-
2
+ """myra-termux — Termux par MYRA: chat + voice + phone control (Gemini)."""
3
+ import argparse
4
+ import getpass
5
+ import sys
6
+
7
+ from . import __version__, config, gemini, lang, tools
8
+
9
+ BASE_PROMPT = (
10
+ "Tum MYRA ho — user ki friendly, smart AI assistant jo unke Android phone (Termux) par chal rahi ho. "
11
+ "Chhote, natural jawab do (voice mein bolne layak). Phone ka koi kaam ho (torch, battery, SMS, call, app kholna, "
12
+ "volume, brightness, clipboard, location, URL) to turant sahi tool call karo — bas bolo mat. "
13
+ "SMS aur call se pehle number sahi hai ye dhyan se dekho. Kabhi andaza mat lagao; tool fail ho to sach batao. "
14
+ "Default style Hinglish hai jab tak user koi aur language na chune."
15
+ )
16
+
17
+
18
+ def system_prompt():
19
+ cfg = config.load()
20
+ s = BASE_PROMPT
21
+ if cfg.get("user_name"):
22
+ s += f"\nUser ka naam {cfg['user_name']} hai — kabhi-kabhi naam se bulao."
23
+ s += lang.prompt_block(cfg.get("language", "auto"))
24
+ return s
25
+
26
+
27
+ def ask(history, text):
28
+ """Ek user message -> MYRA ka jawab (tool calls loop ke saath). history list mutate hoti hai."""
29
+ history.append({"role": "user", "parts": [{"text": text}]})
30
+ for _ in range(6):
31
+ parts = gemini.parts_of(gemini.generate(history, system_prompt(), tools.DECLARATIONS))
32
+ history.append({"role": "model", "parts": parts})
33
+ calls = [p["functionCall"] for p in parts if "functionCall" in p]
34
+ if not calls:
35
+ return "".join(p.get("text", "") for p in parts).strip()
36
+ results = []
37
+ for c in calls:
38
+ out = tools.dispatch(c.get("name"), c.get("args"))
39
+ print(f" 🔧 {c.get('name')} → {out.splitlines()[0][:90] if out else ''}")
40
+ results.append({"functionResponse": {"name": c.get("name"), "response": {"result": out}}})
41
+ history.append({"role": "user", "parts": results})
42
+ return "Kaam bahut lamba ho gaya — dobara try karo."
43
+
44
+
45
+ def _confirm(msg):
46
+ try:
47
+ return input(f"⚠️ {msg} [y/N]: ").strip().lower() in ("y", "yes", "haan", "ha")
48
+ except EOFError:
49
+ return False
50
+
51
+
52
+ def setup():
53
+ print("MYRA Termux setup (Gemini API key: https://aistudio.google.com/apikey)")
54
+ key = getpass.getpass("API key (type karte waqt dikhegi nahi): ").strip()
55
+ if key:
56
+ config.set_("api_key", key)
57
+ name = input("Aapka naam (optional): ").strip()
58
+ if name:
59
+ print(tools.set_user_name(name))
60
+ print("Language: naam likho (jaise Hindi, English, Tamil, Spanish) ya Enter = Hinglish.")
61
+ l = input("Language: ").strip()
62
+ if l:
63
+ print(tools.set_language(l))
64
+ print("✓ Setup ho gaya. Ab `myra-termux` chalao.")
65
+
66
+
67
+ def list_languages():
68
+ for c, e, n in lang.LANGUAGES:
69
+ print(f"{c:8} {e} — {n}" if e != n else f"{c:8} {e}")
70
+
71
+
72
+ def chat(voice=False):
73
+ tools.CONFIRM = _confirm
74
+ if not config.api_key():
75
+ print("API key nahi mili."); setup()
76
+ if not config.api_key():
77
+ return 1
78
+ cfg = config.load()
79
+ who = cfg.get("user_name") or ""
80
+ print(f"MYRA Termux {__version__} — {'voice' if voice else 'chat'} mode. 'exit' ya Ctrl+C se band.")
81
+ history = []
82
+ try:
83
+ greet = ask(history, "Mujhe chhota sa greeting do." + (f" Mera naam {who} hai." if who else ""))
84
+ print(f"MYRA: {greet}")
85
+ if voice:
86
+ tools.tts(greet, cfg.get("language", ""))
87
+ while True:
88
+ if voice:
89
+ print("🎤 Boliye...")
90
+ text = tools.stt()
91
+ if not text:
92
+ continue
93
+ print(f"Aap: {text}")
94
+ else:
95
+ text = input("Aap: ").strip()
96
+ if not text:
97
+ continue
98
+ if text.lower() in ("exit", "quit", "q", "bye"):
99
+ break
100
+ try:
101
+ reply = ask(history, text)
102
+ except gemini.GeminiError as e:
103
+ print(f"❌ {e}"); continue
104
+ print(f"MYRA: {reply}")
105
+ if voice:
106
+ tools.tts(reply, config.get("language", ""))
107
+ except (KeyboardInterrupt, EOFError):
108
+ print()
109
+ return 0
110
+
111
+
112
+ def main(argv=None):
113
+ ap = argparse.ArgumentParser(prog="myra-termux", description="MYRA AI assistant for Termux (Android)")
114
+ ap.add_argument("--setup", action="store_true", help="API key, naam, language set karo")
115
+ ap.add_argument("--voice", action="store_true", help="mic se suno aur bolkar jawab do (Termux:API chahiye)")
116
+ ap.add_argument("--language", "-l", metavar="NAME", help="language set karo (jaise Tamil, Spanish, auto)")
117
+ ap.add_argument("--languages", action="store_true", help="saari languages dikhao")
118
+ ap.add_argument("--name", metavar="NAAM", help="apna naam set karo")
119
+ ap.add_argument("--ask", metavar="TEXT", help="ek sawaal puchho aur band")
120
+ ap.add_argument("--version", action="store_true")
121
+ a = ap.parse_args(argv)
122
+ if a.version:
123
+ print(__version__); return 0
124
+ if a.languages:
125
+ list_languages(); return 0
126
+ if a.setup:
127
+ setup(); return 0
128
+ if a.name:
129
+ print(tools.set_user_name(a.name))
130
+ if a.language:
131
+ print(tools.set_language(a.language))
132
+ if a.name or a.language:
133
+ return 0
134
+ if a.ask:
135
+ tools.CONFIRM = _confirm
136
+ try:
137
+ print(ask([], a.ask))
138
+ except gemini.GeminiError as e:
139
+ print(f"❌ {e}"); return 1
140
+ return 0
141
+ return chat(a.voice)
142
+
143
+
144
+ if __name__ == "__main__":
145
+ sys.exit(main())
@@ -0,0 +1,43 @@
1
+ # -*- coding: utf-8 -*-
2
+ """Settings ~/.myra_termux/config.json mein (API key, naam, language, model). Key kabhi print nahi hoti."""
3
+ import json
4
+ import os
5
+ from pathlib import Path
6
+
7
+ DIR = Path(os.environ.get("MYRA_TERMUX_HOME") or (Path.home() / ".myra_termux"))
8
+ FILE = DIR / "config.json"
9
+ DEFAULT_MODEL = "gemini-2.5-flash"
10
+
11
+
12
+ def load() -> dict:
13
+ try:
14
+ return json.loads(FILE.read_text(encoding="utf-8"))
15
+ except Exception:
16
+ return {}
17
+
18
+
19
+ def save(cfg: dict) -> None:
20
+ DIR.mkdir(parents=True, exist_ok=True)
21
+ FILE.write_text(json.dumps(cfg, indent=2, ensure_ascii=False), encoding="utf-8")
22
+ try:
23
+ os.chmod(FILE, 0o600) # sirf user padh sake (API key hai)
24
+ except Exception:
25
+ pass
26
+
27
+
28
+ def get(key, default=None):
29
+ return load().get(key, default)
30
+
31
+
32
+ def set_(key, value) -> None:
33
+ cfg = load()
34
+ cfg[key] = value
35
+ save(cfg)
36
+
37
+
38
+ def api_key() -> str:
39
+ return (os.environ.get("GEMINI_API_KEY") or os.environ.get("GOOGLE_API_KEY") or load().get("api_key") or "").strip()
40
+
41
+
42
+ def model() -> str:
43
+ return os.environ.get("MYRA_MODEL") or load().get("model") or DEFAULT_MODEL
@@ -0,0 +1,44 @@
1
+ # -*- coding: utf-8 -*-
2
+ """Gemini REST client (sirf standard library) — function calling ke saath."""
3
+ import json
4
+ import urllib.error
5
+ import urllib.request
6
+
7
+ from . import config
8
+
9
+ BASE = "https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent"
10
+
11
+
12
+ class GeminiError(Exception):
13
+ pass
14
+
15
+
16
+ def generate(contents, system, tools=None, timeout=60):
17
+ key = config.api_key()
18
+ if not key:
19
+ raise GeminiError("API key set nahi hai. `myra-termux --setup` chalao.")
20
+ body = {"contents": contents, "systemInstruction": {"parts": [{"text": system}]},
21
+ "generationConfig": {"temperature": 0.7}}
22
+ if tools:
23
+ body["tools"] = [{"functionDeclarations": tools}]
24
+ req = urllib.request.Request(BASE.format(model=config.model()), data=json.dumps(body).encode("utf-8"),
25
+ headers={"Content-Type": "application/json", "x-goog-api-key": key})
26
+ try:
27
+ with urllib.request.urlopen(req, timeout=timeout) as r:
28
+ return json.loads(r.read().decode("utf-8"))
29
+ except urllib.error.HTTPError as e:
30
+ try:
31
+ msg = json.loads(e.read().decode("utf-8"))["error"]["message"]
32
+ except Exception:
33
+ msg = str(e)
34
+ raise GeminiError(f"Gemini error {e.code}: {msg}")
35
+ except Exception as e:
36
+ raise GeminiError(f"Network error: {e}")
37
+
38
+
39
+ def parts_of(resp):
40
+ try:
41
+ return resp["candidates"][0]["content"]["parts"]
42
+ except Exception:
43
+ reason = (resp.get("promptFeedback") or {}).get("blockReason") or "empty response"
44
+ raise GeminiError(f"Jawab nahi mila ({reason})")
@@ -0,0 +1,82 @@
1
+ # -*- coding: utf-8 -*-
2
+ """Duniya ki languages — user chune, MYRA usi mein sunegi aur bolegi."""
3
+ import re
4
+
5
+ # (code, English naam, native naam)
6
+ LANGUAGES = [
7
+ ("hi", "Hindi", "हिन्दी"), ("en-IN", "English (India)", "English (India)"), ("bn", "Bengali", "বাংলা"),
8
+ ("te", "Telugu", "తెలుగు"), ("mr", "Marathi", "मराठी"), ("ta", "Tamil", "தமிழ்"), ("gu", "Gujarati", "ગુજરાતી"),
9
+ ("kn", "Kannada", "ಕನ್ನಡ"), ("ml", "Malayalam", "മലയാളം"), ("pa", "Punjabi", "ਪੰਜਾਬੀ"), ("or", "Odia", "ଓଡ଼ିଆ"),
10
+ ("as", "Assamese", "অসমীয়া"), ("ur", "Urdu", "اردو"), ("ne", "Nepali", "नेपाली"), ("sa", "Sanskrit", "संस्कृतम्"),
11
+ ("sd", "Sindhi", "سنڌي"), ("ks", "Kashmiri", "کٲشُر"), ("kok", "Konkani", "कोंकणी"), ("mai", "Maithili", "मैथिली"),
12
+ ("doi", "Dogri", "डोगरी"), ("mni", "Manipuri (Meitei)", "ꯃꯤꯇꯩꯂꯣꯟ"), ("brx", "Bodo", "बड़ो"), ("sat", "Santali", "ᱥᱟᱱᱛᱟᱲᱤ"),
13
+ ("bho", "Bhojpuri", "भोजपुरी"), ("raj", "Rajasthani", "राजस्थानी"), ("hne", "Chhattisgarhi", "छत्तीसगढ़ी"),
14
+ ("mag", "Magahi", "मगही"), ("awa", "Awadhi", "अवधी"), ("tcy", "Tulu", "ತುಳು"),
15
+ ("en-US", "English (US)", "English (US)"), ("en-GB", "English (UK)", "English (UK)"), ("en-AU", "English (Australia)", "English (Australia)"),
16
+ ("es", "Spanish", "Español"), ("es-MX", "Spanish (Mexico)", "Español (México)"), ("fr", "French", "Français"),
17
+ ("fr-CA", "French (Canada)", "Français (Canada)"), ("de", "German", "Deutsch"), ("it", "Italian", "Italiano"),
18
+ ("pt-BR", "Portuguese (Brazil)", "Português (Brasil)"), ("pt-PT", "Portuguese (Portugal)", "Português (Portugal)"),
19
+ ("nl", "Dutch", "Nederlands"), ("sv", "Swedish", "Svenska"), ("da", "Danish", "Dansk"), ("no", "Norwegian", "Norsk"),
20
+ ("fi", "Finnish", "Suomi"), ("is", "Icelandic", "Íslenska"), ("ru", "Russian", "Русский"), ("uk", "Ukrainian", "Українська"),
21
+ ("pl", "Polish", "Polski"), ("cs", "Czech", "Čeština"), ("sk", "Slovak", "Slovenčina"), ("hu", "Hungarian", "Magyar"),
22
+ ("ro", "Romanian", "Română"), ("bg", "Bulgarian", "Български"), ("el", "Greek", "Ελληνικά"), ("sr", "Serbian", "Српски"),
23
+ ("hr", "Croatian", "Hrvatski"), ("bs", "Bosnian", "Bosanski"), ("sl", "Slovenian", "Slovenščina"), ("mk", "Macedonian", "Македонски"),
24
+ ("sq", "Albanian", "Shqip"), ("lt", "Lithuanian", "Lietuvių"), ("lv", "Latvian", "Latviešu"), ("et", "Estonian", "Eesti"),
25
+ ("be", "Belarusian", "Беларуская"), ("ca", "Catalan", "Català"), ("eu", "Basque", "Euskara"), ("gl", "Galician", "Galego"),
26
+ ("ga", "Irish", "Gaeilge"), ("cy", "Welsh", "Cymraeg"), ("gd", "Scottish Gaelic", "Gàidhlig"), ("mt", "Maltese", "Malti"),
27
+ ("lb", "Luxembourgish", "Lëtzebuergesch"), ("br", "Breton", "Brezhoneg"), ("la", "Latin", "Latina"), ("eo", "Esperanto", "Esperanto"),
28
+ ("zh-CN", "Chinese (Mandarin, Simplified)", "简体中文"), ("zh-TW", "Chinese (Traditional)", "繁體中文"), ("yue", "Cantonese", "粵語"),
29
+ ("ja", "Japanese", "日本語"), ("ko", "Korean", "한국어"), ("vi", "Vietnamese", "Tiếng Việt"), ("th", "Thai", "ไทย"),
30
+ ("id", "Indonesian", "Bahasa Indonesia"), ("ms", "Malay", "Bahasa Melayu"), ("fil", "Filipino (Tagalog)", "Filipino"),
31
+ ("my", "Burmese", "မြန်မာ"), ("km", "Khmer", "ខ្មែរ"), ("lo", "Lao", "ລາວ"), ("si", "Sinhala", "සිංහල"),
32
+ ("mn", "Mongolian", "Монгол"), ("bo", "Tibetan", "བོད་སྐད།"), ("ug", "Uyghur", "ئۇيغۇرچە"), ("jv", "Javanese", "Basa Jawa"),
33
+ ("su", "Sundanese", "Basa Sunda"), ("ceb", "Cebuano", "Cebuano"), ("ilo", "Ilocano", "Ilokano"), ("hmn", "Hmong", "Hmoob"),
34
+ ("ar", "Arabic", "العربية"), ("ar-EG", "Arabic (Egypt)", "العربية (مصر)"), ("he", "Hebrew", "עברית"), ("fa", "Persian (Farsi)", "فارسی"),
35
+ ("tr", "Turkish", "Türkçe"), ("ku", "Kurdish", "Kurdî"), ("ps", "Pashto", "پښتو"), ("prs", "Dari", "دری"), ("az", "Azerbaijani", "Azərbaycanca"),
36
+ ("hy", "Armenian", "Հայերեն"), ("ka", "Georgian", "ქართული"), ("kk", "Kazakh", "Қазақша"), ("uz", "Uzbek", "Oʻzbekcha"),
37
+ ("ky", "Kyrgyz", "Кыргызча"), ("tg", "Tajik", "Тоҷикӣ"), ("tk", "Turkmen", "Türkmençe"), ("tt", "Tatar", "Татарча"),
38
+ ("sw", "Swahili", "Kiswahili"), ("am", "Amharic", "አማርኛ"), ("ha", "Hausa", "Hausa"), ("yo", "Yoruba", "Yorùbá"), ("ig", "Igbo", "Igbo"),
39
+ ("zu", "Zulu", "isiZulu"), ("xh", "Xhosa", "isiXhosa"), ("af", "Afrikaans", "Afrikaans"), ("st", "Sesotho", "Sesotho"),
40
+ ("tn", "Tswana", "Setswana"), ("sn", "Shona", "chiShona"), ("so", "Somali", "Soomaali"), ("rw", "Kinyarwanda", "Ikinyarwanda"),
41
+ ("mg", "Malagasy", "Malagasy"), ("ny", "Chichewa", "Chichewa"), ("wo", "Wolof", "Wolof"), ("ti", "Tigrinya", "ትግርኛ"),
42
+ ("om", "Oromo", "Afaan Oromoo"), ("lg", "Luganda", "Luganda"), ("ln", "Lingala", "Lingála"), ("ff", "Fula", "Fulfulde"),
43
+ ("ber", "Berber (Tamazight)", "ⵜⴰⵎⴰⵣⵉⵖⵜ"),
44
+ ("ht", "Haitian Creole", "Kreyòl ayisyen"), ("qu", "Quechua", "Runasimi"), ("gn", "Guarani", "Avañeʼẽ"), ("ay", "Aymara", "Aymar aru"),
45
+ ("haw", "Hawaiian", "ʻŌlelo Hawaiʻi"), ("mi", "Maori", "Te Reo Māori"), ("sm", "Samoan", "Gagana Samoa"), ("to", "Tongan", "Lea faka-Tonga"),
46
+ ("fj", "Fijian", "Vosa Vakaviti"), ("yi", "Yiddish", "ייִדיש"),
47
+ ("hi-Latn", "Hinglish (Hindi + English)", "Hinglish"),
48
+ ]
49
+
50
+ _ALIASES = {"hinglish": "hi-Latn", "english": "en-US", "chinese": "zh-CN", "mandarin": "zh-CN", "portuguese": "pt-BR",
51
+ "farsi": "fa", "tagalog": "fil"}
52
+ _norm = lambda s: re.sub(r"[^\w]+", "", str(s or "").lower())
53
+ _BY_KEY = {}
54
+ for _it in LANGUAGES:
55
+ for _k in _it:
56
+ _BY_KEY.setdefault(_norm(_k), _it)
57
+ _BY_CODE = {it[0]: it for it in LANGUAGES}
58
+ for _a, _c in _ALIASES.items():
59
+ _BY_KEY[_a] = _BY_CODE[_c]
60
+
61
+
62
+ def find(text):
63
+ k = _norm(text)
64
+ if not k or k in ("auto", "default"):
65
+ return None
66
+ if k in _BY_KEY:
67
+ return _BY_KEY[k]
68
+ for key, v in _BY_KEY.items():
69
+ if len(key) > 4 and key in k:
70
+ return v
71
+ return None
72
+
73
+
74
+ def prompt_block(code):
75
+ it = _BY_CODE.get(code)
76
+ if not it or code == "hi-Latn":
77
+ return ""
78
+ _, e, n = it
79
+ return (f"\nLANGUAGE (sabse zaroori): user ne **{e} ({n})** chuni hai. Ab se SIRF {e} mein bolo aur likho — har jawab, "
80
+ f"confirmation aur error. Natural, bol-chaal wali {e} use karo ({n} script mein). User doosri language mein bole "
81
+ f"to bhi jawab {e} mein do, jab tak wo khud na kahe ('ab English mein bolo') — tab set_language tool se badlo. "
82
+ f"App/website ke naam aur code translate mat karo.\n")
@@ -0,0 +1,176 @@
1
+ # -*- coding: utf-8 -*-
2
+ """Phone ke kaam — Termux:API ke zariye. SMS/call se pehle user ki confirmation zaroori."""
3
+ import json
4
+ import re
5
+ import shutil
6
+ import subprocess
7
+
8
+ from . import config, lang
9
+
10
+ CONFIRM = None # cli.py yahan ek function set karta hai: confirm(text) -> bool
11
+
12
+
13
+ def _run(cmd, timeout=20, stdin=None):
14
+ exe = cmd[0]
15
+ if not shutil.which(exe):
16
+ return None, (f"`{exe}` nahi mila. Termux mein `pkg install termux-api` chalao aur Play Store/F-Droid se "
17
+ f"'Termux:API' app install karo.")
18
+ try:
19
+ p = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout, input=stdin)
20
+ return (p.stdout or "").strip(), (p.stderr or "").strip() if p.returncode else ""
21
+ except subprocess.TimeoutExpired:
22
+ return None, "Timeout — Termux:API app permission/chalu hai ya nahi dekho."
23
+ except Exception as e:
24
+ return None, str(e)
25
+
26
+
27
+ def _ok(out, err, done):
28
+ return f"❌ {err}" if err else (f"✅ {done}" + (f"\n{out}" if out else ""))
29
+
30
+
31
+ _NUM = re.compile(r"^\+?[0-9][0-9 \-()]{2,18}$")
32
+
33
+
34
+ def battery_status():
35
+ out, err = _run(["termux-battery-status"])
36
+ return f"❌ {err}" if err else (out or "no data")
37
+
38
+
39
+ def torch(on: bool = True):
40
+ return _ok(*_run(["termux-torch", "on" if on else "off"]), f"Torch {'on' if on else 'off'}")
41
+
42
+
43
+ def vibrate(ms: int = 300):
44
+ return _ok(*_run(["termux-vibrate", "-d", str(int(ms))]), "Vibrate")
45
+
46
+
47
+ def toast(text: str):
48
+ return _ok(*_run(["termux-toast", text]), "Toast dikha diya")
49
+
50
+
51
+ def notify(title: str, text: str = ""):
52
+ return _ok(*_run(["termux-notification", "-t", title, "-c", text]), "Notification bhej di")
53
+
54
+
55
+ def open_url(url: str):
56
+ if not re.match(r"^(https?://|tel:|geo:|mailto:)", url):
57
+ url = "https://" + url
58
+ return _ok(*_run(["termux-open-url", url]), f"Khol diya: {url}")
59
+
60
+
61
+ def open_app(package: str):
62
+ """package = Android package naam (jaise com.whatsapp, com.spotify.music, com.google.android.youtube)."""
63
+ if not re.match(r"^[A-Za-z0-9_.]+$", package):
64
+ return "❌ Galat package naam"
65
+ return _ok(*_run(["monkey", "-p", package, "-c", "android.intent.category.LAUNCHER", "1"]), f"App khola: {package}")
66
+
67
+
68
+ def set_volume(stream: str = "music", level: int = 7):
69
+ return _ok(*_run(["termux-volume", stream, str(int(level))]), f"Volume ({stream}) = {level}")
70
+
71
+
72
+ def set_brightness(level: int = 128):
73
+ return _ok(*_run(["termux-brightness", str(max(0, min(255, int(level))))]), f"Brightness = {level}/255")
74
+
75
+
76
+ def clipboard_get():
77
+ out, err = _run(["termux-clipboard-get"])
78
+ return f"❌ {err}" if err else (out or "(clipboard khali)")
79
+
80
+
81
+ def clipboard_set(text: str):
82
+ return _ok(*_run(["termux-clipboard-set"], stdin=text), "Clipboard mein copy kiya")
83
+
84
+
85
+ def get_location():
86
+ out, err = _run(["termux-location", "-p", "network"], timeout=40)
87
+ return f"❌ {err}" if err else (out or "location nahi mili")
88
+
89
+
90
+ def send_sms(number: str, message: str):
91
+ if not _NUM.match(number or ""):
92
+ return "❌ Number galat hai"
93
+ if not (CONFIRM and CONFIRM(f"SMS bhejun? → {number}: {message[:80]}")):
94
+ return "❌ User ne SMS bhejne ki permission nahi di"
95
+ return _ok(*_run(["termux-sms-send", "-n", number, message]), f"SMS bhej diya ({number})")
96
+
97
+
98
+ def make_call(number: str):
99
+ if not _NUM.match(number or ""):
100
+ return "❌ Number galat hai"
101
+ if not (CONFIRM and CONFIRM(f"Call karun? → {number}")):
102
+ return "❌ User ne call ki permission nahi di"
103
+ return _ok(*_run(["termux-telephony-call", number]), f"Call laga rahi hoon ({number})")
104
+
105
+
106
+ def set_user_name(name: str):
107
+ n = re.sub(r"\s+", " ", re.sub(r"[\x00-\x1f\x7f]", "", name or "")).strip()[:30]
108
+ if not n:
109
+ return "❌ Naam khali hai"
110
+ config.set_("user_name", n)
111
+ return f"✅ Naam save ho gaya: {n}"
112
+
113
+
114
+ def set_language(language: str):
115
+ k = (language or "").strip().lower()
116
+ if k in ("", "auto", "default", "reset"):
117
+ config.set_("language", "auto")
118
+ return "✅ Language Auto/Hinglish. Pehle jaise Hinglish mein bolo."
119
+ it = lang.find(language)
120
+ if not it:
121
+ return "❌ Ye language nahi mili. User se poora naam pucho."
122
+ config.set_("language", it[0])
123
+ return f"✅ Language set: {it[1]} ({it[2]}). Ab se sirf {it[1]} mein bolo, is jawab se hi."
124
+
125
+
126
+ def _p(props, req=()):
127
+ return {"type": "object", "properties": {k: {"type": v} for k, v in props.items()}, "required": list(req)}
128
+
129
+
130
+ DECLARATIONS = [
131
+ {"name": "battery_status", "description": "Phone ki battery % aur charging status", "parameters": _p({})},
132
+ {"name": "torch", "description": "Torch/flashlight on ya off", "parameters": _p({"on": "boolean"}, ["on"])},
133
+ {"name": "vibrate", "description": "Phone vibrate karo", "parameters": _p({"ms": "integer"})},
134
+ {"name": "toast", "description": "Screen par chhota message dikhao", "parameters": _p({"text": "string"}, ["text"])},
135
+ {"name": "notify", "description": "Notification bhejo", "parameters": _p({"title": "string", "text": "string"}, ["title"])},
136
+ {"name": "open_url", "description": "Browser mein URL kholo (ya tel:/geo:/mailto:)", "parameters": _p({"url": "string"}, ["url"])},
137
+ {"name": "open_app", "description": "Phone ka app kholo — Android package naam se (com.whatsapp, com.spotify.music, com.google.android.youtube, com.instagram.android, com.android.chrome)", "parameters": _p({"package": "string"}, ["package"])},
138
+ {"name": "set_volume", "description": "Volume set karo. stream: music|ring|alarm|notification|system|call, level 0-15", "parameters": _p({"stream": "string", "level": "integer"}, ["level"])},
139
+ {"name": "set_brightness", "description": "Screen brightness 0-255", "parameters": _p({"level": "integer"}, ["level"])},
140
+ {"name": "clipboard_get", "description": "Clipboard ka text padho", "parameters": _p({})},
141
+ {"name": "clipboard_set", "description": "Text clipboard mein copy karo", "parameters": _p({"text": "string"}, ["text"])},
142
+ {"name": "get_location", "description": "Phone ki current location (lat/long)", "parameters": _p({})},
143
+ {"name": "send_sms", "description": "SMS bhejo (user se confirmation li jayegi)", "parameters": _p({"number": "string", "message": "string"}, ["number", "message"])},
144
+ {"name": "make_call", "description": "Phone call lagao (user se confirmation li jayegi)", "parameters": _p({"number": "string"}, ["number"])},
145
+ {"name": "set_user_name", "description": "User ka naam save karo jab wo bataye ('mera naam Rahul hai')", "parameters": _p({"name": "string"}, ["name"])},
146
+ {"name": "set_language", "description": "User ki language badlo (duniya ki koi bhi; 'speak Spanish', 'tamil me bolo'); 'auto' = Hinglish", "parameters": _p({"language": "string"}, ["language"])},
147
+ ]
148
+
149
+ REGISTRY = {f.__name__: f for f in (battery_status, torch, vibrate, toast, notify, open_url, open_app, set_volume, set_brightness,
150
+ clipboard_get, clipboard_set, get_location, send_sms, make_call, set_user_name, set_language)}
151
+
152
+
153
+ def dispatch(name, args):
154
+ fn = REGISTRY.get(name)
155
+ if not fn:
156
+ return f"❌ Tool '{name}' nahi hai"
157
+ try:
158
+ return str(fn(**(args or {})))
159
+ except TypeError as e:
160
+ return f"❌ Galat arguments: {e}"
161
+ except Exception as e:
162
+ return f"❌ {type(e).__name__}: {e}"
163
+
164
+
165
+ def tts(text: str, code: str = ""):
166
+ """Bolkar sunao (Android TTS). Language code mile to usi mein."""
167
+ cmd = ["termux-tts-speak"]
168
+ if code and code not in ("auto", "hi-Latn"):
169
+ cmd += ["-l", code.split("-")[0]]
170
+ _run(cmd + [text[:3000]], timeout=120)
171
+
172
+
173
+ def stt():
174
+ """Mic se suno (Android speech recognizer). Text return karta hai."""
175
+ out, err = _run(["termux-speech-to-text"], timeout=60)
176
+ return out or ""
@@ -0,0 +1,56 @@
1
+ Metadata-Version: 2.4
2
+ Name: myra-termux
3
+ Version: 0.1.0
4
+ Summary: MYRA AI assistant for Termux (Android): chat, voice, 148+ languages and phone control via Gemini
5
+ License: Proprietary
6
+ Keywords: termux,android,assistant,gemini,voice,myra
7
+ Classifier: Environment :: Console
8
+ Classifier: Operating System :: Android
9
+ Classifier: Programming Language :: Python :: 3
10
+ Requires-Python: >=3.8
11
+ Description-Content-Type: text/markdown
12
+
13
+ # MYRA for Termux
14
+
15
+ MYRA AI assistant for your Android phone, running inside [Termux](https://termux.dev). Powered by Google Gemini.
16
+ Chat or talk in **148+ languages**, and let MYRA control your phone (torch, battery, SMS, calls, apps, volume, brightness, clipboard, location).
17
+
18
+ ## Install
19
+
20
+ ```
21
+ pkg update && pkg install python termux-api
22
+ pip install myra-termux
23
+ myra-termux --setup
24
+ myra-termux
25
+ ```
26
+
27
+ Also install the **Termux:API** app (F-Droid) for phone control and voice.
28
+
29
+ Get a free Gemini API key at https://aistudio.google.com/apikey
30
+
31
+ ## Update
32
+
33
+ ```
34
+ pip install --upgrade myra-termux
35
+ ```
36
+
37
+ ## Usage
38
+
39
+ | Command | What it does |
40
+ |---|---|
41
+ | `myra-termux` | Chat mode |
42
+ | `myra-termux --voice` | Speak and listen (Termux:API) |
43
+ | `myra-termux --language Tamil` | Set language (any of 148+; `auto` = Hinglish) |
44
+ | `myra-termux --languages` | List all languages |
45
+ | `myra-termux --name Rahul` | Set your name |
46
+ | `myra-termux --ask "battery kitni hai?"` | One-shot question |
47
+
48
+ You can also say it naturally: "speak Spanish", "tamil me bolo", "mera naam Rahul hai", "torch on karo", "mummy ko SMS bhejo".
49
+
50
+ SMS and calls always ask for your confirmation first.
51
+
52
+ ## Notes
53
+
54
+ - Your API key is stored in `~/.myra_termux/config.json` (user-only permissions). Env var `GEMINI_API_KEY` also works.
55
+ - Voice quality varies by language; big languages (Hindi, English, Spanish, Japanese, Tamil...) are best.
56
+ - This package is pure Python with no dependencies.
@@ -0,0 +1,14 @@
1
+ README.md
2
+ pyproject.toml
3
+ myra_termux/__init__.py
4
+ myra_termux/__main__.py
5
+ myra_termux/cli.py
6
+ myra_termux/config.py
7
+ myra_termux/gemini.py
8
+ myra_termux/lang.py
9
+ myra_termux/tools.py
10
+ myra_termux.egg-info/PKG-INFO
11
+ myra_termux.egg-info/SOURCES.txt
12
+ myra_termux.egg-info/dependency_links.txt
13
+ myra_termux.egg-info/entry_points.txt
14
+ myra_termux.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ myra-termux = myra_termux.cli:main
@@ -0,0 +1 @@
1
+ myra_termux
@@ -0,0 +1,24 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "myra-termux"
7
+ version = "0.1.0"
8
+ description = "MYRA AI assistant for Termux (Android): chat, voice, 148+ languages and phone control via Gemini"
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = { text = "Proprietary" }
12
+ keywords = ["termux", "android", "assistant", "gemini", "voice", "myra"]
13
+ classifiers = [
14
+ "Environment :: Console",
15
+ "Operating System :: Android",
16
+ "Programming Language :: Python :: 3",
17
+ ]
18
+ dependencies = []
19
+
20
+ [project.scripts]
21
+ myra-termux = "myra_termux.cli:main"
22
+
23
+ [tool.setuptools]
24
+ packages = ["myra_termux"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+