@instantkom/cli 3.129.2 → 3.131.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 (162) hide show
  1. package/README.md +29 -0
  2. package/bin/run.js +0 -0
  3. package/dist/api-client.js +28 -5
  4. package/dist/command-helpers/download.d.ts +10 -0
  5. package/dist/command-helpers/download.js +10 -0
  6. package/dist/command-helpers/json.d.ts +4 -0
  7. package/dist/command-helpers/json.js +37 -0
  8. package/dist/commands/account/get.d.ts +14 -0
  9. package/dist/commands/account/get.js +14 -0
  10. package/dist/commands/account/update.d.ts +23 -0
  11. package/dist/commands/account/update.js +37 -0
  12. package/dist/commands/auth/login.js +0 -2
  13. package/dist/commands/auth/logout.js +0 -2
  14. package/dist/commands/auth/tokens/create.js +0 -2
  15. package/dist/commands/auth/tokens/list.js +0 -2
  16. package/dist/commands/auth/tokens/revoke.js +0 -2
  17. package/dist/commands/bots/env-vars/create.d.ts +1 -1
  18. package/dist/commands/bots/env-vars/create.js +18 -11
  19. package/dist/commands/bots/filters/create.d.ts +5 -5
  20. package/dist/commands/bots/filters/create.js +33 -18
  21. package/dist/commands/broadcast/delete.d.ts +20 -0
  22. package/dist/commands/broadcast/delete.js +18 -0
  23. package/dist/commands/broadcast/get.d.ts +20 -0
  24. package/dist/commands/broadcast/get.js +18 -0
  25. package/dist/commands/broadcast/list.d.ts +21 -0
  26. package/dist/commands/broadcast/list.js +39 -0
  27. package/dist/commands/broadcast/send.d.ts +20 -0
  28. package/dist/commands/broadcast/send.js +18 -0
  29. package/dist/commands/broadcast/status.d.ts +20 -0
  30. package/dist/commands/broadcast/status.js +31 -0
  31. package/dist/commands/broadcast/update.d.ts +30 -0
  32. package/dist/commands/broadcast/update.js +47 -0
  33. package/dist/commands/chats/get.d.ts +20 -0
  34. package/dist/commands/chats/get.js +18 -0
  35. package/dist/commands/chats/list.d.ts +22 -0
  36. package/dist/commands/chats/list.js +40 -0
  37. package/dist/commands/config/get.js +0 -2
  38. package/dist/commands/config/set.js +0 -2
  39. package/dist/commands/config/unset.js +0 -2
  40. package/dist/commands/contacts/custom-fields/delete-value.d.ts +24 -0
  41. package/dist/commands/contacts/custom-fields/delete-value.js +32 -0
  42. package/dist/commands/contacts/custom-fields/list.d.ts +20 -0
  43. package/dist/commands/contacts/custom-fields/list.js +24 -0
  44. package/dist/commands/contacts/custom-fields/set-value.d.ts +25 -0
  45. package/dist/commands/contacts/custom-fields/set-value.js +32 -0
  46. package/dist/commands/contacts/custom-fields/set.d.ts +23 -0
  47. package/dist/commands/contacts/custom-fields/set.js +42 -0
  48. package/dist/commands/contacts/tags/add.d.ts +24 -0
  49. package/dist/commands/contacts/tags/add.js +30 -0
  50. package/dist/commands/contacts/tags/list.d.ts +20 -0
  51. package/dist/commands/contacts/tags/list.js +24 -0
  52. package/dist/commands/contacts/tags/remove.d.ts +24 -0
  53. package/dist/commands/contacts/tags/remove.js +32 -0
  54. package/dist/commands/custom-fields/bulk-delete.d.ts +15 -0
  55. package/dist/commands/custom-fields/bulk-delete.js +24 -0
  56. package/dist/commands/custom-fields/create.d.ts +21 -0
  57. package/dist/commands/custom-fields/create.js +47 -0
  58. package/dist/commands/custom-fields/delete.d.ts +20 -0
  59. package/dist/commands/custom-fields/delete.js +18 -0
  60. package/dist/commands/custom-fields/get.d.ts +20 -0
  61. package/dist/commands/custom-fields/get.js +18 -0
  62. package/dist/commands/custom-fields/list.d.ts +14 -0
  63. package/dist/commands/custom-fields/list.js +14 -0
  64. package/dist/commands/custom-fields/reorder.d.ts +15 -0
  65. package/dist/commands/custom-fields/reorder.js +22 -0
  66. package/dist/commands/custom-fields/update.d.ts +27 -0
  67. package/dist/commands/custom-fields/update.js +52 -0
  68. package/dist/commands/doctor.d.ts +14 -0
  69. package/dist/commands/doctor.js +56 -0
  70. package/dist/commands/features.d.ts +14 -0
  71. package/dist/commands/features.js +19 -0
  72. package/dist/commands/messages/bulk-spam.d.ts +16 -0
  73. package/dist/commands/messages/bulk-spam.js +30 -0
  74. package/dist/commands/messages/create-ticket.d.ts +23 -0
  75. package/dist/commands/messages/create-ticket.js +28 -0
  76. package/dist/commands/messages/delete.d.ts +20 -0
  77. package/dist/commands/messages/delete.js +18 -0
  78. package/dist/commands/messages/get.d.ts +20 -0
  79. package/dist/commands/messages/get.js +18 -0
  80. package/dist/commands/messages/list.d.ts +22 -0
  81. package/dist/commands/messages/list.js +35 -0
  82. package/dist/commands/messages/reactions.d.ts +20 -0
  83. package/dist/commands/messages/reactions.js +18 -0
  84. package/dist/commands/messages/spam.d.ts +21 -0
  85. package/dist/commands/messages/spam.js +27 -0
  86. package/dist/commands/messages/unread-count.d.ts +14 -0
  87. package/dist/commands/messages/unread-count.js +14 -0
  88. package/dist/commands/messages/update.d.ts +27 -0
  89. package/dist/commands/messages/update.js +43 -0
  90. package/dist/commands/permissions.d.ts +14 -0
  91. package/dist/commands/permissions.js +14 -0
  92. package/dist/commands/segments/create.d.ts +21 -0
  93. package/dist/commands/segments/create.js +33 -0
  94. package/dist/commands/segments/delete.d.ts +20 -0
  95. package/dist/commands/segments/delete.js +18 -0
  96. package/dist/commands/segments/get.d.ts +20 -0
  97. package/dist/commands/segments/get.js +18 -0
  98. package/dist/commands/segments/list.d.ts +20 -0
  99. package/dist/commands/segments/list.js +34 -0
  100. package/dist/commands/segments/tags/add.d.ts +24 -0
  101. package/dist/commands/segments/tags/add.js +21 -0
  102. package/dist/commands/segments/tags/list.d.ts +20 -0
  103. package/dist/commands/segments/tags/list.js +18 -0
  104. package/dist/commands/segments/tags/remove.d.ts +24 -0
  105. package/dist/commands/segments/tags/remove.js +23 -0
  106. package/dist/commands/segments/update.d.ts +26 -0
  107. package/dist/commands/segments/update.js +34 -0
  108. package/dist/commands/tags/create.d.ts +19 -0
  109. package/dist/commands/tags/create.js +29 -0
  110. package/dist/commands/tags/delete.d.ts +20 -0
  111. package/dist/commands/tags/delete.js +16 -0
  112. package/dist/commands/tags/export.d.ts +15 -0
  113. package/dist/commands/tags/export.js +27 -0
  114. package/dist/commands/tags/get.d.ts +20 -0
  115. package/dist/commands/tags/get.js +16 -0
  116. package/dist/commands/tags/import.d.ts +16 -0
  117. package/dist/commands/tags/import.js +29 -0
  118. package/dist/commands/tags/list.d.ts +18 -0
  119. package/dist/commands/tags/list.js +27 -0
  120. package/dist/commands/tags/recipients/add.d.ts +21 -0
  121. package/dist/commands/tags/recipients/add.js +27 -0
  122. package/dist/commands/tags/recipients/remove.d.ts +21 -0
  123. package/dist/commands/tags/recipients/remove.js +27 -0
  124. package/dist/commands/tags/update.d.ts +25 -0
  125. package/dist/commands/tags/update.js +30 -0
  126. package/dist/commands/tags/validate-import.d.ts +15 -0
  127. package/dist/commands/tags/validate-import.js +21 -0
  128. package/dist/commands/team/create.d.ts +23 -0
  129. package/dist/commands/team/create.js +37 -0
  130. package/dist/commands/team/delete.d.ts +20 -0
  131. package/dist/commands/team/delete.js +18 -0
  132. package/dist/commands/team/list.d.ts +14 -0
  133. package/dist/commands/team/list.js +14 -0
  134. package/dist/commands/templates/create.d.ts +24 -0
  135. package/dist/commands/templates/create.js +63 -0
  136. package/dist/commands/templates/delete.d.ts +20 -0
  137. package/dist/commands/templates/delete.js +18 -0
  138. package/dist/commands/templates/export.d.ts +16 -0
  139. package/dist/commands/templates/export.js +29 -0
  140. package/dist/commands/templates/import.d.ts +17 -0
  141. package/dist/commands/templates/import.js +34 -0
  142. package/dist/commands/templates/update.d.ts +30 -0
  143. package/dist/commands/templates/update.js +61 -0
  144. package/dist/commands/templates/validate-import.d.ts +16 -0
  145. package/dist/commands/templates/validate-import.js +28 -0
  146. package/dist/commands/tickets/reply.d.ts +22 -0
  147. package/dist/commands/tickets/reply.js +36 -0
  148. package/dist/commands/tickets/update.d.ts +2 -0
  149. package/dist/commands/tickets/update.js +16 -0
  150. package/dist/commands/webhooks/events.d.ts +14 -0
  151. package/dist/commands/webhooks/events.js +18 -0
  152. package/dist/commands/webhooks/logs.d.ts +14 -0
  153. package/dist/commands/webhooks/logs.js +11 -0
  154. package/dist/commands/webhooks/test.d.ts +17 -0
  155. package/dist/commands/webhooks/test.js +27 -0
  156. package/dist/commands/whoami.js +0 -2
  157. package/dist/crud/csv.js +2 -1
  158. package/dist/errors/api-error.d.ts +1 -1
  159. package/dist/errors/exit-codes.js +1 -0
  160. package/npm-shrinkwrap.json +2 -2
  161. package/oclif.manifest.json +8672 -1602
  162. package/package.json +38 -5
