ctrader-ts 0.1.0

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 (68) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +301 -0
  3. package/assets/banner.png +0 -0
  4. package/dist/bin/auth.d.ts +3 -0
  5. package/dist/bin/auth.d.ts.map +1 -0
  6. package/dist/bin/auth.js +193 -0
  7. package/dist/bin/auth.js.map +1 -0
  8. package/dist/cli/index.d.ts +3 -0
  9. package/dist/cli/index.d.ts.map +1 -0
  10. package/dist/cli/index.js +359 -0
  11. package/dist/cli/index.js.map +1 -0
  12. package/dist/src/client.d.ts +310 -0
  13. package/dist/src/client.d.ts.map +1 -0
  14. package/dist/src/client.js +507 -0
  15. package/dist/src/client.js.map +1 -0
  16. package/dist/src/config.d.ts +18 -0
  17. package/dist/src/config.d.ts.map +1 -0
  18. package/dist/src/config.js +70 -0
  19. package/dist/src/config.js.map +1 -0
  20. package/dist/src/connect.d.ts +20 -0
  21. package/dist/src/connect.d.ts.map +1 -0
  22. package/dist/src/connect.js +36 -0
  23. package/dist/src/connect.js.map +1 -0
  24. package/dist/src/connection.d.ts +51 -0
  25. package/dist/src/connection.d.ts.map +1 -0
  26. package/dist/src/connection.js +292 -0
  27. package/dist/src/connection.js.map +1 -0
  28. package/dist/src/enums.d.ts +341 -0
  29. package/dist/src/enums.d.ts.map +1 -0
  30. package/dist/src/enums.js +369 -0
  31. package/dist/src/enums.js.map +1 -0
  32. package/dist/src/errors.d.ts +24 -0
  33. package/dist/src/errors.d.ts.map +1 -0
  34. package/dist/src/errors.js +47 -0
  35. package/dist/src/errors.js.map +1 -0
  36. package/dist/src/helpers.d.ts +28 -0
  37. package/dist/src/helpers.d.ts.map +1 -0
  38. package/dist/src/helpers.js +113 -0
  39. package/dist/src/helpers.js.map +1 -0
  40. package/dist/src/index.d.ts +12 -0
  41. package/dist/src/index.d.ts.map +1 -0
  42. package/dist/src/index.js +11 -0
  43. package/dist/src/index.js.map +1 -0
  44. package/dist/src/modules/account.d.ts +67 -0
  45. package/dist/src/modules/account.d.ts.map +1 -0
  46. package/dist/src/modules/account.js +168 -0
  47. package/dist/src/modules/account.js.map +1 -0
  48. package/dist/src/modules/auth.d.ts +20 -0
  49. package/dist/src/modules/auth.d.ts.map +1 -0
  50. package/dist/src/modules/auth.js +43 -0
  51. package/dist/src/modules/auth.js.map +1 -0
  52. package/dist/src/modules/market.d.ts +53 -0
  53. package/dist/src/modules/market.d.ts.map +1 -0
  54. package/dist/src/modules/market.js +192 -0
  55. package/dist/src/modules/market.js.map +1 -0
  56. package/dist/src/modules/trading.d.ts +80 -0
  57. package/dist/src/modules/trading.d.ts.map +1 -0
  58. package/dist/src/modules/trading.js +150 -0
  59. package/dist/src/modules/trading.js.map +1 -0
  60. package/dist/src/symbol-cache.d.ts +14 -0
  61. package/dist/src/symbol-cache.d.ts.map +1 -0
  62. package/dist/src/symbol-cache.js +41 -0
  63. package/dist/src/symbol-cache.js.map +1 -0
  64. package/dist/src/types.d.ts +413 -0
  65. package/dist/src/types.d.ts.map +1 -0
  66. package/dist/src/types.js +2 -0
  67. package/dist/src/types.js.map +1 -0
  68. package/package.json +61 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 thecommandcat
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,301 @@
1
+ <div align="center">
2
+
3
+ <img src="./assets/banner.png" alt="ctrader-ts" width="100%" />
4
+
5
+ <br />
6
+ <br />
7
+
8
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.x-3178C6?style=flat-square&logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
9
+ [![Node](https://img.shields.io/badge/Node.js-≥18-339933?style=flat-square&logo=node.js&logoColor=white)](https://nodejs.org/)
10
+ [![License](https://img.shields.io/badge/license-MIT-green?style=flat-square)](LICENSE)
11
+ [![cTrader](https://img.shields.io/badge/cTrader-Open%20API-FF6B35?style=flat-square)](https://help.ctrader.com/open-api/)
12
+
13
+ **Unofficial community TypeScript client for the cTrader Open API**
14
+
15
+ *Not affiliated with Spotware · Built by the community, for the community* 🤝
16
+
17
+ </div>
18
+
19
+ ---
20
+
21
+ ## What is this?
22
+
23
+ There's no official TypeScript SDK for the cTrader Open API. The API itself is a raw protobuf/WebSocket protocol — great for performance, painful to use. This is a community-built client that wraps the JSON WebSocket mode (port 5036) into something actually enjoyable:
24
+
25
+ ```ts
26
+ const ct = await connect();
27
+ const pos = await ct.buy("EURUSD", { lots: 0.1, sl: { pips: 50 }, tp: { equity: 0.02 } });
28
+ await ct.close(pos.positionId);
29
+ ```
30
+
31
+ No manual auth flows, no volume unit conversions, no raw WebSocket management.
32
+
33
+ ---
34
+
35
+ ## 🚀 Getting started
36
+
37
+ **Clone & link globally** (recommended — gets you the CLI too):
38
+
39
+ ```bash
40
+ git clone https://github.com/thecommandcat/ctrader-ts
41
+ cd ctrader-ts
42
+ npm install && npm run build && npm link
43
+ ```
44
+
45
+ **Or install as a library:**
46
+
47
+ ```bash
48
+ npm install ctrader-ts
49
+ ```
50
+
51
+ ### 🔐 Authenticate
52
+
53
+ Run the interactive OAuth wizard — walks you through the whole flow in 3 steps:
54
+
55
+ ```bash
56
+ ctrader-ts auth
57
+ ```
58
+
59
+ You'll need a cTrader Open API app. Create one at [openapi.ctrader.com/apps](https://openapi.ctrader.com/apps), grab your client ID + secret, and the wizard handles the rest. Credentials are saved to `~/.config/ctrader-ts/config.json`.
60
+
61
+ Prefer env vars? Those work too:
62
+
63
+ ```bash
64
+ CTRADER_CLIENT_ID=...
65
+ CTRADER_CLIENT_SECRET=...
66
+ CTRADER_ACCESS_TOKEN=...
67
+ CTRADER_ACCOUNT_ID=...
68
+ CTRADER_ENVIRONMENT=demo # or live
69
+ ```
70
+
71
+ ---
72
+
73
+ ## 📦 Library
74
+
75
+ ```ts
76
+ import { connect } from "ctrader-ts";
77
+
78
+ const ct = await connect(); // reads stored credentials, connects, authenticates
79
+ ```
80
+
81
+ ### 📊 Account state
82
+
83
+ Start here. One call, complete picture:
84
+
85
+ ```ts
86
+ const state = await ct.getState();
87
+ // {
88
+ // balance, equity, usedMargin, freeMargin,
89
+ // marginLevel, unrealizedPnl,
90
+ // positions, orders, moneyDigits
91
+ // }
92
+ ```
93
+
94
+ ### 💹 Trading
95
+
96
+ **Market orders** — execute immediately, return the opened `Position` directly:
97
+
98
+ ```ts
99
+ const p1 = await ct.buy("EURUSD", { lots: 0.1 });
100
+ const p2 = await ct.buy("EURUSD", { lots: 0.05, sl: { pips: 50 }, tp: { pips: 100 } });
101
+ const p3 = await ct.sell("USDJPY", { lots: 0.1, sl: { dollars: 30 } });
102
+ const p4 = await ct.buy("XAUUSD", { lots: 0.01, sl: { equity: 0.02 } });
103
+ ```
104
+
105
+ **Pending orders:**
106
+
107
+ ```ts
108
+ await ct.buyLimit("EURUSD", { lots: 0.1, limitPrice: 1.0800 });
109
+ await ct.sellLimit("EURUSD", { lots: 0.1, limitPrice: 1.1200 });
110
+ await ct.buyStop("EURUSD", { lots: 0.1, stopPrice: 1.1050 });
111
+ await ct.sellStop("EURUSD", { lots: 0.1, stopPrice: 1.0950 });
112
+ ```
113
+
114
+ ### 🎯 SL/TP — three ways, zero math
115
+
116
+ No manual pip calculations. Every order accepts `sl` and `tp` in whichever unit makes sense:
117
+
118
+ | | Example | What it means |
119
+ |---|---|---|
120
+ | **Pips** | `{ pips: 50 }` | 50 pips from entry — symbol-aware (handles JPY, gold, etc.) |
121
+ | **Dollars** | `{ dollars: 25 }` | Lose/gain exactly $25 on this trade |
122
+ | **Equity %** | `{ equity: 0.02 }` | Risk 2% of your account equity |
123
+
124
+ The library fetches symbol details and current price automatically to do the conversion.
125
+
126
+ ### 🔧 Position management
127
+
128
+ Positions use their real cTrader `positionId` — no invented ID system:
129
+
130
+ ```ts
131
+ // Move SL/TP at any time
132
+ await ct.modify(p1.positionId, { sl: { pips: 30 }, tp: { dollars: 50 } });
133
+
134
+ // Close — full or partial
135
+ await ct.close(p1.positionId);
136
+ await ct.close(p1.positionId, { lots: 0.02 }); // partial
137
+
138
+ // Nuke everything
139
+ await ct.closeSymbol("EURUSD");
140
+ await ct.closeAll();
141
+
142
+ // Cancel pending order
143
+ await ct.cancelOrder(orderId);
144
+ ```
145
+
146
+ ### 📡 Live market data
147
+
148
+ ```ts
149
+ // Stream bid/ask — returns an unsubscribe function
150
+ const stop = await ct.watchSpots(["EURUSD", "GBPUSD"], (price) => {
151
+ console.log(price.symbol, price.bidDecimal, price.askDecimal);
152
+ });
153
+ await stop(); // done
154
+
155
+ // Historical candles
156
+ const { trendbars } = await ct.getTrendbars("EURUSD", {
157
+ period: TrendbarPeriod.H1,
158
+ count: 100,
159
+ });
160
+
161
+ // Raw tick data
162
+ const { ticks } = await ct.getTickData("EURUSD", {
163
+ type: QuoteType.BID,
164
+ fromTimestamp: Date.now() - 3_600_000,
165
+ toTimestamp: Date.now(),
166
+ });
167
+ ```
168
+
169
+ ### 🗂️ History & account data
170
+
171
+ ```ts
172
+ const { deals } = await ct.getDeals({ maxRows: 50 });
173
+ const { positions, orders } = await ct.getPositions();
174
+ const trader = await ct.getTrader();
175
+ const { margins } = await ct.getExpectedMargin("EURUSD", [0.1, 0.5, 1.0]);
176
+ ```
177
+
178
+ ### ⚡ Events
179
+
180
+ ```ts
181
+ ct.onExecution((e) => console.log("fill:", e.executionType, e.position?.positionId));
182
+ ct.onOrderError((e) => console.error("order rejected:", e.errorCode));
183
+ ct.onTrailingSLChanged((e) => console.log("trailing SL moved to:", e.stopPrice));
184
+ ct.onMarginChanged((e) => console.log("margin updated:", e.usedMargin));
185
+ ct.onTokenInvalidated(() => console.warn("token expired — run ctrader-ts auth"));
186
+ ct.onClientDisconnect((e) => console.warn("server dropped connection:", e.reason));
187
+ ```
188
+
189
+ ### 🔩 Raw protocol access
190
+
191
+ Everything the high-level API doesn't expose is available via `ct.raw`:
192
+
193
+ ```ts
194
+ ct.raw.trading.marketRangeOrder({ ... });
195
+ ct.raw.account.getDynamicLeverage(leverageId);
196
+ ct.raw.market.subscribeLiveTrendbar(symbolId, TrendbarPeriod.M1);
197
+ ct.raw.auth.refreshToken(refreshToken);
198
+ ```
199
+
200
+ ### ⚙️ connect() options
201
+
202
+ ```ts
203
+ const ct = await connect({
204
+ environment: "live", // override stored environment
205
+ accountId: 12345678, // override stored account
206
+ accessToken: "...", // override stored token
207
+ });
208
+ ```
209
+
210
+ ---
211
+
212
+ ## 🖥️ CLI
213
+
214
+ Everything you can do in code, you can do from the terminal.
215
+
216
+ ```bash
217
+ # 🔐 Auth
218
+ ctrader-ts auth
219
+
220
+ # 📊 Account
221
+ ctrader-ts state
222
+ ctrader-ts positions
223
+
224
+ # 💹 Trade
225
+ ctrader-ts buy EURUSD 0.1 --sl-pips 50 --tp-pips 100
226
+ ctrader-ts sell USDJPY 0.1 --sl-dollars 30
227
+ ctrader-ts buy XAUUSD 0.01 --sl-equity 0.02
228
+
229
+ # 📋 Pending orders
230
+ ctrader-ts buy-limit EURUSD 0.1 1.0800
231
+ ctrader-ts sell-limit EURUSD 0.1 1.1200
232
+ ctrader-ts buy-stop EURUSD 0.1 1.1050
233
+
234
+ # 🔧 Manage positions
235
+ ctrader-ts close 12345678 # full close
236
+ ctrader-ts close 12345678 --lots 0.05 # partial
237
+ ctrader-ts modify 12345678 --sl-pips 30 --tp-dollars 50
238
+ ctrader-ts close-symbol EURUSD
239
+ ctrader-ts close-all
240
+ ctrader-ts cancel 87654321
241
+
242
+ # 📡 Market data
243
+ ctrader-ts watch EURUSD GBPUSD # live prices, Ctrl+C to stop
244
+ ctrader-ts bars EURUSD H1 2024-01-01 2024-12-31
245
+
246
+ # 🗂️ History
247
+ ctrader-ts history --from 2024-01-01 --to 2024-12-31
248
+
249
+ # 🤖 Pipe-friendly JSON output
250
+ ctrader-ts state --json
251
+ ctrader-ts positions --json | jq '.positions[].positionId'
252
+ ```
253
+
254
+ ---
255
+
256
+ ## 🤖 For AI agents
257
+
258
+ Built with AI agents in mind from the start:
259
+
260
+ - **`getState()` first** — gives a complete picture of the account before making any decisions
261
+ - **Human units everywhere** — `{ pips }`, `{ dollars }`, `{ equity }` means no internal encoding knowledge needed
262
+ - **Real position IDs** — every `buy()`/`sell()` returns the actual `positionId`, so modifying and closing are unambiguous
263
+ - **Typed errors** — agents can react to failures intelligently instead of just crashing
264
+ - **CLI with `--json`** — agents with shell access get structured output without a Node runtime in the loop
265
+
266
+ ```ts
267
+ import { connect, CTraderError } from "ctrader-ts";
268
+
269
+ const ct = await connect();
270
+ const state = await ct.getState();
271
+ // reason about state.equity, state.positions, state.marginLevel...
272
+
273
+ try {
274
+ const pos = await ct.buy("EURUSD", { lots: 0.1, sl: { equity: 0.01 } });
275
+ console.log("opened", pos.positionId);
276
+ } catch (e) {
277
+ if (e instanceof CTraderError) {
278
+ if (e.isAuthError) { /* re-run auth */ }
279
+ if (e.isRateLimit) { /* wait e.retryAfter ms then retry */ }
280
+ if (e.isMaintenance) { /* server is down, try later */ }
281
+ }
282
+ }
283
+ ```
284
+
285
+ ---
286
+
287
+ ## 🔄 Reconnection
288
+
289
+ Drops happen. The library handles it automatically:
290
+
291
+ - Reconnects with exponential backoff (2s → 60s max)
292
+ - Re-authenticates after reconnect (app auth + account auth)
293
+ - Restores all active spot / depth / trendbar subscriptions
294
+
295
+ You don't need to do anything.
296
+
297
+ ---
298
+
299
+ ## License
300
+
301
+ MIT
Binary file
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../bin/auth.ts"],"names":[],"mappings":""}
@@ -0,0 +1,193 @@
1
+ #!/usr/bin/env node
2
+ import * as p from "@clack/prompts";
3
+ import { saveConfig, loadStoredConfig, getConfigPath, DEMO_ENDPOINT } from "../src/config.js";
4
+ import { CTraderConnection } from "../src/connection.js";
5
+ import { CTraderAuth } from "../src/modules/auth.js";
6
+ const OAUTH_TOKEN_URL = "https://openapi.ctrader.com/apps/token";
7
+ const REDIRECT_URI = "https://openapi.ctrader.com";
8
+ async function exchangeCodeForToken(code, clientId, clientSecret) {
9
+ const url = new URL(OAUTH_TOKEN_URL);
10
+ url.searchParams.set("grant_type", "authorization_code");
11
+ url.searchParams.set("code", code);
12
+ url.searchParams.set("redirect_uri", REDIRECT_URI);
13
+ url.searchParams.set("client_id", clientId);
14
+ url.searchParams.set("client_secret", clientSecret);
15
+ const res = await fetch(url.toString(), { headers: { Accept: "application/json" } });
16
+ if (!res.ok)
17
+ throw new Error(`HTTP ${res.status} from token endpoint`);
18
+ const body = (await res.json());
19
+ if (body["errorCode"])
20
+ throw new Error(`${body["errorCode"]}: ${body["description"]}`);
21
+ if (!body["accessToken"])
22
+ throw new Error("No accessToken in response");
23
+ return {
24
+ accessToken: body["accessToken"],
25
+ refreshToken: body["refreshToken"],
26
+ };
27
+ }
28
+ function buildAuthUrl(clientId) {
29
+ const url = new URL("https://id.ctrader.com/my/settings/openapi/grantingaccess/");
30
+ url.searchParams.set("client_id", clientId);
31
+ url.searchParams.set("redirect_uri", REDIRECT_URI);
32
+ url.searchParams.set("scope", "trading");
33
+ url.searchParams.set("product", "web");
34
+ return url.toString();
35
+ }
36
+ function extractCode(raw) {
37
+ const trimmed = raw.trim();
38
+ try {
39
+ return new URL(trimmed).searchParams.get("code");
40
+ }
41
+ catch {
42
+ if (trimmed.startsWith("code="))
43
+ return trimmed.slice(5);
44
+ if (trimmed && !trimmed.includes(" "))
45
+ return trimmed;
46
+ return null;
47
+ }
48
+ }
49
+ async function run() {
50
+ p.intro("ctrader-ts — setup");
51
+ const stored = loadStoredConfig();
52
+ // ─── Step 1: App credentials ──────────────────────────────────────────────
53
+ p.log.step("Step 1 of 3 — App credentials");
54
+ p.log.info("Go to https://openapi.ctrader.com/apps → select your app → View Credentials");
55
+ const clientId = await p.text({
56
+ message: "Client ID",
57
+ placeholder: "19544_xxxxxx",
58
+ ...(stored.clientId ? { initialValue: stored.clientId } : {}),
59
+ validate: (v) => (v?.trim() ? undefined : "Required"),
60
+ });
61
+ if (p.isCancel(clientId)) {
62
+ p.cancel("Cancelled.");
63
+ process.exit(0);
64
+ }
65
+ const clientSecret = await p.password({
66
+ message: "Client Secret",
67
+ validate: (v) => (v?.trim() ? undefined : "Required"),
68
+ });
69
+ if (p.isCancel(clientSecret)) {
70
+ p.cancel("Cancelled.");
71
+ process.exit(0);
72
+ }
73
+ // ─── Step 2: Get access token ─────────────────────────────────────────────
74
+ p.log.step("Step 2 of 3 — Authorize");
75
+ let accessToken;
76
+ let refreshToken;
77
+ if (stored.accessToken && stored.refreshToken) {
78
+ const refresh = await p.confirm({
79
+ message: "You have a stored token. Re-authorize to get a fresh one?",
80
+ initialValue: false,
81
+ });
82
+ if (p.isCancel(refresh)) {
83
+ p.cancel("Cancelled.");
84
+ process.exit(0);
85
+ }
86
+ if (!refresh) {
87
+ accessToken = stored.accessToken;
88
+ refreshToken = stored.refreshToken;
89
+ }
90
+ else {
91
+ ({ accessToken, refreshToken } = await doOAuthFlow(clientId, clientSecret));
92
+ }
93
+ }
94
+ else {
95
+ ({ accessToken, refreshToken } = await doOAuthFlow(clientId, clientSecret));
96
+ }
97
+ // ─── Step 3: Pick account ─────────────────────────────────────────────────
98
+ p.log.step("Step 3 of 3 — Choose account");
99
+ const spinner = p.spinner();
100
+ spinner.start("Fetching accounts…");
101
+ const connection = new CTraderConnection({ endpoint: DEMO_ENDPOINT });
102
+ const auth = new CTraderAuth(connection);
103
+ let accounts;
104
+ try {
105
+ await connection.connect();
106
+ await auth.authenticateApp(clientId, clientSecret);
107
+ accounts = await auth.getAccountsByToken(accessToken);
108
+ connection.disconnect();
109
+ spinner.stop(`Found ${accounts.length} account${accounts.length !== 1 ? "s" : ""}`);
110
+ }
111
+ catch (err) {
112
+ connection.disconnect();
113
+ spinner.stop("Failed");
114
+ p.log.error(err instanceof Error ? err.message : String(err));
115
+ process.exit(1);
116
+ }
117
+ if (accounts.length === 0) {
118
+ p.log.error("No accounts linked to this token. Re-authorize and make sure to approve at least one account.");
119
+ process.exit(1);
120
+ }
121
+ const accountChoice = await p.select({
122
+ message: "Account",
123
+ options: accounts.map((a) => {
124
+ const type = a.isLive ? "live" : "demo";
125
+ const parts = [type];
126
+ if (a.traderLogin)
127
+ parts.push(`login ${a.traderLogin}`);
128
+ if (a.brokerTitleShort)
129
+ parts.push(a.brokerTitleShort);
130
+ return {
131
+ value: String(a.ctidTraderAccountId),
132
+ label: String(a.ctidTraderAccountId),
133
+ hint: parts.join(" · "),
134
+ };
135
+ }),
136
+ initialValue: stored.accountId ? String(stored.accountId) : undefined,
137
+ });
138
+ if (p.isCancel(accountChoice)) {
139
+ p.cancel("Cancelled.");
140
+ process.exit(0);
141
+ }
142
+ const chosen = accounts.find((a) => String(a.ctidTraderAccountId) === accountChoice);
143
+ const environment = chosen.isLive ? "live" : "demo";
144
+ // ─── Save ─────────────────────────────────────────────────────────────────
145
+ saveConfig({
146
+ clientId: clientId,
147
+ clientSecret: clientSecret,
148
+ accessToken,
149
+ refreshToken,
150
+ accountId: Number(accountChoice),
151
+ environment,
152
+ });
153
+ const login = chosen.traderLogin ? ` · login ${chosen.traderLogin}` : "";
154
+ p.outro(`Saved · account ${accountChoice} (${environment}${login}) → ${getConfigPath()}`);
155
+ }
156
+ async function doOAuthFlow(clientId, clientSecret) {
157
+ const authUrl = buildAuthUrl(clientId);
158
+ p.log.info("1. Make sure this redirect URI is registered in your app:");
159
+ p.log.info(` https://openapi.ctrader.com/apps → Edit → Redirect URIs → add: ${REDIRECT_URI}`);
160
+ p.log.info("");
161
+ p.log.info("2. Open this URL in your browser and log in with your cTrader ID:");
162
+ p.log.info(` ${authUrl}`);
163
+ p.log.info("");
164
+ p.log.info("3. After approving, you'll be redirected to openapi.ctrader.com.");
165
+ p.log.info(" Copy the full URL from the address bar and paste it below.");
166
+ const redirected = await p.text({
167
+ message: "Paste the redirect URL",
168
+ placeholder: "https://openapi.ctrader.com/?code=abc123xyz",
169
+ validate: (v) => (extractCode(v ?? "") ? undefined : "No code found — paste the full URL from your browser's address bar"),
170
+ });
171
+ if (p.isCancel(redirected)) {
172
+ p.cancel("Cancelled.");
173
+ process.exit(0);
174
+ }
175
+ const code = extractCode(redirected);
176
+ const spinner = p.spinner();
177
+ spinner.start("Exchanging code for access token…");
178
+ try {
179
+ const tokens = await exchangeCodeForToken(code, clientId, clientSecret);
180
+ spinner.stop("Access token obtained");
181
+ return tokens;
182
+ }
183
+ catch (err) {
184
+ spinner.stop("Token exchange failed");
185
+ p.log.error(err instanceof Error ? err.message : String(err));
186
+ process.exit(1);
187
+ }
188
+ }
189
+ run().catch((err) => {
190
+ process.stderr.write(String(err) + "\n");
191
+ process.exit(1);
192
+ });
193
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../bin/auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../cli/index.ts"],"names":[],"mappings":""}