@ai-digital-factory/lmt-cli 0.1.1 → 0.1.2

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 (3) hide show
  1. package/README.md +23 -17
  2. package/dist/index.js +24 -75
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -72,37 +72,43 @@ Replace the path with an owner-readable credential file supplied by your secret-
72
72
 
73
73
  ## Headless Zerodha login
74
74
 
75
- First establish an Operator Session with `lmt login` or interactive login using the same environment and profile. Zerodha credentials stay on Broker. Submit the current six-digit OTP:
75
+ First establish the Operator Session with `login`. Zerodha credentials stay on Broker. Headless Zerodha login uses one two-step flow, matching Web: start login first, then obtain the OTP from Kite and submit it to that exact attempt.
76
76
 
77
- ```sh
78
- lmt zerodha login --env dev --profile my-dev-session --otp CURRENT_OTP
79
- lmt zerodha status --env dev --profile my-dev-session
80
- ```
77
+ 1. Start the Zerodha Login Attempt:
81
78
 
82
- Replace `CURRENT_OTP` with the actual six-digit code. `zerodha login` starts or resumes the profile's login attempt and verifies the resulting Zerodha Session through Gate. The OTP is not stored or included in JSON results or diagnostics; it is passed as a command argument. `--otp-stdin` is unsupported.
79
+ ```sh
80
+ lmt zerodha login --env dev --profile my-dev-session
81
+ ```
83
82
 
84
- To start the challenge before obtaining the OTP:
83
+ When Zerodha requests the OTP, the command returns `outcome: "blocked"`, `reason: "awaiting_otp"`, exit code `4`, and `zerodha.attemptId`. This is the expected handoff to obtain the OTP from Kite.
85
84
 
86
- ```sh
87
- lmt zerodha login --env dev --profile my-dev-session
88
- ```
85
+ 2. Copy that attempt ID and submit the current six-digit OTP:
89
86
 
90
- When human input is needed, this returns `outcome: "blocked"`, `reason: "awaiting_otp"`, and exit code `4`. Repeat `zerodha login` with `--otp CURRENT_OTP` once the code is available. Attempt IDs are remembered automatically for the selected profile and target.
87
+ ```sh
88
+ lmt zerodha otp --env dev --profile my-dev-session --attempt ATTEMPT_ID --otp CURRENT_OTP
89
+ ```
90
+
91
+ Replace `ATTEMPT_ID` with `zerodha.attemptId` from step 1 and `CURRENT_OTP` with the code obtained after the challenge. Use the same environment and profile in both steps. Success requires verification of the resulting Zerodha Session through Gate. OTPs are excluded from JSON results and diagnostics and never stored.
92
+
93
+ From the monorepo root, use `pnpm -s cli:headless` in place of `lmt`. Both `local` and `dev` are supported.
91
94
 
92
- Additional commands for an existing attempt:
95
+ To inspect the attempt or verify the session:
93
96
 
94
97
  ```sh
95
- lmt zerodha observe --env dev --profile my-dev-session
96
- lmt zerodha otp --env dev --profile my-dev-session --otp CURRENT_OTP
98
+ lmt zerodha observe --env dev --profile my-dev-session --attempt ATTEMPT_ID
99
+ lmt zerodha status --env dev --profile my-dev-session
97
100
  ```
98
101
 
99
- `observe` and `otp` optionally accept `--attempt ID` to override the remembered attempt. Invalid OTPs report Broker's rejection message and retry count. Successful login, Operator logout, and Operator Session replacement or rejection clear the remembered attempt.
102
+ `--attempt ID` is required for `zerodha otp` and `zerodha observe`. The CLI does not remember attempt IDs. Existing saved attempt IDs are ignored and removed on the next profile-store write; saved cookies and the SMS credential remain usable. `zerodha login --otp` is rejected before network access; there is no single-pass login or automatic resume. `--otp-stdin` remains unsupported.
103
+
104
+ An expired, canceled, replaced, or failed attempt fails without starting a new login or submitting the OTP to another attempt. Explicitly run `zerodha login` again, obtain the new challenge's OTP, and submit it using the new attempt ID. A rejected OTP that leaves the attempt active returns `awaiting_otp` with Broker's rejection message and retry count; retry `zerodha otp` with a fresh code and the same ID. Every command remains noninteractive and honors `--timeout`.
105
+
100
106
 