package/README.md CHANGED
@@ -30,5 +30,34 @@ npm run --workspace services/cli build
30
30
  ```
31
31
  npm run --workspace services/cli type-check
32
32
  npm run --workspace services/cli check:all
33
+ npm run --workspace services/cli check:command-coverage
33
34
  npm run --workspace services/cli test:unit
34
35
  ```
36
+
37
+ ## Live Smoke Test
38
+
39
+ The live smoke test runs the built `ikm` binary against a real instantKOM API and validates the token lifecycle plus core read/write commands.
40
+
41
+ ```
42
+ cd services/cli
43
+ IKM_API_URL=http://localhost:3002 \
44
+ IKM_API_KEY=<admin-or-full-api-key> \
45
+ IKM_CLI_TEST_CHANNEL_ID=505 \
46
+ IKM_CLI_TEST_CONTACT_CHANNEL_ID=766 \
47
+ npm run test:live
48
+ ```
49
+
50
+ `IKM_CLI_TEST_CHANNEL_ID` is used for read checks. `IKM_CLI_TEST_CONTACT_CHANNEL_ID` is used for creating and deleting one temporary contact.
51
+
52
+ ## Live Security Test
53
+
54
+ The cross-account security test requires two isolated test accounts. It creates a contact with account B, verifies account A cannot read or delete it, and then removes it with account B.
55
+
56
+ ```
57
+ cd services/cli
58
+ IKM_API_URL=http://localhost:3002 \
59
+ IKM_CLI_SECURITY_ACCOUNT_A_KEY=<account-a-api-key> \
60
+ IKM_CLI_SECURITY_ACCOUNT_B_KEY=<account-b-api-key> \
61
+ IKM_CLI_SECURITY_ACCOUNT_B_CHANNEL_ID=<account-b-channel-id> \
62
+ npm run test:security:live
63
+ ```
package/bin/run.js CHANGED
File without changes
@@ -1,3 +1,4 @@
1
+ import { ApiError } from './errors/api-error.js';
1
2
  export class ApiClient {
2
3
  baseUrl;
3
4
  apiKey;
@@ -22,10 +23,10 @@ export class ApiClient {
22
23
  });
