@ai-digital-factory/lmt-cli 0.1.0 → 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.
- package/README.md +125 -90
- package/dist/index.js +24 -75
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,151 +1,186 @@
|
|
|
1
1
|
# Live Market Terminal CLI
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Use the published [`@ai-digital-factory/lmt-cli`](https://www.npmjs.com/package/@ai-digital-factory/lmt-cli) package to log in to Live Market Terminal, manage Operator Sessions, and complete Zerodha login from your terminal. The executable is `lmt`; `live-market-terminal` is an alias with the same commands.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Install or update
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
Requires Node.js 22 or later. Install without a source checkout:
|
|
7
|
+
Requires **Node.js 22 or later**, with npm and npx available on your PATH. No source checkout, pnpm, build step, or local configuration file is needed for the published package.
|
|
10
8
|
|
|
11
9
|
```sh
|
|
12
|
-
|
|
10
|
+
node --version
|
|
11
|
+
npm install --global @ai-digital-factory/lmt-cli@latest
|
|
13
12
|
lmt --help
|
|
14
|
-
lmt interactive --env dev --profile my-dev-session
|
|
15
|
-
lmt status --env dev --profile my-dev-session
|
|
16
13
|
```
|
|
17
14
|
|
|
18
|
-
|
|
19
|
-
and headless command syntax. Provision the protected SMS read credential with
|
|
20
|
-
`lmt interactive credential provision --env dev`, or use
|
|
21
|
-
`lmt credential provision --env dev --profile default --credential-stdin` with
|
|
22
|
-
protected stdin as described below. Installation never provisions credentials.
|
|
23
|
-
The package includes canonical non-secret local/dev Origins, Gate scopes, and
|
|
24
|
-
SMS endpoint; it needs no repository configuration in the current directory.
|
|
25
|
-
Local use still requires a running local stack. Production targets remain unavailable.
|
|
15
|
+
Run the same install command to update to the latest published version. To check your installed version:
|
|
26
16
|
|
|
27
|
-
|
|
17
|
+
```sh
|
|
18
|
+
npm list --global @ai-digital-factory/lmt-cli --depth=0
|
|
19
|
+
```
|
|
28
20
|
|
|
29
|
-
The
|
|
30
|
-
current `main`, tests the CLI, builds a standalone package with bundled private
|
|
31
|
-
contracts, selects the next npm patch, and tests a clean installation of the exact
|
|
32
|
-
tarball it publishes publicly under `latest`. Node 22 and pnpm 10.33.3 match CI.
|
|
21
|
+
The package includes the non-secret Origins, Gate scopes, and SMS endpoint. Choose `--env dev` for the deployed dev service, or `--env local` for a running local stack. Only `local` and `dev` are supported; `prod` is rejected. Installing the CLI does not start services or provision credentials.
|
|
33
22
|
|
|
34
|
-
|
|
35
|
-
It must be able to read `NPM_TOKEN` from SMS scope `infra`, at Vault
|
|
36
|
-
`secret/data/infra/NPM_TOKEN` with field `value`. The endpoint and scope are
|
|
37
|
-
non-secret repository configuration. The existing npm token needs publishing
|
|
38
|
-
permission for `@ai-digital-factory/lmt-cli`; it is masked and passed only to the
|
|
39
|
-
publishing step. No credential values are included in the package.
|
|
23
|
+
## Quick start: interactive dev login
|
|
40
24
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
only in the temporary publishing artifact, with no bump commit, git tag, GitHub
|
|
44
|
-
Release, Origin Version, or deployment. A retry after successful npm publication
|
|
45
|
-
selects another patch, since published npm versions are immutable.
|
|
25
|
+
1. Obtain your Operator Login username and password. For headless login and Operator Session validation, also obtain an SMS read credential authorized to read `live-market-terminal-operator-directory` in the selected Gate scope from your administrator.
|
|
26
|
+
2. If using those SMS-backed commands, provision the credential once using the masked prompt:
|
|
46
27
|
|
|
47
|
-
|
|
28
|
+
```sh
|
|
29
|
+
lmt interactive credential provision --env dev
|
|
30
|
+
```
|
|
48
31
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
32
|
+
Paste the SMS read credential and press Enter. After `SMS credential saved.` appears, press Ctrl+C to leave the provisioning screen. The credential is shared across profiles.
|
|
33
|
+
3. Open the interactive terminal:
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
lmt interactive --env dev --profile my-dev-session
|
|
37
|
+
```
|
|
54
38
|
|
|
55
|
-
|
|
39
|
+
Enter your Operator Login username and password when prompted. This interactive login uses the credentials you enter; it does not require reading them from SMS. A valid saved session resumes automatically.
|
|
40
|
+
4. Use the menu to **Login with Zerodha**, **Check Operator Session**, **Validate Operator Session**, or **Logout Operator Session**. Zerodha login prompts for the current six-digit OTP. Validation requires the provisioned SMS credential.
|
|
56
41
|
|
|
57
|
-
|
|
42
|
+
Press Enter to submit each prompt. At the Operator password prompt, Escape returns to the username; Ctrl+U clears either field. Passwords remain masked. Press Ctrl+C to exit the terminal; use the logout command or menu action when you want to revoke the Operator Session.
|
|
58
43
|
|
|
59
|
-
|
|
44
|
+
Interactive mode defaults to `--env local --profile default` when these options are omitted. Keep the same environment and profile across commands to reuse a session:
|
|
60
45
|
|
|
61
46
|
```sh
|
|
62
|
-
|
|
63
|
-
pnpm cli --profile default
|
|
64
|
-
pnpm cli --env dev --profile my-dev-session
|
|
47
|
+
lmt status --env dev --profile my-dev-session
|
|
65
48
|
```
|
|
66
49
|
|
|
67
|
-
|
|
50
|
+
## Headless Operator login and sessions
|
|
51
|
+
|
|
52
|
+
Headless commands require **both `--env` and `--profile`**. They never prompt for input. Provision the SMS credential using the masked interactive command above, then run:
|
|
68
53
|
|
|
69
54
|
```sh
|
|
70
|
-
|
|
55
|
+
lmt login --env dev --profile my-dev-session
|
|
56
|
+
lmt status --env dev --profile my-dev-session
|
|
57
|
+
lmt validate operator-session --env dev --profile my-dev-session
|
|
58
|
+
lmt logout --env dev --profile my-dev-session
|
|
71
59
|
```
|
|
72
60
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
## Headless use
|
|
61
|
+
`login` reads the configured Operator's username and password from the selected Gate scope's SMS Operator Directory and authenticates through Gate. SMS reads invoke `@ai-digital-factory/sms-cli@0.1.2` through npx, so npm registry access is needed when it is not cached. `status` verifies the saved Operator Session with Gate and the Broker-backed operators route; it does not confirm Zerodha login. Validation exercises login and session behavior using a temporary profile, logs out its temporary sessions, and removes only its temporary records.
|
|
76
62
|
|
|
77
|
-
|
|
63
|
+
`logout` revokes the Operator Session through Gate and clears its saved cookies and pending Zerodha attempt. If Gate cannot confirm logout, the command fails and retains cookies for retry. Other profiles, the shared SMS credential, and Broker-owned SMS access tokens remain intact.
|
|
78
64
|
|
|
79
|
-
|
|
65
|
+
For automated credential provisioning, pipe one non-empty credential line through protected stdin:
|
|
80
66
|
|
|
81
67
|
```sh
|
|
82
|
-
|
|
83
|
-
printf '%s\n' "$SMS_CREDENTIAL" | pnpm -s cli:headless credential provision --env dev --profile my-dev-session --credential-stdin
|
|
84
|
-
unset SMS_CREDENTIAL
|
|
68
|
+
lmt credential provision --env dev --profile my-dev-session --credential-stdin < /path/to/protected-sms-credential-file
|
|
85
69
|
```
|
|
86
70
|
|
|
87
|
-
|
|
71
|
+
Replace the path with an owner-readable credential file supplied by your secret-management process. Keep SMS credentials, passwords, and cookies out of command arguments. Provisioning saves the credential locally; it does not verify SMS access.
|
|
88
72
|
|
|
89
|
-
|
|
90
|
-
pnpm -s cli:headless login --env dev --profile my-dev-session
|
|
91
|
-
pnpm -s cli:headless logout --env dev --profile my-dev-session
|
|
92
|
-
pnpm -s cli:headless status --env dev --profile my-dev-session
|
|
93
|
-
pnpm -s cli:headless validate operator-session --env dev --profile my-dev-session
|
|
94
|
-
```
|
|
73
|
+
## Headless Zerodha login
|
|
95
74
|
|
|
96
|
-
|
|
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.
|
|
97
76
|
|
|
98
|
-
|
|
77
|
+
1. Start the Zerodha Login Attempt:
|
|
99
78
|
|
|
100
|
-
```sh
|
|
101
|
-
|
|
102
|
-
```
|
|
79
|
+
```sh
|
|
80
|
+
lmt zerodha login --env dev --profile my-dev-session
|
|
81
|
+
```
|
|
103
82
|
|
|
104
|
-
|
|
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.
|
|
105
84
|
|
|
106
|
-
|
|
85
|
+
2. Copy that attempt ID and submit the current six-digit OTP:
|
|
107
86
|
|
|
108
|
-
```sh
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
pnpm -s cli:headless validate operator-session --env local --profile smoke-local
|
|
87
|
+
```sh
|
|
88
|
+
lmt zerodha otp --env dev --profile my-dev-session --attempt ATTEMPT_ID --otp CURRENT_OTP
|
|
89
|
+
```
|
|
112
90
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
pnpm -s cli:headless
|
|
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.
|
|
94
|
+
|
|
95
|
+
To inspect the attempt or verify the session:
|
|
96
|
+
|
|
97
|
+
```sh
|
|
98
|
+
lmt zerodha observe --env dev --profile my-dev-session --attempt ATTEMPT_ID
|
|
99
|
+
lmt zerodha status --env dev --profile my-dev-session
|
|
116
100
|
```
|
|
117
101
|
|
|
118
|
-
|
|
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`.
|
|
119
105
|
|
|
120
|
-
Interactive Operator Login returns to the username prompt after rejected credentials, with both previous entries available for editing. At the password prompt, press Escape to return to the username; press Ctrl+U to clear either field. The password stays masked and is retained only in memory for retries, then cleared after successful login or the login deadline.
|
|
121
106
|
|
|
122
|
-
|
|
107
|
+
## Profiles and local use
|
|
123
108
|
|
|
124
|
-
|
|
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.
|
|
125
110
|
|
|
126
|
-
|
|
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.
|
|
112
|
+
|
|
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.
|
|
127
114
|
|
|
128
115
|
```sh
|
|
129
|
-
|
|
116
|
+
lmt interactive --env local --profile my-local-session
|
|
117
|
+
lmt status --env local --profile my-local-session
|
|
130
118
|
```
|
|
131
119
|
|
|
132
|
-
|
|
120
|
+
## Output, deadlines, and troubleshooting
|
|
121
|
+
|
|
122
|
+
Headless commands write one versioned JSON object per line to stdout; diagnostics go to stderr. Inspect `outcome`, `reason`, and, on failure, `error`. Use the process exit code for automation:
|
|
123
|
+
|
|
124
|
+
| Exit code | Meaning | Next step |
|
|
125
|
+
| --- | --- | --- |
|
|
126
|
+
| `0` | Success or validation pass | Continue. |
|
|
127
|
+
| `1` | Action failure | Read the diagnostic; check SMS permissions or service reachability. |
|
|
128
|
+
| `2` | Invalid invocation or target | Check `lmt --help`; supply `local` or `dev` and a valid profile. |
|
|
129
|
+
| `3` | Operator Login required | Run `login` or interactive login with the same environment and profile. |
|
|
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`. |
|
|
133
131
|
|
|
134
|
-
|
|
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:
|
|
135
133
|
|
|
136
134
|
```sh
|
|
137
|
-
|
|
135
|
+
lmt status --env dev --profile my-dev-session --timeout 45s
|
|
136
|
+
lmt validate operator-session --env dev --profile my-dev-session --timeout 2m
|
|
138
137
|
```
|
|
139
138
|
|
|
140
|
-
|
|
139
|
+
- **`lmt: command not found`:** confirm the global installation with `npm list --global @ai-digital-factory/lmt-cli --depth=0` and add npm's global executable directory to PATH (`npm prefix --global`, then `bin/` on macOS/Linux).
|
|
140
|
+
- **Missing SMS credential:** run `lmt interactive credential provision --env dev` and retry. The credential is shared, but it needs access to the scope used by your chosen environment.
|
|
141
|
+
- **SMS unavailable or invalid Operator Directory:** check the credential's read access and the selected Gate scope's Operator Directory with your administrator.
|
|
142
|
+
- **Session expired or rejected:** the CLI clears rejected session cookies. Log in again explicitly using the same profile and environment.
|
|
143
|
+
- **Origin or Broker unavailable:** check the selected deployment. For local use, keep the full stack running at the canonical Origin. `status` requires Broker to be available even when Gate still accepts the session.
|
|
144
|
+
|
|
145
|
+
## Run from a source checkout
|
|
146
|
+
|
|
147
|
+
For contributors, from `live-market-terminal/`:
|
|
141
148
|
|
|
142
149
|
```sh
|
|
143
|
-
pnpm
|
|
144
|
-
pnpm -s cli:headless
|
|
150
|
+
pnpm cli --env dev --profile my-dev-session
|
|
151
|
+
pnpm -s cli:headless status --env dev --profile my-dev-session
|
|
152
|
+
pnpm --filter @ai-digital-factory/lmt-cli interactive credential provision --env dev
|
|
145
153
|
```
|
|
146
154
|
|
|
147
|
-
|
|
155
|
+
These repository scripts rebuild the CLI. The published commands above run directly from the installed package. The CLI sets `NODE_USE_ENV_PROXY=0` on startup so local Gate requests are not sent through `HTTP(S)_PROXY`.
|
|
148
156
|
|
|
149
|
-
|
|
157
|
+
## Maintain the npm package
|
|
150
158
|
|
|
151
|
-
|
|
159
|
+
The **Release LMT CLI** GitHub Action is dispatched manually on `main`. It builds
|
|
160
|
+
current `main`, tests the CLI, builds a standalone package with bundled private
|
|
161
|
+
contracts, selects the next npm patch, and tests a clean installation of the exact
|
|
162
|
+
tarball it publishes publicly under `latest`. Node 22 and pnpm 10.33.3 match CI.
|
|
163
|
+
|
|
164
|
+
Only `SMS_CREDENTIAL` is configured as a GitHub Actions secret for this workflow.
|
|
165
|
+
It must be able to read `NPM_TOKEN` from SMS scope `infra`, at Vault
|
|
166
|
+
`secret/data/infra/NPM_TOKEN` with field `value`. The endpoint and scope are
|
|
167
|
+
non-secret repository configuration. The existing npm token needs publishing
|
|
168
|
+
permission for `@ai-digital-factory/lmt-cli`; it is masked and passed only to the
|
|
169
|
+
publishing step. No credential values are included in the package.
|
|
170
|
+
|
|
171
|
+
First publication uses `0.1.0`; later runs increment the highest published stable
|
|
172
|
+
version's patch. Registry/authentication failures stop the release. Versions change
|
|
173
|
+
only in the temporary publishing artifact, with no bump commit, git tag, GitHub
|
|
174
|
+
Release, Origin Version, or deployment. A retry after successful npm publication
|
|
175
|
+
selects another patch, since published npm versions are immutable. npm can accept
|
|
176
|
+
an upload before public registry processing finishes; the action polls for visibility
|
|
177
|
+
for five minutes. If confirmation times out after a successful upload, check npm
|
|
178
|
+
before rerunning the release to avoid publishing an unnecessary patch.
|
|
179
|
+
|
|
180
|
+
To reproduce the packaging checks from the repository root:
|
|
181
|
+
|
|
182
|
+
```sh
|
|
183
|
+
(cd live-market-terminal && pnpm --filter @ai-digital-factory/lmt-cli package:build)
|
|
184
|
+
tarball=$(bash .github/scripts/pack-lmt-cli.sh /tmp/lmt-cli-package)
|
|
185
|
+
node .github/scripts/test-lmt-cli-package.mjs "$tarball"
|
|
186
|
+
```
|
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
|
|
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"
|
|
2108
|
-
throw new InvocationError("--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
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
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 [--
|
|
2427
|
-
live-market-terminal zerodha observe
|
|
2428
|
-
live-market-terminal zerodha otp
|
|
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
|
|
2457
|
-
--otp CODE Six-digit OTP for zerodha
|
|
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
|
|