101
107
  ## Profiles and local use
102
108
 
103
109
  Profiles save Client Identity and Operator Session cookies across invocations. Sessions are isolated by profile, environment, canonical Origin, and configured Operator. Profile names must be 1–64 letters, digits, dots, underscores, or hyphens, starting with a letter or digit.
104
110
 
105
- Storage is `~/.lmt/secrets.pem`: a protected JSON file containing the shared SMS credential, per-profile cookies, and pending Zerodha attempt IDs. The CLI enforces owner-only permissions and migrates legacy storage automatically. Do not share this file or include it in logs or version control.
111
+ Storage is `~/.lmt/secrets.pem`: a protected JSON file containing the shared SMS credential and per-profile cookies. The CLI enforces owner-only permissions and migrates legacy storage automatically. Do not share this file or include it in logs or version control.
106
112
 
107
113
  For local use, first start Web, Gate, and Broker from a configured source checkout with `pnpm dev:local` in `live-market-terminal/`. The installed CLI uses the bundled canonical local Origin (`http://127.0.0.1:5173`) through Web's `/api` proxy; it does not fall back to a different port. The CLI's SMS read credential is separate from the service credential used by Gate and Broker.
108
114
 
@@ -121,7 +127,7 @@ Headless commands write one versioned JSON object per line to stdout; diagnostic
121
127
  | `1` | Action failure | Read the diagnostic; check SMS permissions or service reachability. |
122
128
  | `2` | Invalid invocation or target | Check `lmt --help`; supply `local` or `dev` and a valid profile. |
123
129
  | `3` | Operator Login required | Run `login` or interactive login with the same environment and profile. |
124
- | `4` | Validation blocked or Zerodha awaiting OTP | Read `reason`; for `awaiting_otp`, repeat Zerodha login with the current OTP. |
130
+ | `4` | Validation blocked or Zerodha awaiting OTP | Read `reason`; for `awaiting_otp`, obtain the Kite OTP and run `zerodha otp --attempt ID --otp CODE`. |
125
131
 
126
132
  Actions default to 30 seconds; validation defaults to 2 minutes. Headless actions and validation accept a finite deadline between 1 millisecond and 10 minutes:
127
133
 
package/dist/index.js CHANGED
@@ -1441,8 +1441,7 @@ function normalizeSession(value) {
1441
1441
  operatorId: session.operatorId,
1442
1442
  updatedAt: session.updatedAt,
1443
1443
  ...session.operatorSessionCookie ? { operatorSessionCookie: session.operatorSessionCookie } : {},
1444
- ...session.clientIdentityCookie ? { clientIdentityCookie: session.clientIdentityCookie } : {},
1445
- ...typeof session.zerodhaAttemptId === "string" && /^[a-zA-Z0-9_-]{1,200}$/.test(session.zerodhaAttemptId) ? { zerodhaAttemptId: session.zerodhaAttemptId } : {}
1444
+ ...session.clientIdentityCookie ? { clientIdentityCookie: session.clientIdentityCookie } : {}
1446
1445
  };
1447
1446
  }
