@iterable/mcp 1.2.1 → 1.3.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.
- package/README.md +28 -3
- package/dist/config.d.ts +4 -4
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -1
- package/dist/config.js.map +1 -1
- package/dist/install.d.ts +0 -6
- package/dist/install.d.ts.map +1 -1
- package/dist/install.js +121 -346
- package/dist/install.js.map +1 -1
- package/dist/key-manager.d.ts +37 -3
- package/dist/key-manager.d.ts.map +1 -1
- package/dist/key-manager.js +109 -53
- package/dist/key-manager.js.map +1 -1
- package/dist/keys-cli.d.ts +38 -0
- package/dist/keys-cli.d.ts.map +1 -1
- package/dist/keys-cli.js +454 -373
- package/dist/keys-cli.js.map +1 -1
- package/dist/server.js +1 -1
- package/dist/server.js.map +1 -1
- package/dist/tools/snippets.d.ts.map +1 -1
- package/dist/tools/snippets.js +23 -12
- package/dist/tools/snippets.js.map +1 -1
- package/dist/tools/templates.js +2 -2
- package/dist/tools/templates.js.map +1 -1
- package/dist/utils/cli-env.d.ts.map +1 -1
- package/dist/utils/cli-env.js +2 -1
- package/dist/utils/cli-env.js.map +1 -1
- package/dist/utils/command-info.d.ts +11 -0
- package/dist/utils/command-info.d.ts.map +1 -0
- package/dist/utils/command-info.js +25 -0
- package/dist/utils/command-info.js.map +1 -0
- package/dist/utils/endpoint-prompt.d.ts +1 -0
- package/dist/utils/endpoint-prompt.d.ts.map +1 -1
- package/dist/utils/endpoint-prompt.js +20 -4
- package/dist/utils/endpoint-prompt.js.map +1 -1
- package/dist/utils/tool-config.d.ts +1 -0
- package/dist/utils/tool-config.d.ts.map +1 -1
- package/dist/utils/tool-config.js +3 -0
- package/dist/utils/tool-config.js.map +1 -1
- package/package.json +4 -4
package/dist/install.js
CHANGED
|
@@ -7,12 +7,11 @@ import path from "path";
|
|
|
7
7
|
import { fileURLToPath } from "url";
|
|
8
8
|
import { promisify } from "util";
|
|
9
9
|
import { getKeyManager } from "./key-manager.js";
|
|
10
|
+
import { displayKeyDetails, saveKeyInteractive } from "./keys-cli.js";
|
|
11
|
+
import { COMMAND_NAME, KEYS_COMMAND_TABLE, NPX_PACKAGE_NAME, } from "./utils/command-info.js";
|
|
10
12
|
import { getKeyStorageMessage } from "./utils/formatting.js";
|
|
11
13
|
const { dirname, join } = path;
|
|
12
14
|
// IMPORTANT: UI imports are loaded lazily inside functions to avoid ESM issues in Jest/CommonJS.
|
|
13
|
-
// Executable/package names
|
|
14
|
-
const LOCAL_BINARY_NAME = "iterable-mcp";
|
|
15
|
-
const NPX_PACKAGE_NAME = "@iterable/mcp";
|
|
16
15
|
// Get package version
|
|
17
16
|
const packageJson = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.url)), "..", "package.json"), "utf-8"));
|
|
18
17
|
const TOOL_NAMES = {
|
|
@@ -20,6 +19,7 @@ const TOOL_NAMES = {
|
|
|
20
19
|
"claude-desktop": "Claude Desktop",
|
|
21
20
|
"claude-code": "Claude Code",
|
|
22
21
|
"gemini-cli": "Gemini CLI",
|
|
22
|
+
windsurf: "Windsurf",
|
|
23
23
|
manual: "Manual Setup",
|
|
24
24
|
};
|
|
25
25
|
const TOOL_CONFIGS = {
|
|
@@ -34,6 +34,7 @@ const TOOL_CONFIGS = {
|
|
|
34
34
|
}
|
|
35
35
|
})(),
|
|
36
36
|
cursor: path.join(os.homedir(), ".cursor", "mcp.json"),
|
|
37
|
+
windsurf: path.join(os.homedir(), ".codeium", "windsurf", "mcp_config.json"),
|
|
37
38
|
"gemini-cli": path.join(os.homedir(), ".gemini", "settings.json"),
|
|
38
39
|
};
|
|
39
40
|
const execFileAsync = promisify(execFile);
|
|
@@ -83,24 +84,6 @@ export const buildMcpConfig = (options) => {
|
|
|
83
84
|
env: options.env,
|
|
84
85
|
};
|
|
85
86
|
};
|
|
86
|
-
/**
|
|
87
|
-
* Merge selected env flags with key metadata env (if provided), preferring the key's
|
|
88
|
-
* persisted values. This ensures the written MCP configs reflect the authoritative
|
|
89
|
-
* values saved to the key after any updates in the setup flow.
|
|
90
|
-
*/
|
|
91
|
-
export function resolveFinalMcpEnv(selectedEnv, keyEnv) {
|
|
92
|
-
const normalize = (v) => (v === "true" ? "true" : "false");
|
|
93
|
-
const result = { ...selectedEnv };
|
|
94
|
-
if (keyEnv) {
|
|
95
|
-
result.ITERABLE_USER_PII = normalize(keyEnv.ITERABLE_USER_PII ?? result.ITERABLE_USER_PII);
|
|
96
|
-
result.ITERABLE_ENABLE_WRITES = normalize(keyEnv.ITERABLE_ENABLE_WRITES ?? result.ITERABLE_ENABLE_WRITES);
|
|
97
|
-
result.ITERABLE_ENABLE_SENDS = normalize(keyEnv.ITERABLE_ENABLE_SENDS ?? result.ITERABLE_ENABLE_SENDS);
|
|
98
|
-
}
|
|
99
|
-
result.ITERABLE_USER_PII = normalize(result.ITERABLE_USER_PII);
|
|
100
|
-
result.ITERABLE_ENABLE_WRITES = normalize(result.ITERABLE_ENABLE_WRITES);
|
|
101
|
-
result.ITERABLE_ENABLE_SENDS = normalize(result.ITERABLE_ENABLE_SENDS);
|
|
102
|
-
return result;
|
|
103
|
-
}
|
|
104
87
|
/**
|
|
105
88
|
* Pick only permission-related env flags for persistence into key metadata.
|
|
106
89
|
*/
|
|
@@ -146,16 +129,13 @@ export const setupMcpServer = async () => {
|
|
|
146
129
|
const advanced = args.includes("--advanced");
|
|
147
130
|
const autoUpdate = args.includes("--auto-update");
|
|
148
131
|
let tools = [
|
|
149
|
-
...(args.includes("--claude-desktop") ? ["claude-desktop"] : []),
|
|
150
132
|
...(args.includes("--cursor") ? ["cursor"] : []),
|
|
133
|
+
...(args.includes("--claude-desktop") ? ["claude-desktop"] : []),
|
|
151
134
|
...(args.includes("--claude-code") ? ["claude-code"] : []),
|
|
152
135
|
...(args.includes("--gemini-cli") ? ["gemini-cli"] : []),
|
|
136
|
+
...(args.includes("--windsurf") ? ["windsurf"] : []),
|
|
153
137
|
...(args.includes("--manual") ? ["manual"] : []),
|
|
154
138
|
];
|
|
155
|
-
// Detect how the command was invoked
|
|
156
|
-
const isNpx = process.argv[1]?.includes("npx") ||
|
|
157
|
-
process.env.npm_execpath?.includes("npx");
|
|
158
|
-
const commandName = isNpx ? `npx ${NPX_PACKAGE_NAME}` : LOCAL_BINARY_NAME;
|
|
159
139
|
if (showHelp) {
|
|
160
140
|
const { createTable, icons, showBox, showIterableLogo, showSection } = await import("./utils/ui.js");
|
|
161
141
|
const chalk = (await import("chalk")).default;
|
|
@@ -168,11 +148,14 @@ export const setupMcpServer = async () => {
|
|
|
168
148
|
colWidths: [45, 40],
|
|
169
149
|
style: "normal",
|
|
170
150
|
});
|
|
171
|
-
setupTable.push([`${
|
|
172
|
-
`${
|
|
151
|
+
setupTable.push([`${COMMAND_NAME} setup --cursor`, "Configure for Cursor"], [
|
|
152
|
+
`${COMMAND_NAME} setup --claude-desktop`,
|
|
153
|
+
"Configure for Claude Desktop",
|
|
154
|
+
], [`${COMMAND_NAME} setup --claude-code`, "Configure for Claude Code"], [`${COMMAND_NAME} setup --gemini-cli`, "Configure for Gemini CLI"], [`${COMMAND_NAME} setup --windsurf`, "Configure for Windsurf"], [`${COMMAND_NAME} setup --manual`, "Show manual config instructions"], [
|
|
155
|
+
`${COMMAND_NAME} setup --cursor --claude-desktop`,
|
|
173
156
|
"Configure multiple tools",
|
|
174
|
-
], [`${
|
|
175
|
-
`${
|
|
157
|
+
], [`${COMMAND_NAME} setup --cursor --debug`, "Enable debug logging"], [
|
|
158
|
+
`${COMMAND_NAME} setup --cursor --auto-update`,
|
|
176
159
|
"Always use latest version",
|
|
177
160
|
]);
|
|
178
161
|
console.log(setupTable.toString());
|
|
@@ -185,7 +168,7 @@ export const setupMcpServer = async () => {
|
|
|
185
168
|
colWidths: [45, 40],
|
|
186
169
|
style: "normal",
|
|
187
170
|
});
|
|
188
|
-
keysTable.push(
|
|
171
|
+
keysTable.push(...KEYS_COMMAND_TABLE);
|
|
189
172
|
console.log(keysTable.toString());
|
|
190
173
|
console.log();
|
|
191
174
|
console.log();
|
|
@@ -213,7 +196,7 @@ export const setupMcpServer = async () => {
|
|
|
213
196
|
console.log();
|
|
214
197
|
showBox("Quick Start", [
|
|
215
198
|
chalk.white.bold("1. Run setup for your AI tool:"),
|
|
216
|
-
chalk.cyan(` ${
|
|
199
|
+
chalk.cyan(` ${COMMAND_NAME} setup --cursor`),
|
|
217
200
|
"",
|
|
218
201
|
chalk.white.bold("2. Restart your AI tool"),
|
|
219
202
|
"",
|
|
@@ -227,38 +210,11 @@ export const setupMcpServer = async () => {
|
|
|
227
210
|
], { icon: icons.lock, theme: "info", padding: 1 });
|
|
228
211
|
return;
|
|
229
212
|
}
|
|
230
|
-
//
|
|
231
|
-
if (tools.length === 0) {
|
|
232
|
-
// Show logo first when no flags provided
|
|
233
|
-
const { showIterableLogo } = await import("./utils/ui.js");
|
|
234
|
-
console.clear();
|
|
235
|
-
showIterableLogo(packageJson.version);
|
|
236
|
-
const { selectedTools } = await inquirer.prompt([
|
|
237
|
-
{
|
|
238
|
-
type: "checkbox",
|
|
239
|
-
name: "selectedTools",
|
|
240
|
-
message: "Select your AI tools to configure:",
|
|
241
|
-
choices: [
|
|
242
|
-
{ name: "Cursor", value: "cursor" },
|
|
243
|
-
{ name: "Claude Desktop", value: "claude-desktop" },
|
|
244
|
-
{ name: "Claude Code (CLI)", value: "claude-code" },
|
|
245
|
-
{ name: "Gemini CLI", value: "gemini-cli" },
|
|
246
|
-
{ name: "Other / Manual Setup", value: "manual" },
|
|
247
|
-
],
|
|
248
|
-
validate: (arr) => Array.isArray(arr) && arr.length > 0
|
|
249
|
-
? true
|
|
250
|
-
: "Please select at least one tool",
|
|
251
|
-
},
|
|
252
|
-
]);
|
|
253
|
-
tools = selectedTools;
|
|
254
|
-
}
|
|
255
|
-
// Start the setup flow
|
|
213
|
+
// Start the setup flow - show logo and load UI
|
|
256
214
|
console.clear();
|
|
257
|
-
const { formatKeyValue, icons, showCompletion, showError, showInfo, showIterableLogo, showSection, showSuccess, showWarning, linkColor,
|
|
215
|
+
const { formatKeyValue, icons, showCompletion, showError, showInfo, showIterableLogo, showSection, showSuccess, showWarning, linkColor, } = await import("./utils/ui.js");
|
|
258
216
|
const chalk = (await import("chalk")).default;
|
|
259
217
|
showIterableLogo(packageJson.version);
|
|
260
|
-
// Succinct overview of what will be configured
|
|
261
|
-
console.log(chalk.gray(`Running setup to configure: ${tools.map((t) => TOOL_NAMES[t]).join(", ")}`));
|
|
262
218
|
console.log();
|
|
263
219
|
const ora = (await import("ora")).default;
|
|
264
220
|
const spinner = ora();
|
|
@@ -268,9 +224,6 @@ export const setupMcpServer = async () => {
|
|
|
268
224
|
showSection("API Key Configuration", icons.key);
|
|
269
225
|
console.log();
|
|
270
226
|
let apiKey;
|
|
271
|
-
let keyName;
|
|
272
|
-
let baseUrl;
|
|
273
|
-
let usedKeyName;
|
|
274
227
|
let usedExistingKey = false;
|
|
275
228
|
let selectedExistingMeta;
|
|
276
229
|
// 1) Offer using API key from environment
|
|
@@ -303,6 +256,8 @@ export const setupMcpServer = async () => {
|
|
|
303
256
|
]);
|
|
304
257
|
if (useExisting) {
|
|
305
258
|
const { formatKeychainChoiceLabel } = await import("./utils/ui.js");
|
|
259
|
+
// Find the active key to use as default
|
|
260
|
+
const activeKey = keys.find((k) => k.isActive);
|
|
306
261
|
const { chosenId } = await inquirer.prompt([
|
|
307
262
|
{
|
|
308
263
|
type: "list",
|
|
@@ -313,6 +268,7 @@ export const setupMcpServer = async () => {
|
|
|
313
268
|
value: k.id,
|
|
314
269
|
short: k.name,
|
|
315
270
|
})),
|
|
271
|
+
default: activeKey?.id,
|
|
316
272
|
pageSize: Math.min(10, keys.length),
|
|
317
273
|
},
|
|
318
274
|
]);
|
|
@@ -320,11 +276,8 @@ export const setupMcpServer = async () => {
|
|
|
320
276
|
const meta = keys.find((k) => k.id === chosenId);
|
|
321
277
|
if (!meta)
|
|
322
278
|
throw new Error("Selected key metadata not found");
|
|
323
|
-
baseUrl = meta.baseUrl;
|
|
324
|
-
usedKeyName = meta.name;
|
|
325
279
|
usedExistingKey = true;
|
|
326
280
|
selectedExistingMeta = meta;
|
|
327
|
-
showSuccess(`Using existing key "${meta.name}"`);
|
|
328
281
|
}
|
|
329
282
|
catch (e) {
|
|
330
283
|
showError(e instanceof Error
|
|
@@ -333,275 +286,128 @@ export const setupMcpServer = async () => {
|
|
|
333
286
|
process.exit(1);
|
|
334
287
|
}
|
|
335
288
|
}
|
|
289
|
+
else {
|
|
290
|
+
console.log();
|
|
291
|
+
}
|
|
336
292
|
}
|
|
337
293
|
}
|
|
338
|
-
// 3) If neither env nor existing key chosen, prompt for a new key
|
|
339
294
|
if (!apiKey && !usedExistingKey) {
|
|
340
|
-
const
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
if (!baseUrl) {
|
|
362
|
-
if (process.env.ITERABLE_BASE_URL) {
|
|
363
|
-
baseUrl = process.env.ITERABLE_BASE_URL;
|
|
364
|
-
showSuccess(`Using API endpoint from environment: ${baseUrl}`);
|
|
295
|
+
const keyManager = getKeyManager();
|
|
296
|
+
await keyManager.initialize();
|
|
297
|
+
const { getSpinner } = await import("./utils/cli-env.js");
|
|
298
|
+
const keySpinner = await getSpinner();
|
|
299
|
+
try {
|
|
300
|
+
const newKeyId = await saveKeyInteractive(keyManager, null, {
|
|
301
|
+
chalk,
|
|
302
|
+
icons,
|
|
303
|
+
showError,
|
|
304
|
+
showSuccess,
|
|
305
|
+
showInfo,
|
|
306
|
+
formatKeyValue,
|
|
307
|
+
linkColor,
|
|
308
|
+
showBox: (await import("./utils/ui.js")).showBox,
|
|
309
|
+
}, keySpinner, {
|
|
310
|
+
skipRestartNotice: true,
|
|
311
|
+
advanced,
|
|
312
|
+
autoActivate: true,
|
|
313
|
+
});
|
|
314
|
+
selectedExistingMeta =
|
|
315
|
+
(await keyManager.getKeyMetadata(newKeyId)) ?? undefined;
|
|
365
316
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
baseUrl = await promptForIterableBaseUrl({
|
|
370
|
-
inquirer,
|
|
371
|
-
icons,
|
|
372
|
-
chalk,
|
|
373
|
-
showError,
|
|
374
|
-
});
|
|
375
|
-
}
|
|
376
|
-
catch {
|
|
377
|
-
process.exit(1);
|
|
378
|
-
}
|
|
317
|
+
catch (error) {
|
|
318
|
+
showError(error instanceof Error ? error.message : "Failed to add key");
|
|
319
|
+
process.exit(1);
|
|
379
320
|
}
|
|
380
321
|
}
|
|
381
|
-
else {
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
console.log(formatKeyValue("Selected endpoint", baseUrl, linkColor()));
|
|
386
|
-
console.log();
|
|
387
|
-
// Step 3: Store the key securely
|
|
388
|
-
let existingKeyWithValue = null;
|
|
389
|
-
const keyManager = getKeyManager();
|
|
390
|
-
spinner.start("Initializing secure key storage...");
|
|
391
|
-
await keyManager.initialize();
|
|
392
|
-
spinner.succeed("Key storage ready");
|
|
393
|
-
// Check if key already exists
|
|
394
|
-
if (!usedExistingKey && apiKey) {
|
|
395
|
-
spinner.start("Checking for existing keys...");
|
|
396
|
-
existingKeyWithValue = await keyManager.findKeyByValue(apiKey);
|
|
397
|
-
spinner.stop();
|
|
398
|
-
}
|
|
399
|
-
if (usedExistingKey) {
|
|
400
|
-
// Offer to activate the selected existing key if not already active
|
|
322
|
+
else if (usedExistingKey) {
|
|
323
|
+
const keyManager = getKeyManager();
|
|
324
|
+
await keyManager.initialize();
|
|
325
|
+
console.log();
|
|
401
326
|
if (!selectedExistingMeta?.isActive) {
|
|
402
|
-
const {
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
},
|
|
409
|
-
]);
|
|
410
|
-
if (activateExisting) {
|
|
411
|
-
await keyManager.setActiveKey(selectedExistingMeta.id);
|
|
412
|
-
showSuccess(`"${selectedExistingMeta?.name}" is now your active key`);
|
|
413
|
-
}
|
|
414
|
-
else {
|
|
415
|
-
showInfo("Keeping your current active key");
|
|
416
|
-
}
|
|
327
|
+
const { getSpinner } = await import("./utils/cli-env.js");
|
|
328
|
+
const activateSpinner = await getSpinner();
|
|
329
|
+
activateSpinner.start("Activating key...");
|
|
330
|
+
await keyManager.setActiveKey(selectedExistingMeta.id);
|
|
331
|
+
activateSpinner.succeed("Key activated successfully");
|
|
332
|
+
showSuccess(`"${selectedExistingMeta?.name}" is now your active API key`);
|
|
417
333
|
}
|
|
418
334
|
else {
|
|
419
335
|
showSuccess(`"${selectedExistingMeta?.name}" is already your active key`);
|
|
420
336
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
name: "activateExisting",
|
|
430
|
-
message: `Set "${existingKeyWithValue.name}" as your active API key?`,
|
|
431
|
-
default: true,
|
|
432
|
-
},
|
|
433
|
-
]);
|
|
434
|
-
if (activateExisting) {
|
|
435
|
-
await keyManager.setActiveKey(existingKeyWithValue.id);
|
|
436
|
-
showSuccess(`"${existingKeyWithValue.name}" is now your active key`);
|
|
437
|
-
}
|
|
438
|
-
else {
|
|
439
|
-
showInfo("Keeping your current active key");
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
else {
|
|
443
|
-
showSuccess(`"${existingKeyWithValue.name}" is already your active key`);
|
|
444
|
-
}
|
|
337
|
+
console.log();
|
|
338
|
+
displayKeyDetails(selectedExistingMeta, {
|
|
339
|
+
chalk,
|
|
340
|
+
formatKeyValue,
|
|
341
|
+
linkColor,
|
|
342
|
+
});
|
|
343
|
+
console.log();
|
|
344
|
+
showInfo(`To modify permissions, run: ${chalk.cyan(`${COMMAND_NAME} keys update "${selectedExistingMeta?.name}"`)}`);
|
|
445
345
|
}
|
|
446
346
|
else {
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
name: "newKeyName",
|
|
452
|
-
message: "Enter a name for this API key:",
|
|
453
|
-
default: "default",
|
|
454
|
-
validate: (input) => {
|
|
455
|
-
if (input && input.length > 50)
|
|
456
|
-
return "Key name must be 50 characters or less";
|
|
457
|
-
return true;
|
|
458
|
-
},
|
|
459
|
-
},
|
|
460
|
-
]);
|
|
461
|
-
keyName = newKeyName || "default";
|
|
462
|
-
spinner.start("Storing API key securely...");
|
|
347
|
+
const keyManager = getKeyManager();
|
|
348
|
+
await keyManager.initialize();
|
|
349
|
+
const { getSpinner } = await import("./utils/cli-env.js");
|
|
350
|
+
const keySpinner = await getSpinner();
|
|
463
351
|
try {
|
|
464
|
-
const newKeyId = await keyManager
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
},
|
|
482
|
-
]);
|
|
483
|
-
if (activateNew) {
|
|
484
|
-
await keyManager.setActiveKey(newKeyId);
|
|
485
|
-
showSuccess(`"${keyName}" is now your active key`);
|
|
486
|
-
}
|
|
487
|
-
else {
|
|
488
|
-
showInfo(`Keeping your current active key. Run 'iterable-mcp keys activate "${keyName}"' to switch later.`);
|
|
489
|
-
}
|
|
490
|
-
}
|
|
352
|
+
const newKeyId = await saveKeyInteractive(keyManager, null, {
|
|
353
|
+
chalk,
|
|
354
|
+
icons,
|
|
355
|
+
showError,
|
|
356
|
+
showSuccess,
|
|
357
|
+
showInfo,
|
|
358
|
+
formatKeyValue,
|
|
359
|
+
linkColor,
|
|
360
|
+
showBox: (await import("./utils/ui.js")).showBox,
|
|
361
|
+
}, keySpinner, {
|
|
362
|
+
skipRestartNotice: true,
|
|
363
|
+
advanced,
|
|
364
|
+
prefilledApiKey: apiKey,
|
|
365
|
+
autoActivate: true,
|
|
366
|
+
});
|
|
367
|
+
selectedExistingMeta =
|
|
368
|
+
(await keyManager.getKeyMetadata(newKeyId)) ?? undefined;
|
|
491
369
|
}
|
|
492
370
|
catch (error) {
|
|
493
|
-
|
|
494
|
-
spinner.fail("Failed to store API key");
|
|
495
|
-
console.log();
|
|
496
|
-
const msg = error instanceof Error
|
|
497
|
-
? sanitizeString(error.message)
|
|
498
|
-
: "Unknown error";
|
|
499
|
-
showError(msg);
|
|
500
|
-
console.log();
|
|
501
|
-
showInfo("Your API key was not stored. You can re-run 'iterable-mcp setup' to try again.");
|
|
502
|
-
showInfo("If the problem persists, verify storage access and disk permissions.");
|
|
371
|
+
showError(error instanceof Error ? error.message : "Failed to add key");
|
|
503
372
|
process.exit(1);
|
|
504
373
|
}
|
|
505
374
|
}
|
|
506
|
-
|
|
375
|
+
if (!selectedExistingMeta) {
|
|
376
|
+
showError("Failed to get key metadata after setup");
|
|
377
|
+
process.exit(1);
|
|
378
|
+
}
|
|
379
|
+
let mcpEnv = pickPersistablePermissionEnv(selectedExistingMeta.env || {});
|
|
380
|
+
if (args.includes("--debug")) {
|
|
381
|
+
mcpEnv.ITERABLE_DEBUG = "true";
|
|
382
|
+
mcpEnv.LOG_LEVEL = "debug";
|
|
383
|
+
}
|
|
384
|
+
mcpEnv = enforceSendsRequiresWrites(mcpEnv, (msg) => showWarning(msg));
|
|
385
|
+
// Step 5: Configure AI Tools
|
|
507
386
|
console.log();
|
|
508
|
-
showSection("
|
|
387
|
+
showSection("Configure AI Tools", icons.zap);
|
|
509
388
|
console.log();
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
ITERABLE_USER_PII: "false",
|
|
513
|
-
ITERABLE_ENABLE_WRITES: "false",
|
|
514
|
-
ITERABLE_ENABLE_SENDS: "false",
|
|
515
|
-
};
|
|
516
|
-
if (advanced) {
|
|
517
|
-
const { permFlags } = await inquirer.prompt([
|
|
389
|
+
if (tools.length === 0) {
|
|
390
|
+
const { selectedTools } = await inquirer.prompt([
|
|
518
391
|
{
|
|
519
392
|
type: "checkbox",
|
|
520
|
-
name: "
|
|
521
|
-
message: "Select
|
|
393
|
+
name: "selectedTools",
|
|
394
|
+
message: "Select your AI tools to configure:",
|
|
522
395
|
choices: [
|
|
523
|
-
{ name: "
|
|
524
|
-
{
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
},
|
|
528
|
-
{ name: "
|
|
396
|
+
{ name: "Cursor", value: "cursor" },
|
|
397
|
+
{ name: "Claude Desktop", value: "claude-desktop" },
|
|
398
|
+
{ name: "Claude Code (CLI)", value: "claude-code" },
|
|
399
|
+
{ name: "Gemini CLI", value: "gemini-cli" },
|
|
400
|
+
{ name: "Windsurf", value: "windsurf" },
|
|
401
|
+
{ name: "Other / Manual Setup", value: "manual" },
|
|
529
402
|
],
|
|
530
|
-
|
|
531
|
-
|
|
403
|
+
validate: (arr) => Array.isArray(arr) && arr.length > 0
|
|
404
|
+
? true
|
|
405
|
+
: "Please select at least one tool",
|
|
532
406
|
},
|
|
533
407
|
]);
|
|
534
|
-
|
|
535
|
-
ITERABLE_USER_PII: permFlags.includes("pii")
|
|
536
|
-
? "true"
|
|
537
|
-
: "false",
|
|
538
|
-
ITERABLE_ENABLE_WRITES: permFlags.includes("writes")
|
|
539
|
-
? "true"
|
|
540
|
-
: "false",
|
|
541
|
-
ITERABLE_ENABLE_SENDS: permFlags.includes("sends")
|
|
542
|
-
? "true"
|
|
543
|
-
: "false",
|
|
544
|
-
};
|
|
545
|
-
}
|
|
546
|
-
let mcpEnv = {
|
|
547
|
-
ITERABLE_USER_PII: selectedEnv.ITERABLE_USER_PII,
|
|
548
|
-
ITERABLE_ENABLE_WRITES: selectedEnv.ITERABLE_ENABLE_WRITES,
|
|
549
|
-
ITERABLE_ENABLE_SENDS: selectedEnv.ITERABLE_ENABLE_SENDS,
|
|
550
|
-
};
|
|
551
|
-
if (args.includes("--debug")) {
|
|
552
|
-
mcpEnv.ITERABLE_DEBUG = "true";
|
|
553
|
-
mcpEnv.LOG_LEVEL = "debug";
|
|
554
|
-
}
|
|
555
|
-
// Enforce permission invariants before displaying
|
|
556
|
-
mcpEnv = enforceSendsRequiresWrites(mcpEnv, (msg) => showWarning(msg));
|
|
557
|
-
console.log();
|
|
558
|
-
console.log(formatKeyValue("User PII Access", mcpEnv.ITERABLE_USER_PII === "true"
|
|
559
|
-
? chalk.green("Enabled")
|
|
560
|
-
: chalk.gray("Disabled")));
|
|
561
|
-
console.log(formatKeyValue("Write Operations", mcpEnv.ITERABLE_ENABLE_WRITES === "true"
|
|
562
|
-
? chalk.green("Enabled")
|
|
563
|
-
: chalk.gray("Disabled")));
|
|
564
|
-
console.log(formatKeyValue("Sends", mcpEnv.ITERABLE_ENABLE_SENDS === "true"
|
|
565
|
-
? chalk.green("Enabled")
|
|
566
|
-
: chalk.gray("Disabled")));
|
|
567
|
-
// Only show the default note when all flags are disabled
|
|
568
|
-
if (mcpEnv.ITERABLE_USER_PII !== "true" &&
|
|
569
|
-
mcpEnv.ITERABLE_ENABLE_WRITES !== "true" &&
|
|
570
|
-
mcpEnv.ITERABLE_ENABLE_SENDS !== "true") {
|
|
571
|
-
console.log();
|
|
572
|
-
console.log(chalk.gray("Note: These are disabled by default. If you absolutely need to enable these, run 'iterable-mcp setup --advanced'"));
|
|
573
|
-
}
|
|
574
|
-
else {
|
|
408
|
+
tools = selectedTools;
|
|
575
409
|
console.log();
|
|
576
|
-
showWarning("Warning: Elevated capabilities enabled. Sends, writes, and/or PII access may now occur.");
|
|
577
|
-
}
|
|
578
|
-
console.log();
|
|
579
|
-
// If we used an existing key, update its env overrides with chosen settings
|
|
580
|
-
if (usedKeyName) {
|
|
581
|
-
try {
|
|
582
|
-
await keyManager.updateKeyEnv(usedKeyName, pickPersistablePermissionEnv(mcpEnv));
|
|
583
|
-
// Re-read the key metadata and prefer its persisted env when writing
|
|
584
|
-
// tool configurations. This addresses cases where the key was activated
|
|
585
|
-
// during setup and ensures configs reflect the updated values.
|
|
586
|
-
const keys = await keyManager.listKeys();
|
|
587
|
-
const updatedMeta = keys.find((k) => k.name === usedKeyName || k.id === usedKeyName);
|
|
588
|
-
if (updatedMeta?.env) {
|
|
589
|
-
mcpEnv = resolveFinalMcpEnv(mcpEnv, updatedMeta.env);
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
catch (err) {
|
|
593
|
-
if (process.env.ITERABLE_DEBUG === "true") {
|
|
594
|
-
console.warn("Non-fatal: failed to persist per-key env settings", err);
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
410
|
}
|
|
598
|
-
// Enforce again after merging persisted key env
|
|
599
|
-
mcpEnv = enforceSendsRequiresWrites(mcpEnv, (msg) => showWarning(msg));
|
|
600
|
-
// Step 5: Configure AI Tools
|
|
601
|
-
console.log();
|
|
602
|
-
showSection("Configuring AI Tools", icons.zap);
|
|
603
|
-
console.log();
|
|
604
|
-
// Ask about auto-update unless flag was provided
|
|
605
411
|
let useAutoUpdate = autoUpdate;
|
|
606
412
|
if (!autoUpdate) {
|
|
607
413
|
const { enableAutoUpdate } = await inquirer.prompt([
|
|
@@ -612,6 +418,7 @@ export const setupMcpServer = async () => {
|
|
|
612
418
|
default: false,
|
|
613
419
|
},
|
|
614
420
|
]);
|
|
421
|
+
console.log();
|
|
615
422
|
useAutoUpdate = enableAutoUpdate;
|
|
616
423
|
}
|
|
617
424
|
const iterableMcpConfig = buildMcpConfig({
|
|
@@ -619,35 +426,6 @@ export const setupMcpServer = async () => {
|
|
|
619
426
|
env: omitPersistablePermissionEnv(mcpEnv),
|
|
620
427
|
autoUpdate: useAutoUpdate,
|
|
621
428
|
});
|
|
622
|
-
// Preflight confirmation summary before applying changes
|
|
623
|
-
console.log(chalk.gray("Summary:"));
|
|
624
|
-
console.log(formatKeyValue("Tools", tools.map((t) => TOOL_NAMES[t]).join(", "), valueColor()));
|
|
625
|
-
if (usedKeyName) {
|
|
626
|
-
console.log(formatKeyValue("API Key", usedKeyName, valueColor()));
|
|
627
|
-
}
|
|
628
|
-
console.log(formatKeyValue("Endpoint", baseUrl, valueColor()));
|
|
629
|
-
console.log(formatKeyValue("User PII", mcpEnv.ITERABLE_USER_PII === "true"
|
|
630
|
-
? chalk.green("Enabled")
|
|
631
|
-
: chalk.gray("Disabled")));
|
|
632
|
-
console.log(formatKeyValue("Writes", mcpEnv.ITERABLE_ENABLE_WRITES === "true"
|
|
633
|
-
? chalk.green("Enabled")
|
|
634
|
-
: chalk.gray("Disabled")));
|
|
635
|
-
console.log(formatKeyValue("Sends", mcpEnv.ITERABLE_ENABLE_SENDS === "true"
|
|
636
|
-
? chalk.green("Enabled")
|
|
637
|
-
: chalk.gray("Disabled")));
|
|
638
|
-
console.log();
|
|
639
|
-
const { proceed } = await inquirer.prompt([
|
|
640
|
-
{
|
|
641
|
-
type: "confirm",
|
|
642
|
-
name: "proceed",
|
|
643
|
-
message: "Proceed with configuration?",
|
|
644
|
-
default: true,
|
|
645
|
-
},
|
|
646
|
-
]);
|
|
647
|
-
if (!proceed) {
|
|
648
|
-
showInfo("Setup cancelled by user. No changes were made.");
|
|
649
|
-
return;
|
|
650
|
-
}
|
|
651
429
|
const fileBasedTools = tools.filter((tool) => tool in TOOL_CONFIGS);
|
|
652
430
|
const needsClaudeCode = tools.includes("claude-code");
|
|
653
431
|
const needsManual = tools.includes("manual");
|
|
@@ -677,11 +455,10 @@ export const setupMcpServer = async () => {
|
|
|
677
455
|
spinner.fail("Claude CLI not found");
|
|
678
456
|
showError("Please install Claude Code first: https://docs.claude.com/en/docs/claude-code/overview");
|
|
679
457
|
console.log();
|
|
680
|
-
showInfo(
|
|
458
|
+
showInfo(`After installing, re-run: ${COMMAND_NAME} setup --claude-code`);
|
|
681
459
|
process.exit(1);
|
|
682
460
|
}
|
|
683
461
|
const configJson = JSON.stringify(iterableMcpConfig);
|
|
684
|
-
// Remove existing config (ignore errors)
|
|
685
462
|
await execFileAsync("claude", ["mcp", "remove", "iterable"]).catch(() => { });
|
|
686
463
|
spinner.start("Configuring Claude Code...");
|
|
687
464
|
await new Promise((resolve, reject) => {
|
|
@@ -701,11 +478,9 @@ export const setupMcpServer = async () => {
|
|
|
701
478
|
spinner.succeed("Claude Code configured successfully");
|
|
702
479
|
}
|
|
703
480
|
if (needsManual) {
|
|
704
|
-
console.log();
|
|
705
481
|
showSection("Manual Configuration", icons.rocket);
|
|
706
482
|
console.log();
|
|
707
483
|
const { type, command, args, env } = iterableMcpConfig;
|
|
708
|
-
showInfo("Your API key has been stored.");
|
|
709
484
|
showInfo("Add the MCP server to your AI tool with these settings:");
|
|
710
485
|
console.log();
|
|
711
486
|
console.log(chalk.white.bold(" Type:") + ` ${type}`);
|
|
@@ -721,16 +496,17 @@ export const setupMcpServer = async () => {
|
|
|
721
496
|
console.log();
|
|
722
497
|
showInfo("Refer to your AI tool's documentation for configuration instructions.");
|
|
723
498
|
}
|
|
724
|
-
// Build configured tools list
|
|
725
499
|
const configuredTools = [];
|
|
726
500
|
if (fileBasedTools.includes("cursor"))
|
|
727
501
|
configuredTools.push("Cursor");
|
|
728
502
|
if (fileBasedTools.includes("claude-desktop"))
|
|
729
503
|
configuredTools.push("Claude Desktop");
|
|
730
|
-
if (fileBasedTools.includes("gemini-cli"))
|
|
731
|
-
configuredTools.push("Gemini CLI");
|
|
732
504
|
if (needsClaudeCode)
|
|
733
505
|
configuredTools.push("Claude Code");
|
|
506
|
+
if (fileBasedTools.includes("gemini-cli"))
|
|
507
|
+
configuredTools.push("Gemini CLI");
|
|
508
|
+
if (fileBasedTools.includes("windsurf"))
|
|
509
|
+
configuredTools.push("Windsurf");
|
|
734
510
|
if (needsManual)
|
|
735
511
|
configuredTools.push("your AI tool");
|
|
736
512
|
const toolsList = configuredTools.length === 1
|
|
@@ -740,7 +516,6 @@ export const setupMcpServer = async () => {
|
|
|
740
516
|
: configuredTools.slice(0, -1).join(", ") +
|
|
741
517
|
", and " +
|
|
742
518
|
configuredTools[configuredTools.length - 1];
|
|
743
|
-
// Success!
|
|
744
519
|
console.log();
|
|
745
520
|
const nextSteps = [
|
|
746
521
|
...(needsManual ? ["Configure your AI tool as described above"] : []),
|
|
@@ -752,8 +527,8 @@ export const setupMcpServer = async () => {
|
|
|
752
527
|
];
|
|
753
528
|
const tips = [
|
|
754
529
|
`Try: 'list my Iterable campaigns' in ${toolsList}`,
|
|
755
|
-
|
|
756
|
-
|
|
530
|
+
`Manage keys with '${COMMAND_NAME} keys list'`,
|
|
531
|
+
`Switch keys with '${COMMAND_NAME} keys activate <name>'`,
|
|
757
532
|
];
|
|
758
533
|
showCompletion("Setup Complete!", nextSteps, tips);
|
|
759
534
|
}
|