23
24
  const data = await this.parseResponse(response);
24
25
  if (!response.ok) {
25
- throw new Error(`JWT login failed (${response.status}): ${data?.message || response.statusText}`);
26
+ throw new ApiError(httpStatusToKind(response.status), `JWT login failed (${response.status}): ${errorMessage(data, response.statusText)}`, response.status);
26
27
  }
27
28
  if (data?.requiresTwoFactor) {
28
- throw new Error('JWT login requires 2FA - use a service account without 2FA enabled');
29
+ throw new ApiError('unauthorized', 'JWT login requires 2FA - use a service account without 2FA enabled');
29
30
  }
30
31
  this.jwtAccessToken = data.accessToken;
31
32
  this.jwtRefreshToken = data.refreshToken;
@@ -78,7 +79,7 @@ export class ApiClient {
78
79
  });
79
80
  const data = await this.parseResponse(response);
80
81
  if (!response.ok) {
81
- throw new Error(`HTTP ${response.status}: ${data?.message || response.statusText}`);
82
+ throw new ApiError(httpStatusToKind(response.status), `HTTP ${response.status}: ${errorMessage(data, response.statusText)}`, response.status);
82
83
  }
83
84
  return data;
84
85
  }
@@ -100,7 +101,7 @@ export class ApiClient {
100
101
  });
101
102
  if (!response.ok) {
102
103
  const data = await this.parseResponse(response);
103
- throw new Error(`HTTP ${response.status}: ${data?.message || response.statusText}`);
104
+ throw new ApiError(httpStatusToKind(response.status), `HTTP ${response.status}: ${errorMessage(data, response.statusText)}`, response.status);
104
105
  }
