@iterable/mcp 1.2.2 → 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.map +1 -1
- package/dist/config.js +2 -1
- package/dist/config.js.map +1 -1
- package/dist/install.d.ts.map +1 -1
- package/dist/install.js +121 -318
- 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 +453 -293
- package/dist/keys-cli.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);
|
|
@@ -128,16 +129,13 @@ export const setupMcpServer = async () => {
|
|
|
128
129
|
const advanced = args.includes("--advanced");
|
|
129
130
|
const autoUpdate = args.includes("--auto-update");
|
|
130
131
|
let tools = [
|
|
131
|
-
...(args.includes("--claude-desktop") ? ["claude-desktop"] : []),
|
|
132
132
|
...(args.includes("--cursor") ? ["cursor"] : []),
|
|
133
|
+
...(args.includes("--claude-desktop") ? ["claude-desktop"] : []),
|
|
133
134
|
...(args.includes("--claude-code") ? ["claude-code"] : []),
|
|
134
135
|
...(args.includes("--gemini-cli") ? ["gemini-cli"] : []),
|
|
136
|
+
...(args.includes("--windsurf") ? ["windsurf"] : []),
|
|
135
137
|
...(args.includes("--manual") ? ["manual"] : []),
|
|
136
138
|
];
|
|
137
|
-
// Detect how the command was invoked
|
|
138
|
-
const isNpx = process.argv[1]?.includes("npx") ||
|
|
139
|
-
process.env.npm_execpath?.includes("npx");
|
|
140
|
-
const commandName = isNpx ? `npx ${NPX_PACKAGE_NAME}` : LOCAL_BINARY_NAME;
|
|
141
139
|
if (showHelp) {
|
|
142
140
|
const { createTable, icons, showBox, showIterableLogo, showSection } = await import("./utils/ui.js");
|
|
143
141
|
const chalk = (await import("chalk")).default;
|
|
@@ -150,11 +148,14 @@ export const setupMcpServer = async () => {
|
|
|
150
148
|
colWidths: [45, 40],
|
|
151
149
|
style: "normal",
|
|
152
150
|
});
|
|
153
|
-
setupTable.push([`${
|
|
154
|
-
`${
|
|
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`,
|
|
155
156
|
"Configure multiple tools",
|
|
156
|
-
], [`${
|
|
157
|
-
`${
|
|
157
|
+
], [`${COMMAND_NAME} setup --cursor --debug`, "Enable debug logging"], [
|
|
158
|
+
`${COMMAND_NAME} setup --cursor --auto-update`,
|
|
158
159
|
"Always use latest version",
|
|
159
160
|
]);
|
|
160
161
|
console.log(setupTable.toString());
|
|
@@ -167,7 +168,7 @@ export const setupMcpServer = async () => {
|
|
|
167
168
|
colWidths: [45, 40],
|
|
168
169
|
style: "normal",
|
|
169
170
|
});
|
|
170
|
-
keysTable.push(
|
|
171
|
+
keysTable.push(...KEYS_COMMAND_TABLE);
|
|
171
172
|
console.log(keysTable.toString());
|
|
172
173
|
console.log();
|
|
173
174
|
console.log();
|
|
@@ -195,7 +196,7 @@ export const setupMcpServer = async () => {
|
|
|
195
196
|
console.log();
|
|
196
197
|
showBox("Quick Start", [
|
|
197
198
|
chalk.white.bold("1. Run setup for your AI tool:"),
|
|
198
|
-
chalk.cyan(` ${
|
|
199
|
+
chalk.cyan(` ${COMMAND_NAME} setup --cursor`),
|
|
199
200
|
"",
|
|
200
201
|
chalk.white.bold("2. Restart your AI tool"),
|
|
201
202
|
"",
|
|
@@ -209,38 +210,11 @@ export const setupMcpServer = async () => {
|
|
|
209
210
|
], { icon: icons.lock, theme: "info", padding: 1 });
|
|
210
211
|
return;
|
|
211
212
|
}
|
|
212
|
-
//
|
|
213
|
-
if (tools.length === 0) {
|
|
214
|
-
// Show logo first when no flags provided
|
|
215
|
-
const { showIterableLogo } = await import("./utils/ui.js");
|
|
216
|
-
console.clear();
|
|
217
|
-
showIterableLogo(packageJson.version);
|
|
218
|
-
const { selectedTools } = await inquirer.prompt([
|
|
219
|
-
{
|
|
220
|
-
type: "checkbox",
|
|
221
|
-
name: "selectedTools",
|
|
222
|
-
message: "Select your AI tools to configure:",
|
|
223
|
-
choices: [
|
|
224
|
-
{ name: "Cursor", value: "cursor" },
|
|
225
|
-
{ name: "Claude Desktop", value: "claude-desktop" },
|
|
226
|
-
{ name: "Claude Code (CLI)", value: "claude-code" },
|
|
227
|
-
{ name: "Gemini CLI", value: "gemini-cli" },
|
|
228
|
-
{ name: "Other / Manual Setup", value: "manual" },
|
|
229
|
-
],
|
|
230
|
-
validate: (arr) => Array.isArray(arr) && arr.length > 0
|
|
231
|
-
? true
|
|
232
|
-
: "Please select at least one tool",
|
|
233
|
-
},
|
|
234
|
-
]);
|
|
235
|
-
tools = selectedTools;
|
|
236
|
-
}
|
|
237
|
-
// Start the setup flow
|
|
213
|
+
// Start the setup flow - show logo and load UI
|
|
238
214
|
console.clear();
|
|
239
|
-
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");
|
|
240
216
|
const chalk = (await import("chalk")).default;
|
|
241
217
|
showIterableLogo(packageJson.version);
|
|
242
|
-
// Succinct overview of what will be configured
|
|
243
|
-
console.log(chalk.gray(`Running setup to configure: ${tools.map((t) => TOOL_NAMES[t]).join(", ")}`));
|
|
244
218
|
console.log();
|
|
245
219
|
const ora = (await import("ora")).default;
|
|
246
220
|
const spinner = ora();
|
|
@@ -250,9 +224,6 @@ export const setupMcpServer = async () => {
|
|
|
250
224
|
showSection("API Key Configuration", icons.key);
|
|
251
225
|
console.log();
|
|
252
226
|
let apiKey;
|
|
253
|
-
let keyName;
|
|
254
|
-
let baseUrl;
|
|
255
|
-
let usedKeyName;
|
|
256
227
|
let usedExistingKey = false;
|
|
257
228
|
let selectedExistingMeta;
|
|
258
229
|
// 1) Offer using API key from environment
|
|
@@ -285,6 +256,8 @@ export const setupMcpServer = async () => {
|
|
|
285
256
|
]);
|
|
286
257
|
if (useExisting) {
|
|
287
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);
|
|
288
261
|
const { chosenId } = await inquirer.prompt([
|
|
289
262
|
{
|
|
290
263
|
type: "list",
|
|
@@ -295,6 +268,7 @@ export const setupMcpServer = async () => {
|
|
|
295
268
|
value: k.id,
|
|
296
269
|
short: k.name,
|
|
297
270
|
})),
|
|
271
|
+
default: activeKey?.id,
|
|
298
272
|
pageSize: Math.min(10, keys.length),
|
|
299
273
|
},
|
|
300
274
|
]);
|
|
@@ -302,11 +276,8 @@ export const setupMcpServer = async () => {
|
|
|
302
276
|
const meta = keys.find((k) => k.id === chosenId);
|
|
303
277
|
if (!meta)
|
|
304
278
|
throw new Error("Selected key metadata not found");
|
|
305
|
-
baseUrl = meta.baseUrl;
|
|
306
|
-
usedKeyName = meta.name;
|
|
307
279
|
usedExistingKey = true;
|
|
308
280
|
selectedExistingMeta = meta;
|
|
309
|
-
showSuccess(`Using existing key "${meta.name}"`);
|
|
310
281
|
}
|
|
311
282
|
catch (e) {
|
|
312
283
|
showError(e instanceof Error
|
|
@@ -315,265 +286,128 @@ export const setupMcpServer = async () => {
|
|
|
315
286
|
process.exit(1);
|
|
316
287
|
}
|
|
317
288
|
}
|
|
289
|
+
else {
|
|
290
|
+
console.log();
|
|
291
|
+
}
|
|
318
292
|
}
|
|
319
293
|
}
|
|
320
|
-
// 3) If neither env nor existing key chosen, prompt for a new key
|
|
321
294
|
if (!apiKey && !usedExistingKey) {
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
if (!baseUrl) {
|
|
344
|
-
if (process.env.ITERABLE_BASE_URL) {
|
|
345
|
-
baseUrl = process.env.ITERABLE_BASE_URL;
|
|
346
|
-
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;
|
|
347
316
|
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
baseUrl = await promptForIterableBaseUrl({
|
|
352
|
-
inquirer,
|
|
353
|
-
icons,
|
|
354
|
-
chalk,
|
|
355
|
-
showError,
|
|
356
|
-
});
|
|
357
|
-
}
|
|
358
|
-
catch {
|
|
359
|
-
process.exit(1);
|
|
360
|
-
}
|
|
317
|
+
catch (error) {
|
|
318
|
+
showError(error instanceof Error ? error.message : "Failed to add key");
|
|
319
|
+
process.exit(1);
|
|
361
320
|
}
|
|
362
321
|
}
|
|
363
|
-
else {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
console.log(formatKeyValue("Selected endpoint", baseUrl, linkColor()));
|
|
368
|
-
console.log();
|
|
369
|
-
// Step 3: Store the key securely
|
|
370
|
-
let existingKeyWithValue = null;
|
|
371
|
-
const keyManager = getKeyManager();
|
|
372
|
-
spinner.start("Initializing secure key storage...");
|
|
373
|
-
await keyManager.initialize();
|
|
374
|
-
spinner.succeed("Key storage ready");
|
|
375
|
-
// Check if key already exists
|
|
376
|
-
if (!usedExistingKey && apiKey) {
|
|
377
|
-
spinner.start("Checking for existing keys...");
|
|
378
|
-
existingKeyWithValue = await keyManager.findKeyByValue(apiKey);
|
|
379
|
-
spinner.stop();
|
|
380
|
-
}
|
|
381
|
-
if (usedExistingKey) {
|
|
382
|
-
// 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();
|
|
383
326
|
if (!selectedExistingMeta?.isActive) {
|
|
384
|
-
const {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
},
|
|
391
|
-
]);
|
|
392
|
-
if (activateExisting) {
|
|
393
|
-
await keyManager.setActiveKey(selectedExistingMeta.id);
|
|
394
|
-
showSuccess(`"${selectedExistingMeta?.name}" is now your active key`);
|
|
395
|
-
}
|
|
396
|
-
else {
|
|
397
|
-
showInfo("Keeping your current active key");
|
|
398
|
-
}
|
|
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`);
|
|
399
333
|
}
|
|
400
334
|
else {
|
|
401
335
|
showSuccess(`"${selectedExistingMeta?.name}" is already your active key`);
|
|
402
336
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
name: "activateExisting",
|
|
412
|
-
message: `Set "${existingKeyWithValue.name}" as your active API key?`,
|
|
413
|
-
default: true,
|
|
414
|
-
},
|
|
415
|
-
]);
|
|
416
|
-
if (activateExisting) {
|
|
417
|
-
await keyManager.setActiveKey(existingKeyWithValue.id);
|
|
418
|
-
showSuccess(`"${existingKeyWithValue.name}" is now your active key`);
|
|
419
|
-
}
|
|
420
|
-
else {
|
|
421
|
-
showInfo("Keeping your current active key");
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
else {
|
|
425
|
-
showSuccess(`"${existingKeyWithValue.name}" is already your active key`);
|
|
426
|
-
}
|
|
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}"`)}`);
|
|
427
345
|
}
|
|
428
346
|
else {
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
name: "newKeyName",
|
|
434
|
-
message: "Enter a name for this API key:",
|
|
435
|
-
default: "default",
|
|
436
|
-
validate: (input) => {
|
|
437
|
-
if (input && input.length > 50)
|
|
438
|
-
return "Key name must be 50 characters or less";
|
|
439
|
-
return true;
|
|
440
|
-
},
|
|
441
|
-
},
|
|
442
|
-
]);
|
|
443
|
-
keyName = newKeyName || "default";
|
|
444
|
-
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();
|
|
445
351
|
try {
|
|
446
|
-
const newKeyId = await keyManager
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
},
|
|
464
|
-
]);
|
|
465
|
-
if (activateNew) {
|
|
466
|
-
await keyManager.setActiveKey(newKeyId);
|
|
467
|
-
showSuccess(`"${keyName}" is now your active key`);
|
|
468
|
-
}
|
|
469
|
-
else {
|
|
470
|
-
showInfo(`Keeping your current active key. Run 'iterable-mcp keys activate "${keyName}"' to switch later.`);
|
|
471
|
-
}
|
|
472
|
-
}
|
|
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;
|
|
473
369
|
}
|
|
474
370
|
catch (error) {
|
|
475
|
-
|
|
476
|
-
spinner.fail("Failed to store API key");
|
|
477
|
-
console.log();
|
|
478
|
-
const msg = error instanceof Error
|
|
479
|
-
? sanitizeString(error.message)
|
|
480
|
-
: "Unknown error";
|
|
481
|
-
showError(msg);
|
|
482
|
-
console.log();
|
|
483
|
-
showInfo("Your API key was not stored. You can re-run 'iterable-mcp setup' to try again.");
|
|
484
|
-
showInfo("If the problem persists, verify storage access and disk permissions.");
|
|
371
|
+
showError(error instanceof Error ? error.message : "Failed to add key");
|
|
485
372
|
process.exit(1);
|
|
486
373
|
}
|
|
487
374
|
}
|
|
488
|
-
|
|
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
|
|
489
386
|
console.log();
|
|
490
|
-
showSection("
|
|
387
|
+
showSection("Configure AI Tools", icons.zap);
|
|
491
388
|
console.log();
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
ITERABLE_USER_PII: "false",
|
|
495
|
-
ITERABLE_ENABLE_WRITES: "false",
|
|
496
|
-
ITERABLE_ENABLE_SENDS: "false",
|
|
497
|
-
};
|
|
498
|
-
if (advanced) {
|
|
499
|
-
const { permFlags } = await inquirer.prompt([
|
|
389
|
+
if (tools.length === 0) {
|
|
390
|
+
const { selectedTools } = await inquirer.prompt([
|
|
500
391
|
{
|
|
501
392
|
type: "checkbox",
|
|
502
|
-
name: "
|
|
503
|
-
message: "Select
|
|
393
|
+
name: "selectedTools",
|
|
394
|
+
message: "Select your AI tools to configure:",
|
|
504
395
|
choices: [
|
|
505
|
-
{ name: "
|
|
506
|
-
{
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
},
|
|
510
|
-
{ 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" },
|
|
511
402
|
],
|
|
512
|
-
|
|
513
|
-
|
|
403
|
+
validate: (arr) => Array.isArray(arr) && arr.length > 0
|
|
404
|
+
? true
|
|
405
|
+
: "Please select at least one tool",
|
|
514
406
|
},
|
|
515
407
|
]);
|
|
516
|
-
|
|
517
|
-
ITERABLE_USER_PII: permFlags.includes("pii")
|
|
518
|
-
? "true"
|
|
519
|
-
: "false",
|
|
520
|
-
ITERABLE_ENABLE_WRITES: permFlags.includes("writes")
|
|
521
|
-
? "true"
|
|
522
|
-
: "false",
|
|
523
|
-
ITERABLE_ENABLE_SENDS: permFlags.includes("sends")
|
|
524
|
-
? "true"
|
|
525
|
-
: "false",
|
|
526
|
-
};
|
|
527
|
-
}
|
|
528
|
-
let mcpEnv = {
|
|
529
|
-
ITERABLE_USER_PII: selectedEnv.ITERABLE_USER_PII,
|
|
530
|
-
ITERABLE_ENABLE_WRITES: selectedEnv.ITERABLE_ENABLE_WRITES,
|
|
531
|
-
ITERABLE_ENABLE_SENDS: selectedEnv.ITERABLE_ENABLE_SENDS,
|
|
532
|
-
};
|
|
533
|
-
if (args.includes("--debug")) {
|
|
534
|
-
mcpEnv.ITERABLE_DEBUG = "true";
|
|
535
|
-
mcpEnv.LOG_LEVEL = "debug";
|
|
536
|
-
}
|
|
537
|
-
// Enforce permission invariants before displaying
|
|
538
|
-
mcpEnv = enforceSendsRequiresWrites(mcpEnv, (msg) => showWarning(msg));
|
|
539
|
-
console.log();
|
|
540
|
-
console.log(formatKeyValue("User PII Access", mcpEnv.ITERABLE_USER_PII === "true"
|
|
541
|
-
? chalk.green("Enabled")
|
|
542
|
-
: chalk.gray("Disabled")));
|
|
543
|
-
console.log(formatKeyValue("Write Operations", mcpEnv.ITERABLE_ENABLE_WRITES === "true"
|
|
544
|
-
? chalk.green("Enabled")
|
|
545
|
-
: chalk.gray("Disabled")));
|
|
546
|
-
console.log(formatKeyValue("Sends", mcpEnv.ITERABLE_ENABLE_SENDS === "true"
|
|
547
|
-
? chalk.green("Enabled")
|
|
548
|
-
: chalk.gray("Disabled")));
|
|
549
|
-
// Only show the default note when all flags are disabled
|
|
550
|
-
if (mcpEnv.ITERABLE_USER_PII !== "true" &&
|
|
551
|
-
mcpEnv.ITERABLE_ENABLE_WRITES !== "true" &&
|
|
552
|
-
mcpEnv.ITERABLE_ENABLE_SENDS !== "true") {
|
|
553
|
-
console.log();
|
|
554
|
-
console.log(chalk.gray("Note: These are disabled by default. If you absolutely need to enable these, run 'iterable-mcp setup --advanced'"));
|
|
555
|
-
}
|
|
556
|
-
else {
|
|
408
|
+
tools = selectedTools;
|
|
557
409
|
console.log();
|
|
558
|
-
showWarning("Warning: Elevated capabilities enabled. Sends, writes, and/or PII access may now occur.");
|
|
559
|
-
}
|
|
560
|
-
console.log();
|
|
561
|
-
// If we used an existing key, persist the chosen settings to it
|
|
562
|
-
if (usedKeyName) {
|
|
563
|
-
try {
|
|
564
|
-
await keyManager.updateKeyEnv(usedKeyName, pickPersistablePermissionEnv(mcpEnv));
|
|
565
|
-
}
|
|
566
|
-
catch (err) {
|
|
567
|
-
if (process.env.ITERABLE_DEBUG === "true") {
|
|
568
|
-
console.warn("Non-fatal: failed to persist per-key env settings", err);
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
410
|
}
|
|
572
|
-
// Step 5: Configure AI Tools
|
|
573
|
-
console.log();
|
|
574
|
-
showSection("Configuring AI Tools", icons.zap);
|
|
575
|
-
console.log();
|
|
576
|
-
// Ask about auto-update unless flag was provided
|
|
577
411
|
let useAutoUpdate = autoUpdate;
|
|
578
412
|
if (!autoUpdate) {
|
|
579
413
|
const { enableAutoUpdate } = await inquirer.prompt([
|
|
@@ -584,6 +418,7 @@ export const setupMcpServer = async () => {
|
|
|
584
418
|
default: false,
|
|
585
419
|
},
|
|
586
420
|
]);
|
|
421
|
+
console.log();
|
|
587
422
|
useAutoUpdate = enableAutoUpdate;
|
|
588
423
|
}
|
|
589
424
|
const iterableMcpConfig = buildMcpConfig({
|
|
@@ -591,35 +426,6 @@ export const setupMcpServer = async () => {
|
|
|
591
426
|
env: omitPersistablePermissionEnv(mcpEnv),
|
|
592
427
|
autoUpdate: useAutoUpdate,
|
|
593
428
|
});
|
|
594
|
-
// Preflight confirmation summary before applying changes
|
|
595
|
-
console.log(chalk.gray("Summary:"));
|
|
596
|
-
console.log(formatKeyValue("Tools", tools.map((t) => TOOL_NAMES[t]).join(", "), valueColor()));
|
|
597
|
-
if (usedKeyName) {
|
|
598
|
-
console.log(formatKeyValue("API Key", usedKeyName, valueColor()));
|
|
599
|
-
}
|
|
600
|
-
console.log(formatKeyValue("Endpoint", baseUrl, valueColor()));
|
|
601
|
-
console.log(formatKeyValue("User PII", mcpEnv.ITERABLE_USER_PII === "true"
|
|
602
|
-
? chalk.green("Enabled")
|
|
603
|
-
: chalk.gray("Disabled")));
|
|
604
|
-
console.log(formatKeyValue("Writes", mcpEnv.ITERABLE_ENABLE_WRITES === "true"
|
|
605
|
-
? chalk.green("Enabled")
|
|
606
|
-
: chalk.gray("Disabled")));
|
|
607
|
-
console.log(formatKeyValue("Sends", mcpEnv.ITERABLE_ENABLE_SENDS === "true"
|
|
608
|
-
? chalk.green("Enabled")
|
|
609
|
-
: chalk.gray("Disabled")));
|
|
610
|
-
console.log();
|
|
611
|
-
const { proceed } = await inquirer.prompt([
|
|
612
|
-
{
|
|
613
|
-
type: "confirm",
|
|
614
|
-
name: "proceed",
|
|
615
|
-
message: "Proceed with configuration?",
|
|
616
|
-
default: true,
|
|
617
|
-
},
|
|
618
|
-
]);
|
|
619
|
-
if (!proceed) {
|
|
620
|
-
showInfo("Setup cancelled by user. No changes were made.");
|
|
621
|
-
return;
|
|
622
|
-
}
|
|
623
429
|
const fileBasedTools = tools.filter((tool) => tool in TOOL_CONFIGS);
|
|
624
430
|
const needsClaudeCode = tools.includes("claude-code");
|
|
625
431
|
const needsManual = tools.includes("manual");
|
|
@@ -649,11 +455,10 @@ export const setupMcpServer = async () => {
|
|
|
649
455
|
spinner.fail("Claude CLI not found");
|
|
650
456
|
showError("Please install Claude Code first: https://docs.claude.com/en/docs/claude-code/overview");
|
|
651
457
|
console.log();
|
|
652
|
-
showInfo(
|
|
458
|
+
showInfo(`After installing, re-run: ${COMMAND_NAME} setup --claude-code`);
|
|
653
459
|
process.exit(1);
|
|
654
460
|
}
|
|
655
461
|
const configJson = JSON.stringify(iterableMcpConfig);
|
|
656
|
-
// Remove existing config (ignore errors)
|
|
657
462
|
await execFileAsync("claude", ["mcp", "remove", "iterable"]).catch(() => { });
|
|
658
463
|
spinner.start("Configuring Claude Code...");
|
|
659
464
|
await new Promise((resolve, reject) => {
|
|
@@ -673,11 +478,9 @@ export const setupMcpServer = async () => {
|
|
|
673
478
|
spinner.succeed("Claude Code configured successfully");
|
|
674
479
|
}
|
|
675
480
|
if (needsManual) {
|
|
676
|
-
console.log();
|
|
677
481
|
showSection("Manual Configuration", icons.rocket);
|
|
678
482
|
console.log();
|
|
679
483
|
const { type, command, args, env } = iterableMcpConfig;
|
|
680
|
-
showInfo("Your API key has been stored.");
|
|
681
484
|
showInfo("Add the MCP server to your AI tool with these settings:");
|
|
682
485
|
console.log();
|
|
683
486
|
console.log(chalk.white.bold(" Type:") + ` ${type}`);
|
|
@@ -693,16 +496,17 @@ export const setupMcpServer = async () => {
|
|
|
693
496
|
console.log();
|
|
694
497
|
showInfo("Refer to your AI tool's documentation for configuration instructions.");
|
|
695
498
|
}
|
|
696
|
-
// Build configured tools list
|
|
697
499
|
const configuredTools = [];
|
|
698
500
|
if (fileBasedTools.includes("cursor"))
|
|
699
501
|
configuredTools.push("Cursor");
|
|
700
502
|
if (fileBasedTools.includes("claude-desktop"))
|
|
701
503
|
configuredTools.push("Claude Desktop");
|
|
702
|
-
if (fileBasedTools.includes("gemini-cli"))
|
|
703
|
-
configuredTools.push("Gemini CLI");
|
|
704
504
|
if (needsClaudeCode)
|
|
705
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");
|
|
706
510
|
if (needsManual)
|
|
707
511
|
configuredTools.push("your AI tool");
|
|
708
512
|
const toolsList = configuredTools.length === 1
|
|
@@ -712,7 +516,6 @@ export const setupMcpServer = async () => {
|
|
|
712
516
|
: configuredTools.slice(0, -1).join(", ") +
|
|
713
517
|
", and " +
|
|
714
518
|
configuredTools[configuredTools.length - 1];
|
|
715
|
-
// Success!
|
|
716
519
|
console.log();
|
|
717
520
|
const nextSteps = [
|
|
718
521
|
...(needsManual ? ["Configure your AI tool as described above"] : []),
|
|
@@ -724,8 +527,8 @@ export const setupMcpServer = async () => {
|
|
|
724
527
|
];
|
|
725
528
|
const tips = [
|
|
726
529
|
`Try: 'list my Iterable campaigns' in ${toolsList}`,
|
|
727
|
-
|
|
728
|
-
|
|
530
|
+
`Manage keys with '${COMMAND_NAME} keys list'`,
|
|
531
|
+
`Switch keys with '${COMMAND_NAME} keys activate <name>'`,
|
|
729
532
|
];
|
|
730
533
|
showCompletion("Setup Complete!", nextSteps, tips);
|
|
731
534
|
}
|