@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.
Files changed (227) hide show
  1. package/README.md +34 -0
  2. package/bin/run.js +3 -0
  3. package/dist/api-client.d.ts +55 -0
  4. package/dist/api-client.js +199 -0
  5. package/dist/auth/device-flow-client.d.ts +22 -0
  6. package/dist/auth/device-flow-client.js +70 -0
  7. package/dist/auth/token-resolver.d.ts +31 -0
  8. package/dist/auth/token-resolver.js +48 -0
  9. package/dist/auth/token-store.d.ts +13 -0
  10. package/dist/auth/token-store.js +39 -0
  11. package/dist/base-command.d.ts +18 -0
  12. package/dist/base-command.js +84 -0
  13. package/dist/commands/ai/reply.d.ts +18 -0
  14. package/dist/commands/ai/reply.js +46 -0
  15. package/dist/commands/auth/login.d.ts +15 -0
  16. package/dist/commands/auth/login.js +37 -0
  17. package/dist/commands/auth/logout.d.ts +14 -0
  18. package/dist/commands/auth/logout.js +17 -0
  19. package/dist/commands/auth/tokens/create.d.ts +17 -0
  20. package/dist/commands/auth/tokens/create.js +62 -0
  21. package/dist/commands/auth/tokens/list.d.ts +14 -0
  22. package/dist/commands/auth/tokens/list.js +41 -0
  23. package/dist/commands/auth/tokens/revoke.d.ts +20 -0
  24. package/dist/commands/auth/tokens/revoke.js +41 -0
  25. package/dist/commands/autocomplete/script.d.ts +11 -0
  26. package/dist/commands/autocomplete/script.js +90 -0
  27. package/dist/commands/autocomplete.d.ts +13 -0
  28. package/dist/commands/autocomplete.js +95 -0
  29. package/dist/commands/bots/create.d.ts +22 -0
  30. package/dist/commands/bots/create.js +39 -0
  31. package/dist/commands/bots/delete.d.ts +20 -0
  32. package/dist/commands/bots/delete.js +19 -0
  33. package/dist/commands/bots/env-vars/bots.d.ts +20 -0
  34. package/dist/commands/bots/env-vars/bots.js +18 -0
  35. package/dist/commands/bots/env-vars/create.d.ts +18 -0
  36. package/dist/commands/bots/env-vars/create.js +28 -0
  37. package/dist/commands/bots/env-vars/delete.d.ts +20 -0
  38. package/dist/commands/bots/env-vars/delete.js +19 -0
  39. package/dist/commands/bots/env-vars/get.d.ts +20 -0
  40. package/dist/commands/bots/env-vars/get.js +19 -0
  41. package/dist/commands/bots/env-vars/list.d.ts +18 -0
  42. package/dist/commands/bots/env-vars/list.js +28 -0
  43. package/dist/commands/bots/env-vars/update.d.ts +23 -0
  44. package/dist/commands/bots/env-vars/update.js +29 -0
  45. package/dist/commands/bots/env-vars/values/delete.d.ts +20 -0
  46. package/dist/commands/bots/env-vars/values/delete.js +18 -0
  47. package/dist/commands/bots/env-vars/values/update.d.ts +23 -0
  48. package/dist/commands/bots/env-vars/values/update.js +28 -0
  49. package/dist/commands/bots/env-vars/values.d.ts +24 -0
  50. package/dist/commands/bots/env-vars/values.js +30 -0
  51. package/dist/commands/bots/filters/create.d.ts +28 -0
  52. package/dist/commands/bots/filters/create.js +38 -0
  53. package/dist/commands/bots/filters/delete.d.ts +24 -0
  54. package/dist/commands/bots/filters/delete.js +19 -0
  55. package/dist/commands/bots/filters/get.d.ts +24 -0
  56. package/dist/commands/bots/filters/get.js +19 -0
  57. package/dist/commands/bots/filters/list.d.ts +24 -0
  58. package/dist/commands/bots/filters/list.js +30 -0
  59. package/dist/commands/bots/filters/update.d.ts +32 -0
  60. package/dist/commands/bots/filters/update.js +39 -0
  61. package/dist/commands/bots/get.d.ts +20 -0
  62. package/dist/commands/bots/get.js +19 -0
  63. package/dist/commands/bots/list.d.ts +21 -0
  64. package/dist/commands/bots/list.js +34 -0
  65. package/dist/commands/bots/matches.d.ts +23 -0
  66. package/dist/commands/bots/matches.js +28 -0
  67. package/dist/commands/bots/tags/add.d.ts +24 -0
  68. package/dist/commands/bots/tags/add.js +19 -0
  69. package/dist/commands/bots/tags/list.d.ts +20 -0
  70. package/dist/commands/bots/tags/list.js +18 -0
  71. package/dist/commands/bots/tags/remove.d.ts +24 -0
  72. package/dist/commands/bots/tags/remove.js +19 -0
  73. package/dist/commands/bots/update.d.ts +27 -0
  74. package/dist/commands/bots/update.js +36 -0
  75. package/dist/commands/broadcast/create.d.ts +25 -0
  76. package/dist/commands/broadcast/create.js +117 -0
  77. package/dist/commands/channels/create.d.ts +19 -0
  78. package/dist/commands/channels/create.js +43 -0
  79. package/dist/commands/channels/get.d.ts +20 -0
  80. package/dist/commands/channels/get.js +25 -0
  81. package/dist/commands/channels/kpis.d.ts +21 -0
  82. package/dist/commands/channels/kpis.js +29 -0
  83. package/dist/commands/channels/list.d.ts +19 -0
  84. package/dist/commands/channels/list.js +43 -0
  85. package/dist/commands/channels/update.d.ts +24 -0
  86. package/dist/commands/channels/update.js +43 -0
  87. package/dist/commands/chats/reply.d.ts +21 -0
  88. package/dist/commands/chats/reply.js +28 -0
  89. package/dist/commands/config/get.d.ts +17 -0
  90. package/dist/commands/config/get.js +29 -0
  91. package/dist/commands/config/set.d.ts +18 -0
  92. package/dist/commands/config/set.js +30 -0
  93. package/dist/commands/config/unset.d.ts +17 -0
  94. package/dist/commands/config/unset.js +25 -0
  95. package/dist/commands/contacts/create.d.ts +18 -0
  96. package/dist/commands/contacts/create.js +39 -0
  97. package/dist/commands/contacts/delete.d.ts +20 -0
  98. package/dist/commands/contacts/delete.js +25 -0
  99. package/dist/commands/contacts/export.d.ts +19 -0
  100. package/dist/commands/contacts/export.js +50 -0
  101. package/dist/commands/contacts/get.d.ts +20 -0
  102. package/dist/commands/contacts/get.js +25 -0
  103. package/dist/commands/contacts/import.d.ts +16 -0
  104. package/dist/commands/contacts/import.js +62 -0
  105. package/dist/commands/contacts/list.d.ts +25 -0
  106. package/dist/commands/contacts/list.js +71 -0
  107. package/dist/commands/contacts/update.d.ts +23 -0
  108. package/dist/commands/contacts/update.js +39 -0
  109. package/dist/commands/exports/create.d.ts +23 -0
  110. package/dist/commands/exports/create.js +69 -0
  111. package/dist/commands/exports/delete.d.ts +20 -0
  112. package/dist/commands/exports/delete.js +25 -0
  113. package/dist/commands/exports/download.d.ts +21 -0
  114. package/dist/commands/exports/download.js +37 -0
  115. package/dist/commands/exports/get.d.ts +20 -0
  116. package/dist/commands/exports/get.js +25 -0
  117. package/dist/commands/exports/list.d.ts +18 -0
  118. package/dist/commands/exports/list.js +39 -0
  119. package/dist/commands/flow/edges/create.d.ts +25 -0
  120. package/dist/commands/flow/edges/create.js +32 -0
  121. package/dist/commands/flow/edges/delete.d.ts +24 -0
  122. package/dist/commands/flow/edges/delete.js +19 -0
  123. package/dist/commands/flow/edges/get.d.ts +24 -0
  124. package/dist/commands/flow/edges/get.js +19 -0
  125. package/dist/commands/flow/edges/list.d.ts +20 -0
  126. package/dist/commands/flow/edges/list.js +18 -0
  127. package/dist/commands/flow/edges/update.d.ts +29 -0
  128. package/dist/commands/flow/edges/update.js +33 -0
  129. package/dist/commands/flow/nodes/create.d.ts +25 -0
  130. package/dist/commands/flow/nodes/create.js +32 -0
  131. package/dist/commands/flow/nodes/delete.d.ts +24 -0
  132. package/dist/commands/flow/nodes/delete.js +19 -0
  133. package/dist/commands/flow/nodes/get.d.ts +24 -0
  134. package/dist/commands/flow/nodes/get.js +19 -0
  135. package/dist/commands/flow/nodes/list.d.ts +20 -0
  136. package/dist/commands/flow/nodes/list.js +18 -0
  137. package/dist/commands/flow/nodes/update.d.ts +29 -0
  138. package/dist/commands/flow/nodes/update.js +33 -0
  139. package/dist/commands/flows/create.d.ts +20 -0
  140. package/dist/commands/flows/create.js +32 -0
  141. package/dist/commands/flows/delete.d.ts +20 -0
  142. package/dist/commands/flows/delete.js +19 -0
  143. package/dist/commands/flows/get.d.ts +20 -0
  144. package/dist/commands/flows/get.js +19 -0
  145. package/dist/commands/flows/list.d.ts +19 -0
  146. package/dist/commands/flows/list.js +30 -0
  147. package/dist/commands/flows/update.d.ts +25 -0
  148. package/dist/commands/flows/update.js +33 -0
  149. package/dist/commands/send.d.ts +23 -0
  150. package/dist/commands/send.js +129 -0
  151. package/dist/commands/status.d.ts +23 -0
  152. package/dist/commands/status.js +81 -0
  153. package/dist/commands/tail.d.ts +15 -0
  154. package/dist/commands/tail.js +36 -0
  155. package/dist/commands/templates/get.d.ts +20 -0
  156. package/dist/commands/templates/get.js +24 -0
  157. package/dist/commands/templates/list.d.ts +18 -0
  158. package/dist/commands/templates/list.js +37 -0
  159. package/dist/commands/templates/render.d.ts +21 -0
  160. package/dist/commands/templates/render.js +30 -0
  161. package/dist/commands/ticket/messages/create.d.ts +22 -0
  162. package/dist/commands/ticket/messages/create.js +26 -0
  163. package/dist/commands/ticket/messages/delete.d.ts +24 -0
  164. package/dist/commands/ticket/messages/delete.js +20 -0
  165. package/dist/commands/ticket/messages/get.d.ts +24 -0
  166. package/dist/commands/ticket/messages/get.js +19 -0
  167. package/dist/commands/ticket/messages/list.d.ts +20 -0
  168. package/dist/commands/ticket/messages/list.js +18 -0
  169. package/dist/commands/tickets/create.d.ts +22 -0
  170. package/dist/commands/tickets/create.js +34 -0
  171. package/dist/commands/tickets/delete.d.ts +21 -0
  172. package/dist/commands/tickets/delete.js +21 -0
  173. package/dist/commands/tickets/get.d.ts +21 -0
  174. package/dist/commands/tickets/get.js +21 -0
  175. package/dist/commands/tickets/list.d.ts +17 -0
  176. package/dist/commands/tickets/list.js +24 -0
  177. package/dist/commands/tickets/update.d.ts +25 -0
  178. package/dist/commands/tickets/update.js +31 -0
  179. package/dist/commands/webhooks/add.d.ts +16 -0
  180. package/dist/commands/webhooks/add.js +38 -0
  181. package/dist/commands/webhooks/list.d.ts +14 -0
  182. package/dist/commands/webhooks/list.js +18 -0
  183. package/dist/commands/webhooks/remove.d.ts +17 -0
  184. package/dist/commands/webhooks/remove.js +24 -0
  185. package/dist/commands/whoami.d.ts +15 -0
  186. package/dist/commands/whoami.js +40 -0
  187. package/dist/config/config-file.d.ts +36 -0
  188. package/dist/config/config-file.js +111 -0
  189. package/dist/config/config-path.d.ts +8 -0
  190. package/dist/config/config-path.js +25 -0
  191. package/dist/crud/csv.d.ts +6 -0
  192. package/dist/crud/csv.js +89 -0
  193. package/dist/crud/data.d.ts +4 -0
  194. package/dist/crud/data.js +38 -0
  195. package/dist/crud/request.d.ts +14 -0
  196. package/dist/crud/request.js +26 -0
  197. package/dist/errors/api-error.d.ts +6 -0
  198. package/dist/errors/api-error.js +10 -0
  199. package/dist/errors/exit-codes.d.ts +12 -0
  200. package/dist/errors/exit-codes.js +24 -0
  201. package/dist/errors/redact-token.d.ts +10 -0
  202. package/dist/errors/redact-token.js +17 -0
  203. package/dist/index.d.ts +1 -0
  204. package/dist/index.js +1 -0
  205. package/dist/output/formatter.d.ts +15 -0
  206. package/dist/output/formatter.js +57 -0
  207. package/dist/output/text-format.d.ts +13 -0
  208. package/dist/output/text-format.js +67 -0
  209. package/dist/send/api.d.ts +3 -0
  210. package/dist/send/api.js +21 -0
  211. package/dist/send/fallback.d.ts +5 -0
  212. package/dist/send/fallback.js +38 -0
  213. package/dist/send/media.d.ts +2 -0
  214. package/dist/send/media.js +41 -0
  215. package/dist/send/payload.d.ts +23 -0
  216. package/dist/send/payload.js +22 -0
  217. package/dist/send/recipient-resolver.d.ts +13 -0
  218. package/dist/send/recipient-resolver.js +28 -0
  219. package/dist/send/schedule.d.ts +1 -0
  220. package/dist/send/schedule.js +19 -0
  221. package/dist/tail/sse.d.ts +24 -0
  222. package/dist/tail/sse.js +139 -0
  223. package/dist/templates/render-template.d.ts +14 -0
  224. package/dist/templates/render-template.js +46 -0
  225. package/npm-shrinkwrap.json +11444 -0
  226. package/oclif.manifest.json +9286 -0
  227. package/package.json +157 -0