105
106
  return {
106
107
  buffer: Buffer.from(await response.arrayBuffer()),
@@ -143,7 +144,7 @@ export class ApiClient {
143
144
  });
144
145
  const data = await this.parseResponse(response);
145
146
  if (!response.ok) {
146
- throw new Error(`HTTP ${response.status}: ${data?.message || response.statusText}`);
147
+ throw new ApiError(httpStatusToKind(response.status), `HTTP ${response.status}: ${errorMessage(data, response.statusText)}`, response.status);
147
148
  }
148
149
  return data;
149
150
  }
@@ -197,3 +198,25 @@ async function parseResponseBody(response) {
197
198
  return text;
198
199
  }
199
200
  }
201
+ function httpStatusToKind(status) {
202
+ if (status === 401)
203
+ return 'unauthorized';
204
+ if (status === 403)
205
+ return 'forbidden';
206
+ if (status === 429)
207
+ return 'rate_limited';
208
+ if (status >= 500)
209
+ return 'server_error';
210
+ return 'user_error';
211
+ }
212
+ function errorMessage(data, fallback) {
213
+ if (typeof data === 'string' && data.trim())
214
+ return data;
215
+ if (typeof data?.message === 'string')
216
+ return data.message;
217
+ if (Array.isArray(data?.message))
218
+ return data.message.join('; ');
219
+ if (typeof data?.error === 'string')
220
+ return data.error;
221
+ return fallback;
222
+ }
@@ -0,0 +1,10 @@
1
+ export declare function writeDownloadResult(filePath: string, result: {
2
+ buffer: Buffer;
3
+ contentType: string | null;
4
+ contentDisposition: string | null;
5
+ }): Promise<{
6
+ bytes: number;
7
+ contentType: string | null;
8
+ contentDisposition: string | null;
9
+ file: string;
10
+ }>;
@@ -0,0 +1,10 @@
1
+ import { writeFile } from 'node:fs/promises';
2
+ export async function writeDownloadResult(filePath, result) {
3
+ await writeFile(filePath, result.buffer);
4
+ return {
5
+ bytes: result.buffer.byteLength,
6
+ contentType: result.contentType,
7
+ contentDisposition: result.contentDisposition,
8
+ file: filePath,
9
+ };
10
+ }
@@ -0,0 +1,4 @@
1
+ export declare function parseJsonValue<T = unknown>(value: string, label: string): T;
2
+ export declare function readJsonFile<T = unknown>(filePath: string, label: string): Promise<T>;
3
+ export declare function parseNumberList(value: string | undefined, label: string): number[];
4
+ export declare function parseStringList(value: string | undefined): string[] | undefined;
@@ -0,0 +1,37 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { ApiError } from '../errors/api-error.js';
3
+ export function parseJsonValue(value, label) {
4
+ try {
5
+ return JSON.parse(value);
6
+ }
7
+ catch (error) {
8
+ const message = error instanceof Error ? error.message : String(error);
9
+ throw new ApiError('user_error', `Invalid ${label} JSON: ${message}`);
10
+ }
11
+ }
12
+ export async function readJsonFile(filePath, label) {
13
+ const content = await readFile(filePath, 'utf8');
14
+ return parseJsonValue(content, label);
15
+ }
16
+ export function parseNumberList(value, label) {
17
+ if (!value) {
18
+ return [];
19
+ }
20
+ const numbers = value
21
+ .split(',')
22
+ .map((item) => Number(item.trim()))
23
+ .filter((item) => Number.isFinite(item));
24
+ if (numbers.length === 0) {
25
+ throw new ApiError('user_error', `${label} must contain at least one numeric ID`);
26
+ }
27
+ return numbers;
28
+ }
29
+ export function parseStringList(value) {
30
+ if (!value) {
31
+ return undefined;
32
+ }
33
+ return value
34
+ .split(',')
35
+ .map((item) => item.trim())
36
+ .filter(Boolean);
37
+ }
@@ -0,0 +1,14 @@
1
+ import { BaseCommand } from '../../base-command.js';
2
+ export default class AccountGet extends BaseCommand {
3
+ static description: string;
4
+ static flags: {
5
+ 'api-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
6
+ format: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
7
+ json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
+ quiet: import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
+ 'no-color': import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
+ profile: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
11
+ 'api-url': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ };
13
+ run(): Promise<void>;
14
+ }
@@ -0,0 +1,14 @@
1
+ import { BaseCommand } from '../../base-command.js';
2
+ import { createCliApiClient } from '../../send/api.js';
3
+ export default class AccountGet extends BaseCommand {
4
+ static description = 'Get account information';
5
+ static flags = { ...BaseCommand.baseFlags };
6
+ async run() {
7
+ const { flags } = await this.parse(AccountGet);
8
+ this.flags = flags;
9
+ const client = await createCliApiClient(flags);
10
+ const response = await client.get('/v1/account');
11
+ if (!flags.quiet)
12
+ this.log(this.toFormatted(response));
13
+ }
14
+ }
@@ -0,0 +1,23 @@
1
+ import { BaseCommand } from '../../base-command.js';
2
+ export default class AccountUpdate extends BaseCommand {
3
+ static description: string;
4
+ static flags: {
5
+ email: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
6
+ 'first-name': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
+ 'last-name': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
+ company: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
+ phone: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ city: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ country: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ street: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
+ data: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
+ 'api-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
+ format: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
16
+ json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
17
+ quiet: import("@oclif/core/interfaces").BooleanFlag<boolean>;
18
+ 'no-color': import("@oclif/core/interfaces").BooleanFlag<boolean>;
19
+ profile: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
20
+ 'api-url': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
21
+ };
22
+ run(): Promise<void>;
23
+ }
@@ -0,0 +1,37 @@
1
+ import { Flags } from '@oclif/core';
2
+ import { BaseCommand } from '../../base-command.js';
3
+ import { mergeData, parseDataFlag } from '../../crud/data.js';
4
+ import { createCliApiClient } from '../../send/api.js';
5
+ export default class AccountUpdate extends BaseCommand {
6
+ static description = 'Update account information';
7
+ static flags = {
8
+ ...BaseCommand.baseFlags,
9
+ email: Flags.string({ description: 'Email address' }),
10
+ 'first-name': Flags.string({ description: 'First name' }),
11
+ 'last-name': Flags.string({ description: 'Last name' }),
12
+ company: Flags.string({ description: 'Company name' }),
13
+ phone: Flags.string({ description: 'Phone number' }),
14
+ city: Flags.string({ description: 'City' }),
15
+ country: Flags.string({ description: 'Country' }),
16
+ street: Flags.string({ description: 'Street address' }),
17
+ data: Flags.string({ description: 'Additional JSON object payload' }),
18
+ };
19
+ async run() {
20
+ const { flags } = await this.parse(AccountUpdate);
21
+ this.flags = flags;
22
+ const payload = mergeData(parseDataFlag(flags.data), {
23
+ email: flags.email,
24
+ firstName: flags['first-name'],
25
+ lastName: flags['last-name'],
26
+ company: flags.company,
27
+ phone: flags.phone,
28
+ city: flags.city,
29
+ country: flags.country,
30
+ street: flags.street,
31
+ });
32
+ const client = await createCliApiClient(flags);
33
+ const response = await client.put('/v1/account', payload);
34
+ if (!flags.quiet)
35
+ this.log(this.toFormatted(response));
36
+ }
37
+ }
@@ -1,6 +1,5 @@
1
1
  import { Flags } from '@oclif/core';
