kitecli 0.2.5__tar.gz → 0.2.6__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 (30) hide show
  1. {kitecli-0.2.5 → kitecli-0.2.6}/PKG-INFO +1 -1
  2. {kitecli-0.2.5 → kitecli-0.2.6}/cli/api_client.py +4 -2
  3. {kitecli-0.2.5 → kitecli-0.2.6}/cli/kotak_manager.py +12 -7
  4. {kitecli-0.2.5 → kitecli-0.2.6}/cli/live_session.py +126 -5
  5. {kitecli-0.2.5 → kitecli-0.2.6}/cli/main.py +1 -0
  6. {kitecli-0.2.5 → kitecli-0.2.6}/kitecli.egg-info/PKG-INFO +1 -1
  7. {kitecli-0.2.5 → kitecli-0.2.6}/pyproject.toml +1 -1
  8. {kitecli-0.2.5 → kitecli-0.2.6}/README.md +0 -0
  9. {kitecli-0.2.5 → kitecli-0.2.6}/cli/__init__.py +0 -0
  10. {kitecli-0.2.5 → kitecli-0.2.6}/cli/advisor.py +0 -0
  11. {kitecli-0.2.5 → kitecli-0.2.6}/cli/base_manager.py +0 -0
  12. {kitecli-0.2.5 → kitecli-0.2.6}/cli/config.py +0 -0
  13. {kitecli-0.2.5 → kitecli-0.2.6}/cli/display.py +0 -0
  14. {kitecli-0.2.5 → kitecli-0.2.6}/cli/executor.py +0 -0
  15. {kitecli-0.2.5 → kitecli-0.2.6}/cli/kite_manager.py +0 -0
  16. {kitecli-0.2.5 → kitecli-0.2.6}/cli/nli.py +0 -0
  17. {kitecli-0.2.5 → kitecli-0.2.6}/cli/parser.py +0 -0
  18. {kitecli-0.2.5 → kitecli-0.2.6}/cli/recorder.py +0 -0
  19. {kitecli-0.2.5 → kitecli-0.2.6}/cli/telegram_bot.py +0 -0
  20. {kitecli-0.2.5 → kitecli-0.2.6}/kitecli.egg-info/SOURCES.txt +0 -0
  21. {kitecli-0.2.5 → kitecli-0.2.6}/kitecli.egg-info/dependency_links.txt +0 -0
  22. {kitecli-0.2.5 → kitecli-0.2.6}/kitecli.egg-info/entry_points.txt +0 -0
  23. {kitecli-0.2.5 → kitecli-0.2.6}/kitecli.egg-info/requires.txt +0 -0
  24. {kitecli-0.2.5 → kitecli-0.2.6}/kitecli.egg-info/top_level.txt +0 -0
  25. {kitecli-0.2.5 → kitecli-0.2.6}/setup.cfg +0 -0
  26. {kitecli-0.2.5 → kitecli-0.2.6}/tests/test_multi_broker.py +0 -0
  27. {kitecli-0.2.5 → kitecli-0.2.6}/tests/test_nli.py +0 -0
  28. {kitecli-0.2.5 → kitecli-0.2.6}/tests/test_parser.py +0 -0
  29. {kitecli-0.2.5 → kitecli-0.2.6}/tests/test_telegram.py +0 -0
  30. {kitecli-0.2.5 → kitecli-0.2.6}/tests/test_ui.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kitecli
3
- Version: 0.2.5
3
+ Version: 0.2.6
4
4
  Summary: KiteCLI — Multi-account, multi-broker trading positions viewer (Zerodha + Kotak Neo)
5
5
  Author: KiteCLI Team
6
6
  License: MIT
@@ -141,11 +141,13 @@ class KCLIClient:
141
141
  is_valid = False
142
142
  if mgr.is_authenticated(account_key):
143
143
  try:
144
- mgr._clients[account_key].limits()
144
+ resp = mgr._clients[account_key].limits()
145
+ from cli.kotak_manager import _check_neo_error
146
+ _check_neo_error(resp)
145
147
  is_valid = True
146
148
  except Exception as exc:
147
149
  msg = str(exc).lower()
148
- is_auth_error = any(x in msg for x in ["100008", "unauthorized", "invalid token", "session expired", "session has been closed", "session closed"])
150
+ is_auth_error = any(x in msg for x in ["100008", "100022", "1037", "unauthorized", "invalid token", "invalid session", "session expired", "session has been closed", "session closed"])
149
151
  if is_auth_error:
150
152
  logger.info("Kotak validation failed (expired/invalid) for %s: %s", name, exc)
151
153
  mgr._authenticated[account_key] = False
