neuroncli 2.1.1__tar.gz → 2.3.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.
Files changed (25) hide show
  1. {neuroncli-2.1.1 → neuroncli-2.3.0}/PKG-INFO +3 -20
  2. {neuroncli-2.1.1 → neuroncli-2.3.0}/README.md +2 -19
  3. {neuroncli-2.1.1 → neuroncli-2.3.0}/neuroncli/auth.py +159 -74
  4. {neuroncli-2.1.1 → neuroncli-2.3.0}/neuroncli/config.py +1 -1
  5. {neuroncli-2.1.1 → neuroncli-2.3.0}/neuroncli.egg-info/PKG-INFO +3 -20
  6. {neuroncli-2.1.1 → neuroncli-2.3.0}/pyproject.toml +1 -1
  7. {neuroncli-2.1.1 → neuroncli-2.3.0}/neuroncli/__init__.py +0 -0
  8. {neuroncli-2.1.1 → neuroncli-2.3.0}/neuroncli/__main__.py +0 -0
  9. {neuroncli-2.1.1 → neuroncli-2.3.0}/neuroncli/agent.py +0 -0
  10. {neuroncli-2.1.1 → neuroncli-2.3.0}/neuroncli/cli.py +0 -0
  11. {neuroncli-2.1.1 → neuroncli-2.3.0}/neuroncli/git_integration.py +0 -0
  12. {neuroncli-2.1.1 → neuroncli-2.3.0}/neuroncli/ollama_client.py +0 -0
  13. {neuroncli-2.1.1 → neuroncli-2.3.0}/neuroncli/openrouter_client.py +0 -0
  14. {neuroncli-2.1.1 → neuroncli-2.3.0}/neuroncli/prompts.py +0 -0
  15. {neuroncli-2.1.1 → neuroncli-2.3.0}/neuroncli/provider.py +0 -0
  16. {neuroncli-2.1.1 → neuroncli-2.3.0}/neuroncli/session.py +0 -0
  17. {neuroncli-2.1.1 → neuroncli-2.3.0}/neuroncli/tools.py +0 -0
  18. {neuroncli-2.1.1 → neuroncli-2.3.0}/neuroncli/ui.py +0 -0
  19. {neuroncli-2.1.1 → neuroncli-2.3.0}/neuroncli.egg-info/SOURCES.txt +0 -0
  20. {neuroncli-2.1.1 → neuroncli-2.3.0}/neuroncli.egg-info/dependency_links.txt +0 -0
  21. {neuroncli-2.1.1 → neuroncli-2.3.0}/neuroncli.egg-info/entry_points.txt +0 -0
  22. {neuroncli-2.1.1 → neuroncli-2.3.0}/neuroncli.egg-info/requires.txt +0 -0
  23. {neuroncli-2.1.1 → neuroncli-2.3.0}/neuroncli.egg-info/top_level.txt +0 -0
  24. {neuroncli-2.1.1 → neuroncli-2.3.0}/setup.cfg +0 -0
  25. {neuroncli-2.1.1 → neuroncli-2.3.0}/tests/test_agent.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: neuroncli
3
- Version: 2.1.1
3
+ Version: 2.3.0
4
4
  Summary: AI Coding Agent — Free Claude Code alternative powered by Kimi K2.5 + Ollama
5
5
  Author-email: Rahul S <rahultech72216@gmail.com>
6
6
  License: MIT
@@ -34,25 +34,8 @@ Requires-Dist: requests>=2.28.0
34
34
  ## Install
35
35
 
36
36
  ```bash
37
- # Windows
38
- pip install neuroncli
39
-
40
- # Linux / macOS (modern distros use pipx)
41
- pipx install neuroncli
42
-
43
- # If pipx not installed:
44
- # sudo apt install pipx && pipx ensurepath (Debian/Ubuntu)
45
- # sudo dnf install pipx && pipx ensurepath (Fedora)
46
- # brew install pipx && pipx ensurepath (macOS)
47
-
48
- # From source (any platform)
49
- git clone https://github.com/RAHUL-DevelopeRR/neuroncli.git
50
- cd neuroncli && pip install -e .
51
- ```
52
-
53
- ```bash
54
- # Linux/macOS auto-install script
55
- curl -fsSL https://raw.githubusercontent.com/RAHUL-DevelopeRR/neuroncli/master/install.sh | bash
37
+ pip install neuroncli # Windows / macOS / Cloud VMs
38
+ pip install neuroncli --break-system-packages # Linux (Ubuntu 23+, Debian 12+, Fedora 38+)
56
39
  ```
57
40
 