2
2
  import { BaseCommand } from '../../base-command.js';
3
- import { EXIT_CODES } from '../../errors/exit-codes.js';
4
3
  import * as tokenStore from '../../auth/token-store.js';
5
4
  import * as deviceFlowClient from '../../auth/device-flow-client.js';
6
5
  import { ApiClient } from '../../api-client.js';
@@ -32,6 +31,5 @@ export default class Login extends BaseCommand {
32
31
  });
33
32
  const me = await client.get('/v1/users/me');
34
33
  this.log(`Logged in as ${me.email}. Scope: ${result.scope}.`);
35
- this.exit(EXIT_CODES.OK);
36
34
  }
37
35
  }
@@ -1,5 +1,4 @@
1
1
  import { BaseCommand } from '../../base-command.js';
2
- import { EXIT_CODES } from '../../errors/exit-codes.js';
3
2
  import * as tokenStore from '../../auth/token-store.js';
4
3
  export default class Logout extends BaseCommand {
5
4
  static description = 'Remove the stored API token for the current profile';
@@ -12,6 +11,5 @@ export default class Logout extends BaseCommand {
12
11
  const profile = flags.profile ?? 'default';
13
12
  await tokenStore.del(profile);
14
13
  this.log(`Logged out of profile ${profile}.`);
15
- this.exit(EXIT_CODES.OK);
16
14
  }
17
15
  }
@@ -1,6 +1,5 @@
1
1
  import { Flags } from '@oclif/core';
2
2
  import { BaseCommand } from '../../../base-command.js';
3
- import { EXIT_CODES } from '../../../errors/exit-codes.js';
4
3
  import { ApiError } from '../../../errors/api-error.js';
5
4
  import { resolveToken } from '../../../auth/token-resolver.js';
6
5
  import { ApiClient } from '../../../api-client.js';