@@ -0,0 +1,36 @@
1
+ /** A profile block is a flat record of non-secret config values. */
2
+ export type ProfileBlock = Record<string, string | number | boolean | undefined>;
3
+ /**
4
+ * Reads and writes the instantkom CLI config file.
5
+ *
6
+ * Top-level YAML keys are profile names per D-37.
7
+ * Secret keys are refused per D-38.
8
+ * File is created with mode 0o600 per D-36.
9
+ */
10
+ export declare class ConfigFile {
11
+ private readonly filePath;
12
+ constructor(filePath?: string);
13
+ /**
14
+ * Read a named profile block. Returns empty object if file or profile missing.
15
+ * Per D-37: missing profile = empty object, NOT an error.
16
+ */
17
+ read(profile?: string): ProfileBlock;
18
+ /**
19
+ * Write a key/value into the named profile block.
20
+ *
21
+ * Refuses secret-shaped keys per D-38.
22
+ * Creates the file with 0o600 mode if absent per D-36.
23
+ * Warns to stderr if existing file has wider mode.
24
+ */
25
+ write(profile: string, key: string, value: string | number | boolean): void;
26
+ /**
27
+ * Remove a key from the named profile block.
28
+ * No-op if file, profile, or key doesn't exist.
29
+ */
30
+ unset(profile: string, key: string): void;
31
+ private readAll;
32
+ private writeAll;
33
+ private ensureDir;
34
+ /** Warn if the existing config file has permissions wider than 0o600. */
35
+ private checkFileMode;
36
+ }
@@ -0,0 +1,111 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { parse, stringify } from 'yaml';
4
+ import { configPath } from './config-path.js';
5
+ import { ApiError } from '../errors/api-error.js';
6
+ /** Regex pattern for secret-shaped keys -- refused by write() per D-38. */
7
+ const SECRET_KEY_PATTERN = /token|secret|password|key/i;
8
+ /**
9
+ * Reads and writes the instantkom CLI config file.
10
+ *
11
+ * Top-level YAML keys are profile names per D-37.
12
+ * Secret keys are refused per D-38.
13
+ * File is created with mode 0o600 per D-36.
14
+ */
15
+ export class ConfigFile {
16
+ filePath;
17
+ constructor(filePath) {
18
+ this.filePath = filePath ?? configPath();
19
+ }
20
+ /**
21
+ * Read a named profile block. Returns empty object if file or profile missing.
22
+ * Per D-37: missing profile = empty object, NOT an error.
23
+ */
24
+ read(profile) {
25
+ const profileName = profile ?? 'default';
26
+ if (!fs.existsSync(this.filePath)) {
27
+ return {};
28
+ }
29
+ try {
30
+ const content = fs.readFileSync(this.filePath, 'utf-8');
31
+ const parsed = parse(content);
32
+ if (!parsed || typeof parsed !== 'object')
33
+ return {};
34
+ return parsed[profileName] ?? {};
35
+ }
36
+ catch {
37
+ return {};
38
+ }
39
+ }
40
+ /**
41
+ * Write a key/value into the named profile block.
42
+ *
43
+ * Refuses secret-shaped keys per D-38.
44
+ * Creates the file with 0o600 mode if absent per D-36.
45
+ * Warns to stderr if existing file has wider mode.
46
+ */
47
+ write(profile, key, value) {
48
+ if (SECRET_KEY_PATTERN.test(key)) {
49
+ throw new ApiError('user_error', `Refusing to write secret key '${key}' to config file. Use 'ikm auth login' to manage credentials.`);
50
+ }
51
+ this.ensureDir();
52
+ this.checkFileMode();
53
+ const existing = this.readAll();
54
+ if (!existing[profile]) {
55
+ existing[profile] = {};
56
+ }
57
+ existing[profile][key] = value;
58
+ this.writeAll(existing);
59
+ }
60
+ /**
61
+ * Remove a key from the named profile block.
62
+ * No-op if file, profile, or key doesn't exist.
63
+ */
64
+ unset(profile, key) {
65
+ if (!fs.existsSync(this.filePath))
66
+ return;
67
+ const existing = this.readAll();
68
+ if (!existing[profile])
69
+ return;
70
+ delete existing[profile][key];
71
+ this.writeAll(existing);
72
+ }
73
+ // ---- private helpers ----
74
+ readAll() {
75
+ if (!fs.existsSync(this.filePath))
76
+ return {};
77
+ try {
78
+ const content = fs.readFileSync(this.filePath, 'utf-8');
79
+ const parsed = parse(content);
80
+ return (parsed && typeof parsed === 'object') ? parsed : {};
81
+ }
82
+ catch {
83
+ return {};
84
+ }
85
+ }
86
+ writeAll(data) {
87
+ const content = stringify(data);
88
+ fs.writeFileSync(this.filePath, content, { mode: 0o600, encoding: 'utf-8' });
89
+ }
90
+ ensureDir() {
91
+ const dir = path.dirname(this.filePath);
92
+ if (!fs.existsSync(dir)) {
93
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
94
+ }
95
+ }
96
+ /** Warn if the existing config file has permissions wider than 0o600. */
97
+ checkFileMode() {
98
+ if (!fs.existsSync(this.filePath))
99
+ return;
100
+ try {
101
+ const stat = fs.statSync(this.filePath);
102
+ const mode = stat.mode & 0o777;
103
+ if (mode > 0o600) {
104
+ process.stderr.write(`Warning: config file ${this.filePath} has wide permissions (${mode.toString(8)}). Expected 0600.\n`);
105
+ }
106
+ }
107
+ catch {
108
+ // Ignore stat errors -- file may have been created concurrently
109
+ }
110
+ }
111
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Returns the OS-correct config file path per D-36.
3
+ *
4
+ * Linux: $XDG_CONFIG_HOME/instantkom/config.yml || $HOME/.config/instantkom/config.yml
5
+ * macOS: ~/Library/Application Support/instantkom/config.yml
6
+ * Windows: %APPDATA%/instantkom/config.yml
7
+ */
8
+ export declare function configPath(): string;
@@ -0,0 +1,25 @@
1
+ import * as os from 'os';
2
+ import * as path from 'path';
3
+ /**
4
+ * Returns the OS-correct config file path per D-36.
5
+ *
6
+ * Linux: $XDG_CONFIG_HOME/instantkom/config.yml || $HOME/.config/instantkom/config.yml
7
+ * macOS: ~/Library/Application Support/instantkom/config.yml
8
+ * Windows: %APPDATA%/instantkom/config.yml
9
+ */
10
+ export function configPath() {
11
+ const platform = process.platform;
12
+ if (platform === 'win32') {
13
+ const appData = process.env.APPDATA ?? path.join(os.homedir(), 'AppData', 'Roaming');
14
+ return path.join(appData, 'instantkom', 'config.yml');
15
+ }
16
+ if (platform === 'darwin') {
17
+ return path.join(os.homedir(), 'Library', 'Application Support', 'instantkom', 'config.yml');
18
+ }
19
+ // Linux and other Unix-like systems
20
+ const xdgConfigHome = process.env.XDG_CONFIG_HOME;
21
+ const configBase = xdgConfigHome
22
+ ? xdgConfigHome
23
+ : path.join(os.homedir(), '.config');
24
+ return path.join(configBase, 'instantkom', 'config.yml');
25
+ }
@@ -0,0 +1,6 @@
1
+ export interface CsvRow {
2
+ rowNumber: number;
3
+ values: Record<string, string>;
4
+ }
5
+ export declare function parseCsv(input: string): CsvRow[];
6
+ export declare function serializeCsv(rows: Record<string, unknown>[]): string;
@@ -0,0 +1,89 @@
1
+ import { ApiError } from '../errors/api-error.js';
2
+ function parseCsvRecords(input) {
3
+ const records = [];
4
+ let record = [];
5
+ let cell = '';
6
+ let inQuotes = false;
7
+ for (let index = 0; index < input.length; index += 1) {
8
+ const char = input[index];
9
+ const next = input[index + 1];
10
+ if (char === '"') {
11
+ if (inQuotes && next === '"') {
12
+ cell += '"';
13
+ index += 1;
14
+ }
15
+ else {
16
+ inQuotes = !inQuotes;
17
+ }
18
+ continue;
19
+ }
20
+ if (char === ',' && !inQuotes) {
21
+ record.push(cell);
22
+ cell = '';
23
+ continue;
24
+ }
25
+ if ((char === '\n' || char === '\r') && !inQuotes) {
26
+ if (char === '\r' && next === '\n') {
27
+ index += 1;
28
+ }
29
+ record.push(cell);
30
+ records.push(record);
31
+ record = [];
32
+ cell = '';
33
+ continue;
34
+ }
35
+ cell += char;
36
+ }
37
+ if (inQuotes) {
38
+ throw new ApiError('user_error', 'Invalid CSV: unterminated quoted cell');
39
+ }
40
+ record.push(cell);
41
+ records.push(record);
42
+ return records.filter((entry) => entry.some((value) => value !== ''));
43
+ }
44
+ export function parseCsv(input) {
45
+ const records = parseCsvRecords(input);
46
+ const headers = records[0]?.map((header) => header.trim()) ?? [];
47
+ if (headers.length === 0) {
48
+ return [];
49
+ }
50
+ if (headers.some((header) => header === '')) {
51
+ throw new ApiError('user_error', 'Invalid CSV: headers must not be empty');
52
+ }
53
+ return records.slice(1).map((record, index) => {
54
+ const values = {};
55
+ for (const [headerIndex, header] of headers.entries()) {
56
+ values[header] = record[headerIndex] ?? '';
57
+ }
58
+ return {
59
+ rowNumber: index + 2,
60
+ values,
61
+ };
62
+ });
63
+ }
64
+ function serializeValue(value) {
65
+ if (value === undefined || value === null) {
66
+ return '';
67
+ }
68
+ const text = typeof value === 'object' ? JSON.stringify(value) : String(value);
69
+ if (/[",\r\n]/.test(text)) {
70
+ return `"${text.replaceAll('"', '""')}"`;
71
+ }
72
+ return text;
73
+ }
74
+ export function serializeCsv(rows) {
75
+ if (rows.length === 0) {
76
+ return '';
77
+ }
78
+ const headers = Array.from(rows.reduce((keys, row) => {
79
+ for (const key of Object.keys(row)) {
80
+ keys.add(key);
81
+ }
82
+ return keys;
83
+ }, new Set()));
84
+ const lines = [
85
+ headers.map(serializeValue).join(','),
86
+ ...rows.map((row) => headers.map((header) => serializeValue(row[header])).join(',')),
87
+ ];
88
+ return `${lines.join('\n')}\n`;
89
+ }
@@ -0,0 +1,4 @@
1
+ export type JsonObject = Record<string, unknown>;
2
+ export declare function parseDataFlag(value?: string): JsonObject;
3
+ export declare function mergeData(data: JsonObject, flags: Record<string, unknown>): JsonObject;
4
+ export declare function cleanQuery(query: Record<string, unknown>): JsonObject;
@@ -0,0 +1,38 @@
1
+ import { ApiError } from '../errors/api-error.js';
2
+ export function parseDataFlag(value) {
3
+ if (value === undefined || value.trim() === '') {
4
+ return {};
5
+ }
6
+ let parsed;
7
+ try {
8
+ parsed = JSON.parse(value);
9
+ }
10
+ catch (error) {
11
+ const message = error instanceof Error ? error.message : String(error);
12
+ throw new ApiError('user_error', `Invalid --data JSON: ${message}`);
13
+ }
14
+ if (typeof parsed !== 'object'
15
+ || parsed === null
16
+ || Array.isArray(parsed)) {
17
+ throw new ApiError('user_error', '--data must be a JSON object');
18
+ }
19
+ return parsed;
20
+ }
21
+ export function mergeData(data, flags) {
22
+ const merged = { ...data };
23
+ for (const [key, value] of Object.entries(flags)) {
24
+ if (value !== undefined && value !== null && value !== '') {
25
+ merged[key] = value;
26
+ }
27
+ }
28
+ return merged;
29
+ }
30
+ export function cleanQuery(query) {
31
+ const cleaned = {};
32
+ for (const [key, value] of Object.entries(query)) {
33
+ if (value !== undefined && value !== null && value !== '') {
34
+ cleaned[key] = value;
35
+ }
36
+ }
37
+ return cleaned;
38
+ }
@@ -0,0 +1,14 @@
1
+ import type { ApiClient, ApiRequestOptions } from '../api-client.js';
2
+ import { type JsonObject } from './data.js';
3
+ type ResourceId = string | number;
4
+ export declare function listResource<T>(client: Pick<ApiClient, 'get'>, path: string, query?: Record<string, unknown>, options?: ApiRequestOptions): Promise<T>;
5
+ export declare function getResource<T>(client: Pick<ApiClient, 'get'>, path: string, id: ResourceId, options?: ApiRequestOptions): Promise<T>;
6
+ export declare function createResource<T>(client: Pick<ApiClient, 'post'>, path: string, payload: JsonObject, options?: ApiRequestOptions): Promise<T>;
7
+ export declare function updateResource<T>(client: Pick<ApiClient, 'put'>, path: string, id: ResourceId, payload: JsonObject, options?: ApiRequestOptions): Promise<T>;
8
+ export declare function deleteResource<T>(client: Pick<ApiClient, 'delete'>, path: string, id: ResourceId, options?: ApiRequestOptions): Promise<T>;
9
+ export declare function downloadResource(client: Pick<ApiClient, 'getBinary'>, path: string, id: ResourceId, query?: Record<string, unknown>): Promise<{
10
+ buffer: Buffer;
11
+ contentType: string | null;
12
+ contentDisposition: string | null;
13
+ }>;
14
+ export {};
@@ -0,0 +1,26 @@
1
+ import { cleanQuery } from './data.js';
2
+ function resourcePath(path, id) {
3
+ const basePath = path.replace(/\/+$/, '');
4
+ if (id === undefined) {
5
+ return basePath;
6
+ }
7
+ return `${basePath}/${encodeURIComponent(String(id))}`;
8
+ }
9
+ export async function listResource(client, path, query = {}, options) {
10
+ return client.get(resourcePath(path), cleanQuery(query), options);
11
+ }
12
+ export async function getResource(client, path, id, options) {
13
+ return client.get(resourcePath(path, id), undefined, options);
14
+ }
15
+ export async function createResource(client, path, payload, options) {
16
+ return client.post(resourcePath(path), payload, options);
17
+ }
18
+ export async function updateResource(client, path, id, payload, options) {
19
+ return client.put(resourcePath(path, id), payload, options);
20
+ }
21
+ export async function deleteResource(client, path, id, options) {
22
+ return client.delete(resourcePath(path, id), undefined, options);
23
+ }
24
+ export async function downloadResource(client, path, id, query = {}) {
25
+ return client.getBinary(resourcePath(path, id), cleanQuery(query));
26
+ }
@@ -0,0 +1,6 @@
1
+ export type ApiErrorKind = 'unauthorized' | 'rate_limited' | 'server_error' | 'network' | 'user_error';
2
+ export declare class ApiError extends Error {
3
+ kind: ApiErrorKind;
4
+ status?: number | undefined;
5
+ constructor(kind: ApiErrorKind, message: string, status?: number | undefined);
6
+ }
@@ -0,0 +1,10 @@
1
+ export class ApiError extends Error {
2
+ kind;
3
+ status;
4
+ constructor(kind, message, status) {
5
+ super(message);
6
+ this.kind = kind;
7
+ this.status = status;
8
+ this.name = 'ApiError';
9
+ }
10
+ }
@@ -0,0 +1,12 @@
1
+ export declare const EXIT_CODES: Readonly<{
2
+ readonly OK: 0;
3
+ readonly USER_ERROR: 1;
4
+ readonly AUTH: 2;
5
+ readonly RATE_LIMIT: 3;
6
+ readonly SERVER: 4;
7
+ readonly NETWORK: 5;
8
+ }>;
9
+ export type ExitCode = (typeof EXIT_CODES)[keyof typeof EXIT_CODES];
10
+ export declare function mapApiErrorToCode(err: {
11
+ kind?: string;
12
+ }): ExitCode;
@@ -0,0 +1,24 @@
1
+ export const EXIT_CODES = Object.freeze({
2
+ OK: 0,
3
+ USER_ERROR: 1,
4
+ AUTH: 2,
5
+ RATE_LIMIT: 3,
6
+ SERVER: 4,
7
+ NETWORK: 5,
8
+ });
9
+ export function mapApiErrorToCode(err) {
10
+ switch (err.kind) {
11
+ case 'unauthorized':
12
+ return EXIT_CODES.AUTH;
13
+ case 'rate_limited':
14
+ return EXIT_CODES.RATE_LIMIT;
15
+ case 'server_error':
16
+ return EXIT_CODES.SERVER;
17
+ case 'network':
18
+ return EXIT_CODES.NETWORK;
19
+ case 'user_error':
20
+ return EXIT_CODES.USER_ERROR;
21
+ default:
22
+ return EXIT_CODES.USER_ERROR;
23
+ }
24
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Redact a token string, showing only first 4 and last 4 characters.
3
+ * Tokens of 12 chars or fewer are fully redacted to '***' (no leak).
4
+ */
5
+ export declare function redactToken(token: string): string;
6
+ /**
7
+ * Redact all token-like strings (>= 24 alphanumeric chars) within a string.
8
+ * Uses a regex to find and replace each match via redactToken.
9
+ */
10
+ export declare function redactString(s: string): string;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Redact a token string, showing only first 4 and last 4 characters.
3
+ * Tokens of 12 chars or fewer are fully redacted to '***' (no leak).
4
+ */
5
+ export function redactToken(token) {
6
+ if (token.length <= 12) {
7
+ return '***';
8
+ }
9
+ return token.slice(0, 4) + '...' + token.slice(-4);
10
+ }
11
+ /**
12
+ * Redact all token-like strings (>= 24 alphanumeric chars) within a string.
13
+ * Uses a regex to find and replace each match via redactToken.
14
+ */
15
+ export function redactString(s) {
16
+ return s.replace(/[A-Za-z0-9_-]{24,}/g, (match) => redactToken(match));
17
+ }
@@ -0,0 +1 @@
1
+ export { run } from '@oclif/core';
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export { run } from '@oclif/core';
@@ -0,0 +1,15 @@
1
+ export type FormatMode = 'text' | 'json' | 'yaml' | 'table';
2
+ export interface FormatOptions {
3
+ columns?: string[];
4
+ noColor?: boolean;
5
+ }
6
+ /**
7
+ * Format a payload into a string using the specified mode.
8
+ *
9
+ * Modes:
10
+ * text -- human-readable key=value lines (auto-table for homogeneous arrays)
11
+ * json -- JSON.stringify with 2-space indent, trailing newline
12
+ * yaml -- js-yaml dump, sorted keys
13
+ * table -- cli-table3 ASCII table
14
+ */
15
+ export declare function format(payload: unknown, mode: FormatMode, opts?: FormatOptions): string;
@@ -0,0 +1,57 @@
1
+ import yaml from 'js-yaml';
2
+ import Table from 'cli-table3';
3
+ import { textFormat, isHomogeneousArray } from './text-format.js';
4
+ /**
5
+ * Format a payload into a string using the specified mode.
6
+ *
7
+ * Modes:
8
+ * text -- human-readable key=value lines (auto-table for homogeneous arrays)
9
+ * json -- JSON.stringify with 2-space indent, trailing newline
10
+ * yaml -- js-yaml dump, sorted keys
11
+ * table -- cli-table3 ASCII table
12
+ */
13
+ export function format(payload, mode, opts) {
14
+ switch (mode) {
15
+ case 'json':
16
+ return JSON.stringify(payload, null, 2) + '\n';
17
+ case 'yaml':
18
+ return yaml.dump(payload, { sortKeys: true, lineWidth: -1 });
19
+ case 'table': {
20
+ const rows = Array.isArray(payload) ? payload : [payload];
21
+ const firstItem = rows[0];
22
+ const isObj = typeof firstItem === 'object' && firstItem !== null && !Array.isArray(firstItem);
23
+ if (!isObj) {
24
+ // Fallback for non-object payloads
25
+ return textFormat(payload);
26
+ }
27
+ const headers = opts?.columns ?? Object.keys(firstItem);
28
+ const table = new Table({ head: headers });
29
+ for (const row of rows) {
30
+ if (typeof row === 'object' && row !== null) {
31
+ const record = row;
32
+ table.push(headers.map((h) => {
33
+ const v = record[h];
34
+ return typeof v === 'object' && v !== null ? JSON.stringify(v) : String(v ?? '');
35
+ }));
36
+ }
37
+ }
38
+ return table.toString() + '\n';
39
+ }
40
+ case 'text':
41
+ default: {
42
+ // Auto-detect homogeneous arrays and render as table
43
+ if (Array.isArray(payload) && isHomogeneousArray(payload)) {
44
+ const headers = opts?.columns ?? Object.keys(payload[0]);
45
+ const table = new Table({ head: headers });
46
+ for (const row of payload) {
47
+ table.push(headers.map((h) => {
48
+ const v = row[h];
49
+ return typeof v === 'object' && v !== null ? JSON.stringify(v) : String(v ?? '');
50
+ }));
51
+ }
52
+ return table.toString() + '\n';
53
+ }
54
+ return textFormat(payload) + '\n';
55
+ }
56
+ }
57
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Detect whether an array is "homogeneous" -- every element has the same
3
+ * set of own enumerable keys (required for auto-table detection in text mode).
4
+ */
5
+ declare function isHomogeneousArray(arr: unknown[]): arr is Record<string, unknown>[];
6
+ /**
7
+ * Format an unknown payload as human-readable text.
8
+ * - Objects -> key: value lines
9
+ * - Homogeneous arrays -> tab-aligned table
10
+ * - Everything else -> JSON.stringify
11
+ */
12
+ export declare function textFormat(payload: unknown): string;
13
+ export { isHomogeneousArray };
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Detect whether an array is "homogeneous" -- every element has the same
3
+ * set of own enumerable keys (required for auto-table detection in text mode).
4
+ */
5
+ function isHomogeneousArray(arr) {
6
+ if (arr.length === 0)
7
+ return false;
8
+ const first = arr[0];
9
+ if (typeof first !== 'object' || first === null)
10
+ return false;
11
+ const keys = Object.keys(first).sort().join(',');
12
+ return arr.every((item) => typeof item === 'object'
13
+ && item !== null
14
+ && Object.keys(item).sort().join(',') === keys);
15
+ }
16
+ /**
17
+ * Format a plain object as key: value lines.
18
+ */
19
+ function formatRecord(record) {
20
+ const lines = [];
21
+ for (const [key, value] of Object.entries(record)) {
22
+ const display = typeof value === 'object' && value !== null
23
+ ? JSON.stringify(value)
24
+ : String(value ?? '');
25
+ lines.push(`${key}: ${display}`);
26
+ }
27
+ return lines.join('\n');
28
+ }
29
+ /**
30
+ * Format a homogeneous array of objects as a tab-aligned table fallback.
31
+ * This is the plain-text table (not cli-table3) used in text mode.
32
+ */
33
+ function formatHomogeneousArray(arr) {
34
+ if (arr.length === 0)
35
+ return '';
36
+ const keys = Object.keys(arr[0]);
37
+ const rows = arr.map((item) => keys.map((k) => {
38
+ const v = item[k];
39
+ return typeof v === 'object' && v !== null ? JSON.stringify(v) : String(v ?? '');
40
+ }));
41
+ // Compute column widths
42
+ const widths = keys.map((k, i) => Math.max(k.length, ...rows.map((r) => r[i].length)));
43
+ const pad = (s, w) => s.padEnd(w);
44
+ const header = keys.map((k, i) => pad(k, widths[i])).join(' ');
45
+ const separator = widths.map((w) => '-'.repeat(w)).join(' ');
46
+ const body = rows.map((r) => r.map((cell, i) => pad(cell, widths[i])).join(' ')).join('\n');
47
+ return [header, separator, body].join('\n');
48
+ }
49
+ /**
50
+ * Format an unknown payload as human-readable text.
51
+ * - Objects -> key: value lines
52
+ * - Homogeneous arrays -> tab-aligned table
53
+ * - Everything else -> JSON.stringify
54
+ */
55
+ export function textFormat(payload) {
56
+ if (Array.isArray(payload)) {
57
+ if (isHomogeneousArray(payload)) {
58
+ return formatHomogeneousArray(payload);
59
+ }
60
+ return payload.map((item) => textFormat(item)).join('\n');
61
+ }
62
+ if (typeof payload === 'object' && payload !== null) {
63
+ return formatRecord(payload);
64
+ }
65
+ return String(payload ?? '');
66
+ }
67
+ export { isHomogeneousArray };
@@ -0,0 +1,3 @@
1
+ import { ApiClient } from '../api-client.js';
2
+ import type { BaseFlags } from '../base-command.js';
3
+ export declare function createCliApiClient(flags: Partial<BaseFlags>): Promise<ApiClient>;
@@ -0,0 +1,21 @@
1
+ import { ApiClient } from '../api-client.js';
2
+ import { resolveToken } from '../auth/token-resolver.js';
3
+ import { ApiError } from '../errors/api-error.js';
4
+ export async function createCliApiClient(flags) {
5
+ const profile = flags.profile ?? 'default';
6
+ const token = await resolveToken({
7
+ flag: flags['api-key'],
8
+ env: process.env.IKM_API_KEY,
9
+ profile,
10
+ });
11
+ if (!token) {
12
+ throw new ApiError('unauthorized', 'Not logged in. Run: ikm auth login');
13
+ }
14
+ return new ApiClient({
15
+ id: profile,
16
+ name: 'ikm-cli',
17
+ apiUrl: flags['api-url'] ?? process.env.IKM_API_URL ?? 'https://api.instantkom.app',
18
+ apiKey: token,
19
+ scope: 'customer',
20
+ });
21
+ }
@@ -0,0 +1,5 @@
1
+ import type { ApiClient } from '../api-client.js';
2
+ export declare function resolveFallbackChannels(client: Pick<ApiClient, 'get'>, aliasesCsv: string): Promise<Array<{
3
+ alias: string;
4
+ channelId: number;
5
+ }>>;