1448
1447
  function parseRecord(raw) {
@@ -1539,10 +1538,6 @@ var CliProfileStore = class {
1539
1538
  ...cookies.clientIdentityCookie ? { clientIdentityCookie: cookies.clientIdentityCookie } : {},
1540
1539
  ...cookies.operatorSessionCookie ? { operatorSessionCookie: cookies.operatorSessionCookie } : {}
1541
1540
  };
1542
- const previous = profileRecord.sessions[index];
1543
- if (previous?.operatorSessionCookie && previous.operatorSessionCookie === next.operatorSessionCookie && previous.zerodhaAttemptId) {
1544
- next.zerodhaAttemptId = previous.zerodhaAttemptId;
1545
- }
1546
1541
  if (!sessionHasCookies(next)) {
1547
1542
  if (index >= 0) profileRecord.sessions.splice(index, 1);
1548
1543
  } else if (index >= 0) {
@@ -1563,32 +1558,11 @@ var CliProfileStore = class {
1563
1558
  if (index < 0) return;
1564
1559
  const stored = profileRecord.sessions[index];
1565
1560
  delete stored.operatorSessionCookie;
1566
- delete stored.zerodhaAttemptId;
1567
1561
  stored.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1568
1562
  this.cleanEmptySession(profileRecord, index);
1569
1563
  if (profileRecord.sessions.length === 0) delete record.profiles[profile];
1570
1564
  });
1571
1565
  }
1572
- async getZerodhaAttempt(profile, target) {
1573
- assertClientProfileName(profile);
1574
- return this.withLock(async () => {
1575
- const session = (await this.readRecord()).profiles[profile]?.sessions.find((value) => targetsEqual(value, target));
1576
- return session?.operatorSessionCookie ? session.zerodhaAttemptId : void 0;
1577
- });
1578
- }
1579
- async saveZerodhaAttempt(profile, target, attemptId) {
1580
- assertClientProfileName(profile);
1581
- if (attemptId !== void 0 && !/^[a-zA-Z0-9_-]{1,200}$/.test(attemptId)) throw new Error("Invalid Zerodha attempt identifier.");
1582
- await this.update((record) => {
1583
- const session = record.profiles[profile]?.sessions.find((value) => targetsEqual(value, target));
1584
- if (!session?.operatorSessionCookie) {
1585
- if (attemptId) throw new Error("An Operator Session is required to save a Zerodha login attempt.");
1586
- return;
1587
- }
1588
- if (attemptId) session.zerodhaAttemptId = attemptId;
1589
- else delete session.zerodhaAttemptId;
1590
- });
1591
- }
1592
1566
  async removeTarget(profile, target) {
1593
1567
  assertClientProfileName(profile);
1594
1568
  await this.update((record) => {
@@ -1716,7 +1690,6 @@ var CliProfileStore = class {
1716
1690
  operatorId: session.operatorId,
1717
1691
  ...session.operatorSessionCookie ? { operatorSessionCookie: session.operatorSessionCookie } : {},
1718
1692
  ...session.clientIdentityCookie ? { clientIdentityCookie: session.clientIdentityCookie } : {},
1719
- ...session.zerodhaAttemptId ? { zerodhaAttemptId: session.zerodhaAttemptId } : {},
1720
1693
  updatedAt: session.updatedAt
1721
1694
  }))
1722
1695
  }
@@ -1750,7 +1723,7 @@ var CliProfileStore = class {
1750
1723
  };
1751
1724
 
1752
1725
  // src/zerodha-actions.ts
