@inboxychat/sdk 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,323 @@
1
+ /**
2
+ * Local credentials for the Inboxy MCP server and CLI.
3
+ *
4
+ * Where the key may live, in the order it is read:
5
+ * 1. INBOXY_API_KEY (environment) — what an MCP client config passes.
6
+ * 2. The OS keychain: macOS Keychain (`security`), Linux Secret Service
7
+ * (`secret-tool`). One entry per profile, service "inboxy-cli".
8
+ * 3. The config file, only when no keychain exists (`keyStorage: "file"`),
9
+ * and only if the file is private (mode 0600, directory 0700). A config
10
+ * file that group or others can read is refused, like ssh does.
11
+ *
12
+ * The API URL: INBOXY_API_URL, then the profile's `apiUrl`, then
13
+ * https://api.inboxy.chat.
14
+ *
15
+ * Nothing here logs. Keychain tools run through `spawn` with an argument
16
+ * array (no shell), and the secret reaches them on stdin, never on argv.
17
+ */
18
+ import { spawn } from 'node:child_process';
19
+ import { promises as fs } from 'node:fs';
20
+ import os from 'node:os';
21
+ import path from 'node:path';
22
+ import { DEFAULT_API_URL } from './client.js';
23
+ import { InboxyError } from './errors.js';
24
+ export const KEYCHAIN_SERVICE = 'inboxy-cli';
25
+ export const CONFIG_FILE_NAME = 'config.json';
26
+ const PROFILE_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
27
+ export function validateProfileName(profile) {
28
+ if (!PROFILE_PATTERN.test(profile)) {
29
+ throw new InboxyError({
30
+ kind: 'config',
31
+ code: 'INVALID_PROFILE',
32
+ message: 'Profile names are 1-64 characters of letters, digits, ".", "_" or "-", starting with a letter or digit.',
33
+ });
34
+ }
35
+ return profile;
36
+ }
37
+ // ============================================
38
+ // Config file
39
+ // ============================================
40
+ export function configDir(env = process.env) {
41
+ if (env.INBOXY_CONFIG_DIR)
42
+ return env.INBOXY_CONFIG_DIR;
43
+ const base = env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config');
44
+ return path.join(base, 'inboxy');
45
+ }
46
+ export function configPath(env = process.env) {
47
+ return path.join(configDir(env), CONFIG_FILE_NAME);
48
+ }
49
+ function emptyConfig() {
50
+ return { version: 1, profiles: {} };
51
+ }
52
+ export async function loadConfig(env = process.env) {
53
+ const file = configPath(env);
54
+ let raw;
55
+ let mode = null;
56
+ try {
57
+ const stat = await fs.stat(file);
58
+ mode = process.platform === 'win32' ? null : stat.mode & 0o777;
59
+ raw = await fs.readFile(file, 'utf8');
60
+ }
61
+ catch (error) {
62
+ if (error.code === 'ENOENT') {
63
+ return { path: file, exists: false, mode: null, config: emptyConfig() };
64
+ }
65
+ throw new InboxyError({
66
+ kind: 'config',
67
+ code: 'CONFIG_UNREADABLE',
68
+ message: `Cannot read ${file}.`,
69
+ });
70
+ }
71
+ let parsed;
72
+ try {
73
+ parsed = JSON.parse(raw);
74
+ }
75
+ catch {
76
+ throw new InboxyError({
77
+ kind: 'config',
78
+ code: 'CONFIG_INVALID',
79
+ message: `${file} is not valid JSON.`,
80
+ });
81
+ }
82
+ const obj = (parsed && typeof parsed === 'object' ? parsed : {});
83
+ const profiles = obj.profiles && typeof obj.profiles === 'object'
84
+ ? obj.profiles
85
+ : {};
86
+ return {
87
+ path: file,
88
+ exists: true,
89
+ mode,
90
+ config: {
91
+ version: 1,
92
+ defaultProfile: typeof obj.defaultProfile === 'string' ? obj.defaultProfile : undefined,
93
+ profiles,
94
+ },
95
+ };
96
+ }
97
+ /** True when group or others have any access to the file. */
98
+ export function isModeTooOpen(mode) {
99
+ return mode !== null && (mode & 0o077) !== 0;
100
+ }
101
+ /**
102
+ * Writes the config with 0700 on the directory and 0600 on the file. The
103
+ * temporary file is created 0600 before any byte is written, then renamed, so
104
+ * there is no moment where the content is world-readable.
105
+ */
106
+ export async function saveConfig(config, env = process.env) {
107
+ const dir = configDir(env);
108
+ await fs.mkdir(dir, { recursive: true, mode: 0o700 });
109
+ if (process.platform !== 'win32')
110
+ await fs.chmod(dir, 0o700);
111
+ const file = path.join(dir, CONFIG_FILE_NAME);
112
+ const tmp = path.join(dir, `.${CONFIG_FILE_NAME}.${process.pid}.${Date.now()}.tmp`);
113
+ const handle = await fs.open(tmp, 'wx', 0o600);
114
+ try {
115
+ await handle.writeFile(`${JSON.stringify(config, null, 2)}\n`, 'utf8');
116
+ }
117
+ finally {
118
+ await handle.close();
119
+ }
120
+ await fs.rename(tmp, file);
121
+ if (process.platform !== 'win32')
122
+ await fs.chmod(file, 0o600);
123
+ return file;
124
+ }
125
+ export const spawnRunner = (command, args, input) => new Promise((resolve) => {
126
+ let child;
127
+ try {
128
+ child = spawn(command, [...args], { shell: false, stdio: ['pipe', 'pipe', 'pipe'] });
129
+ }
130
+ catch {
131
+ resolve({ code: null, stdout: '', stderr: 'spawn failed' });
132
+ return;
133
+ }
134
+ let stdout = '';
135
+ let stderr = '';
136
+ const timer = setTimeout(() => child.kill('SIGKILL'), 15_000);
137
+ child.stdout.on('data', (d) => (stdout += d.toString('utf8')));
138
+ child.stderr.on('data', (d) => (stderr += d.toString('utf8')));
139
+ child.on('error', () => {
140
+ clearTimeout(timer);
141
+ resolve({ code: null, stdout, stderr: 'not available' });
142
+ });
143
+ child.on('close', (code) => {
144
+ clearTimeout(timer);
145
+ resolve({ code, stdout, stderr });
146
+ });
147
+ child.stdin.on('error', () => undefined);
148
+ child.stdin.end(input ?? '');
149
+ });
150
+ function keychainError(name, action) {
151
+ return new InboxyError({
152
+ kind: 'config',
153
+ code: 'KEYCHAIN_ERROR',
154
+ message: `Could not ${action} the key in the ${name}.`,
155
+ });
156
+ }
157
+ /** macOS Keychain through /usr/bin/security. */
158
+ export function macosKeychain(run = spawnRunner) {
159
+ const name = 'macOS Keychain';
160
+ return {
161
+ name,
162
+ async get(account) {
163
+ validateProfileName(account);
164
+ const r = await run('security', [
165
+ 'find-generic-password',
166
+ '-s',
167
+ KEYCHAIN_SERVICE,
168
+ '-a',
169
+ account,
170
+ '-w',
171
+ ]);
172
+ if (r.code === 0)
173
+ return r.stdout.replace(/\r?\n$/, '') || null;
174
+ if (r.code === 44)
175
+ return null; // errSecItemNotFound
176
+ throw keychainError(name, 'read');
177
+ },
178
+ async set(account, secret) {
179
+ validateProfileName(account);
180
+ // `security -i` reads commands from stdin; the secret goes as hex (-X),
181
+ // so it is never on argv (visible in `ps`) and needs no quoting.
182
+ const hex = Buffer.from(secret, 'utf8').toString('hex');
183
+ const command = `add-generic-password -U -s ${KEYCHAIN_SERVICE} -a ${account} -l ${KEYCHAIN_SERVICE} -X ${hex}\n`;
184
+ const r = await run('security', ['-i'], command);
185
+ if (r.code !== 0 || /error/i.test(r.stderr))
186
+ throw keychainError(name, 'store');
187
+ },
188
+ async delete(account) {
189
+ validateProfileName(account);
190
+ const r = await run('security', [
191
+ 'delete-generic-password',
192
+ '-s',
193
+ KEYCHAIN_SERVICE,
194
+ '-a',
195
+ account,
196
+ ]);
197
+ if (r.code === 0)
198
+ return true;
199
+ if (r.code === 44)
200
+ return false;
201
+ throw keychainError(name, 'delete');
202
+ },
203
+ };
204
+ }
205
+ /** Linux Secret Service (GNOME Keyring, KWallet) through `secret-tool`. */
206
+ export function secretServiceKeychain(run = spawnRunner) {
207
+ const name = 'Secret Service keyring';
208
+ const attrs = (account) => ['service', KEYCHAIN_SERVICE, 'account', account];
209
+ return {
210
+ name,
211
+ async get(account) {
212
+ validateProfileName(account);
213
+ const r = await run('secret-tool', ['lookup', ...attrs(account)]);
214
+ if (r.code === 0)
215
+ return r.stdout.replace(/\r?\n$/, '') || null;
216
+ if (r.code === 1 && !r.stderr.trim())
217
+ return null;
218
+ throw keychainError(name, 'read');
219
+ },
220
+ async set(account, secret) {
221
+ validateProfileName(account);
222
+ const r = await run('secret-tool', ['store', `--label=Inboxy CLI (${account})`, ...attrs(account)], secret);
223
+ if (r.code !== 0)
224
+ throw keychainError(name, 'store');
225
+ },
226
+ async delete(account) {
227
+ validateProfileName(account);
228
+ const r = await run('secret-tool', ['clear', ...attrs(account)]);
229
+ return r.code === 0;
230
+ },
231
+ };
232
+ }
233
+ /**
234
+ * The keychain for this machine, or null when there is none (Windows, a Linux
235
+ * box without secret-tool, or INBOXY_KEYCHAIN=off).
236
+ */
237
+ export async function systemKeychain(env = process.env, platform = process.platform, run = spawnRunner) {
238
+ if ((env.INBOXY_KEYCHAIN ?? '').toLowerCase() === 'off')
239
+ return null;
240
+ if (platform === 'darwin')
241
+ return macosKeychain(run);
242
+ if (platform === 'linux') {
243
+ const probe = await run('secret-tool', ['--version']);
244
+ return probe.code === 0 ? secretServiceKeychain(run) : null;
245
+ }
246
+ return null;
247
+ }
248
+ export async function resolveCredentials(options = {}) {
249
+ const env = options.env ?? process.env;
250
+ const loaded = await loadConfig(env);
251
+ const profile = validateProfileName(options.profile || env.INBOXY_PROFILE || loaded.config.defaultProfile || 'default');
252
+ const profileConfig = loaded.config.profiles[profile] ?? {};
253
+ const warnings = [];
254
+ let apiUrl = DEFAULT_API_URL;
255
+ let urlSource = 'default';
256
+ if (env.INBOXY_API_URL) {
257
+ apiUrl = env.INBOXY_API_URL;
258
+ urlSource = 'env';
259
+ }
260
+ else if (profileConfig.apiUrl) {
261
+ apiUrl = profileConfig.apiUrl;
262
+ urlSource = 'config';
263
+ }
264
+ if (env.INBOXY_API_KEY) {
265
+ return {
266
+ profile,
267
+ apiKey: env.INBOXY_API_KEY.trim(),
268
+ keySource: 'env',
269
+ apiUrl,
270
+ urlSource,
271
+ configPath: loaded.path,
272
+ warnings,
273
+ };
274
+ }
275
+ const keychain = options.keychain === undefined ? await systemKeychain(env) : options.keychain;
276
+ if (keychain && profileConfig.keyStorage !== 'file') {
277
+ try {
278
+ const fromKeychain = await keychain.get(profile);
279
+ if (fromKeychain) {
280
+ return {
281
+ profile,
282
+ apiKey: fromKeychain,
283
+ keySource: 'keychain',
284
+ apiUrl,
285
+ urlSource,
286
+ configPath: loaded.path,
287
+ warnings,
288
+ };
289
+ }
290
+ }
291
+ catch {
292
+ warnings.push(`Could not read the ${keychain.name}.`);
293
+ }
294
+ }
295
+ if (profileConfig.apiKey) {
296
+ if (isModeTooOpen(loaded.mode)) {
297
+ throw new InboxyError({
298
+ kind: 'config',
299
+ code: 'CONFIG_PERMISSIONS_TOO_OPEN',
300
+ message: `${loaded.path} holds an API key but is readable by other users (mode ${(loaded.mode ?? 0).toString(8)}). Run: chmod 600 "${loaded.path}"`,
301
+ });
302
+ }
303
+ return {
304
+ profile,
305
+ apiKey: profileConfig.apiKey,
306
+ keySource: 'config-file',
307
+ apiUrl,
308
+ urlSource,
309
+ configPath: loaded.path,
310
+ warnings,
311
+ };
312
+ }
313
+ return {
314
+ profile,
315
+ apiKey: null,
316
+ keySource: 'none',
317
+ apiUrl,
318
+ urlSource,
319
+ configPath: loaded.path,
320
+ warnings,
321
+ };
322
+ }
323
+ //# sourceMappingURL=credentials.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credentials.js","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Error kinds a caller can branch on without parsing messages.
3
+ *
4
+ * - `auth` 401: missing, unknown, revoked or expired key (or a dashboard JWT)
5
+ * - `forbidden` 403: scope missing, company paused, key owner no longer admin
6
+ * - `not_found` 404
7
+ * - `rate_limited` 429: per-key or per-company budget; see `retryAfterSeconds`
8
+ * - `invalid_request` 400 / other 4xx
9
+ * - `server` 5xx
10
+ * - `timeout` no response before the client's timeout
11
+ * - `cancelled` the caller aborted (MCP cancellation, Ctrl-C)
12
+ * - `network` DNS, TLS, connection refused, redirect refused
13
+ * - `invalid_response` the server answered something that is not the envelope
14
+ * - `config` no key / bad URL — nothing was sent
15
+ */
16
+ export type InboxyErrorKind = 'auth' | 'forbidden' | 'not_found' | 'rate_limited' | 'invalid_request' | 'server' | 'timeout' | 'cancelled' | 'network' | 'invalid_response' | 'config';
17
+ export declare class InboxyError extends Error {
18
+ readonly kind: InboxyErrorKind;
19
+ /** HTTP status; 0 when no response was received. */
20
+ readonly status: number;
21
+ /** The envelope's `error.code`, or a local code (TIMEOUT, NETWORK_ERROR, ...). */
22
+ readonly code: string;
23
+ readonly details: Record<string, unknown>;
24
+ readonly retryAfterSeconds: number | null;
25
+ constructor(init: {
26
+ kind: InboxyErrorKind;
27
+ status?: number;
28
+ code: string;
29
+ message: string;
30
+ details?: Record<string, unknown>;
31
+ retryAfterSeconds?: number | null;
32
+ });
33
+ toJSON(): {
34
+ kind: InboxyErrorKind;
35
+ status: number;
36
+ code: string;
37
+ message: string;
38
+ details: Record<string, unknown>;
39
+ retryAfterSeconds: number | null;
40
+ };
41
+ }
42
+ export declare function kindForStatus(status: number): InboxyErrorKind;
43
+ /**
44
+ * A short, human explanation for an error, with the next step. Used by both
45
+ * the CLI and the MCP tool errors so they say the same thing.
46
+ */
47
+ export declare function describeError(error: InboxyError): string;
48
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,eAAe,GACvB,MAAM,GACN,WAAW,GACX,WAAW,GACX,cAAc,GACd,iBAAiB,GACjB,QAAQ,GACR,SAAS,GACT,WAAW,GACX,SAAS,GACT,kBAAkB,GAClB,QAAQ,CAAC;AAEb,qBAAa,WAAY,SAAQ,KAAK;IACpC,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAC/B,oDAAoD;IACpD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,kFAAkF;IAClF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC1C,QAAQ,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;gBAE9B,IAAI,EAAE;QAChB,IAAI,EAAE,eAAe,CAAC;QACtB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAClC,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KACnC;IAYD,MAAM;;;;;;;;CAUP;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe,CAO7D;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CA+BxD"}
package/dist/errors.js ADDED
@@ -0,0 +1,78 @@
1
+ import { redactSecrets } from './redact.js';
2
+ export class InboxyError extends Error {
3
+ kind;
4
+ /** HTTP status; 0 when no response was received. */
5
+ status;
6
+ /** The envelope's `error.code`, or a local code (TIMEOUT, NETWORK_ERROR, ...). */
7
+ code;
8
+ details;
9
+ retryAfterSeconds;
10
+ constructor(init) {
11
+ // Every message is redacted at construction, so no later formatting step
12
+ // can forget to.
13
+ super(redactSecrets(init.message));
14
+ this.name = 'InboxyError';
15
+ this.kind = init.kind;
16
+ this.status = init.status ?? 0;
17
+ this.code = init.code;
18
+ this.details = init.details ?? {};
19
+ this.retryAfterSeconds = init.retryAfterSeconds ?? null;
20
+ }
21
+ toJSON() {
22
+ return {
23
+ kind: this.kind,
24
+ status: this.status,
25
+ code: this.code,
26
+ message: this.message,
27
+ details: this.details,
28
+ retryAfterSeconds: this.retryAfterSeconds,
29
+ };
30
+ }
31
+ }
32
+ export function kindForStatus(status) {
33
+ if (status === 401)
34
+ return 'auth';
35
+ if (status === 403)
36
+ return 'forbidden';
37
+ if (status === 404)
38
+ return 'not_found';
39
+ if (status === 429)
40
+ return 'rate_limited';
41
+ if (status >= 500)
42
+ return 'server';
43
+ return 'invalid_request';
44
+ }
45
+ /**
46
+ * A short, human explanation for an error, with the next step. Used by both
47
+ * the CLI and the MCP tool errors so they say the same thing.
48
+ */
49
+ export function describeError(error) {
50
+ const scope = typeof error.details.requiredScope === 'string' ? error.details.requiredScope : null;
51
+ switch (error.kind) {
52
+ case 'auth':
53
+ return `Inboxy refused the API key (${error.code}). The key is missing, unknown, revoked or expired. Create a new key on the API page of the Inboxy dashboard.`;
54
+ case 'forbidden':
55
+ if (error.code === 'INSUFFICIENT_PERMISSIONS' && scope) {
56
+ return `This API key does not have the "${scope}" scope. Create a key that includes it on the API page of the Inboxy dashboard.`;
57
+ }
58
+ if (error.code === 'COMPANY_PAUSED') {
59
+ return 'This Inboxy company is paused, so its API keys cannot read data.';
60
+ }
61
+ return `Inboxy denied access (${error.code}): ${error.message}`;
62
+ case 'not_found':
63
+ return `Not found in this company (${error.code}). The id may be wrong or belong to another company.`;
64
+ case 'rate_limited':
65
+ return `Rate limited by Inboxy (${error.code}).${error.retryAfterSeconds !== null ? ` Retry after ${error.retryAfterSeconds} s.` : ''}`;
66
+ case 'timeout':
67
+ return `Inboxy did not answer in time (${error.message}).`;
68
+ case 'cancelled':
69
+ return 'The request was cancelled.';
70
+ case 'network':
71
+ return `Could not reach Inboxy: ${error.message}`;
72
+ case 'config':
73
+ return error.message;
74
+ default:
75
+ return `${error.message} (${error.code}${error.status ? `, HTTP ${error.status}` : ''})`;
76
+ }
77
+ }
78
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export * from './types.js';
2
+ export * from './routes.js';
3
+ export * from './errors.js';
4
+ export * from './redact.js';
5
+ export * from './client.js';
6
+ export * from './credentials.js';
7
+ export * from './capabilities.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ export * from './types.js';
2
+ export * from './routes.js';
3
+ export * from './errors.js';
4
+ export * from './redact.js';
5
+ export * from './client.js';
6
+ export * from './credentials.js';
7
+ export * from './capabilities.js';
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC"}
@@ -0,0 +1,6 @@
1
+ export declare function redactSecrets(text: string, extraSecrets?: readonly string[]): string;
2
+ /** True when the value has the shape of an Inboxy API key. */
3
+ export declare function looksLikeInboxyKey(value: string): boolean;
4
+ /** Display form: the prefix the dashboard also shows. */
5
+ export declare function maskKey(value: string): string;
6
+ //# sourceMappingURL=redact.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact.d.ts","sourceRoot":"","sources":["../src/redact.ts"],"names":[],"mappings":"AASA,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,GAAE,SAAS,MAAM,EAAO,GAAG,MAAM,CAMxF;AAED,8DAA8D;AAC9D,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED,yDAAyD;AACzD,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE7C"}
package/dist/redact.js ADDED
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Secret redaction. Inboxy keys look like `inboxy_` + 64 hex characters. Any
3
+ * text that leaves this package for a terminal, a log or an MCP client passes
4
+ * through `redactSecrets` so a key can never be echoed back, even when a
5
+ * server message or an exception happens to quote it.
6
+ */
7
+ const KEY_PATTERN = /inboxy_[A-Za-z0-9]{6,}/g;
8
+ const BEARER_PATTERN = /(Bearer\s+)[^\s"']+/gi;
9
+ export function redactSecrets(text, extraSecrets = []) {
10
+ let out = text;
11
+ for (const secret of extraSecrets) {
12
+ if (secret && secret.length >= 6)
13
+ out = out.split(secret).join('[REDACTED]');
14
+ }
15
+ return out.replace(KEY_PATTERN, 'inboxy_[REDACTED]').replace(BEARER_PATTERN, '$1[REDACTED]');
16
+ }
17
+ /** True when the value has the shape of an Inboxy API key. */
18
+ export function looksLikeInboxyKey(value) {
19
+ return /^inboxy_[0-9a-f]{64}$/.test(value);
20
+ }
21
+ /** Display form: the prefix the dashboard also shows. */
22
+ export function maskKey(value) {
23
+ return value.length > 8 ? `${value.slice(0, 8)}…` : '…';
24
+ }
25
+ //# sourceMappingURL=redact.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact.js","sourceRoot":"","sources":["../src/redact.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,WAAW,GAAG,yBAAyB,CAAC;AAC9C,MAAM,cAAc,GAAG,uBAAuB,CAAC;AAE/C,MAAM,UAAU,aAAa,CAAC,IAAY,EAAE,eAAkC,EAAE;IAC9E,IAAI,GAAG,GAAG,IAAI,CAAC;IACf,KAAK,MAAM,MAAM,IAAI,YAAY,EAAE,CAAC;QAClC,IAAI,MAAM,IAAI,MAAM,CAAC,MAAM,IAAI,CAAC;YAAE,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAC/E,CAAC;IACD,OAAO,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,mBAAmB,CAAC,CAAC,OAAO,CAAC,cAAc,EAAE,cAAc,CAAC,CAAC;AAC/F,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,kBAAkB,CAAC,KAAa;IAC9C,OAAO,uBAAuB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC7C,CAAC;AAED,yDAAyD;AACzD,MAAM,UAAU,OAAO,CAAC,KAAa;IACnC,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;AAC1D,CAAC"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * The public API v1 allowlist as this client knows it. Must equal
3
+ * `PUBLIC_API_V1_ROUTES` in apps/api/src/modules/api-public/api-public.contract.ts
4
+ * (method, path and scope) — `test/contract-drift.test.ts` checks it.
5
+ */
6
+ export declare const PUBLIC_API_PREFIX = "/api/v1/public";
7
+ export interface PublicRoute {
8
+ method: 'GET';
9
+ path: string;
10
+ /** Scope the key must carry; null = any valid key. */
11
+ scope: string | null;
12
+ }
13
+ export declare const PUBLIC_ROUTES: readonly [{
14
+ readonly method: "GET";
15
+ readonly path: "/company";
16
+ readonly scope: null;
17
+ }, {
18
+ readonly method: "GET";
19
+ readonly path: "/usage";
20
+ readonly scope: "analytics:read";
21
+ }, {
22
+ readonly method: "GET";
23
+ readonly path: "/contacts";
24
+ readonly scope: "contacts:read";
25
+ }, {
26
+ readonly method: "GET";
27
+ readonly path: "/contacts/:id";
28
+ readonly scope: "contacts:read";
29
+ }, {
30
+ readonly method: "GET";
31
+ readonly path: "/conversations";
32
+ readonly scope: "conversations:read";
33
+ }, {
34
+ readonly method: "GET";
35
+ readonly path: "/conversations/:id";
36
+ readonly scope: "conversations:read";
37
+ }, {
38
+ readonly method: "GET";
39
+ readonly path: "/conversations/:id/messages";
40
+ readonly scope: "messages:read";
41
+ }, {
42
+ readonly method: "GET";
43
+ readonly path: "/orders";
44
+ readonly scope: "orders:read";
45
+ }, {
46
+ readonly method: "GET";
47
+ readonly path: "/orders/:id";
48
+ readonly scope: "orders:read";
49
+ }, {
50
+ readonly method: "GET";
51
+ readonly path: "/products";
52
+ readonly scope: "products:read";
53
+ }, {
54
+ readonly method: "GET";
55
+ readonly path: "/products/:id";
56
+ readonly scope: "products:read";
57
+ }];
58
+ export type PublicRoutePath = (typeof PUBLIC_ROUTES)[number]['path'];
59
+ export declare function scopeFor(path: PublicRoutePath): string | null;
60
+ //# sourceMappingURL=routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,mBAAmB,CAAC;AAElD,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,KAAK,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,sDAAsD;IACtD,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAYiB,CAAC;AAE5C,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC;AAErE,wBAAgB,QAAQ,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,GAAG,IAAI,CAG7D"}
package/dist/routes.js ADDED
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The public API v1 allowlist as this client knows it. Must equal
3
+ * `PUBLIC_API_V1_ROUTES` in apps/api/src/modules/api-public/api-public.contract.ts
4
+ * (method, path and scope) — `test/contract-drift.test.ts` checks it.
5
+ */
6
+ export const PUBLIC_API_PREFIX = '/api/v1/public';
7
+ export const PUBLIC_ROUTES = [
8
+ { method: 'GET', path: '/company', scope: null },
9
+ { method: 'GET', path: '/usage', scope: 'analytics:read' },
10
+ { method: 'GET', path: '/contacts', scope: 'contacts:read' },
11
+ { method: 'GET', path: '/contacts/:id', scope: 'contacts:read' },
12
+ { method: 'GET', path: '/conversations', scope: 'conversations:read' },
13
+ { method: 'GET', path: '/conversations/:id', scope: 'conversations:read' },
14
+ { method: 'GET', path: '/conversations/:id/messages', scope: 'messages:read' },
15
+ { method: 'GET', path: '/orders', scope: 'orders:read' },
16
+ { method: 'GET', path: '/orders/:id', scope: 'orders:read' },
17
+ { method: 'GET', path: '/products', scope: 'products:read' },
18
+ { method: 'GET', path: '/products/:id', scope: 'products:read' },
19
+ ];
20
+ export function scopeFor(path) {
21
+ const route = PUBLIC_ROUTES.find((r) => r.path === path);
22
+ return route ? route.scope : null;
23
+ }
24
+ //# sourceMappingURL=routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.js","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,gBAAgB,CAAC;AASlD,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,EAAE;IAChD,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,gBAAgB,EAAE;IAC1D,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,eAAe,EAAE;IAC5D,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,eAAe,EAAE;IAChE,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,gBAAgB,EAAE,KAAK,EAAE,oBAAoB,EAAE;IACtE,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,oBAAoB,EAAE,KAAK,EAAE,oBAAoB,EAAE;IAC1E,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,6BAA6B,EAAE,KAAK,EAAE,eAAe,EAAE;IAC9E,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,aAAa,EAAE;IACxD,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,aAAa,EAAE;IAC5D,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,eAAe,EAAE;IAC5D,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,eAAe,EAAE;CACvB,CAAC;AAI5C,MAAM,UAAU,QAAQ,CAAC,IAAqB;IAC5C,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;IACzD,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;AACpC,CAAC"}