58
41
  ## Quick Start
@@ -9,25 +9,8 @@
9
9
  ## Install
10
10
 
11
11
  ```bash
12
- # Windows
13
- pip install neuroncli
14
-
15
- # Linux / macOS (modern distros use pipx)
16
- pipx install neuroncli
17
-
18
- # If pipx not installed:
19
- # sudo apt install pipx && pipx ensurepath (Debian/Ubuntu)
20
- # sudo dnf install pipx && pipx ensurepath (Fedora)
21
- # brew install pipx && pipx ensurepath (macOS)
22
-
23
- # From source (any platform)
24
- git clone https://github.com/RAHUL-DevelopeRR/neuroncli.git
25
- cd neuroncli && pip install -e .
26
- ```
27
-
28
- ```bash
29
- # Linux/macOS auto-install script
30
- curl -fsSL https://raw.githubusercontent.com/RAHUL-DevelopeRR/neuroncli/master/install.sh | bash
12
+ pip install neuroncli # Windows / macOS / Cloud VMs
13
+ pip install neuroncli --break-system-packages # Linux (Ubuntu 23+, Debian 12+, Fedora 38+)
31
14
  ```
32
15
 
33
16
  ## Quick Start
@@ -27,8 +27,10 @@ from pathlib import Path
27
27
 
28
28
  CONFIG_DIR = Path.home() / ".neuroncli"
29
29
  CONFIG_FILE = CONFIG_DIR / "config.json"
30
- CALLBACK_PORT = 19284 # Obscure port to avoid collisions
31
- CALLBACK_URL = f"http://localhost:{CALLBACK_PORT}"
30
+ CALLBACK_PORT = 19284 # Local server port to catch the redirect from zero-x.live
31
+ LOCAL_CALLBACK = f"http://localhost:{CALLBACK_PORT}"
32
+ # OpenRouter callback → zero-x.live → redirects to localhost with ?code=
33
+ CALLBACK_URL = "https://zero-x.live/neuroncli/callback"
32
34
 
33
35
 
34
36
  # ── PKCE helpers ──────────────────────────────────────────────────
@@ -78,10 +80,22 @@ def store_api_key(key: str) -> None:
78
80
  # ── OAuth PKCE flow ──────────────────────────────────────────────
79
81
 
80
82
  class _OAuthCallbackHandler(http.server.BaseHTTPRequestHandler):
81
- """Tiny HTTP handler that captures the OAuth redirect code."""
83
+ """Tiny HTTP handler that captures the OAuth redirect code.
84
+ Handles redirects from zero-x.live callback page."""
82
85
 
83
86
  auth_code: str | None = None
84
87
 
88
+ def _send_cors_headers(self):
89
+ """Allow cross-origin requests from zero-x.live."""
90
+ self.send_header("Access-Control-Allow-Origin", "*")
91
+ self.send_header("Access-Control-Allow-Methods", "GET, OPTIONS")
92
+
93
+ def do_OPTIONS(self):
94
+ """Handle CORS preflight."""
95
+ self.send_response(200)
96
+ self._send_cors_headers()
97
+ self.end_headers()
98
+
85
99
  def do_GET(self):
86
100
  parsed = urllib.parse.urlparse(self.path)
87
101
  params = urllib.parse.parse_qs(parsed.query)
@@ -93,6 +107,7 @@ class _OAuthCallbackHandler(http.server.BaseHTTPRequestHandler):
93
107
  # Show success page
94
108
  self.send_response(200)
95
109
  self.send_header("Content-Type", "text/html")
110
+ self._send_cors_headers()
96
111
  self.end_headers()