1753
- async function runZerodhaAction(api, action, operatorId, signal, attemptId, otp, onStarted) {
1726
+ async function runZerodhaAction(api, action, operatorId, signal, attemptId, otp) {
1754
1727
  if (action === "status") {
1755
1728
  await api.confirmZerodhaSession();
1756
1729
  return { status: "succeeded" };
@@ -1759,7 +1732,6 @@ async function runZerodhaAction(api, action, operatorId, signal, attemptId, otp,
1759
1732
  const attempt = await api.startLogin();
1760
1733
  if (attempt.operatorId !== operatorId) throw new Error("Zerodha login reached an unexpected Operator.");
1761
1734
  attemptId = attempt.attemptId;
1762
- await onStarted?.(attemptId);
1763
1735
  }
1764
1736
  if (!attemptId) throw new Error("Zerodha login requires an attempt identifier.");
1765
1737
  if (action === "otp") {
@@ -2103,9 +2075,12 @@ function parseArguments(argv) {
2103
2075
  if (attemptId && (action === "login" || action === "status") || attemptId && !/^[a-zA-Z0-9_-]{1,200}$/.test(attemptId)) {
2104
2076
  throw new InvocationError("--attempt must be a valid Zerodha attempt ID and is only supported for observe and otp.");
2105
2077
  }
2078
+ if ((action === "observe" || action === "otp") && !attemptId) {
2079
+ throw new InvocationError("--attempt ID is required for zerodha observe and otp. Use the attemptId returned by zerodha login.");
2080
+ }
2106
2081
  const otp = options.get("--otp");
2107
- if (action === "otp" && otp === void 0 || otp !== void 0 && action !== "otp" && action !== "login") {
2108
- throw new InvocationError("--otp VALUE is supported for zerodha login and required for zerodha otp.");
2082
+ if (action === "otp" && otp === void 0 || otp !== void 0 && action !== "otp") {
2083
+ throw new InvocationError("--otp CODE is required for zerodha otp and is not supported for zerodha login. Start with zerodha login, then submit with zerodha otp --attempt ID --otp CODE.");
2109
2084
  }
2110
2085
  if (otp !== void 0 && !/^\d{6}$/.test(otp)) throw new InvocationError("OTP must be six digits.");
2111
2086
  return {
@@ -2294,40 +2269,14 @@ async function runHeadlessCli(argv, dependencies = {}) {
2294
2269
  actionApi = await actions.getApi();
2295
2270
  if (command.kind === "zerodha") {
2296
2271
  await actions.status();
2297
- const otp = command.otp;
2298
- let attemptId = command.attemptId;
2299
- if (!attemptId && (command.action === "otp" || command.action === "observe" || command.action === "login" && otp)) {
2300
- attemptId = await store.getZerodhaAttempt(profile, target);
2301
- }
2302
- if (!attemptId && (command.action === "otp" || command.action === "observe")) {
2303
- throw new InvocationError("No pending Zerodha login for this profile. Run zerodha login first.");
2304
- }
2305
- let completed;
2306
- if (command.action === "login" && otp && attemptId) {
2307
- try {
2308
- const pending = await runZerodhaAction(actionApi, "observe", target.operatorId, signal, attemptId);
2309
- if (pending.status === "succeeded") completed = pending;
2310
- } catch (error) {
2311
- if (!(error instanceof ZerodhaLoginFailedError || error instanceof ZerodhaLoginAttemptUnavailableError) || signal.aborted) throw error;
2312
- await store.saveZerodhaAttempt(profile, target);
2313
- attemptId = void 0;
2314
- }
2315
- }
2316
- const action = command.action === "login" && otp && attemptId ? "otp" : command.action;
2317
- const saveAttempt = (id) => store.saveZerodhaAttempt(profile, target, id);
2318
- let zerodha;
2319
- let startedFresh = action === "login";
2320
- try {
2321
- zerodha = completed ?? await runZerodhaAction(actionApi, action, target.operatorId, signal, attemptId, otp, saveAttempt);
2322
- } catch (error) {
2323
- if (!(error instanceof ZerodhaLoginAttemptUnavailableError) || command.action !== "login" || !otp || signal.aborted) throw error;
2324
- startedFresh = true;
2325
- zerodha = await runZerodhaAction(actionApi, "login", target.operatorId, signal, void 0, void 0, saveAttempt);
2326
- }
2327
- if (command.action === "login" && otp && zerodha.status === "awaiting_otp" && startedFresh) {
2328
- zerodha = await runZerodhaAction(actionApi, "otp", target.operatorId, signal, zerodha.attemptId, otp);
2329
- }
2330
- if (zerodha.status === "succeeded") await store.saveZerodhaAttempt(profile, target);
2272
+ const zerodha = await runZerodhaAction(
2273
+ actionApi,
2274
+ command.action,
2275
+ target.operatorId,
2276
+ signal,
2277
+ command.attemptId,
2278
+ command.otp
2279
+ );
2331
2280
  const awaitingOtp = zerodha.status === "awaiting_otp";
2332
2281
  emit({
2333
2282
  version: 1,
@@ -2377,9 +2326,6 @@ async function runHeadlessCli(argv, dependencies = {}) {
2377
2326
  }, streams);
2378
2327
  return 0;
2379
2328
  } catch (error) {
2380
- if ((error instanceof ZerodhaLoginFailedError || error instanceof ZerodhaLoginAttemptUnavailableError) && command?.kind === "zerodha" && !command.attemptId && store && target && profile && !signal?.aborted) {
2381
- await store.saveZerodhaAttempt(profile, target).catch(() => void 0);
2382
- }
2383
2329
  if (error instanceof OperatorSessionRejectedError && store && actionApi && target && profile && !signal?.aborted) {
2384
2330
  await store.saveSession(profile, target, actionApi.getCookies()).catch(() => void 0);
2385
2331
  }
@@ -2423,14 +2369,14 @@ Usage (lmt is an alias of live-market-terminal):
2423
2369
  live-market-terminal login --env local|dev --profile NAME [--timeout DURATION]
2424
2370
  live-market-terminal logout --env local|dev --profile NAME [--timeout DURATION]
2425
2371
  live-market-terminal status --env local|dev --profile NAME [--timeout DURATION]
2426
- live-market-terminal zerodha login --env local|dev --profile NAME [--otp CODE] [--timeout DURATION]
2427
- live-market-terminal zerodha observe [--attempt ID] --env local|dev --profile NAME [--timeout DURATION]
2428
- live-market-terminal zerodha otp [--attempt ID] --otp CODE --env local|dev --profile NAME [--timeout DURATION]
2372
+ live-market-terminal zerodha login --env local|dev --profile NAME [--timeout DURATION]
2373
+ live-market-terminal zerodha observe --attempt ID --env local|dev --profile NAME [--timeout DURATION]
2374
+ live-market-terminal zerodha otp --attempt ID --otp CODE --env local|dev --profile NAME [--timeout DURATION]
2429
2375
  live-market-terminal zerodha status --env local|dev --profile NAME [--timeout DURATION]
2430
2376
  live-market-terminal validate operator-session --env local|dev --profile NAME [--timeout DURATION]
2431
2377
  live-market-terminal credential provision --env local|dev --profile NAME --credential-stdin
2432
2378
 
2433
- Headless options (--env, --profile, --timeout, --otp, --credential-stdin) may appear before or after the command.
2379
+ Headless options (--env, --profile, --timeout, --attempt, --otp, --credential-stdin) may appear before or after the command.
2434
2380
 
2435
2381
  From the monorepo root:
2436
2382
  pnpm cli [--profile NAME] # interactive; --env defaults to local
@@ -2439,6 +2385,9 @@ From the monorepo root:
2439
2385
 
2440
2386
  Targets: local and dev only. Production is not permitted.
2441
2387
 
2388
+ Zerodha login is two steps: zerodha login, then zerodha otp --attempt ID --otp CODE.
2389
+ Obtain the OTP from Kite after login returns awaiting_otp; expired attempts require a new login.
2390
+
2442
2391
  Interactive mode restores a saved Operator Session when Gate still accepts it.
2443
2392
  Headless commands write one JSON object per line to stdout; diagnostics go to stderr.
2444
2393
 
@@ -2453,8 +2402,8 @@ Options:
2453
2402
  --env local|dev Target environment (interactive default: local; headless: required)
2454
2403
  --profile NAME Client profile (default: default for interactive)
2455
2404
  --timeout DURATION Action deadline, e.g. 45s, 2m, 30000ms (headless)
2456
- --attempt ID Override the remembered Zerodha attempt (optional)
2457
- --otp CODE Six-digit OTP for zerodha login or otp
2405
+ --attempt ID Attempt ID returned by zerodha login (required for observe and otp)
2406
+ --otp CODE Six-digit OTP for zerodha otp only
2458
2407
  --credential-stdin Read SMS credential from piped stdin (headless provision)
2459
2408
  --help, -h Show this help
2460
2409
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-digital-factory/lmt-cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "live-market-terminal": "dist/index.js",