@@ -137,7 +137,9 @@ class KotakAccountManager(BaseBrokerManager):
137
137
 
138
138
  def init_account(self, account_key: str, **credentials) -> str:
139
139
  """ABC entry point — delegates to init_account_kotak."""
140
- return self.init_account_kotak(consumer_key=account_key, **credentials)
140
+ creds = dict(credentials)
141
+ creds.pop("consumer_key", None)
142
+ return self.init_account_kotak(consumer_key=account_key, **creds)
141
143
 
142
144
  def init_account_kotak(
143
145
  self,
@@ -265,17 +267,19 @@ class KotakAccountManager(BaseBrokerManager):
265
267
  except Exception:
266
268
  logger.debug("Kotak REST response body (raw): %s", resp.text[:200])
267
269
 
268
- # Check for IP mismatch error (stCode 1037) or Session expired (stCode 100008)
270
+ # Check for IP mismatch error (stCode 1037), Session expired (stCode 100008, 100022), or HTTP 401
269
271
  try:
270
- resp_json = resp.json()
272
+ resp_json = resp.json() if resp.text else {}
271
273
  if isinstance(resp_json, dict):
272
274
  st_code = resp_json.get("stCode")
273
275
  err_msg = str(resp_json.get("errMsg", "")).lower()
274
276
  if (
275
- st_code == 1037 or
276
- st_code == 100008 or
277
- "session ip" in err_msg or
278
- "unauthorized" in err_msg
277
+ resp.status_code == 401 or
278
+ st_code in (1037, 100008, 100022) or
279
+ "session ip" in err_msg or
280
+ "unauthorized" in err_msg or
281
+ "invalid session" in err_msg or
282
+ "invalid token" in err_msg
279
283
  ):
280
284
  logger.info("Kotak session invalid (stCode=%s, msg='%s') for account '%s'. Triggering auto-login...", st_code, resp_json.get("errMsg"), name)
281
285
  if self.auto_login(consumer_key):
@@ -655,6 +659,7 @@ class KotakAccountManager(BaseBrokerManager):
655
659
 
656
660
  try:
657
661
  resp = client.limits()
662
+ _check_neo_error(resp)
658
663
  if isinstance(resp, dict):
659
664
  data = resp.get("data", resp)
660
665
  net = data.get("Net", data.get("net"))
@@ -1003,6 +1003,14 @@ class KCLILiveSession:
1003
1003
  " Advisor ",
1004
1004
  make_click_handler("advisor")
1005
1005
  ))
1006
+ fragments.append(("", " "))
1007
+
1008
+ # Tab 5: Help
1009
+ fragments.append((
1010
+ "class:tab.active" if self.info_mode == "help" else "class:tab.inactive",
1011
+ " Help ",
1012
+ make_click_handler("help")
1013
+ ))
1006
1014
 
1007
1015
  return fragments
1008
1016
 
@@ -1162,6 +1170,12 @@ class KCLILiveSession:
1162
1170
  async def _initial_fetch_and_connect(self) -> None:
1163
1171
  """Initial fetch of data and connect all WebSockets."""
1164
1172
  self.log_message("Initializing connections and fetching data...")
1173
+
1174
+ # Ensure accounts are validated & auto-logged in
1175
+ try:
1176
+ await self._run_api_call(self.client.init_accounts, self.accounts)
1177
+ except Exception as e:
1178
+ self.log_message(f"[#ff8700]Account init warning:[/#] {e}")
1165
1179
 
1166
1180
  # Fire off REST data fetch in the background so it doesn't block WebSockets
1167
1181
  if hasattr(self, "app") and self.app and self.app.loop:
@@ -1188,7 +1202,7 @@ class KCLILiveSession:
1188
1202
  )
1189
1203
 
1190
1204
  # Connect WebSockets for accounts that support it (Zerodha and Kotak).
1191
- for idx, acct in enumerate(self.accounts):
1205
+ for acct in self.accounts:
1192
1206
  api_key = acct.get("api_key")
1193
1207
  broker = acct.get("broker", "zerodha").lower()
1194
1208
  access_token = self.client.get_access_token(api_key)
@@ -1201,9 +1215,6 @@ class KCLILiveSession:
1201
1215
  )
1202
1216
  continue
1203
1217
 
1204
- if idx > 0:
1205
- await asyncio.sleep(0.2)
1206
-
1207
1218
  if api_key and (access_token or broker == "kotak"):
1208
1219
  try:
1209
1220
  if broker == "kotak":
@@ -2320,6 +2331,44 @@ class KCLILiveSession:
2320
2331
  )
2321
2332
  return
2322
2333
 