97
112
  self.wfile.write(b"""
98
113
  <html>
@@ -112,7 +127,7 @@ class _OAuthCallbackHandler(http.server.BaseHTTPRequestHandler):
112
127
  border-radius: 24px;
113
128
  backdrop-filter: blur(20px);
114
129
  }
115
- h1 { color: #00ff88; font-size: 2em; }
130
+ h1 { color: #f0a028; font-size: 2em; }
116
131
  p { color: #aaa; font-size: 1.1em; }
117
132
  </style>
118
133
  </head>
@@ -164,16 +179,11 @@ def _exchange_code_for_key(code: str, code_verifier: str) -> str:
164
179
 
165
180
  def run_oauth_flow() -> str | None:
166
181
  """
167
- Run the full OAuth PKCE flow:
168
- 1. Generate PKCE challenge
169
- 2. Open browser to OpenRouter auth
170
- 3. Start localhost server to catch redirect
171
- 4. Exchange code for API key
172
- 5. Store key locally
173
-
174
- Returns the API key on success, None on failure.
182
+ Run OAuth PKCE + manual paste IN PARALLEL:
183
+ - Background thread: localhost server waits for callback (10 min)
184
+ - Foreground: user can paste key manually while waiting
185
+ - Whichever succeeds first wins.
175
186
  """
176
- # Reset
177
187
  _OAuthCallbackHandler.auth_code = None
178
188
 
179
189
  # Generate PKCE pair
@@ -188,83 +198,160 @@ def run_oauth_flow() -> str | None:
188
198
  f"&code_challenge_method=S256"
189
199
  )
190
200
 
191
- # Start callback server in background
201
+ # Start callback server in background thread
202
+ server = None
192
203
  try:
193
204
  server = http.server.HTTPServer(("127.0.0.1", CALLBACK_PORT), _OAuthCallbackHandler)
194
205
  except OSError:
195
- # Port in use — another instance is running
196
- print(f"\n \033[91m[X] Port {CALLBACK_PORT} is in use. Close other instances first.\033[0m")
197
- return None
198
- server.timeout = 120 # 2 minute timeout
199
-
200
- print("\n \033[96m\033[1m[AUTH]\033[0m Opening browser for OpenRouter login...")
201
- print(f" \033[90mIf browser doesn't open, visit:\033[0m")
202
- print(f" \033[90m{auth_url[:80]}...\033[0m\n")
206
+ pass # Port in use — skip background listener
207
+
208
+ server_thread = None
209
+ if server:
210
+ server.timeout = 600 # 10 MINUTES — enough for signup + email verify + survey
211
+ def _listen():
212
+ try:
213
+ server.handle_request()
214
+ except Exception:
215
+ pass
216
+ finally:
217
+ server.server_close()
218
+ server_thread = threading.Thread(target=_listen, daemon=True)
219
+ server_thread.start()
203
220
 
204
221
  # Open browser
222
+ print("\n \033[96m\033[1m[AUTH]\033[0m Opening browser for OpenRouter login...")
205
223
  try:
206
224
  webbrowser.open(auth_url)
207
225
  except Exception:
208
- print(" \033[90m(Browser could not be opened. Use the URL above.)\033[0m")
226
+ pass
209
227
 
210
- print(" \033[93mWaiting for authentication...\033[0m", end="", flush=True)
228
+ # Show user-friendly instructions
229
+ print(f" \033[90m──────────────────────────────────────────────\033[0m")
230
+ print(f" \033[97mSign up / log in to OpenRouter in your browser.\033[0m")
231
+ print(f" \033[97mAfter login, you'll be redirected back here.\033[0m")
232
+ print(f" \033[90m──────────────────────────────────────────────\033[0m")
233
+ print(f" \033[90mIf auto-redirect doesn't work:\033[0m")
234
+ print(f" \033[90m1. Go to: \033[4mhttps://openrouter.ai/settings/keys\033[0m")
235
+ print(f" \033[90m2. Click 'Create Key' -> copy the key\033[0m")
236
+ print(f" \033[90m3. Paste it below\033[0m")
237
+ print(f" \033[90m──────────────────────────────────────────────\033[0m\n")
238
+
239
+ # PARALLEL: Wait for EITHER OAuth callback OR manual paste
240
+ # Poll every 2 seconds to check if background OAuth succeeded
241
+ import time
242
+ import sys
243
+ import select
244
+
245
+ api_key = None
246
+
247
+ # On Windows, we can't use select() on stdin. Use a simpler approach.
248
+ if sys.platform == "win32":
249
+ api_key = _windows_parallel_auth(server_thread, code_verifier)
250
+ else:
251
+ api_key = _unix_parallel_auth(server_thread, code_verifier)
211
252
 
212
- # Wait for the callback (blocks until request or timeout)
213
- try:
214
- server.handle_request() # Handles exactly one request
215
- except Exception:
216
- pass
217
- finally:
218
- server.server_close()
253
+ # Clean up server
254
+ if server:
255
+ try:
256
+ server.server_close()
257
+ except Exception:
258
+ pass
259
+
260
+ if api_key:
261
+ store_api_key(api_key)
262
+ print(f"\n \033[92m\033[1m[OK] API key saved to {CONFIG_FILE}\033[0m")
263
+ print(f" \033[92m\033[1m You won't be asked again.\033[0m\n")
264
+ return api_key
219
265
 
266
+ print(f"\n \033[90mNo API key provided. Using Ollama fallback if available.\033[0m\n")
267
+ return None
268
+
269
+
270
+ def _check_oauth_result(code_verifier: str) -> str | None:
271
+ """Check if the background OAuth server received a code."""
220
272
  code = _OAuthCallbackHandler.auth_code
221
273
  if not code:
222
- print(f"\n \033[91m[X] OAuth timed out or failed.\033[0m")
223
- return _manual_key_fallback()
274
+ return None
275
+ try:
276
+ return _exchange_code_for_key(code, code_verifier)
277
+ except RuntimeError:
278
+ return None
279
+
280
+
281
+ def _validate_key(key: str) -> bool:
282
+ """Check if a pasted key looks valid."""
283
+ key = key.strip()
284
+ return len(key) > 10 and (key.startswith("sk-or-") or key.startswith("sk-"))
285
+
286
+
287
+ def _windows_parallel_auth(server_thread, code_verifier: str) -> str | None:
288
+ """Windows: prompt for manual paste while checking OAuth in background."""
289
+ import time
224
290
 
225
- print(f" \033[92mv\033[0m")
226
- print(" \033[96mExchanging code for API key...\033[0m", end="", flush=True)
291
+ print(" \033[93mPaste your API key below (or wait for auto-redirect):\033[0m")
292
+ print(" \033[93m> \033[0m", end="", flush=True)
227
293
 
228
- # Exchange code for key
229
294
  try:
230
- api_key = _exchange_code_for_key(code, code_verifier)
231
- except RuntimeError as e:
232
- print(f"\n \033[91m[X] {e}\033[0m")
233
- return _manual_key_fallback()
295
+ key = input().strip()
296
+ except (EOFError, KeyboardInterrupt):
297
+ key = ""
298
+
299
+ if key and _validate_key(key):
300
+ return key
301
+
302
+ # Check if OAuth completed while user was typing
303
+ result = _check_oauth_result(code_verifier)
304
+ if result:
305
+ print(" \033[92m[v] Auto-redirect succeeded!\033[0m")
306
+ return result
234
307
 
235
- # Store
236
- store_api_key(api_key)
237
- print(f" \033[92mv\033[0m")
238
- print(f" \033[92m\033[1m[OK] API key saved to {CONFIG_FILE}\033[0m\n")
308
+ # Neither worked — wait up to 30 more seconds for OAuth
309
+ print(" \033[90mWaiting for browser redirect...\033[0m", end="", flush=True)
310
+ for _ in range(15):
311
+ time.sleep(2)
312
+ result = _check_oauth_result(code_verifier)
313
+ if result:
314
+ print(f" \033[92mv\033[0m")
315
+ return result
316
+ print(".", end="", flush=True)
239
317
 
240
- return api_key
318
+ print(f" \033[91mx\033[0m")
319
+ return None
241
320
 
242
321
 
243
- def _manual_key_fallback() -> str | None:
244
- """Fallback: let user manually paste their OpenRouter API key."""
245
- print(f"\n \033[96m\033[1m[MANUAL SETUP]\033[0m")
246
- print(f" \033[90mOAuth didn't work. You can paste your key instead:\033[0m")
247
- print(f" \033[90m1. Go to: https://openrouter.ai/settings/keys\033[0m")
248
- print(f" \033[90m2. Click 'Create Key'\033[0m")
249
- print(f" \033[90m3. Copy the key (starts with sk-or-...)\033[0m\n")
322
+ def _unix_parallel_auth(server_thread, code_verifier: str) -> str | None:
323
+ """Unix: prompt for manual paste while checking OAuth in background."""
324
+ import time
325
+
326
+ print(" \033[93mPaste your API key below (or wait for auto-redirect):\033[0m")
327
+ print(" \033[93m> \033[0m", end="", flush=True)
250
328
 
251
329
  try:
252
- key = input(" \033[93mPaste your API key (or Enter to skip): \033[0m").strip()
330
+ key = input().strip()
253
331
  except (EOFError, KeyboardInterrupt):
254
- return None
332
+ key = ""
255
333
 
256
- if not key:
257
- print(f"\n \033[90mSkipped. Using Ollama fallback if available.\033[0m\n")
258
- return None
259
-
260
- if key.startswith("sk-or-") or key.startswith("sk-"):
261
- store_api_key(key)
262
- print(f" \033[92m\033[1m[OK] API key saved to {CONFIG_FILE}\033[0m\n")
334
+ if key and _validate_key(key):
263
335
  return key
264
- else:
265
- print(f" \033[91m[X] Invalid key format. Expected 'sk-or-...' \033[0m")
266
- print(f" \033[90mGet one at: https://openrouter.ai/settings/keys\033[0m\n")
267
- return None
336
+
337
+ # Check if OAuth completed while user was typing
338
+ result = _check_oauth_result(code_verifier)
339
+ if result:
340
+ print(" \033[92m[v] Auto-redirect succeeded!\033[0m")
341
+ return result
342
+
343
+ # Neither worked — wait up to 30 more seconds for OAuth
344
+ print(" \033[90mWaiting for browser redirect...\033[0m", end="", flush=True)
345
+ for _ in range(15):
346
+ time.sleep(2)
347
+ result = _check_oauth_result(code_verifier)
348
+ if result:
349
+ print(f" \033[92mv\033[0m")
350
+ return result
351
+ print(".", end="", flush=True)
352
+
353
+ print(f" \033[91mx\033[0m")
354
+ return None
268
355
 
269
356
 
270
357
  # ── Public API ────────────────────────────────────────────────────
@@ -274,12 +361,11 @@ def ensure_api_key() -> str | None:
274
361
  Ensure an API key is available. Checks in order:
275
362
  1. OPENROUTER_API_KEY env var
276
363
  2. ~/.neuroncli/config.json
277
- 3. Interactive OAuth PKCE flow (first-run)
278
- 4. Manual key paste (fallback if OAuth fails)
364
+ 3. OAuth PKCE (background) + manual paste (foreground) — in parallel
279
365
 
280
366
  Returns the API key or None if all methods fail.
281
367
  """
282
- # Check env var first
368
+ # Check env var
283
369
  env_key = os.environ.get("OPENROUTER_API_KEY")
284
370
  if env_key:
285
371
  return env_key
@@ -289,12 +375,11 @@ def ensure_api_key() -> str | None:
289
375
  if stored_key:
290
376
  return stored_key
291
377
 
292
- # First run — interactive OAuth
293
- print("\n \033[96m\033[1m╔════════════════════════════════════════════════╗")
294
- print(" ║ Welcome to NeuronCLI! ║")
295
- print(" ║ Let's connect your free OpenRouter account. ║")
296
- print(" ║ This only happens once. ║")
297
- print(" ╚════════════════════════════════════════════════╝\033[0m")
378
+ # First run — welcome message
379
+ print("\n \033[96m\033[1m╔══════════════════════════════════════════════════╗")
380
+ print(" ║ Welcome to NeuronCLI! ║")
381
+ print(" ║ Free AI coding agent — one-time setup below. ║")
382
+ print(" ╚══════════════════════════════════════════════════╝\033[0m")
298
383
 
299
384
  return run_oauth_flow()
300
385
 
@@ -109,4 +109,4 @@ class AgentConfig:
109
109
  # ── App-wide constants ────────────────────────────────────────────
110
110
 
111
111
  APP_NAME = "NeuronCLI"
112
- VERSION = "2.1.1"
112
+ VERSION = "2.3.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: neuroncli
3
- Version: 2.1.1
3
+ Version: 2.3.0
4
4
  Summary: AI Coding Agent — Free Claude Code alternative powered by Kimi K2.5 + Ollama
5
5
  Author-email: Rahul S <rahultech72216@gmail.com>
6
6
  License: MIT
@@ -34,25 +34,8 @@ Requires-Dist: requests>=2.28.0
34
34
  ## Install
35
35
 
36
36
  ```bash
37
- # Windows
38
- pip install neuroncli
39
-
40
- # Linux / macOS (modern distros use pipx)
41
- pipx install neuroncli
42
-
43
- # If pipx not installed:
44
- # sudo apt install pipx && pipx ensurepath (Debian/Ubuntu)
45
- # sudo dnf install pipx && pipx ensurepath (Fedora)
46
- # brew install pipx && pipx ensurepath (macOS)
47
-
48
- # From source (any platform)
49
- git clone https://github.com/RAHUL-DevelopeRR/neuroncli.git
50
- cd neuroncli && pip install -e .
51
- ```
52
-
53
- ```bash
54
- # Linux/macOS auto-install script
55
- curl -fsSL https://raw.githubusercontent.com/RAHUL-DevelopeRR/neuroncli/master/install.sh | bash
37
+ pip install neuroncli # Windows / macOS / Cloud VMs
38
+ pip install neuroncli --break-system-packages # Linux (Ubuntu 23+, Debian 12+, Fedora 38+)
56
39
  ```
57
40
 
58
41
  ## Quick Start
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "neuroncli"
7
- version = "2.1.1"
7
+ version = "2.3.0"
8
8
  description = "AI Coding Agent — Free Claude Code alternative powered by Kimi K2.5 + Ollama"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes