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.
- {kitecli-0.2.5 → kitecli-0.2.6}/PKG-INFO +1 -1
- {kitecli-0.2.5 → kitecli-0.2.6}/cli/api_client.py +4 -2
- {kitecli-0.2.5 → kitecli-0.2.6}/cli/kotak_manager.py +12 -7
- {kitecli-0.2.5 → kitecli-0.2.6}/cli/live_session.py +126 -5
- {kitecli-0.2.5 → kitecli-0.2.6}/cli/main.py +1 -0
- {kitecli-0.2.5 → kitecli-0.2.6}/kitecli.egg-info/PKG-INFO +1 -1
- {kitecli-0.2.5 → kitecli-0.2.6}/pyproject.toml +1 -1
- {kitecli-0.2.5 → kitecli-0.2.6}/README.md +0 -0
- {kitecli-0.2.5 → kitecli-0.2.6}/cli/__init__.py +0 -0
- {kitecli-0.2.5 → kitecli-0.2.6}/cli/advisor.py +0 -0
- {kitecli-0.2.5 → kitecli-0.2.6}/cli/base_manager.py +0 -0
- {kitecli-0.2.5 → kitecli-0.2.6}/cli/config.py +0 -0
- {kitecli-0.2.5 → kitecli-0.2.6}/cli/display.py +0 -0
- {kitecli-0.2.5 → kitecli-0.2.6}/cli/executor.py +0 -0
- {kitecli-0.2.5 → kitecli-0.2.6}/cli/kite_manager.py +0 -0
- {kitecli-0.2.5 → kitecli-0.2.6}/cli/nli.py +0 -0
- {kitecli-0.2.5 → kitecli-0.2.6}/cli/parser.py +0 -0
- {kitecli-0.2.5 → kitecli-0.2.6}/cli/recorder.py +0 -0
- {kitecli-0.2.5 → kitecli-0.2.6}/cli/telegram_bot.py +0 -0
- {kitecli-0.2.5 → kitecli-0.2.6}/kitecli.egg-info/SOURCES.txt +0 -0
- {kitecli-0.2.5 → kitecli-0.2.6}/kitecli.egg-info/dependency_links.txt +0 -0
- {kitecli-0.2.5 → kitecli-0.2.6}/kitecli.egg-info/entry_points.txt +0 -0
- {kitecli-0.2.5 → kitecli-0.2.6}/kitecli.egg-info/requires.txt +0 -0
- {kitecli-0.2.5 → kitecli-0.2.6}/kitecli.egg-info/top_level.txt +0 -0
- {kitecli-0.2.5 → kitecli-0.2.6}/setup.cfg +0 -0
- {kitecli-0.2.5 → kitecli-0.2.6}/tests/test_multi_broker.py +0 -0
- {kitecli-0.2.5 → kitecli-0.2.6}/tests/test_nli.py +0 -0
- {kitecli-0.2.5 → kitecli-0.2.6}/tests/test_parser.py +0 -0
- {kitecli-0.2.5 → kitecli-0.2.6}/tests/test_telegram.py +0 -0
- {kitecli-0.2.5 → kitecli-0.2.6}/tests/test_ui.py +0 -0
|
@@ -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
|
-
|
|
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)
|
|
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
|
-
|
|
276
|
-
st_code
|
|
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
|
|
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
|
|
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)
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "kitecli"
|
|
7
|
-
version = "0.2.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|