2334
+ # Direct commands to switch info pane mode
2335
+ cmd_norm = cmd.strip().lower()
2336
+ if cmd_norm in ("pending", "p", "1", "/1", "/pending"):
2337
+ self.info_mode = "orders_pending"
2338
+ self._update_info_buffer()
2339
+ if hasattr(self, "app") and self.app:
2340
+ self.app.invalidate()
2341
+ self.log_message("Switched info pane to [bold]Pending Orders[/bold].")
2342
+ return
2343
+ elif cmd_norm in ("executed", "e", "ex", "2", "/2", "/executed"):
2344
+ self.info_mode = "orders_executed"
2345
+ self._update_info_buffer()
2346
+ if hasattr(self, "app") and self.app:
2347
+ self.app.invalidate()
2348
+ self.log_message("Switched info pane to [bold]Executed Orders[/bold].")
2349
+ return
2350
+ elif cmd_norm in ("oc", "optionchain", "chain", "3", "/3", "/oc"):
2351
+ self.info_mode = "oc"
2352
+ self._update_info_buffer()
2353
+ if hasattr(self, "app") and self.app:
2354
+ self.app.invalidate()
2355
+ self.log_message("Switched info pane to [bold]Option Chain[/bold].")
2356
+ return
2357
+ elif cmd_norm in ("advisor", "adv", "ai", "4", "/4", "/advisor"):
2358
+ self.info_mode = "advisor"
2359
+ self._update_info_buffer()
2360
+ if hasattr(self, "app") and self.app:
2361
+ self.app.invalidate()
2362
+ self.log_message("Switched info pane to [bold]AI Advisor[/bold].")
2363
+ return
2364
+ elif cmd_norm in ("help", "h", "5", "/5", "/help"):
2365
+ self.info_mode = "help"
2366
+ self._update_info_buffer()
2367
+ if hasattr(self, "app") and self.app:
2368
+ self.app.invalidate()
2369
+ self.log_message("Switched info pane to [bold]Help & Shortcuts[/bold].")
2370
+ return
2371
+
2323
2372
  # Attempt parsing via the unified parser
2324
2373
  try:
2325
2374
  parsed_cmds = parse_command_line(cmd)
@@ -3826,6 +3875,59 @@ class KCLILiveSession:
3826
3875
  lines.append("No pending orders across all accounts.")
3827
3876
  return "\n".join(lines)
3828
3877
 
3878
+ def _build_help_text(self) -> str:
3879
+ """Return formatted keyboard shortcuts & command cheat sheet."""
3880
+ return """=== KiteCLI Keyboard & Command Cheat Sheet ===
3881
+
3882
+ 1. TAB SWITCHING (RIGHT PANE)
3883
+ Type at prompt:
3884
+ p or pending or 1 -> Switch to Pending Orders tab
3885
+ e or executed or 2 -> Switch to Executed Orders tab
3886
+ oc or chain or 3 -> Switch to Option Chain tab
3887
+ adv or advisor or 4-> Switch to AI Advisor tab
3888
+ h or help or 5 -> Switch to this Help tab
3889
+
3890
+ Universal Hotkeys:
3891
+ Ctrl + P -> Pending Orders
3892
+ Ctrl + E -> Executed Orders
3893
+ Ctrl + O -> Option Chain
3894
+ Ctrl + G -> AI Advisor
3895
+ Ctrl + H -> Help
3896
+
3897
+ Function Keys / Alt:
3898
+ F1 / F2 / F3 / F4 / F5
3899
+ Option+1 / 2 / 3 / 4 / 5 (or Esc then 1..5)
3900
+ Mouse Click: Click directly on any tab title in header bar
3901
+
3902
+ 2. ORDER PLACEMENT & TRADING COMMANDS
3903
+ Buy / Sell Orders:
3904
+ b <symbol> <qty> [price] -> Buy limit/market (e.g. b NIFTY26JUL22500PE 100 1.40)
3905
+ s <symbol> <qty> [price] -> Sell limit/market (e.g. s BANKNIFTY26JUL50000CE 15@2.50)
3906
+ b 1 100 -> Buy 100 qty of active position #1
3907
+ s 2 -> Sell/exit all qty of active position #2
3908
+
3909
+ Exiting Positions:
3910
+ exit <symbol|id> [price] -> Exit position by symbol or table ID (e.g. exit 1, exit 2 1.50)
3911
+ exit all [price] -> Exit all active positions (e.g. exit all)
3912
+
3913
+ Modifying & Cancelling:
3914
+ modify <order_id> [qty] [price] -> Modify open order
3915
+ cancel <order_id> -> Cancel pending order
3916
+ cancel all -> Cancel all open/pending orders
3917
+
3918
+ 3. SELECTION & SELECTION CLEARING
3919
+ Selection:
3920
+ Click on symbol/account row -> Target specific position/account
3921
+ Esc -> Clear active selection / reset account filter
3922
+
3923
+ 4. GLOBAL NAVIGATION & CONTROL
3924
+ Tab -> Cycle focus (Input box -> Positions -> Right Pane)
3925
+ Ctrl + R -> Search command history
3926
+ Ctrl + Left -> Narrow left pane split width
3927
+ Ctrl + Right -> Widen left pane split width
3928
+ Ctrl + C -> Exit KiteCLI
3929
+ """
3930
+
3829
3931
  def _update_info_buffer(self, reset_scroll: bool = True) -> None:
