@pungoyal/kite-cli 0.1.1 → 0.2.1
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 +113 -6
- package/dist/commands/alerts.d.ts +12 -0
- package/dist/commands/alerts.js +529 -0
- package/dist/commands/auth.js +104 -28
- package/dist/commands/config.js +56 -13
- package/dist/commands/profiles.d.ts +10 -0
- package/dist/commands/profiles.js +213 -0
- package/dist/commands/watch.js +1 -1
- package/dist/context.d.ts +6 -0
- package/dist/context.js +36 -18
- package/dist/core/api.d.ts +249 -42
- package/dist/core/api.js +100 -1
- package/dist/core/config.d.ts +77 -23
- package/dist/core/config.js +53 -21
- package/dist/core/credentials.d.ts +3 -2
- package/dist/core/credentials.js +13 -7
- package/dist/core/errors.d.ts +1 -1
- package/dist/core/errors.js +2 -2
- package/dist/core/paths.d.ts +9 -2
- package/dist/core/paths.js +12 -3
- package/dist/core/profiles.d.ts +78 -0
- package/dist/core/profiles.js +140 -0
- package/dist/core/schemas.d.ts +78 -5
- package/dist/core/schemas.js +55 -0
- package/dist/core/session.d.ts +3 -2
- package/dist/core/session.js +7 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/run.js +6 -0
- package/dist/safety.js +12 -1
- package/package.json +9 -3
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { EnvironmentSchema, SANDBOX_CREDENTIALS } from './config.js';
|
|
2
|
+
import { ExitCode, KiteCliError } from './errors.js';
|
|
3
|
+
/**
|
|
4
|
+
* Multi-account resolution.
|
|
5
|
+
*
|
|
6
|
+
* A *profile* is a named Zerodha account. Selection is stateless per invocation
|
|
7
|
+
* — there is deliberately no sticky "active account" that persists across
|
|
8
|
+
* commands, because forgetting which account is active while placing real
|
|
9
|
+
* orders is exactly the mistake this feature must not introduce. Instead the
|
|
10
|
+
* target is resolved fresh every run, and the money-moving preview shows the
|
|
11
|
+
* verified user id (see `safety.ts`).
|
|
12
|
+
*
|
|
13
|
+
* Two profile names are reserved:
|
|
14
|
+
* - `default` — today's single-account setup (top-level apiKey/env in config,
|
|
15
|
+
* secrets stored unprefixed). Chosen so existing installs need
|
|
16
|
+
* no migration.
|
|
17
|
+
* - `sandbox` — Zerodha's public sandbox. One fixed identity, so there is no
|
|
18
|
+
* value in per-account sandbox profiles.
|
|
19
|
+
*/
|
|
20
|
+
export const DEFAULT_PROFILE = 'default';
|
|
21
|
+
export const SANDBOX_PROFILE = 'sandbox';
|
|
22
|
+
export const RESERVED_PROFILES = [DEFAULT_PROFILE, SANDBOX_PROFILE];
|
|
23
|
+
/** Filesystem- and keyring-safe. Also keeps names out of `profile:<n>:` key collisions. */
|
|
24
|
+
const PROFILE_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,31}$/;
|
|
25
|
+
/** Reject names that could escape a filename or collide with a reserved prefix. */
|
|
26
|
+
export function assertValidProfileName(name) {
|
|
27
|
+
if (!PROFILE_NAME_RE.test(name)) {
|
|
28
|
+
throw new KiteCliError(`Invalid profile name "${name}".`, ExitCode.Usage, 'Use 1–32 characters: letters, digits, hyphen or underscore, starting alphanumeric.');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Keyring / encrypted-file namespace for a profile's secrets.
|
|
33
|
+
*
|
|
34
|
+
* The reserved profiles reproduce the historical env-keyed scheme byte-for-byte
|
|
35
|
+
* (`production` unprefixed, `sandbox:` otherwise), so no stored secret has to be
|
|
36
|
+
* migrated. Every other profile gets a `profile:<name>:` namespace that cannot
|
|
37
|
+
* collide with an env name.
|
|
38
|
+
*/
|
|
39
|
+
export function storagePrefixFor(profile) {
|
|
40
|
+
if (profile.name === DEFAULT_PROFILE || profile.name === SANDBOX_PROFILE) {
|
|
41
|
+
return profile.env === 'production' ? '' : `${profile.env}:`;
|
|
42
|
+
}
|
|
43
|
+
return `profile:${profile.name}:`;
|
|
44
|
+
}
|
|
45
|
+
/** Every profile the user has, reserved ones first, without duplicates. */
|
|
46
|
+
export function listProfileNames(config) {
|
|
47
|
+
const names = [DEFAULT_PROFILE, SANDBOX_PROFILE];
|
|
48
|
+
for (const name of Object.keys(config.profiles)) {
|
|
49
|
+
if (!names.includes(name))
|
|
50
|
+
names.push(name);
|
|
51
|
+
}
|
|
52
|
+
return names;
|
|
53
|
+
}
|
|
54
|
+
export function isKnownProfile(config, name) {
|
|
55
|
+
return name === DEFAULT_PROFILE || name === SANDBOX_PROFILE || Object.hasOwn(config.profiles, name);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Look up a profile by name without applying command-line overrides.
|
|
59
|
+
*
|
|
60
|
+
* An unknown name yields an empty, production profile rather than throwing, so
|
|
61
|
+
* `kite --profile new login` (and `profiles add`) can create it.
|
|
62
|
+
*/
|
|
63
|
+
export function getProfile(config, name) {
|
|
64
|
+
if (name === SANDBOX_PROFILE) {
|
|
65
|
+
return { name, apiKey: SANDBOX_CREDENTIALS.apiKey, env: 'sandbox', trading: undefined, explicit: false };
|
|
66
|
+
}
|
|
67
|
+
if (name === DEFAULT_PROFILE) {
|
|
68
|
+
return { name, apiKey: config.apiKey ?? '', env: config.env, trading: undefined, explicit: false };
|
|
69
|
+
}
|
|
70
|
+
const p = config.profiles[name];
|
|
71
|
+
return {
|
|
72
|
+
name,
|
|
73
|
+
apiKey: p?.apiKey ?? '',
|
|
74
|
+
env: p?.env ?? 'production',
|
|
75
|
+
trading: p?.trading,
|
|
76
|
+
explicit: false,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function parseEnvFlag(value) {
|
|
80
|
+
if (value === undefined || value.trim() === '')
|
|
81
|
+
return undefined;
|
|
82
|
+
const result = EnvironmentSchema.safeParse(value);
|
|
83
|
+
if (!result.success) {
|
|
84
|
+
throw new KiteCliError(`Unknown environment "${value}". Expected "production" or "sandbox".`, ExitCode.Usage);
|
|
85
|
+
}
|
|
86
|
+
return result.data;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Resolve the effective profile for this invocation.
|
|
90
|
+
*
|
|
91
|
+
* Precedence: `--profile` > KITE_PROFILE > (`--env sandbox` alias) >
|
|
92
|
+
* `config.defaultProfile` > `default`. An explicit `--env`/KITE_ENV then
|
|
93
|
+
* overrides the resolved profile's environment, so `--env production` still
|
|
94
|
+
* forces production the way it always has.
|
|
95
|
+
*/
|
|
96
|
+
export function resolveProfile(selectors, config) {
|
|
97
|
+
const named = selectors.profileFlag?.trim() || process.env['KITE_PROFILE']?.trim() || '';
|
|
98
|
+
const explicitEnv = parseEnvFlag(selectors.envFlag ?? process.env['KITE_ENV']);
|
|
99
|
+
let name;
|
|
100
|
+
let explicit;
|
|
101
|
+
if (named) {
|
|
102
|
+
assertValidProfileName(named);
|
|
103
|
+
name = named;
|
|
104
|
+
explicit = true;
|
|
105
|
+
}
|
|
106
|
+
else if (explicitEnv === 'sandbox') {
|
|
107
|
+
// Back-compat: `--env sandbox` selects the sandbox profile. This is an alias,
|
|
108
|
+
// not an explicit account choice, so it does not arm the env-var guard.
|
|
109
|
+
name = SANDBOX_PROFILE;
|
|
110
|
+
explicit = false;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
name = config.defaultProfile ?? DEFAULT_PROFILE;
|
|
114
|
+
explicit = false;
|
|
115
|
+
}
|
|
116
|
+
const base = getProfile(config, name);
|
|
117
|
+
const env = explicitEnv ?? base.env;
|
|
118
|
+
// Any profile resolved into the sandbox environment uses the public demo key —
|
|
119
|
+
// in particular a `default` profile pinned to sandbox via `config.env`, which
|
|
120
|
+
// would otherwise carry the (production) config apiKey and fail to authenticate.
|
|
121
|
+
const apiKey = env === 'sandbox' ? SANDBOX_CREDENTIALS.apiKey : base.apiKey;
|
|
122
|
+
return { ...base, apiKey, env, explicit };
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* The trading config actually in force: global settings overlaid with the
|
|
126
|
+
* profile's overrides. Fail-closed by construction — a field the profile leaves
|
|
127
|
+
* unset falls back to the global value, so an omitted cap never becomes "no cap".
|
|
128
|
+
*/
|
|
129
|
+
export function resolveTradingConfig(config, profile) {
|
|
130
|
+
const base = config.trading;
|
|
131
|
+
const o = profile.trading;
|
|
132
|
+
if (!o)
|
|
133
|
+
return base;
|
|
134
|
+
return {
|
|
135
|
+
enabled: o.enabled ?? base.enabled,
|
|
136
|
+
confirm: o.confirm ?? base.confirm,
|
|
137
|
+
maxOrderValue: o.maxOrderValue ?? base.maxOrderValue,
|
|
138
|
+
strictConfirmAbove: o.strictConfirmAbove ?? base.strictConfirmAbove,
|
|
139
|
+
};
|
|
140
|
+
}
|
package/dist/core/schemas.d.ts
CHANGED
|
@@ -141,11 +141,11 @@ export type Margins = z.infer<typeof MarginsSchema>;
|
|
|
141
141
|
export type SegmentMargin = z.infer<typeof SegmentMarginSchema>;
|
|
142
142
|
/** Known terminal states. Not exhaustive — see the note at the top of this file. */
|
|
143
143
|
export declare const ORDER_STATUS: {
|
|
144
|
-
readonly Complete:
|
|
145
|
-
readonly Cancelled:
|
|
146
|
-
readonly Rejected:
|
|
147
|
-
readonly Open:
|
|
148
|
-
readonly TriggerPending:
|
|
144
|
+
readonly Complete: 'COMPLETE';
|
|
145
|
+
readonly Cancelled: 'CANCELLED';
|
|
146
|
+
readonly Rejected: 'REJECTED';
|
|
147
|
+
readonly Open: 'OPEN';
|
|
148
|
+
readonly TriggerPending: 'TRIGGER PENDING';
|
|
149
149
|
};
|
|
150
150
|
export declare const TERMINAL_ORDER_STATUSES: Set<string>;
|
|
151
151
|
export declare const OrderSchema: z.ZodObject<{
|
|
@@ -474,6 +474,79 @@ export type Gtt = z.infer<typeof GttSchema>;
|
|
|
474
474
|
export declare const GttCreateResultSchema: z.ZodObject<{
|
|
475
475
|
trigger_id: z.ZodNumber;
|
|
476
476
|
}, z.core.$loose>;
|
|
477
|
+
/**
|
|
478
|
+
* An ATO (Alert-Triggers-Order) alert carries a basket — a full order spec that
|
|
479
|
+
* is placed when the alert fires. Every field is optional and the object is
|
|
480
|
+
* loose: we only *read* baskets on existing alerts, and Kite's basket shape is
|
|
481
|
+
* richer than we display, so anything we don't touch must pass through
|
|
482
|
+
* untouched rather than fail parsing (invariant #5).
|
|
483
|
+
*/
|
|
484
|
+
export declare const AlertBasketItemSchema: z.ZodObject<{
|
|
485
|
+
type: z.ZodOptional<z.ZodString>;
|
|
486
|
+
tradingsymbol: z.ZodOptional<z.ZodString>;
|
|
487
|
+
exchange: z.ZodOptional<z.ZodString>;
|
|
488
|
+
instrument_token: z.ZodOptional<z.ZodNumber>;
|
|
489
|
+
weight: z.ZodOptional<z.ZodNumber>;
|
|
490
|
+
params: z.ZodOptional<z.ZodObject<{}, z.core.$loose>>;
|
|
491
|
+
}, z.core.$loose>;
|
|
492
|
+
export declare const AlertBasketSchema: z.ZodObject<{
|
|
493
|
+
name: z.ZodOptional<z.ZodString>;
|
|
494
|
+
type: z.ZodOptional<z.ZodString>;
|
|
495
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
|
|
496
|
+
items: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
497
|
+
type: z.ZodOptional<z.ZodString>;
|
|
498
|
+
tradingsymbol: z.ZodOptional<z.ZodString>;
|
|
499
|
+
exchange: z.ZodOptional<z.ZodString>;
|
|
500
|
+
instrument_token: z.ZodOptional<z.ZodNumber>;
|
|
501
|
+
weight: z.ZodOptional<z.ZodNumber>;
|
|
502
|
+
params: z.ZodOptional<z.ZodObject<{}, z.core.$loose>>;
|
|
503
|
+
}, z.core.$loose>>>;
|
|
504
|
+
}, z.core.$loose>;
|
|
505
|
+
export declare const AlertSchema: z.ZodObject<{
|
|
506
|
+
uuid: z.ZodString;
|
|
507
|
+
user_id: z.ZodOptional<z.ZodString>;
|
|
508
|
+
type: z.ZodString;
|
|
509
|
+
name: z.ZodOptional<z.ZodString>;
|
|
510
|
+
status: z.ZodString;
|
|
511
|
+
disabled_reason: z.ZodOptional<z.ZodString>;
|
|
512
|
+
lhs_attribute: z.ZodOptional<z.ZodString>;
|
|
513
|
+
lhs_exchange: z.ZodOptional<z.ZodString>;
|
|
514
|
+
lhs_tradingsymbol: z.ZodOptional<z.ZodString>;
|
|
515
|
+
operator: z.ZodOptional<z.ZodString>;
|
|
516
|
+
rhs_type: z.ZodOptional<z.ZodString>;
|
|
517
|
+
rhs_attribute: z.ZodOptional<z.ZodString>;
|
|
518
|
+
rhs_exchange: z.ZodOptional<z.ZodString>;
|
|
519
|
+
rhs_tradingsymbol: z.ZodOptional<z.ZodString>;
|
|
520
|
+
rhs_constant: z.ZodOptional<z.ZodNumber>;
|
|
521
|
+
alert_count: z.ZodOptional<z.ZodNumber>;
|
|
522
|
+
basket: z.ZodOptional<z.ZodObject<{
|
|
523
|
+
name: z.ZodOptional<z.ZodString>;
|
|
524
|
+
type: z.ZodOptional<z.ZodString>;
|
|
525
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
|
|
526
|
+
items: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
527
|
+
type: z.ZodOptional<z.ZodString>;
|
|
528
|
+
tradingsymbol: z.ZodOptional<z.ZodString>;
|
|
529
|
+
exchange: z.ZodOptional<z.ZodString>;
|
|
530
|
+
instrument_token: z.ZodOptional<z.ZodNumber>;
|
|
531
|
+
weight: z.ZodOptional<z.ZodNumber>;
|
|
532
|
+
params: z.ZodOptional<z.ZodObject<{}, z.core.$loose>>;
|
|
533
|
+
}, z.core.$loose>>>;
|
|
534
|
+
}, z.core.$loose>>;
|
|
535
|
+
created_at: z.ZodOptional<z.ZodString>;
|
|
536
|
+
updated_at: z.ZodOptional<z.ZodString>;
|
|
537
|
+
}, z.core.$loose>;
|
|
538
|
+
export type Alert = z.infer<typeof AlertSchema>;
|
|
539
|
+
/** One entry in an alert's trigger history. `meta`/`order_meta` are large and
|
|
540
|
+
* only shown verbatim in `--json`, so they pass through unvalidated. */
|
|
541
|
+
export declare const AlertHistoryEntrySchema: z.ZodObject<{
|
|
542
|
+
uuid: z.ZodOptional<z.ZodString>;
|
|
543
|
+
type: z.ZodOptional<z.ZodString>;
|
|
544
|
+
condition: z.ZodOptional<z.ZodString>;
|
|
545
|
+
created_at: z.ZodOptional<z.ZodString>;
|
|
546
|
+
meta: z.ZodOptional<z.ZodUnknown>;
|
|
547
|
+
order_meta: z.ZodOptional<z.ZodNullable<z.ZodUnknown>>;
|
|
548
|
+
}, z.core.$loose>;
|
|
549
|
+
export type AlertHistoryEntry = z.infer<typeof AlertHistoryEntrySchema>;
|
|
477
550
|
export declare const OrderMarginSchema: z.ZodObject<{
|
|
478
551
|
type: z.ZodOptional<z.ZodString>;
|
|
479
552
|
tradingsymbol: z.ZodOptional<z.ZodString>;
|
package/dist/core/schemas.js
CHANGED
|
@@ -330,6 +330,61 @@ export const GttSchema = z.looseObject({
|
|
|
330
330
|
});
|
|
331
331
|
export const GttCreateResultSchema = z.looseObject({ trigger_id: z.number() });
|
|
332
332
|
// ---------------------------------------------------------------------------
|
|
333
|
+
// Alerts
|
|
334
|
+
// ---------------------------------------------------------------------------
|
|
335
|
+
/**
|
|
336
|
+
* An ATO (Alert-Triggers-Order) alert carries a basket — a full order spec that
|
|
337
|
+
* is placed when the alert fires. Every field is optional and the object is
|
|
338
|
+
* loose: we only *read* baskets on existing alerts, and Kite's basket shape is
|
|
339
|
+
* richer than we display, so anything we don't touch must pass through
|
|
340
|
+
* untouched rather than fail parsing (invariant #5).
|
|
341
|
+
*/
|
|
342
|
+
export const AlertBasketItemSchema = z.looseObject({
|
|
343
|
+
type: z.string().optional(),
|
|
344
|
+
tradingsymbol: z.string().optional(),
|
|
345
|
+
exchange: z.string().optional(),
|
|
346
|
+
instrument_token: z.number().optional(),
|
|
347
|
+
weight: z.number().optional(),
|
|
348
|
+
params: z.looseObject({}).optional(),
|
|
349
|
+
});
|
|
350
|
+
export const AlertBasketSchema = z.looseObject({
|
|
351
|
+
name: z.string().optional(),
|
|
352
|
+
type: z.string().optional(),
|
|
353
|
+
tags: z.array(z.unknown()).optional(),
|
|
354
|
+
items: z.array(AlertBasketItemSchema).default([]),
|
|
355
|
+
});
|
|
356
|
+
export const AlertSchema = z.looseObject({
|
|
357
|
+
uuid: z.string(),
|
|
358
|
+
user_id: z.string().optional(),
|
|
359
|
+
type: z.string(),
|
|
360
|
+
name: z.string().optional(),
|
|
361
|
+
status: z.string(),
|
|
362
|
+
disabled_reason: z.string().optional(),
|
|
363
|
+
lhs_attribute: z.string().optional(),
|
|
364
|
+
lhs_exchange: z.string().optional(),
|
|
365
|
+
lhs_tradingsymbol: z.string().optional(),
|
|
366
|
+
operator: z.string().optional(),
|
|
367
|
+
rhs_type: z.string().optional(),
|
|
368
|
+
rhs_attribute: z.string().optional(),
|
|
369
|
+
rhs_exchange: z.string().optional(),
|
|
370
|
+
rhs_tradingsymbol: z.string().optional(),
|
|
371
|
+
rhs_constant: z.number().optional(),
|
|
372
|
+
alert_count: z.number().optional(),
|
|
373
|
+
basket: AlertBasketSchema.optional(),
|
|
374
|
+
created_at: z.string().optional(),
|
|
375
|
+
updated_at: z.string().optional(),
|
|
376
|
+
});
|
|
377
|
+
/** One entry in an alert's trigger history. `meta`/`order_meta` are large and
|
|
378
|
+
* only shown verbatim in `--json`, so they pass through unvalidated. */
|
|
379
|
+
export const AlertHistoryEntrySchema = z.looseObject({
|
|
380
|
+
uuid: z.string().optional(),
|
|
381
|
+
type: z.string().optional(),
|
|
382
|
+
condition: z.string().optional(),
|
|
383
|
+
created_at: z.string().optional(),
|
|
384
|
+
meta: z.unknown().optional(),
|
|
385
|
+
order_meta: z.unknown().nullish(),
|
|
386
|
+
});
|
|
387
|
+
// ---------------------------------------------------------------------------
|
|
333
388
|
// Margins / charges calculator
|
|
334
389
|
// ---------------------------------------------------------------------------
|
|
335
390
|
export const OrderMarginSchema = z.looseObject({
|
package/dist/core/session.d.ts
CHANGED
|
@@ -13,15 +13,16 @@ export declare const SessionMetaSchema: z.ZodObject<{
|
|
|
13
13
|
broker: z.ZodOptional<z.ZodString>;
|
|
14
14
|
env: z.ZodString;
|
|
15
15
|
apiKey: z.ZodString;
|
|
16
|
+
profile: z.ZodOptional<z.ZodString>;
|
|
16
17
|
expiresAt: z.ZodString;
|
|
17
18
|
loginTime: z.ZodOptional<z.ZodString>;
|
|
18
19
|
exchanges: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
19
20
|
products: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
20
21
|
}, z.core.$strip>;
|
|
21
22
|
export type SessionMeta = z.infer<typeof SessionMetaSchema>;
|
|
22
|
-
export declare function loadSessionMeta(): Promise<SessionMeta | null>;
|
|
23
|
+
export declare function loadSessionMeta(profile?: string): Promise<SessionMeta | null>;
|
|
23
24
|
export declare function saveSessionMeta(meta: SessionMeta): Promise<void>;
|
|
24
|
-
export declare function clearSessionMeta(): Promise<void>;
|
|
25
|
+
export declare function clearSessionMeta(profile?: string): Promise<void>;
|
|
25
26
|
/**
|
|
26
27
|
* Kite access tokens expire at 06:00 IST the following day — a regulatory
|
|
27
28
|
* requirement, not a configurable session length.
|
package/dist/core/session.js
CHANGED
|
@@ -15,16 +15,18 @@ export const SessionMetaSchema = z.object({
|
|
|
15
15
|
broker: z.string().optional(),
|
|
16
16
|
env: z.string(),
|
|
17
17
|
apiKey: z.string(),
|
|
18
|
+
/** The profile this session belongs to. Optional so pre-profile files parse. */
|
|
19
|
+
profile: z.string().optional(),
|
|
18
20
|
/** ISO 8601. Kite invalidates all tokens at 06:00 IST daily. */
|
|
19
21
|
expiresAt: z.string(),
|
|
20
22
|
loginTime: z.string().optional(),
|
|
21
23
|
exchanges: z.array(z.string()).default([]),
|
|
22
24
|
products: z.array(z.string()).default([]),
|
|
23
25
|
});
|
|
24
|
-
export async function loadSessionMeta() {
|
|
26
|
+
export async function loadSessionMeta(profile = 'default') {
|
|
25
27
|
let raw;
|
|
26
28
|
try {
|
|
27
|
-
raw = await readFile(sessionFile(), 'utf8');
|
|
29
|
+
raw = await readFile(sessionFile(profile), 'utf8');
|
|
28
30
|
}
|
|
29
31
|
catch (err) {
|
|
30
32
|
if (err.code === 'ENOENT')
|
|
@@ -41,7 +43,7 @@ export async function loadSessionMeta() {
|
|
|
41
43
|
}
|
|
42
44
|
export async function saveSessionMeta(meta) {
|
|
43
45
|
await ensurePrivateDir(configDir());
|
|
44
|
-
const path = sessionFile();
|
|
46
|
+
const path = sessionFile(meta.profile ?? 'default');
|
|
45
47
|
await writeFile(path, `${JSON.stringify(meta, null, 2)}\n`, {
|
|
46
48
|
mode: 0o600,
|
|
47
49
|
encoding: 'utf8',
|
|
@@ -50,9 +52,9 @@ export async function saveSessionMeta(meta) {
|
|
|
50
52
|
await chmod(path, 0o600);
|
|
51
53
|
}
|
|
52
54
|
}
|
|
53
|
-
export async function clearSessionMeta() {
|
|
55
|
+
export async function clearSessionMeta(profile = 'default') {
|
|
54
56
|
try {
|
|
55
|
-
await unlink(sessionFile());
|
|
57
|
+
await unlink(sessionFile(profile));
|
|
56
58
|
}
|
|
57
59
|
catch (err) {
|
|
58
60
|
if (err.code !== 'ENOENT')
|
package/dist/index.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export { type ClientOptions, KiteClient, setDispatcher, } from './core/client.js
|
|
|
11
11
|
export { type Endpoints, type Environment, endpointsFor, SANDBOX_CREDENTIALS, } from './core/config.js';
|
|
12
12
|
export { AuthRequiredError, ExitCode, KiteApiError, KiteCliError, type KiteErrorType, NetworkError, UsageError, } from './core/errors.js';
|
|
13
13
|
export { InstrumentStore, parseInstrumentKey, parseInstrumentsCsv, } from './core/instruments.js';
|
|
14
|
+
export { DEFAULT_PROFILE, getProfile, listProfileNames, type ResolvedProfile, resolveProfile, resolveTradingConfig, SANDBOX_PROFILE, storagePrefixFor, } from './core/profiles.js';
|
|
14
15
|
export { ORDER_LIMITS, type RateCategory, RateLimiter, } from './core/ratelimit.js';
|
|
15
16
|
export { maskSecret, redact, redactString, redactUrl, registerSecret, } from './core/redact.js';
|
|
16
17
|
export * from './core/schemas.js';
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,7 @@ export { KiteClient, setDispatcher, } from './core/client.js';
|
|
|
11
11
|
export { endpointsFor, SANDBOX_CREDENTIALS, } from './core/config.js';
|
|
12
12
|
export { AuthRequiredError, ExitCode, KiteApiError, KiteCliError, NetworkError, UsageError, } from './core/errors.js';
|
|
13
13
|
export { InstrumentStore, parseInstrumentKey, parseInstrumentsCsv, } from './core/instruments.js';
|
|
14
|
+
export { DEFAULT_PROFILE, getProfile, listProfileNames, resolveProfile, resolveTradingConfig, SANDBOX_PROFILE, storagePrefixFor, } from './core/profiles.js';
|
|
14
15
|
export { ORDER_LIMITS, RateLimiter, } from './core/ratelimit.js';
|
|
15
16
|
export { maskSecret, redact, redactString, redactUrl, registerSecret, } from './core/redact.js';
|
|
16
17
|
export * from './core/schemas.js';
|
package/dist/run.js
CHANGED
|
@@ -43,6 +43,7 @@ export async function run(opts = {}) {
|
|
|
43
43
|
.option('--quiet', 'Suppress informational messages')
|
|
44
44
|
.option('--debug', 'Print redacted request diagnostics to stderr')
|
|
45
45
|
.option('--env <env>', 'Environment: production or sandbox')
|
|
46
|
+
.option('--profile <name>', 'Account profile to use (see `kite profiles`)')
|
|
46
47
|
.option('-y, --yes', 'Skip confirmation prompts (use with care)')
|
|
47
48
|
.option('--dry-run', 'Show what would happen without sending anything to Kite')
|
|
48
49
|
.showHelpAfterError('(run `kite --help` for usage)')
|
|
@@ -70,10 +71,12 @@ export async function run(opts = {}) {
|
|
|
70
71
|
// Registered lazily so a bare `kite quote` never pays to import the
|
|
71
72
|
// dashboard renderer, the ticker, or the prompt library.
|
|
72
73
|
const { authCommands } = await import('./commands/auth.js');
|
|
74
|
+
const { profileCommands } = await import('./commands/profiles.js');
|
|
73
75
|
const { portfolioCommands } = await import('./commands/portfolio.js');
|
|
74
76
|
const { marketCommands } = await import('./commands/market.js');
|
|
75
77
|
const { orderCommands } = await import('./commands/orders.js');
|
|
76
78
|
const { gttCommands } = await import('./commands/gtt.js');
|
|
79
|
+
const { alertCommands } = await import('./commands/alerts.js');
|
|
77
80
|
const { watchCommands } = await import('./commands/watch.js');
|
|
78
81
|
const { configCommands } = await import('./commands/config.js');
|
|
79
82
|
// commandsGroup applies to every command registered after it, so the group
|
|
@@ -81,6 +84,7 @@ export async function run(opts = {}) {
|
|
|
81
84
|
// difference between a readable --help and a wall of text.
|
|
82
85
|
program.commandsGroup('Account:');
|
|
83
86
|
authCommands(program, withContext);
|
|
87
|
+
profileCommands(program, withContext);
|
|
84
88
|
program.commandsGroup('Portfolio:');
|
|
85
89
|
portfolioCommands(program, withContext);
|
|
86
90
|
program.commandsGroup('Market data:');
|
|
@@ -88,6 +92,7 @@ export async function run(opts = {}) {
|
|
|
88
92
|
program.commandsGroup('Trading:');
|
|
89
93
|
orderCommands(program, withContext);
|
|
90
94
|
gttCommands(program, withContext);
|
|
95
|
+
alertCommands(program, withContext);
|
|
91
96
|
program.commandsGroup('Streaming:');
|
|
92
97
|
watchCommands(program, withContext);
|
|
93
98
|
program.commandsGroup('Settings:');
|
|
@@ -102,6 +107,7 @@ Examples:
|
|
|
102
107
|
$ kite orders place NSE:INFY -s BUY -q 1 --dry-run
|
|
103
108
|
Preview an order without sending it
|
|
104
109
|
$ kite positions --json | jq '.net[].pnl' Machine-readable output
|
|
110
|
+
$ kite --profile huf holdings Run against another account (kite profiles)
|
|
105
111
|
|
|
106
112
|
Safety:
|
|
107
113
|
Order commands preview the resolved order and ask for confirmation.
|
package/dist/safety.js
CHANGED
|
@@ -106,10 +106,14 @@ function renderPreview(ctx, opts, force = false) {
|
|
|
106
106
|
const { io } = ctx;
|
|
107
107
|
if ((io.json || io.quiet) && !force)
|
|
108
108
|
return;
|
|
109
|
-
const width = Math.max(...opts.details.map((d) => d.label.length));
|
|
109
|
+
const width = Math.max('Account'.length, ...opts.details.map((d) => d.label.length));
|
|
110
110
|
const emit = force ? (text) => io.forceNote(text) : (text) => io.note(text);
|
|
111
111
|
emit('');
|
|
112
112
|
emit(io.bold(opts.action));
|
|
113
|
+
// Which account this will hit, first and by verified identity — not just the
|
|
114
|
+
// profile label a user chose, which can point at the wrong app. This is the
|
|
115
|
+
// primary guard against placing an order on the wrong account.
|
|
116
|
+
emit(` ${io.dim('Account'.padEnd(width))} ${accountLine(ctx)}`);
|
|
113
117
|
for (const detail of opts.details) {
|
|
114
118
|
emit(` ${io.dim(detail.label.padEnd(width))} ${detail.value}`);
|
|
115
119
|
}
|
|
@@ -117,6 +121,13 @@ function renderPreview(ctx, opts, force = false) {
|
|
|
117
121
|
emit(` ${io.cyan('sandbox — no real money involved')}`);
|
|
118
122
|
}
|
|
119
123
|
}
|
|
124
|
+
/** The verified identity behind this invocation, with the profile for context. */
|
|
125
|
+
function accountLine(ctx) {
|
|
126
|
+
const { io, session, profile } = ctx;
|
|
127
|
+
const who = session ? `${session.userName ?? session.userId} (${session.userId})` : io.dim('token from environment');
|
|
128
|
+
const suffix = profile.name === 'default' ? '' : ` · profile ${io.bold(profile.name)}`;
|
|
129
|
+
return `${who}${suffix}`;
|
|
130
|
+
}
|
|
120
131
|
/**
|
|
121
132
|
* Generate a unique order tag.
|
|
122
133
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pungoyal/kite-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Unofficial command-line interface for the Zerodha Kite Connect API — not affiliated with or endorsed by Zerodha",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"zerodha",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|
|
46
46
|
"build": "tsc -p tsconfig.build.json",
|
|
47
|
-
"dev": "
|
|
47
|
+
"dev": "tsx src/cli.ts",
|
|
48
48
|
"test": "vitest run",
|
|
49
49
|
"test:watch": "vitest",
|
|
50
50
|
"typecheck": "tsc --noEmit",
|
|
@@ -53,6 +53,11 @@
|
|
|
53
53
|
"format": "biome format --write",
|
|
54
54
|
"hooks:install": "git config core.hooksPath .githooks",
|
|
55
55
|
"check:deps": "node scripts/check-deps.mjs",
|
|
56
|
+
"docs:commands": "node scripts/generate-command-reference.mjs",
|
|
57
|
+
"docs:commands:check": "node scripts/generate-command-reference.mjs --check",
|
|
58
|
+
"docs:dev": "npm --prefix docs install && npm --prefix docs run dev",
|
|
59
|
+
"docs:build": "npm --prefix docs ci && npm --prefix docs run build",
|
|
60
|
+
"docs:preview": "npm --prefix docs run preview",
|
|
56
61
|
"lint:publish": "publint && attw --pack . --profile esm-only",
|
|
57
62
|
"check:dist": "node -e \"const{existsSync,readFileSync}=require('node:fs');if(!existsSync('dist/cli.js'))throw new Error('dist/ is missing — run npm run build before publishing');if(!readFileSync('dist/cli.js','utf8').startsWith('#!/usr/bin/env node'))throw new Error('dist/cli.js lost its shebang')\"",
|
|
58
63
|
"prepublishOnly": "npm run build && npm run check:dist"
|
|
@@ -79,7 +84,8 @@
|
|
|
79
84
|
"msw": "^2.15.0",
|
|
80
85
|
"publint": "^0.3.0",
|
|
81
86
|
"strip-ansi": "^7.1.0",
|
|
82
|
-
"
|
|
87
|
+
"tsx": "^4.23.1",
|
|
88
|
+
"typescript": "^7.0.2",
|
|
83
89
|
"vitest": "^4.1.10"
|
|
84
90
|
}
|
|
85
91
|
}
|