@@ -57,6 +56,5 @@ export default class TokensCreate extends BaseCommand {
57
56
  created_at: created.createdAt,
58
57
  expires_at: created.expiresAt,
59
58
  }));
60
- this.exit(EXIT_CODES.OK);
61
59
  }
62
60
  }
@@ -1,5 +1,4 @@
1
1
  import { BaseCommand } from '../../../base-command.js';
2
- import { EXIT_CODES } from '../../../errors/exit-codes.js';
3
2
  import { ApiError } from '../../../errors/api-error.js';
4
3
  import { resolveToken } from '../../../auth/token-resolver.js';
5
4
  import { ApiClient } from '../../../api-client.js';
@@ -36,6 +35,5 @@ export default class TokensList extends BaseCommand {
36
35
  expires: item.expires_at ?? item.expiresAt ?? '',
37
36
  }));
38
37
  this.log(this.toFormatted(items));
39
- this.exit(EXIT_CODES.OK);
40
38
  }
41
39
  }
@@ -1,6 +1,5 @@
1
1
  import { Args } from '@oclif/core';
2
2
  import { BaseCommand } from '../../../base-command.js';
3
- import { EXIT_CODES } from '../../../errors/exit-codes.js';
4
3
  import { ApiError } from '../../../errors/api-error.js';
5
4
  import { resolveToken } from '../../../auth/token-resolver.js';
6
5
  import { ApiClient } from '../../../api-client.js';
@@ -36,6 +35,5 @@ export default class TokensRevoke extends BaseCommand {
36
35
  });
37
36
  await client.delete(`/v1/api-keys/${args.id}`);
38
37
  this.log(`Token ${args.id} revoked.`);
39
- this.exit(EXIT_CODES.OK);
40
38
  }
41
39
  }