3830
3932
  """Push current info_mode text into the info_buffer.
3831
3933
 
@@ -3846,6 +3948,8 @@ class KCLILiveSession:
3846
3948
  except Exception as exc:
3847
3949
  self._last_advisor_text = f"=== Tuesday Expiry Advisor ===\n\nFailed to plan: {exc}"
3848
3950
  text = self._last_advisor_text
3951
+ elif self.info_mode == "help":
3952
+ text = self._build_help_text()
3849
3953
  else:
3850
3954
  text = self._last_oc_text
3851
3955
  if reset_scroll:
@@ -4217,8 +4321,10 @@ class KCLILiveSession:
4217
4321
  nxt = self.input_field.control
4218
4322
  event.app.layout.focus(nxt)
4219
4323
 
4220
- # Ctrl+1/2/3/4 & F1-F4 — switch info pane content
4324
+ # Ctrl+1/2/3/4, Ctrl+P/E/O/G, Alt/Esc+1/2/3/4, F1-F4 — switch info pane content
4221
4325
  @kb.add("c-1")
4326
+ @kb.add("c-p")
4327
+ @kb.add("escape", "1")
4222
4328
  @kb.add("f1")
4223
4329
  def _c1(event):
4224
4330
  self.info_mode = "orders_pending"
@@ -4226,6 +4332,8 @@ class KCLILiveSession:
4226
4332
  event.app.invalidate()
4227
4333
 
4228
4334
  @kb.add("c-2")
4335
+ @kb.add("c-e")
4336
+ @kb.add("escape", "2")
4229
4337
  @kb.add("f2")
4230
4338
  def _c2(event):
4231
4339
  self.info_mode = "orders_executed"
@@ -4233,6 +4341,8 @@ class KCLILiveSession:
4233
4341
  event.app.invalidate()
4234
4342
 
4235
4343
  @kb.add("c-3")
4344
+ @kb.add("c-o")
4345
+ @kb.add("escape", "3")
4236
4346
  @kb.add("f3")
4237
4347
  def _c3(event):
4238
4348
  self.info_mode = "oc"
@@ -4240,12 +4350,23 @@ class KCLILiveSession:
4240
4350
  event.app.invalidate()
4241
4351
 
4242
4352
  @kb.add("c-4")
4353
+ @kb.add("c-g")
4354
+ @kb.add("escape", "4")
4243
4355
  @kb.add("f4")
4244
4356
  def _c4(event):
4245
4357
  self.info_mode = "advisor"
4246
4358
  self._update_info_buffer()
4247
4359
  event.app.invalidate()
4248
4360
 
4361
+ @kb.add("c-5")
4362
+ @kb.add("c-h")
4363
+ @kb.add("escape", "5")
4364
+ @kb.add("f5")
4365
+ def _c5(event):
4366
+ self.info_mode = "help"
4367
+ self._update_info_buffer()
4368
+ event.app.invalidate()
4369
+
4249
4370
  # Ctrl+Left/Right — adjust left/right pane split
4250
4371
  @kb.add("c-left")
4251
4372
  def _narrow_left(event):
@@ -206,6 +206,7 @@ def live() -> None:
206
206
  display_error("No accounts configured. Edit your config file to add accounts.")
207
207
  raise typer.Exit(code=1)
208
208
  client = _build_client(config)
209
+ client.init_accounts(accounts)
209
210
 
210
211
  # Launch interactive session
211
212
  session = KCLILiveSession(client, accounts)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kitecli
3
- Version: 0.2.5
3
+ Version: 0.2.6
4
4
  Summary: KiteCLI — Multi-account, multi-broker trading positions viewer (Zerodha + Kotak Neo)
5
5
  Author: KiteCLI Team
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "kitecli"
7
- version = "0.2.5"
7
+ version = "0.2.6"
8
8
  description = "KiteCLI — Multi-account, multi-broker trading positions viewer (Zerodha + Kotak Neo)"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes