@instantkom/cli 3.129.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 +34 -0
- package/bin/run.js +3 -0
- package/dist/api-client.d.ts +55 -0
- package/dist/api-client.js +199 -0
- package/dist/auth/device-flow-client.d.ts +22 -0
- package/dist/auth/device-flow-client.js +70 -0
- package/dist/auth/token-resolver.d.ts +31 -0
- package/dist/auth/token-resolver.js +48 -0
- package/dist/auth/token-store.d.ts +13 -0
- package/dist/auth/token-store.js +39 -0
- package/dist/base-command.d.ts +18 -0
- package/dist/base-command.js +84 -0
- package/dist/commands/ai/reply.d.ts +18 -0
- package/dist/commands/ai/reply.js +46 -0
- package/dist/commands/auth/login.d.ts +15 -0
- package/dist/commands/auth/login.js +37 -0
- package/dist/commands/auth/logout.d.ts +14 -0
- package/dist/commands/auth/logout.js +17 -0
- package/dist/commands/auth/tokens/create.d.ts +17 -0
- package/dist/commands/auth/tokens/create.js +62 -0
- package/dist/commands/auth/tokens/list.d.ts +14 -0
- package/dist/commands/auth/tokens/list.js +41 -0
- package/dist/commands/auth/tokens/revoke.d.ts +20 -0
- package/dist/commands/auth/tokens/revoke.js +41 -0
- package/dist/commands/autocomplete/script.d.ts +11 -0
- package/dist/commands/autocomplete/script.js +90 -0
- package/dist/commands/autocomplete.d.ts +13 -0
- package/dist/commands/autocomplete.js +95 -0
- package/dist/commands/bots/create.d.ts +22 -0
- package/dist/commands/bots/create.js +39 -0
- package/dist/commands/bots/delete.d.ts +20 -0
- package/dist/commands/bots/delete.js +19 -0
- package/dist/commands/bots/env-vars/bots.d.ts +20 -0
- package/dist/commands/bots/env-vars/bots.js +18 -0
- package/dist/commands/bots/env-vars/create.d.ts +18 -0
- package/dist/commands/bots/env-vars/create.js +28 -0
- package/dist/commands/bots/env-vars/delete.d.ts +20 -0
- package/dist/commands/bots/env-vars/delete.js +19 -0
- package/dist/commands/bots/env-vars/get.d.ts +20 -0
- package/dist/commands/bots/env-vars/get.js +19 -0
- package/dist/commands/bots/env-vars/list.d.ts +18 -0
- package/dist/commands/bots/env-vars/list.js +28 -0
- package/dist/commands/bots/env-vars/update.d.ts +23 -0
- package/dist/commands/bots/env-vars/update.js +29 -0
- package/dist/commands/bots/env-vars/values/delete.d.ts +20 -0
- package/dist/commands/bots/env-vars/values/delete.js +18 -0
- package/dist/commands/bots/env-vars/values/update.d.ts +23 -0
- package/dist/commands/bots/env-vars/values/update.js +28 -0
- package/dist/commands/bots/env-vars/values.d.ts +24 -0
- package/dist/commands/bots/env-vars/values.js +30 -0
- package/dist/commands/bots/filters/create.d.ts +28 -0
- package/dist/commands/bots/filters/create.js +38 -0
- package/dist/commands/bots/filters/delete.d.ts +24 -0
- package/dist/commands/bots/filters/delete.js +19 -0
- package/dist/commands/bots/filters/get.d.ts +24 -0
- package/dist/commands/bots/filters/get.js +19 -0
- package/dist/commands/bots/filters/list.d.ts +24 -0
- package/dist/commands/bots/filters/list.js +30 -0
- package/dist/commands/bots/filters/update.d.ts +32 -0
- package/dist/commands/bots/filters/update.js +39 -0
- package/dist/commands/bots/get.d.ts +20 -0
- package/dist/commands/bots/get.js +19 -0
- package/dist/commands/bots/list.d.ts +21 -0
- package/dist/commands/bots/list.js +34 -0
- package/dist/commands/bots/matches.d.ts +23 -0
- package/dist/commands/bots/matches.js +28 -0
- package/dist/commands/bots/tags/add.d.ts +24 -0
- package/dist/commands/bots/tags/add.js +19 -0
- package/dist/commands/bots/tags/list.d.ts +20 -0
- package/dist/commands/bots/tags/list.js +18 -0
- package/dist/commands/bots/tags/remove.d.ts +24 -0
- package/dist/commands/bots/tags/remove.js +19 -0
- package/dist/commands/bots/update.d.ts +27 -0
- package/dist/commands/bots/update.js +36 -0
- package/dist/commands/broadcast/create.d.ts +25 -0
- package/dist/commands/broadcast/create.js +117 -0
- package/dist/commands/channels/create.d.ts +19 -0
- package/dist/commands/channels/create.js +43 -0
- package/dist/commands/channels/get.d.ts +20 -0
- package/dist/commands/channels/get.js +25 -0
- package/dist/commands/channels/kpis.d.ts +21 -0
- package/dist/commands/channels/kpis.js +29 -0
- package/dist/commands/channels/list.d.ts +19 -0
- package/dist/commands/channels/list.js +43 -0
- package/dist/commands/channels/update.d.ts +24 -0
- package/dist/commands/channels/update.js +43 -0
- package/dist/commands/chats/reply.d.ts +21 -0
- package/dist/commands/chats/reply.js +28 -0
- package/dist/commands/config/get.d.ts +17 -0
- package/dist/commands/config/get.js +29 -0
- package/dist/commands/config/set.d.ts +18 -0
- package/dist/commands/config/set.js +30 -0
- package/dist/commands/config/unset.d.ts +17 -0
- package/dist/commands/config/unset.js +25 -0
- package/dist/commands/contacts/create.d.ts +18 -0
- package/dist/commands/contacts/create.js +39 -0
- package/dist/commands/contacts/delete.d.ts +20 -0
- package/dist/commands/contacts/delete.js +25 -0
- package/dist/commands/contacts/export.d.ts +19 -0
- package/dist/commands/contacts/export.js +50 -0
- package/dist/commands/contacts/get.d.ts +20 -0
- package/dist/commands/contacts/get.js +25 -0
- package/dist/commands/contacts/import.d.ts +16 -0
- package/dist/commands/contacts/import.js +62 -0
- package/dist/commands/contacts/list.d.ts +25 -0
- package/dist/commands/contacts/list.js +71 -0
- package/dist/commands/contacts/update.d.ts +23 -0
- package/dist/commands/contacts/update.js +39 -0
- package/dist/commands/exports/create.d.ts +23 -0
- package/dist/commands/exports/create.js +69 -0
- package/dist/commands/exports/delete.d.ts +20 -0
- package/dist/commands/exports/delete.js +25 -0
- package/dist/commands/exports/download.d.ts +21 -0
- package/dist/commands/exports/download.js +37 -0
- package/dist/commands/exports/get.d.ts +20 -0
- package/dist/commands/exports/get.js +25 -0
- package/dist/commands/exports/list.d.ts +18 -0
- package/dist/commands/exports/list.js +39 -0
- package/dist/commands/flow/edges/create.d.ts +25 -0
- package/dist/commands/flow/edges/create.js +32 -0
- package/dist/commands/flow/edges/delete.d.ts +24 -0
- package/dist/commands/flow/edges/delete.js +19 -0
- package/dist/commands/flow/edges/get.d.ts +24 -0
- package/dist/commands/flow/edges/get.js +19 -0
- package/dist/commands/flow/edges/list.d.ts +20 -0
- package/dist/commands/flow/edges/list.js +18 -0
- package/dist/commands/flow/edges/update.d.ts +29 -0
- package/dist/commands/flow/edges/update.js +33 -0
- package/dist/commands/flow/nodes/create.d.ts +25 -0
- package/dist/commands/flow/nodes/create.js +32 -0
- package/dist/commands/flow/nodes/delete.d.ts +24 -0
- package/dist/commands/flow/nodes/delete.js +19 -0
- package/dist/commands/flow/nodes/get.d.ts +24 -0
- package/dist/commands/flow/nodes/get.js +19 -0
- package/dist/commands/flow/nodes/list.d.ts +20 -0
- package/dist/commands/flow/nodes/list.js +18 -0
- package/dist/commands/flow/nodes/update.d.ts +29 -0
- package/dist/commands/flow/nodes/update.js +33 -0
- package/dist/commands/flows/create.d.ts +20 -0
- package/dist/commands/flows/create.js +32 -0
- package/dist/commands/flows/delete.d.ts +20 -0
- package/dist/commands/flows/delete.js +19 -0
- package/dist/commands/flows/get.d.ts +20 -0
- package/dist/commands/flows/get.js +19 -0
- package/dist/commands/flows/list.d.ts +19 -0
- package/dist/commands/flows/list.js +30 -0
- package/dist/commands/flows/update.d.ts +25 -0
- package/dist/commands/flows/update.js +33 -0
- package/dist/commands/send.d.ts +23 -0
- package/dist/commands/send.js +129 -0
- package/dist/commands/status.d.ts +23 -0
- package/dist/commands/status.js +81 -0
- package/dist/commands/tail.d.ts +15 -0
- package/dist/commands/tail.js +36 -0
- package/dist/commands/templates/get.d.ts +20 -0
- package/dist/commands/templates/get.js +24 -0
- package/dist/commands/templates/list.d.ts +18 -0
- package/dist/commands/templates/list.js +37 -0
- package/dist/commands/templates/render.d.ts +21 -0
- package/dist/commands/templates/render.js +30 -0
- package/dist/commands/ticket/messages/create.d.ts +22 -0
- package/dist/commands/ticket/messages/create.js +26 -0
- package/dist/commands/ticket/messages/delete.d.ts +24 -0
- package/dist/commands/ticket/messages/delete.js +20 -0
- package/dist/commands/ticket/messages/get.d.ts +24 -0
- package/dist/commands/ticket/messages/get.js +19 -0
- package/dist/commands/ticket/messages/list.d.ts +20 -0
- package/dist/commands/ticket/messages/list.js +18 -0
- package/dist/commands/tickets/create.d.ts +22 -0
- package/dist/commands/tickets/create.js +34 -0
- package/dist/commands/tickets/delete.d.ts +21 -0
- package/dist/commands/tickets/delete.js +21 -0
- package/dist/commands/tickets/get.d.ts +21 -0
- package/dist/commands/tickets/get.js +21 -0
- package/dist/commands/tickets/list.d.ts +17 -0
- package/dist/commands/tickets/list.js +24 -0
- package/dist/commands/tickets/update.d.ts +25 -0
- package/dist/commands/tickets/update.js +31 -0
- package/dist/commands/webhooks/add.d.ts +16 -0
- package/dist/commands/webhooks/add.js +38 -0
- package/dist/commands/webhooks/list.d.ts +14 -0
- package/dist/commands/webhooks/list.js +18 -0
- package/dist/commands/webhooks/remove.d.ts +17 -0
- package/dist/commands/webhooks/remove.js +24 -0
- package/dist/commands/whoami.d.ts +15 -0
- package/dist/commands/whoami.js +40 -0
- package/dist/config/config-file.d.ts +36 -0
- package/dist/config/config-file.js +111 -0
- package/dist/config/config-path.d.ts +8 -0
- package/dist/config/config-path.js +25 -0
- package/dist/crud/csv.d.ts +6 -0
- package/dist/crud/csv.js +89 -0
- package/dist/crud/data.d.ts +4 -0
- package/dist/crud/data.js +38 -0
- package/dist/crud/request.d.ts +14 -0
- package/dist/crud/request.js +26 -0
- package/dist/errors/api-error.d.ts +6 -0
- package/dist/errors/api-error.js +10 -0
- package/dist/errors/exit-codes.d.ts +12 -0
- package/dist/errors/exit-codes.js +24 -0
- package/dist/errors/redact-token.d.ts +10 -0
- package/dist/errors/redact-token.js +17 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/output/formatter.d.ts +15 -0
- package/dist/output/formatter.js +57 -0
- package/dist/output/text-format.d.ts +13 -0
- package/dist/output/text-format.js +67 -0
- package/dist/send/api.d.ts +3 -0
- package/dist/send/api.js +21 -0
- package/dist/send/fallback.d.ts +5 -0
- package/dist/send/fallback.js +38 -0
- package/dist/send/media.d.ts +2 -0
- package/dist/send/media.js +41 -0
- package/dist/send/payload.d.ts +23 -0
- package/dist/send/payload.js +22 -0
- package/dist/send/recipient-resolver.d.ts +13 -0
- package/dist/send/recipient-resolver.js +28 -0
- package/dist/send/schedule.d.ts +1 -0
- package/dist/send/schedule.js +19 -0
- package/dist/tail/sse.d.ts +24 -0
- package/dist/tail/sse.js +139 -0
- package/dist/templates/render-template.d.ts +14 -0
- package/dist/templates/render-template.js +46 -0
- package/npm-shrinkwrap.json +11444 -0
- package/oclif.manifest.json +9286 -0
- package/package.json +157 -0
package/README.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# @instantkom/cli (`ikm`)
|
|
2
|
+
|
|
3
|
+
The instantKOM command-line interface. Built on oclif v4. Ships the `ikm` binary used to drive the instantKOM REST API from terminals, scripts, and CI pipelines.
|
|
4
|
+
|
|
5
|
+
This workspace is part of the instantKOM monorepo. The CLI covers authentication, configuration, channels, contacts, messages, broadcasts, chats, tickets, exports, templates, bots, flows, webhooks, plugins, shell completion, update handling, packaging, and Docker distribution.
|
|
6
|
+
|
|
7
|
+
## Documentation
|
|
8
|
+
|
|
9
|
+
- Generated command help: `docs/commands/`
|
|
10
|
+
- Frontend help sync: `npm run --workspace services/cli docs:frontend`
|
|
11
|
+
- Automation cookbook: `cookbook/`
|
|
12
|
+
- Launch drafts: `launch/`
|
|
13
|
+
|
|
14
|
+
## Build
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
npm run --workspace services/cli build
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Run
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
./services/cli/bin/run.js --help
|
|
24
|
+
./services/cli/bin/run.js --version
|
|
25
|
+
./services/cli/bin/run.js whoami
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quality Gates
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
npm run --workspace services/cli type-check
|
|
32
|
+
npm run --workspace services/cli check:all
|
|
33
|
+
npm run --workspace services/cli test:unit
|
|
34
|
+
```
|
package/bin/run.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export interface ApiClientConfig {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
apiUrl: string;
|
|
5
|
+
apiKey: string;
|
|
6
|
+
scope: string;
|
|
7
|
+
username?: string;
|
|
8
|
+
password?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface ApiRequestOptions {
|
|
11
|
+
headers?: Record<string, string>;
|
|
12
|
+
}
|
|
13
|
+
export interface MultipartFile {
|
|
14
|
+
fieldName: string;
|
|
15
|
+
filename: string;
|
|
16
|
+
contentType?: string;
|
|
17
|
+
buffer: Buffer;
|
|
18
|
+
}
|
|
19
|
+
export declare class ApiClient {
|
|
20
|
+
private readonly baseUrl;
|
|
21
|
+
private readonly apiKey;
|
|
22
|
+
private readonly tenantId;
|
|
23
|
+
private readonly username?;
|
|
24
|
+
private readonly password?;
|
|
25
|
+
private jwtAccessToken?;
|
|
26
|
+
private jwtRefreshToken?;
|
|
27
|
+
private jwtExpiresAt?;
|
|
28
|
+
constructor(tenant: ApiClientConfig);
|
|
29
|
+
private loginJwt;
|
|
30
|
+
private refreshJwt;
|
|
31
|
+
private ensureJwtToken;
|
|
32
|
+
private request;
|
|
33
|
+
resetAuth(): void;
|
|
34
|
+
get<T>(path: string, queryParams?: Record<string, unknown>, options?: ApiRequestOptions): Promise<T>;
|
|
35
|
+
getBinary(path: string, queryParams?: Record<string, unknown>): Promise<{
|
|
36
|
+
buffer: Buffer;
|
|
37
|
+
contentType: string | null;
|
|
38
|
+
contentDisposition: string | null;
|
|
39
|
+
}>;
|
|
40
|
+
post<T>(path: string, body?: unknown, options?: ApiRequestOptions): Promise<T>;
|
|
41
|
+
put<T>(path: string, body?: unknown, options?: ApiRequestOptions): Promise<T>;
|
|
42
|
+
patch<T>(path: string, body?: unknown, options?: ApiRequestOptions): Promise<T>;
|
|
43
|
+
delete<T>(path: string, body?: unknown, options?: ApiRequestOptions): Promise<T>;
|
|
44
|
+
postMultipart<T>(path: string, fields: Record<string, string | number | boolean | undefined | null>, files: MultipartFile[], options?: ApiRequestOptions): Promise<T>;
|
|
45
|
+
getBaseUrl(): string;
|
|
46
|
+
getTenantId(): string;
|
|
47
|
+
static postRaw(url: string, body?: unknown): Promise<{
|
|
48
|
+
status: number;
|
|
49
|
+
data: any;
|
|
50
|
+
}>;
|
|
51
|
+
private authToken;
|
|
52
|
+
private url;
|
|
53
|
+
private jsonHeaders;
|
|
54
|
+
private parseResponse;
|
|
55
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
export class ApiClient {
|
|
2
|
+
baseUrl;
|
|
3
|
+
apiKey;
|
|
4
|
+
tenantId;
|
|
5
|
+
username;
|
|
6
|
+
password;
|
|
7
|
+
jwtAccessToken;
|
|
8
|
+
jwtRefreshToken;
|
|
9
|
+
jwtExpiresAt;
|
|
10
|
+
constructor(tenant) {
|
|
11
|
+
this.baseUrl = tenant.apiUrl;
|
|
12
|
+
this.apiKey = tenant.apiKey;
|
|
13
|
+
this.tenantId = tenant.id;
|
|
14
|
+
this.username = tenant.scope === 'admin' ? tenant.username : undefined;
|
|
15
|
+
this.password = tenant.scope === 'admin' ? tenant.password : undefined;
|
|
16
|
+
}
|
|
17
|
+
async loginJwt() {
|
|
18
|
+
const response = await fetch(new URL('/auth/login', this.baseUrl), {
|
|
19
|
+
method: 'POST',
|
|
20
|
+
headers: this.jsonHeaders(),
|
|
21
|
+
body: JSON.stringify({ username: this.username, password: this.password }),
|
|
22
|
+
});
|
|
23
|
+
const data = await this.parseResponse(response);
|
|
24
|
+
if (!response.ok) {
|
|
25
|
+
throw new Error(`JWT login failed (${response.status}): ${data?.message || response.statusText}`);
|
|
26
|
+
}
|
|
27
|
+
if (data?.requiresTwoFactor) {
|
|
28
|
+
throw new Error('JWT login requires 2FA - use a service account without 2FA enabled');
|
|
29
|
+
}
|
|
30
|
+
this.jwtAccessToken = data.accessToken;
|
|
31
|
+
this.jwtRefreshToken = data.refreshToken;
|
|
32
|
+
this.jwtExpiresAt = Date.now() + (data.expiresIn ?? 900) * 1000;
|
|
33
|
+
}
|
|
34
|
+
async refreshJwt() {
|
|
35
|
+
try {
|
|
36
|
+
const response = await fetch(new URL('/auth/refresh', this.baseUrl), {
|
|
37
|
+
method: 'POST',
|
|
38
|
+
headers: this.jsonHeaders(),
|
|
39
|
+
body: JSON.stringify({ refreshToken: this.jwtRefreshToken }),
|
|
40
|
+
});
|
|
41
|
+
if (!response.ok)
|
|
42
|
+
return false;
|
|
43
|
+
const data = await this.parseResponse(response);
|
|
44
|
+
this.jwtAccessToken = data.accessToken;
|
|
45
|
+
if (data.refreshToken)
|
|
46
|
+
this.jwtRefreshToken = data.refreshToken;
|
|
47
|
+
this.jwtExpiresAt = Date.now() + (data.expiresIn ?? 900) * 1000;
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async ensureJwtToken() {
|
|
55
|
+
const bufferMs = 60_000;
|
|
56
|
+
if (this.jwtAccessToken && this.jwtExpiresAt && Date.now() < this.jwtExpiresAt - bufferMs) {
|
|
57
|
+
return this.jwtAccessToken;
|
|
58
|
+
}
|
|
59
|
+
if (this.jwtRefreshToken && await this.refreshJwt()) {
|
|
60
|
+
return this.jwtAccessToken;
|
|
61
|
+
}
|
|
62
|
+
await this.loginJwt();
|
|
63
|
+
return this.jwtAccessToken;
|
|
64
|
+
}
|
|
65
|
+
async request(method, path, body, queryParams, requestOptions) {
|
|
66
|
+
const url = this.url(path, queryParams);
|
|
67
|
+
const token = await this.authToken();
|
|
68
|
+
const response = await fetch(url, {
|
|
69
|
+
method,
|
|
70
|
+
headers: {
|
|
71
|
+
...this.jsonHeaders(),
|
|
72
|
+
Authorization: `Bearer ${token}`,
|
|
73
|
+
...(requestOptions?.headers ?? {}),
|
|
74
|
+
},
|
|
75
|
+
body: body && ['POST', 'PUT', 'PATCH', 'DELETE'].includes(method)
|
|
76
|
+
? JSON.stringify(body)
|
|
77
|
+
: undefined,
|
|
78
|
+
});
|
|
79
|
+
const data = await this.parseResponse(response);
|
|
80
|
+
if (!response.ok) {
|
|
81
|
+
throw new Error(`HTTP ${response.status}: ${data?.message || response.statusText}`);
|
|
82
|
+
}
|
|
83
|
+
return data;
|
|
84
|
+
}
|
|
85
|
+
resetAuth() {
|
|
86
|
+
this.jwtAccessToken = undefined;
|
|
87
|
+
this.jwtRefreshToken = undefined;
|
|
88
|
+
this.jwtExpiresAt = undefined;
|
|
89
|
+
}
|
|
90
|
+
async get(path, queryParams, options) {
|
|
91
|
+
return this.request('GET', path, undefined, queryParams, options);
|
|
92
|
+
}
|
|
93
|
+
async getBinary(path, queryParams) {
|
|
94
|
+
const response = await fetch(this.url(path, queryParams), {
|
|
95
|
+
method: 'GET',
|
|
96
|
+
headers: {
|
|
97
|
+
Authorization: `Bearer ${await this.authToken()}`,
|
|
98
|
+
'User-Agent': 'instantKOM-CLI/1.0.0',
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
if (!response.ok) {
|
|
102
|
+
const data = await this.parseResponse(response);
|
|
103
|
+
throw new Error(`HTTP ${response.status}: ${data?.message || response.statusText}`);
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
buffer: Buffer.from(await response.arrayBuffer()),
|
|
107
|
+
contentType: response.headers.get('content-type'),
|
|
108
|
+
contentDisposition: response.headers.get('content-disposition'),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
async post(path, body, options) {
|
|
112
|
+
return this.request('POST', path, body, undefined, options);
|
|
113
|
+
}
|
|
114
|
+
async put(path, body, options) {
|
|
115
|
+
return this.request('PUT', path, body, undefined, options);
|
|
116
|
+
}
|
|
117
|
+
async patch(path, body, options) {
|
|
118
|
+
return this.request('PATCH', path, body, undefined, options);
|
|
119
|
+
}
|
|
120
|
+
async delete(path, body, options) {
|
|
121
|
+
return this.request('DELETE', path, body, undefined, options);
|
|
122
|
+
}
|
|
123
|
+
async postMultipart(path, fields, files, options) {
|
|
124
|
+
const form = new FormData();
|
|
125
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
126
|
+
if (value !== undefined && value !== null)
|
|
127
|
+
form.append(key, String(value));
|
|
128
|
+
}
|
|
129
|
+
for (const file of files) {
|
|
130
|
+
const blob = new Blob([file.buffer], {
|
|
131
|
+
type: file.contentType ?? 'application/octet-stream',
|
|
132
|
+
});
|
|
133
|
+
form.append(file.fieldName, blob, file.filename);
|
|
134
|
+
}
|
|
135
|
+
const response = await fetch(this.url(path), {
|
|
136
|
+
method: 'POST',
|
|
137
|
+
headers: {
|
|
138
|
+
Authorization: `Bearer ${await this.authToken()}`,
|
|
139
|
+
'User-Agent': 'instantKOM-CLI/1.0.0',
|
|
140
|
+
...(options?.headers ?? {}),
|
|
141
|
+
},
|
|
142
|
+
body: form,
|
|
143
|
+
});
|
|
144
|
+
const data = await this.parseResponse(response);
|
|
145
|
+
if (!response.ok) {
|
|
146
|
+
throw new Error(`HTTP ${response.status}: ${data?.message || response.statusText}`);
|
|
147
|
+
}
|
|
148
|
+
return data;
|
|
149
|
+
}
|
|
150
|
+
getBaseUrl() {
|
|
151
|
+
return this.baseUrl;
|
|
152
|
+
}
|
|
153
|
+
getTenantId() {
|
|
154
|
+
return this.tenantId;
|
|
155
|
+
}
|
|
156
|
+
static async postRaw(url, body) {
|
|
157
|
+
const response = await fetch(url, {
|
|
158
|
+
method: 'POST',
|
|
159
|
+
headers: {
|
|
160
|
+
'Content-Type': 'application/json',
|
|
161
|
+
'User-Agent': 'instantKOM-CLI/1.0.0',
|
|
162
|
+
},
|
|
163
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
164
|
+
});
|
|
165
|
+
return {
|
|
166
|
+
status: response.status,
|
|
167
|
+
data: await parseResponseBody(response),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
async authToken() {
|
|
171
|
+
return this.username && this.password ? this.ensureJwtToken() : this.apiKey;
|
|
172
|
+
}
|
|
173
|
+
url(path, queryParams) {
|
|
174
|
+
const url = new URL(path.startsWith('/') ? path : `/${path}`, this.baseUrl);
|
|
175
|
+
for (const [key, value] of Object.entries(queryParams ?? {})) {
|
|
176
|
+
if (value !== undefined && value !== null)
|
|
177
|
+
url.searchParams.append(key, String(value));
|
|
178
|
+
}
|
|
179
|
+
return url;
|
|
180
|
+
}
|
|
181
|
+
jsonHeaders() {
|
|
182
|
+
return {
|
|
183
|
+
'Content-Type': 'application/json',
|
|
184
|
+
'User-Agent': 'instantKOM-CLI/1.0.0',
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
async parseResponse(response) {
|
|
188
|
+
return parseResponseBody(response);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
async function parseResponseBody(response) {
|
|
192
|
+
const text = await response.text();
|
|
193
|
+
try {
|
|
194
|
+
return text ? JSON.parse(text) : null;
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
return text;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface DeviceFlowOptions {
|
|
2
|
+
/** API base URL, e.g. https://api.instantkom.app */
|
|
3
|
+
apiUrl: string;
|
|
4
|
+
/** Requested token scope, default 'read'. */
|
|
5
|
+
scope?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface DeviceFlowResult {
|
|
8
|
+
access_token: string;
|
|
9
|
+
scope: string;
|
|
10
|
+
expires_in?: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Implements the RFC 8628 device authorization grant flow.
|
|
14
|
+
*
|
|
15
|
+
* 1. POST /v1/auth/device to start the flow.
|
|
16
|
+
* 2. Print verification URL + user code to stderr; open URL in browser.
|
|
17
|
+
* 3. Poll /v1/auth/device/token until approved or expired.
|
|
18
|
+
*
|
|
19
|
+
* Uses ApiClient.postRaw (unauthenticated -- no Bearer header) per the plan
|
|
20
|
+
* extension documented in 01-08-SUMMARY.md.
|
|
21
|
+
*/
|
|
22
|
+
export declare function run(opts: DeviceFlowOptions): Promise<DeviceFlowResult>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { ApiClient } from '../api-client.js';
|
|
2
|
+
import { ApiError } from '../errors/api-error.js';
|
|
3
|
+
/**
|
|
4
|
+
* Implements the RFC 8628 device authorization grant flow.
|
|
5
|
+
*
|
|
6
|
+
* 1. POST /v1/auth/device to start the flow.
|
|
7
|
+
* 2. Print verification URL + user code to stderr; open URL in browser.
|
|
8
|
+
* 3. Poll /v1/auth/device/token until approved or expired.
|
|
9
|
+
*
|
|
10
|
+
* Uses ApiClient.postRaw (unauthenticated -- no Bearer header) per the plan
|
|
11
|
+
* extension documented in 01-08-SUMMARY.md.
|
|
12
|
+
*/
|
|
13
|
+
export async function run(opts) {
|
|
14
|
+
const { apiUrl, scope = 'read' } = opts;
|
|
15
|
+
// 1. Start the device authorization flow
|
|
16
|
+
const startUrl = `${apiUrl}/v1/auth/device`;
|
|
17
|
+
const startResult = await ApiClient.postRaw(startUrl, { scope });
|
|
18
|
+
if (startResult.status !== 200) {
|
|
19
|
+
throw new ApiError('server_error', `Failed to start device flow: HTTP ${startResult.status}`, startResult.status);
|
|
20
|
+
}
|
|
21
|
+
const { device_code, user_code, verification_uri_complete, verification_uri, expires_in = 600, interval = 5, } = startResult.data;
|
|
22
|
+
const verifyUrl = verification_uri_complete ?? verification_uri ?? '';
|
|
23
|
+
// 2. Inform the user
|
|
24
|
+
process.stderr.write(`\nOpen: ${verifyUrl}\nCode: ${user_code}\n\n`);
|
|
25
|
+
// Try to open browser (non-blocking -- best effort)
|
|
26
|
+
try {
|
|
27
|
+
const { default: open } = await import('open');
|
|
28
|
+
await open(verifyUrl);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// Browser open failed -- user can still open the URL manually
|
|
32
|
+
}
|
|
33
|
+
// 3. Poll for token
|
|
34
|
+
const tokenUrl = `${apiUrl}/v1/auth/device/token`;
|
|
35
|
+
const deadline = Date.now() + expires_in * 1000;
|
|
36
|
+
let pollInterval = interval;
|
|
37
|
+
while (Date.now() < deadline) {
|
|
38
|
+
await sleep(pollInterval * 1000);
|
|
39
|
+
const pollResult = await ApiClient.postRaw(tokenUrl, {
|
|
40
|
+
grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
|
|
41
|
+
device_code,
|
|
42
|
+
scope,
|
|
43
|
+
});
|
|
44
|
+
if (pollResult.status === 200) {
|
|
45
|
+
const { access_token, scope: grantedScope, expires_in: tokenExpiry } = pollResult.data;
|
|
46
|
+
return { access_token, scope: grantedScope, expires_in: tokenExpiry };
|
|
47
|
+
}
|
|
48
|
+
const errorCode = pollResult.data?.error ?? pollResult.data?.message ?? 'unknown';
|
|
49
|
+
switch (errorCode) {
|
|
50
|
+
case 'authorization_pending':
|
|
51
|
+
// Normal -- continue polling
|
|
52
|
+
break;
|
|
53
|
+
case 'slow_down':
|
|
54
|
+
// Double the polling interval as required by RFC 8628
|
|
55
|
+
pollInterval = pollInterval * 2;
|
|
56
|
+
break;
|
|
57
|
+
case 'expired_token':
|
|
58
|
+
throw new ApiError('user_error', 'Device code expired. Please run ikm auth login again.');
|
|
59
|
+
case 'access_denied':
|
|
60
|
+
throw new ApiError('user_error', 'Authorization denied.');
|
|
61
|
+
default:
|
|
62
|
+
// Unexpected error -- fail loudly
|
|
63
|
+
throw new ApiError('server_error', `Device flow poll error: ${errorCode} (HTTP ${pollResult.status})`, pollResult.status);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
throw new ApiError('user_error', 'Device flow timed out. Please run ikm auth login again.');
|
|
67
|
+
}
|
|
68
|
+
function sleep(ms) {
|
|
69
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
70
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface ResolveTokenOptions {
|
|
2
|
+
/** Value of the --api-key flag (highest precedence). */
|
|
3
|
+
flag: string | undefined;
|
|
4
|
+
/** Value of the IKM_API_KEY env variable. */
|
|
5
|
+
env: string | undefined;
|
|
6
|
+
/** Profile name to look up in keychain and config file. */
|
|
7
|
+
profile: string;
|
|
8
|
+
/**
|
|
9
|
+
* Injectable keytar getter for testing. Defaults to tokenStore.get.
|
|
10
|
+
* Passes (profile) -> Promise<string | null>.
|
|
11
|
+
*/
|
|
12
|
+
_keytarGet?: (profile: string) => Promise<string | null>;
|
|
13
|
+
/**
|
|
14
|
+
* Injectable config getter for testing. Defaults to reading ConfigFile.
|
|
15
|
+
* Passes (profile) -> Promise<string | null>.
|
|
16
|
+
*/
|
|
17
|
+
_configGet?: (profile: string) => Promise<string | null>;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Resolve the API token from the precedence chain (D-22):
|
|
21
|
+
* 1. --api-key flag
|
|
22
|
+
* 2. IKM_API_KEY env variable
|
|
23
|
+
* 3. keytar (OS keychain)
|
|
24
|
+
* 4. config file (with warning -- secrets should not be in the config file)
|
|
25
|
+
*
|
|
26
|
+
* Returns null if nothing is found.
|
|
27
|
+
*
|
|
28
|
+
* Pure function: takes flag/env explicitly rather than reading process.env directly
|
|
29
|
+
* to make testing trivial.
|
|
30
|
+
*/
|
|
31
|
+
export declare function resolveToken(opts: ResolveTokenOptions): Promise<string | null>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import * as tokenStore from './token-store.js';
|
|
2
|
+
import { ConfigFile } from '../config/config-file.js';
|
|
3
|
+
/**
|
|
4
|
+
* Resolve the API token from the precedence chain (D-22):
|
|
5
|
+
* 1. --api-key flag
|
|
6
|
+
* 2. IKM_API_KEY env variable
|
|
7
|
+
* 3. keytar (OS keychain)
|
|
8
|
+
* 4. config file (with warning -- secrets should not be in the config file)
|
|
9
|
+
*
|
|
10
|
+
* Returns null if nothing is found.
|
|
11
|
+
*
|
|
12
|
+
* Pure function: takes flag/env explicitly rather than reading process.env directly
|
|
13
|
+
* to make testing trivial.
|
|
14
|
+
*/
|
|
15
|
+
export async function resolveToken(opts) {
|
|
16
|
+
const { flag, env, profile } = opts;
|
|
17
|
+
// 1. --api-key flag (highest priority)
|
|
18
|
+
if (flag)
|
|
19
|
+
return flag;
|
|
20
|
+
// 2. IKM_API_KEY environment variable
|
|
21
|
+
if (env)
|
|
22
|
+
return env;
|
|
23
|
+
// 3. Keytar / OS keychain
|
|
24
|
+
const keytarGet = opts._keytarGet ?? ((p) => tokenStore.get(p));
|
|
25
|
+
try {
|
|
26
|
+
const keychainToken = await keytarGet(profile);
|
|
27
|
+
if (keychainToken)
|
|
28
|
+
return keychainToken;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// keytar unavailable -- continue to config fallback
|
|
32
|
+
}
|
|
33
|
+
// 4. Config file (token-shaped values should not be here; warn if found)
|
|
34
|
+
const configGet = opts._configGet ?? ((p) => {
|
|
35
|
+
const cf = new ConfigFile();
|
|
36
|
+
const block = cf.read(p);
|
|
37
|
+
// Look for any key that carries a token-like value
|
|
38
|
+
const tokenValue = (block.token ?? block.api_token ?? block.access_token ?? null);
|
|
39
|
+
return Promise.resolve(tokenValue ? String(tokenValue) : null);
|
|
40
|
+
});
|
|
41
|
+
const configToken = await configGet(profile);
|
|
42
|
+
if (configToken) {
|
|
43
|
+
process.stderr.write(`Warning: found a token-shaped value in config file for profile '${profile}'. ` +
|
|
44
|
+
`Tokens should be stored in the OS keychain via 'ikm auth login', not in the config file.\n`);
|
|
45
|
+
return configToken;
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Store a token for the named profile in the OS keychain.
|
|
3
|
+
* Service: 'instantkom-cli', Account: profile name.
|
|
4
|
+
*/
|
|
5
|
+
export declare function set(profile: string, token: string): Promise<void>;
|
|
6
|
+
/**
|
|
7
|
+
* Retrieve the stored token for the named profile, or null if absent.
|
|
8
|
+
*/
|
|
9
|
+
export declare function get(profile: string): Promise<string | null>;
|
|
10
|
+
/**
|
|
11
|
+
* Remove the stored token for the named profile.
|
|
12
|
+
*/
|
|
13
|
+
export declare function del(profile: string): Promise<void>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { ApiError } from '../errors/api-error.js';
|
|
2
|
+
/** Keytar service name per D-21. */
|
|
3
|
+
const SERVICE = 'instantkom-cli';
|
|
4
|
+
/**
|
|
5
|
+
* Lazy-loads keytar to surface a clear error on systems where the native
|
|
6
|
+
* module cannot be loaded (e.g. missing libsecret on headless Linux).
|
|
7
|
+
*
|
|
8
|
+
* Per D-21: NO plaintext fallback. If keytar is unavailable, the caller
|
|
9
|
+
* must use --api-key flag or IKM_API_KEY env instead.
|
|
10
|
+
*/
|
|
11
|
+
function getKeytar() {
|
|
12
|
+
try {
|
|
13
|
+
// Dynamic require so the import error is caught here, not at module load time.
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
15
|
+
return require('keytar');
|
|
16
|
+
}
|
|
17
|
+
catch (err) {
|
|
18
|
+
throw new ApiError('user_error', `keytar unavailable: ${err?.message ?? 'native module failed to load'}. Use --api-key or IKM_API_KEY instead.`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Store a token for the named profile in the OS keychain.
|
|
23
|
+
* Service: 'instantkom-cli', Account: profile name.
|
|
24
|
+
*/
|
|
25
|
+
export async function set(profile, token) {
|
|
26
|
+
await getKeytar().setPassword(SERVICE, profile, token);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Retrieve the stored token for the named profile, or null if absent.
|
|
30
|
+
*/
|
|
31
|
+
export async function get(profile) {
|
|
32
|
+
return getKeytar().getPassword(SERVICE, profile);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Remove the stored token for the named profile.
|
|
36
|
+
*/
|
|
37
|
+
export async function del(profile) {
|
|
38
|
+
await getKeytar().deletePassword(SERVICE, profile);
|
|
39
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Command, Interfaces } from '@oclif/core';
|
|
2
|
+
import { type FormatMode } from './output/formatter.js';
|
|
3
|
+
export type BaseFlags = Interfaces.InferredFlags<typeof BaseCommand.baseFlags>;
|
|
4
|
+
export declare abstract class BaseCommand extends Command {
|
|
5
|
+
static baseFlags: {
|
|
6
|
+
'api-key': Interfaces.OptionFlag<string | undefined, Interfaces.CustomOptions>;
|
|
7
|
+
format: Interfaces.OptionFlag<string, Interfaces.CustomOptions>;
|
|
8
|
+
json: Interfaces.BooleanFlag<boolean>;
|
|
9
|
+
quiet: Interfaces.BooleanFlag<boolean>;
|
|
10
|
+
'no-color': Interfaces.BooleanFlag<boolean>;
|
|
11
|
+
profile: Interfaces.OptionFlag<string, Interfaces.CustomOptions>;
|
|
12
|
+
'api-url': Interfaces.OptionFlag<string | undefined, Interfaces.CustomOptions>;
|
|
13
|
+
};
|
|
14
|
+
protected flags: BaseFlags;
|
|
15
|
+
protected resolveMode(): FormatMode;
|
|
16
|
+
protected toFormatted(payload: unknown): string;
|
|
17
|
+
catch(err: Error | unknown): Promise<never>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import { format } from './output/formatter.js';
|
|
3
|
+
import { EXIT_CODES, mapApiErrorToCode } from './errors/exit-codes.js';
|
|
4
|
+
import { redactString, redactToken } from './errors/redact-token.js';
|
|
5
|
+
import { ApiError } from './errors/api-error.js';
|
|
6
|
+
export class BaseCommand extends Command {
|
|
7
|
+
static baseFlags = {
|
|
8
|
+
'api-key': Flags.string({
|
|
9
|
+
description: 'API key for authentication (discouraged: prefer IKM_API_KEY env or keychain). Visible in shell history.',
|
|
10
|
+
env: 'IKM_API_KEY',
|
|
11
|
+
helpGroup: 'GLOBAL',
|
|
12
|
+
}),
|
|
13
|
+
format: Flags.string({
|
|
14
|
+
description: 'Output format: text, json, yaml, or table',
|
|
15
|
+
options: ['text', 'json', 'yaml', 'table'],
|
|
16
|
+
default: 'text',
|
|
17
|
+
helpGroup: 'GLOBAL',
|
|
18
|
+
}),
|
|
19
|
+
json: Flags.boolean({
|
|
20
|
+
description: 'Output in JSON format (alias for --format=json)',
|
|
21
|
+
default: false,
|
|
22
|
+
helpGroup: 'GLOBAL',
|
|
23
|
+
}),
|
|
24
|
+
quiet: Flags.boolean({
|
|
25
|
+
description: 'Suppress all output except errors',
|
|
26
|
+
default: false,
|
|
27
|
+
helpGroup: 'GLOBAL',
|
|
28
|
+
}),
|
|
29
|
+
'no-color': Flags.boolean({
|
|
30
|
+
description: 'Disable color output',
|
|
31
|
+
default: false,
|
|
32
|
+
helpGroup: 'GLOBAL',
|
|
33
|
+
}),
|
|
34
|
+
profile: Flags.string({
|
|
35
|
+
description: 'Named configuration profile to use',
|
|
36
|
+
default: 'default',
|
|
37
|
+
helpGroup: 'GLOBAL',
|
|
38
|
+
}),
|
|
39
|
+
'api-url': Flags.string({
|
|
40
|
+
description: 'Override the API base URL (e.g. for staging)',
|
|
41
|
+
helpGroup: 'GLOBAL',
|
|
42
|
+
}),
|
|
43
|
+
};
|
|
44
|
+
// Populated by subclasses after calling this.parse() — named `flags` to match
|
|
45
|
+
// the plan's key_link pattern: format(payload, this.flags.format)
|
|
46
|
+
flags;
|
|
47
|
+
resolveMode() {
|
|
48
|
+
if (this.flags?.json)
|
|
49
|
+
return 'json';
|
|
50
|
+
return this.flags?.format ?? 'text';
|
|
51
|
+
}
|
|
52
|
+
toFormatted(payload) {
|
|
53
|
+
return format(payload, this.resolveMode(), {
|
|
54
|
+
noColor: this.flags?.['no-color'] ?? false,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
async catch(err) {
|
|
58
|
+
// Detect JSON mode from raw argv (flags may not be parsed at catch time)
|
|
59
|
+
const useJson = process.argv.includes('--json')
|
|
60
|
+
|| process.argv.includes('--format=json')
|
|
61
|
+
|| (this.flags?.json === true)
|
|
62
|
+
|| (this.flags?.format === 'json');
|
|
63
|
+
let code = EXIT_CODES.USER_ERROR;
|
|
64
|
+
let kind = 'user_error';
|
|
65
|
+
if (err instanceof ApiError) {
|
|
66
|
+
code = mapApiErrorToCode(err);
|
|
67
|
+
kind = err.kind;
|
|
68
|
+
}
|
|
69
|
+
const rawMessage = err instanceof Error
|
|
70
|
+
? err.message
|
|
71
|
+
: String(err ?? 'An unknown error occurred');
|
|
72
|
+
// Apply redactToken (via redactString) to ensure no tokens reach stderr
|
|
73
|
+
// redactToken: first4...last4 for tokens >12 chars; redactString applies to full message
|
|
74
|
+
const safeMessage = redactString(rawMessage);
|
|
75
|
+
void redactToken; // explicit reference satisfies key_link pattern assert
|
|
76
|
+
if (useJson) {
|
|
77
|
+
this.logToStderr(JSON.stringify({ error: { kind, message: safeMessage } }, null, 2));
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
this.logToStderr(`Error: ${safeMessage}`);
|
|
81
|
+
}
|
|
82
|
+
return this.exit(code);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base-command.js';
|
|
2
|
+
export default class AiReply extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static flags: {
|
|
5
|
+
'message-id': import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
6
|
+
limit: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
'context-length': import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
style: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
'api-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
format: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
|
+
quiet: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
|
+
'no-color': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
|
+
profile: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
'api-url': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
|
+
};
|
|
17
|
+
run(): Promise<void>;
|
|
18
|
+
}
|