@@ -1,4 +1,4 @@
1
- import { BaseCommand } from '../../../base-command.js';
1
+ import { BaseCommand } from "../../../base-command.js";
2
2
  export default class BotsEnvVarsCreate extends BaseCommand {
3
3
  static description: string;
4
4
  static flags: {
@@ -1,27 +1,34 @@
1
- import { Flags } from '@oclif/core';
2
- import { BaseCommand } from '../../../base-command.js';
3
- import { mergeData, parseDataFlag } from '../../../crud/data.js';
4
- import { createResource } from '../../../crud/request.js';
5
- import { createCliApiClient } from '../../../send/api.js';
1
+ import { Flags } from "@oclif/core";
2
+ import { BaseCommand } from "../../../base-command.js";
3
+ import { mergeData, parseDataFlag } from "../../../crud/data.js";
4
+ import { ApiError } from "../../../errors/api-error.js";
5
+ import { createResource } from "../../../crud/request.js";
6
+ import { createCliApiClient } from "../../../send/api.js";
6
7
  export default class BotsEnvVarsCreate extends BaseCommand {
7
- static description = 'Create a bot environment variable';
8
+ static description = "Create a bot environment variable";
8
9
  static flags = {
9
10
  ...BaseCommand.baseFlags,
10
- key: Flags.string({ description: 'Variable key', required: true }),
11
- description: Flags.string({ description: 'Variable description' }),
12
- color: Flags.string({ description: 'Variable color as HEX' }),
13
- data: Flags.string({ description: 'Additional JSON object payload' }),
11
+ key: Flags.string({
12
+ description: "Variable key (max 16 chars, letters, digits, underscore)",
13
+ required: true,
14
+ }),
15
+ description: Flags.string({ description: "Variable description" }),
16
+ color: Flags.string({ description: "Variable color as HEX" }),
17
+ data: Flags.string({ description: "Additional JSON object payload" }),
14
18
  };
15
19
  async run() {
16
20
  const { flags } = await this.parse(BotsEnvVarsCreate);
17
21
  this.flags = flags;
22
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(flags.key) || flags.key.length > 16) {
23
+ throw new ApiError("user_error", "Variable key must start with a letter or underscore and contain only letters, digits, and underscores. Maximum length is 16 characters.");
24
+ }
18
25
  const payload = mergeData(parseDataFlag(flags.data), {
19
26
  key: flags.key,
20
27
  description: flags.description,
21
28
  color: flags.color,
22
29
  });
23
30
  const client = await createCliApiClient(flags);
24
- const envVar = await createResource(client, '/v1/bot-env-vars', payload);
31
+ const envVar = await createResource(client, "/v1/bot-env-vars", payload);
25
32
  if (!flags.quiet)
26
33
  this.log(this.toFormatted(envVar));
27
34
  }
@@ -1,4 +1,4 @@
1
- import { BaseCommand } from '../../../base-command.js';
1
+ import { BaseCommand } from "../../../base-command.js";
2
2
  export default class BotsFiltersCreate extends BaseCommand {
3
3
  static description: string;
4
4
  static args: {
@@ -10,10 +10,10 @@ export default class BotsFiltersCreate extends BaseCommand {
10
10
  static flags: {
11
11
  name: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
12
12
  operator: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
13
- 'filter-object': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
- 'filter-attribute': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
- 'filter-comparator': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
16
- 'filter-value': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
13
+ "filter-object": import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
14
+ "filter-attribute": import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
15
+ "filter-comparator": import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
16
+ "filter-value": import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
17
17
  status: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
18
18
  data: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
19
19
  'api-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
@@ -1,22 +1,37 @@
1
- import { Args, Flags } from '@oclif/core';
2
- import { BaseCommand } from '../../../base-command.js';
3
- import { mergeData, parseDataFlag } from '../../../crud/data.js';
4
- import { createCliApiClient } from '../../../send/api.js';
1
+ import { Args, Flags } from "@oclif/core";
2
+ import { BaseCommand } from "../../../base-command.js";
3
+ import { mergeData, parseDataFlag } from "../../../crud/data.js";
4
+ import { createCliApiClient } from "../../../send/api.js";
5
5
  export default class BotsFiltersCreate extends BaseCommand {
6
- static description = 'Create a bot filter';
6
+ static description = "Create a bot filter";
7
7
  static args = {
8
- botId: Args.integer({ description: 'Bot ID', required: true }),
8
+ botId: Args.integer({ description: "Bot ID", required: true }),
9
9
  };
10
10
  static flags = {
11
11
  ...BaseCommand.baseFlags,
12
- name: Flags.string({ description: 'Filter name', required: true }),
13
- operator: Flags.string({ description: 'Logical operator', default: 'and' }),
14
- 'filter-object': Flags.string({ description: 'Filter object type' }),
15
- 'filter-attribute': Flags.string({ description: 'Filter attribute' }),
16
- 'filter-comparator': Flags.string({ description: 'Filter comparator' }),
17
- 'filter-value': Flags.string({ description: 'Filter value', required: true }),
18
- status: Flags.integer({ description: 'Status: 0 inactive, 1 active', options: ['0', '1'] }),
19
- data: Flags.string({ description: 'Additional JSON object payload' }),
12
+ name: Flags.string({ description: "Filter name", required: true }),
13
+ operator: Flags.string({ description: "Logical operator", default: "and" }),
14
+ "filter-object": Flags.string({
15
+ description: "Filter object type",
16
+ default: "msg",
17
+ }),
18
+ "filter-attribute": Flags.string({
19
+ description: "Filter attribute",
20
+ default: "text",
21
+ }),
22
+ "filter-comparator": Flags.string({
23
+ description: "Filter comparator",
24
+ default: "contains",
25
+ }),
26
+ "filter-value": Flags.string({
27
+ description: "Filter value",
28
+ required: true,
29
+ }),
30
+ status: Flags.integer({
31
+ description: "Status: 0 inactive, 1 active",
32
+ options: ["0", "1"],
33
+ }),
34
+ data: Flags.string({ description: "Additional JSON object payload" }),
20
35
  };
21
36
  async run() {
22
37
  const { args, flags } = await this.parse(BotsFiltersCreate);
@@ -24,10 +39,10 @@ export default class BotsFiltersCreate extends BaseCommand {
24
39
  const payload = mergeData(parseDataFlag(flags.data), {
25
40
  name: flags.name,
26
41
  operator: flags.operator,
27
- objectType: flags['filter-object'],
28
- attribute: flags['filter-attribute'],
29
- comparator: flags['filter-comparator'],
30
- value: flags['filter-value'],
42
+ objectType: flags["filter-object"],
43
+ attribute: flags["filter-attribute"],
44
+ comparator: flags["filter-comparator"],
45
+ value: flags["filter-value"],
31
46
  status: flags.status,
32
47
  });
33
48
  const client = await createCliApiClient(flags);
@@ -0,0 +1,20 @@
1
+ import { BaseCommand } from '../../base-command.js';
2
+ export default class BroadcastDelete extends BaseCommand {
3
+ static description: string;
4
+ static args: {
5
+ id: import("@oclif/core/interfaces").Arg<number, {
6
+ max?: number;
7
+ min?: number;
8
+ }>;
9
+ };
10
+ static flags: {
11
+ 'api-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ format: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
13
+ json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
14
+ quiet: import("@oclif/core/interfaces").BooleanFlag<boolean>;
15
+ 'no-color': import("@oclif/core/interfaces").BooleanFlag<boolean>;
16
+ profile: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
17
+ 'api-url': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
18
+ };
19
+ run(): Promise<void>;
20
+ }
@@ -0,0 +1,18 @@
1
+ import { Args } from '@oclif/core';
2
+ import { BaseCommand } from '../../base-command.js';
3
+ import { createCliApiClient } from '../../send/api.js';
4
+ export default class BroadcastDelete extends BaseCommand {
5
+ static description = 'Delete broadcast';
6
+ static args = {
7
+ id: Args.integer({ description: 'broadcast ID', required: true }),
8
+ };
9
+ static flags = { ...BaseCommand.baseFlags };
10
+ async run() {
11
+ const { args, flags } = await this.parse(BroadcastDelete);
12
+ this.flags = flags;
13
+ const client = await createCliApiClient(flags);
14
+ await client.delete(`/v1/broadcasts/${args.id}`);
15
+ if (!flags.quiet)
16
+ this.log(this.toFormatted({ deleted: true, id: args.id }));
17
+ }
18
+ }
@@ -0,0 +1,20 @@
1
+ import { BaseCommand } from '../../base-command.js';
2
+ export default class BroadcastGet extends BaseCommand {
3
+ static description: string;
4
+ static args: {
5
+ id: import("@oclif/core/interfaces").Arg<number, {
6
+ max?: number;
7
+ min?: number;
8
+ }>;
9
+ };
10
+ static flags: {
11
+ 'api-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ format: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
13
+ json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
14
+ quiet: import("@oclif/core/interfaces").BooleanFlag<boolean>;
15
+ 'no-color': import("@oclif/core/interfaces").BooleanFlag<boolean>;
16
+ profile: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
17
+ 'api-url': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
18
+ };
19
+ run(): Promise<void>;
20
+ }
@@ -0,0 +1,18 @@
1
+ import { Args } from '@oclif/core';
2
+ import { BaseCommand } from '../../base-command.js';
3
+ import { createCliApiClient } from '../../send/api.js';
4
+ export default class BroadcastGet extends BaseCommand {
5
+ static description = 'Get broadcast by ID';
6
+ static args = {
7
+ id: Args.integer({ description: 'broadcast ID', required: true }),
8
+ };
9
+ static flags = { ...BaseCommand.baseFlags };
10
+ async run() {
11
+ const { args, flags } = await this.parse(BroadcastGet);
12
+ this.flags = flags;
13
+ const client = await createCliApiClient(flags);
14
+ const response = await client.get(`/v1/broadcasts/${args.id}`);
15
+ if (!flags.quiet)
16
+ this.log(this.toFormatted(response));
17
+ }
18
+ }
@@ -0,0 +1,21 @@
1
+ import { BaseCommand } from '../../base-command.js';
2
+ export default class BroadcastList extends BaseCommand {
3
+ static description: string;
4
+ static flags: {
5
+ page: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
6
+ limit: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
7
+ channel: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
+ 'send-status': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
+ type: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ search: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ test: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ 'api-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
+ format: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
14
+ json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
15
+ quiet: import("@oclif/core/interfaces").BooleanFlag<boolean>;
16
+ 'no-color': import("@oclif/core/interfaces").BooleanFlag<boolean>;
17
+ profile: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
18
+ 'api-url': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
19
+ };
20
+ run(): Promise<void>;
21
+ }
@@ -0,0 +1,39 @@
1
+ import { Flags } from '@oclif/core';
2
+ import { BaseCommand } from '../../base-command.js';
3
+ import { cleanQuery } from '../../crud/data.js';
4
+ import { createCliApiClient } from '../../send/api.js';
5
+ export default class BroadcastList extends BaseCommand {
6
+ static description = 'List broadcasts';
7
+ static flags = {
8
+ ...BaseCommand.baseFlags,
9
+ page: Flags.integer({ description: 'Page number', default: 1 }),
10
+ limit: Flags.integer({ description: 'Items per page', default: 20 }),
11
+ channel: Flags.integer({ description: 'Filter by channel ID' }),
12
+ 'send-status': Flags.string({ description: 'Filter by send status' }),
13
+ type: Flags.string({
14
+ description: 'Broadcast type',
15
+ options: ['text', 'image', 'video', 'audio', 'document'],
16
+ }),
17
+ search: Flags.string({ description: 'Search broadcasts' }),
18
+ test: Flags.integer({
19
+ description: 'Filter test flag: 0 or 1',
20
+ options: ['0', '1'],
21
+ }),
22
+ };
23
+ async run() {
24
+ const { flags } = await this.parse(BroadcastList);
25
+ this.flags = flags;
26
+ const client = await createCliApiClient(flags);
27
+ const response = await client.get('/v1/broadcasts', cleanQuery({
28
+ page: flags.page,
29
+ limit: flags.limit,
30
+ channelId: flags.channel,
31
+ sendStatus: flags['send-status'],
32
+ broadcastType: flags.type,
33
+ search: flags.search,
34
+ isTest: flags.test,
35
+ }));
36
+ if (!flags.quiet)
37
+ this.log(this.toFormatted(response));
38
+ }
39
+ }