@iterable/mcp 1.0.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/LICENSE +21 -0
- package/README.md +318 -0
- package/TOOLS.md +133 -0
- package/dist/client-detection.d.ts +7 -0
- package/dist/client-detection.d.ts.map +1 -0
- package/dist/client-detection.js +22 -0
- package/dist/client-detection.js.map +1 -0
- package/dist/config.d.ts +25 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +74 -0
- package/dist/config.js.map +1 -0
- package/dist/http-headers.d.ts +5 -0
- package/dist/http-headers.d.ts.map +1 -0
- package/dist/http-headers.js +5 -0
- package/dist/http-headers.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +43 -0
- package/dist/index.js.map +1 -0
- package/dist/install.d.ts +41 -0
- package/dist/install.d.ts.map +1 -0
- package/dist/install.js +725 -0
- package/dist/install.js.map +1 -0
- package/dist/key-manager.d.ts +236 -0
- package/dist/key-manager.d.ts.map +1 -0
- package/dist/key-manager.js +776 -0
- package/dist/key-manager.js.map +1 -0
- package/dist/keys-cli.d.ts +8 -0
- package/dist/keys-cli.d.ts.map +1 -0
- package/dist/keys-cli.js +487 -0
- package/dist/keys-cli.js.map +1 -0
- package/dist/prompts.d.ts +15 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +66 -0
- package/dist/prompts.js.map +1 -0
- package/dist/schema-utils.d.ts +22 -0
- package/dist/schema-utils.d.ts.map +1 -0
- package/dist/schema-utils.js +65 -0
- package/dist/schema-utils.js.map +1 -0
- package/dist/server.d.ts +13 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +172 -0
- package/dist/server.js.map +1 -0
- package/dist/shutdown.d.ts +5 -0
- package/dist/shutdown.d.ts.map +1 -0
- package/dist/shutdown.js +68 -0
- package/dist/shutdown.js.map +1 -0
- package/dist/tool-filter.d.ts +19 -0
- package/dist/tool-filter.d.ts.map +1 -0
- package/dist/tool-filter.js +144 -0
- package/dist/tool-filter.js.map +1 -0
- package/dist/tools/campaigns.d.ts +7 -0
- package/dist/tools/campaigns.d.ts.map +1 -0
- package/dist/tools/campaigns.js +88 -0
- package/dist/tools/campaigns.js.map +1 -0
- package/dist/tools/catalogs.d.ts +7 -0
- package/dist/tools/catalogs.d.ts.map +1 -0
- package/dist/tools/catalogs.js +85 -0
- package/dist/tools/catalogs.js.map +1 -0
- package/dist/tools/events.d.ts +7 -0
- package/dist/tools/events.d.ts.map +1 -0
- package/dist/tools/events.js +34 -0
- package/dist/tools/events.js.map +1 -0
- package/dist/tools/experiments.d.ts +7 -0
- package/dist/tools/experiments.d.ts.map +1 -0
- package/dist/tools/experiments.js +16 -0
- package/dist/tools/experiments.js.map +1 -0
- package/dist/tools/export.d.ts +7 -0
- package/dist/tools/export.d.ts.map +1 -0
- package/dist/tools/export.js +41 -0
- package/dist/tools/export.js.map +1 -0
- package/dist/tools/index.d.ts +19 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +44 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/journeys.d.ts +10 -0
- package/dist/tools/journeys.d.ts.map +1 -0
- package/dist/tools/journeys.js +25 -0
- package/dist/tools/journeys.js.map +1 -0
- package/dist/tools/lists.d.ts +7 -0
- package/dist/tools/lists.d.ts.map +1 -0
- package/dist/tools/lists.js +58 -0
- package/dist/tools/lists.js.map +1 -0
- package/dist/tools/messaging.d.ts +11 -0
- package/dist/tools/messaging.d.ts.map +1 -0
- package/dist/tools/messaging.js +103 -0
- package/dist/tools/messaging.js.map +1 -0
- package/dist/tools/snippets.d.ts +7 -0
- package/dist/tools/snippets.d.ts.map +1 -0
- package/dist/tools/snippets.js +46 -0
- package/dist/tools/snippets.js.map +1 -0
- package/dist/tools/subscriptions.d.ts +4 -0
- package/dist/tools/subscriptions.d.ts.map +1 -0
- package/dist/tools/subscriptions.js +37 -0
- package/dist/tools/subscriptions.js.map +1 -0
- package/dist/tools/templates.d.ts +7 -0
- package/dist/tools/templates.d.ts.map +1 -0
- package/dist/tools/templates.js +118 -0
- package/dist/tools/templates.js.map +1 -0
- package/dist/tools/users.d.ts +10 -0
- package/dist/tools/users.d.ts.map +1 -0
- package/dist/tools/users.js +74 -0
- package/dist/tools/users.js.map +1 -0
- package/dist/tools/webhooks.d.ts +7 -0
- package/dist/tools/webhooks.d.ts.map +1 -0
- package/dist/tools/webhooks.js +22 -0
- package/dist/tools/webhooks.js.map +1 -0
- package/dist/utils/cli-env.d.ts +11 -0
- package/dist/utils/cli-env.d.ts.map +1 -0
- package/dist/utils/cli-env.js +32 -0
- package/dist/utils/cli-env.js.map +1 -0
- package/dist/utils/endpoint-prompt.d.ts +14 -0
- package/dist/utils/endpoint-prompt.d.ts.map +1 -0
- package/dist/utils/endpoint-prompt.js +73 -0
- package/dist/utils/endpoint-prompt.js.map +1 -0
- package/dist/utils/formatting.d.ts +10 -0
- package/dist/utils/formatting.d.ts.map +1 -0
- package/dist/utils/formatting.js +12 -0
- package/dist/utils/formatting.js.map +1 -0
- package/dist/utils/password-prompt.d.ts +19 -0
- package/dist/utils/password-prompt.d.ts.map +1 -0
- package/dist/utils/password-prompt.js +37 -0
- package/dist/utils/password-prompt.js.map +1 -0
- package/dist/utils/sanitize.d.ts +13 -0
- package/dist/utils/sanitize.d.ts.map +1 -0
- package/dist/utils/sanitize.js +23 -0
- package/dist/utils/sanitize.js.map +1 -0
- package/dist/utils/tool-config.d.ts +13 -0
- package/dist/utils/tool-config.d.ts.map +1 -0
- package/dist/utils/tool-config.js +45 -0
- package/dist/utils/tool-config.js.map +1 -0
- package/dist/utils/ui.d.ts +90 -0
- package/dist/utils/ui.d.ts.map +1 -0
- package/dist/utils/ui.js +298 -0
- package/dist/utils/ui.js.map +1 -0
- package/dist/utils/url.d.ts +20 -0
- package/dist/utils/url.d.ts.map +1 -0
- package/dist/utils/url.js +53 -0
- package/dist/utils/url.js.map +1 -0
- package/package.json +105 -0
package/dist/install.js
ADDED
|
@@ -0,0 +1,725 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
import { exec, spawn } from "child_process";
|
|
3
|
+
import { existsSync, readFileSync } from "fs";
|
|
4
|
+
import inquirer from "inquirer";
|
|
5
|
+
import os from "os";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
|
+
import { promisify } from "util";
|
|
9
|
+
const { dirname, join } = path;
|
|
10
|
+
// IMPORTANT: UI imports are loaded lazily inside functions to avoid ESM issues in Jest/CommonJS.
|
|
11
|
+
// Executable/package names
|
|
12
|
+
const LOCAL_BINARY_NAME = "iterable-mcp";
|
|
13
|
+
const NPX_PACKAGE_NAME = "@iterable/mcp";
|
|
14
|
+
// Get package version
|
|
15
|
+
const packageJson = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.url)), "..", "package.json"), "utf-8"));
|
|
16
|
+
const TOOL_CONFIGS = {
|
|
17
|
+
"claude-desktop": (() => {
|
|
18
|
+
switch (process.platform) {
|
|
19
|
+
case "darwin":
|
|
20
|
+
return path.join(os.homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
21
|
+
case "win32":
|
|
22
|
+
return path.join(process.env.APPDATA || "", "Claude", "claude_desktop_config.json");
|
|
23
|
+
default:
|
|
24
|
+
return path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config"), "Claude", "claude_desktop_config.json");
|
|
25
|
+
}
|
|
26
|
+
})(),
|
|
27
|
+
cursor: path.join(os.homedir(), ".cursor", "mcp.json"),
|
|
28
|
+
};
|
|
29
|
+
// Cross-platform command finder
|
|
30
|
+
const findCommand = async (command) => {
|
|
31
|
+
const finder = process.platform === "win32" ? "where" : "which";
|
|
32
|
+
const { stdout } = await promisify(exec)(`${finder} ${command}`);
|
|
33
|
+
const lines = stdout.trim().split("\n");
|
|
34
|
+
if (!lines?.[0])
|
|
35
|
+
throw new Error(`${command} not found`);
|
|
36
|
+
return lines[0];
|
|
37
|
+
};
|
|
38
|
+
// Auto-detect if running from a local development build
|
|
39
|
+
const isLocalDevelopmentBuild = () => {
|
|
40
|
+
const scriptPath = process.argv[1] || "";
|
|
41
|
+
if (scriptPath.includes("/dist/index.js")) {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
const distPath = path.resolve(process.cwd(), "dist", "index.js");
|
|
46
|
+
return existsSync(distPath);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
// Build MCP config - exported for testing
|
|
53
|
+
export const buildMcpConfig = (options) => {
|
|
54
|
+
const isLocal = options.isLocal ?? isLocalDevelopmentBuild();
|
|
55
|
+
const nodePath = options.nodePath || process.env.ITERABLE_MCP_NODE_PATH || "node";
|
|
56
|
+
const npxPath = options.npxPath || process.env.ITERABLE_MCP_NPX_PATH || "npx";
|
|
57
|
+
return {
|
|
58
|
+
type: "stdio",
|
|
59
|
+
command: isLocal ? nodePath : npxPath,
|
|
60
|
+
args: isLocal
|
|
61
|
+
? [path.resolve(process.cwd(), "dist", "index.js")]
|
|
62
|
+
: ["-y", NPX_PACKAGE_NAME],
|
|
63
|
+
env: options.env,
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Merge selected env flags with key metadata env (if provided), preferring the key's
|
|
68
|
+
* persisted values. This ensures the written MCP configs reflect the authoritative
|
|
69
|
+
* values saved to the key after any updates in the setup flow.
|
|
70
|
+
*/
|
|
71
|
+
export function resolveFinalMcpEnv(selectedEnv, keyEnv) {
|
|
72
|
+
const normalize = (v) => (v === "true" ? "true" : "false");
|
|
73
|
+
const result = { ...selectedEnv };
|
|
74
|
+
if (keyEnv) {
|
|
75
|
+
result.ITERABLE_USER_PII = normalize(keyEnv.ITERABLE_USER_PII ?? result.ITERABLE_USER_PII);
|
|
76
|
+
result.ITERABLE_ENABLE_WRITES = normalize(keyEnv.ITERABLE_ENABLE_WRITES ?? result.ITERABLE_ENABLE_WRITES);
|
|
77
|
+
result.ITERABLE_ENABLE_SENDS = normalize(keyEnv.ITERABLE_ENABLE_SENDS ?? result.ITERABLE_ENABLE_SENDS);
|
|
78
|
+
}
|
|
79
|
+
result.ITERABLE_USER_PII = normalize(result.ITERABLE_USER_PII);
|
|
80
|
+
result.ITERABLE_ENABLE_WRITES = normalize(result.ITERABLE_ENABLE_WRITES);
|
|
81
|
+
result.ITERABLE_ENABLE_SENDS = normalize(result.ITERABLE_ENABLE_SENDS);
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Pick only permission-related env flags for persistence into key metadata.
|
|
86
|
+
*/
|
|
87
|
+
export function pickPersistablePermissionEnv(env) {
|
|
88
|
+
const normalize = (v) => (v === "true" ? "true" : "false");
|
|
89
|
+
return {
|
|
90
|
+
ITERABLE_USER_PII: normalize(env.ITERABLE_USER_PII),
|
|
91
|
+
ITERABLE_ENABLE_WRITES: normalize(env.ITERABLE_ENABLE_WRITES),
|
|
92
|
+
ITERABLE_ENABLE_SENDS: normalize(env.ITERABLE_ENABLE_SENDS),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Enforce that Sends require Writes. If Sends is enabled but Writes is disabled,
|
|
97
|
+
* disable Sends and optionally emit a warning.
|
|
98
|
+
*/
|
|
99
|
+
export function enforceSendsRequiresWrites(env, warn) {
|
|
100
|
+
if (env.ITERABLE_ENABLE_SENDS === "true" &&
|
|
101
|
+
env.ITERABLE_ENABLE_WRITES !== "true") {
|
|
102
|
+
env.ITERABLE_ENABLE_SENDS = "false";
|
|
103
|
+
if (warn)
|
|
104
|
+
warn("Sends require Writes. Disabling Sends.");
|
|
105
|
+
}
|
|
106
|
+
return env;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Security defaults used for Privacy & Security prompts.
|
|
110
|
+
* Always conservative: all disabled by default.
|
|
111
|
+
*/
|
|
112
|
+
export function getSecurityDefaults(_activeMeta) {
|
|
113
|
+
return { defaultPii: false, defaultWrites: false, defaultSends: false };
|
|
114
|
+
}
|
|
115
|
+
export const setupMcpServer = async () => {
|
|
116
|
+
const args = process.argv.slice(2);
|
|
117
|
+
const showHelp = args.includes("--help") || args.includes("-h");
|
|
118
|
+
const advanced = args.includes("--advanced");
|
|
119
|
+
let tools = [
|
|
120
|
+
...(args.includes("--claude-desktop") ? ["claude-desktop"] : []),
|
|
121
|
+
...(args.includes("--cursor") ? ["cursor"] : []),
|
|
122
|
+
...(args.includes("--claude-code") ? ["claude-code"] : []),
|
|
123
|
+
];
|
|
124
|
+
// Detect how the command was invoked
|
|
125
|
+
const isNpx = process.argv[1]?.includes("npx") ||
|
|
126
|
+
process.env.npm_execpath?.includes("npx");
|
|
127
|
+
const commandName = isNpx ? `npx ${NPX_PACKAGE_NAME}` : LOCAL_BINARY_NAME;
|
|
128
|
+
if (showHelp) {
|
|
129
|
+
const { createTable, icons, showBox, showIterableLogo, showSection } = await import("./utils/ui.js");
|
|
130
|
+
const chalk = (await import("chalk")).default;
|
|
131
|
+
console.clear();
|
|
132
|
+
showIterableLogo(packageJson.version);
|
|
133
|
+
showSection("Setup Commands", icons.rocket);
|
|
134
|
+
console.log();
|
|
135
|
+
const setupTable = createTable({
|
|
136
|
+
head: ["Command", "Description"],
|
|
137
|
+
colWidths: [45, 40],
|
|
138
|
+
style: "normal",
|
|
139
|
+
});
|
|
140
|
+
setupTable.push([`${commandName} setup --claude-desktop`, "Configure for Claude Desktop"], [`${commandName} setup --cursor`, "Configure for Cursor"], [`${commandName} setup --claude-code`, "Configure for Claude Code"], [
|
|
141
|
+
`${commandName} setup --cursor --claude-desktop`,
|
|
142
|
+
"Configure multiple tools",
|
|
143
|
+
], [`${commandName} setup --cursor --debug`, "Enable debug logging"]);
|
|
144
|
+
console.log(setupTable.toString());
|
|
145
|
+
console.log();
|
|
146
|
+
console.log();
|
|
147
|
+
showSection("Key Management" + chalk.gray(" (macOS only)"), icons.key);
|
|
148
|
+
console.log();
|
|
149
|
+
const keysTable = createTable({
|
|
150
|
+
head: ["Command", "Description"],
|
|
151
|
+
colWidths: [45, 40],
|
|
152
|
+
style: "normal",
|
|
153
|
+
});
|
|
154
|
+
keysTable.push([`${commandName} keys list`, "View all stored API keys"], [`${commandName} keys add`, "Add a new API key"], [`${commandName} keys activate <name>`, "Switch to a different key"], [`${commandName} keys delete <id>`, "Remove a key by ID"]);
|
|
155
|
+
console.log(keysTable.toString());
|
|
156
|
+
console.log();
|
|
157
|
+
console.log();
|
|
158
|
+
showSection("Environment Variables", icons.globe);
|
|
159
|
+
console.log();
|
|
160
|
+
const envTable = createTable({
|
|
161
|
+
head: ["Variable", "Description"],
|
|
162
|
+
colWidths: [30, 50],
|
|
163
|
+
style: "compact",
|
|
164
|
+
});
|
|
165
|
+
envTable.push(["ITERABLE_API_KEY", chalk.gray("Your Iterable API key")], [
|
|
166
|
+
"ITERABLE_BASE_URL",
|
|
167
|
+
chalk.gray("API endpoint (default: api.iterable.com)"),
|
|
168
|
+
], [
|
|
169
|
+
"ITERABLE_USER_PII",
|
|
170
|
+
chalk.gray("Enable user PII access (default: false)"),
|
|
171
|
+
], [
|
|
172
|
+
"ITERABLE_ENABLE_WRITES",
|
|
173
|
+
chalk.gray("Enable write operations (default: false)"),
|
|
174
|
+
], [
|
|
175
|
+
"ITERABLE_ENABLE_SENDS",
|
|
176
|
+
chalk.gray("Enable message sending (default: false)"),
|
|
177
|
+
], ["ITERABLE_MCP_NODE_PATH", chalk.gray("Custom node executable path")], ["ITERABLE_MCP_NPX_PATH", chalk.gray("Custom npx executable path")]);
|
|
178
|
+
console.log(envTable.toString());
|
|
179
|
+
console.log();
|
|
180
|
+
showBox("Quick Start", [
|
|
181
|
+
chalk.white.bold("1. Run setup for your AI tool:"),
|
|
182
|
+
chalk.cyan(` ${commandName} setup --cursor`),
|
|
183
|
+
"",
|
|
184
|
+
chalk.white.bold("2. Restart your AI tool"),
|
|
185
|
+
"",
|
|
186
|
+
chalk.white.bold("3. Start building!"),
|
|
187
|
+
chalk.gray(" Try: 'list my Iterable campaigns'"),
|
|
188
|
+
], { icon: icons.zap, theme: "success" });
|
|
189
|
+
showBox("Security Features", [
|
|
190
|
+
"• API keys prompted interactively (never in shell history)",
|
|
191
|
+
"• macOS: Keys stored securely in Keychain",
|
|
192
|
+
"• Each key coupled to its endpoint (US/EU/custom)",
|
|
193
|
+
"• Windows/Linux: Use ITERABLE_API_KEY environment variable",
|
|
194
|
+
], { icon: icons.lock, theme: "info", padding: 1 });
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
// New behavior: if no tools were provided as flags, prompt user to select one or more
|
|
198
|
+
if (tools.length === 0) {
|
|
199
|
+
// Show logo first when no flags provided
|
|
200
|
+
const { showIterableLogo } = await import("./utils/ui.js");
|
|
201
|
+
console.clear();
|
|
202
|
+
showIterableLogo(packageJson.version);
|
|
203
|
+
const { selectedTools } = await inquirer.prompt([
|
|
204
|
+
{
|
|
205
|
+
type: "checkbox",
|
|
206
|
+
name: "selectedTools",
|
|
207
|
+
message: "Select your AI tools to configure:",
|
|
208
|
+
choices: [
|
|
209
|
+
{ name: "Cursor", value: "cursor" },
|
|
210
|
+
{ name: "Claude Desktop", value: "claude-desktop" },
|
|
211
|
+
{ name: "Claude Code (CLI)", value: "claude-code" },
|
|
212
|
+
],
|
|
213
|
+
validate: (arr) => Array.isArray(arr) && arr.length > 0
|
|
214
|
+
? true
|
|
215
|
+
: "Please select at least one tool",
|
|
216
|
+
},
|
|
217
|
+
]);
|
|
218
|
+
tools = selectedTools;
|
|
219
|
+
}
|
|
220
|
+
// Start the setup flow
|
|
221
|
+
console.clear();
|
|
222
|
+
const { formatKeyValue, icons, showCompletion, showError, showInfo, showIterableLogo, showSection, showSuccess, showWarning, } = await import("./utils/ui.js");
|
|
223
|
+
const chalk = (await import("chalk")).default;
|
|
224
|
+
showIterableLogo(packageJson.version);
|
|
225
|
+
// Succinct overview of what will be configured
|
|
226
|
+
const prettyName = (t) => t === "cursor"
|
|
227
|
+
? "Cursor"
|
|
228
|
+
: t === "claude-desktop"
|
|
229
|
+
? "Claude Desktop"
|
|
230
|
+
: "Claude Code";
|
|
231
|
+
console.log(chalk.gray(`Running setup to configure: ${tools.map(prettyName).join(", ")}`));
|
|
232
|
+
console.log();
|
|
233
|
+
const ora = (await import("ora")).default;
|
|
234
|
+
const spinner = ora();
|
|
235
|
+
try {
|
|
236
|
+
// Step 1: API Key Configuration
|
|
237
|
+
console.log();
|
|
238
|
+
showSection("API Key Configuration", icons.key);
|
|
239
|
+
console.log();
|
|
240
|
+
let apiKey;
|
|
241
|
+
let keyName;
|
|
242
|
+
let baseUrl;
|
|
243
|
+
let usedKeyName;
|
|
244
|
+
let usedExistingKey = false;
|
|
245
|
+
let selectedExistingMeta;
|
|
246
|
+
// 1) Offer using API key from environment
|
|
247
|
+
if (process.env.ITERABLE_API_KEY) {
|
|
248
|
+
const { useEnvKey } = await inquirer.prompt([
|
|
249
|
+
{
|
|
250
|
+
type: "confirm",
|
|
251
|
+
name: "useEnvKey",
|
|
252
|
+
message: "Found API key in ITERABLE_API_KEY environment variable. Import and use this key?",
|
|
253
|
+
default: true,
|
|
254
|
+
},
|
|
255
|
+
]);
|
|
256
|
+
if (useEnvKey) {
|
|
257
|
+
apiKey = process.env.ITERABLE_API_KEY;
|
|
258
|
+
showSuccess("Using API key from environment variable");
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
// 2) If not using env key, offer using an existing Keychain key (macOS)
|
|
262
|
+
if (!apiKey && process.platform === "darwin") {
|
|
263
|
+
const { getKeyManager } = await import("./key-manager.js");
|
|
264
|
+
const km = getKeyManager();
|
|
265
|
+
const keys = await km.listKeys().catch(() => []);
|
|
266
|
+
if (Array.isArray(keys) && keys.length > 0) {
|
|
267
|
+
const { useExisting } = await inquirer.prompt([
|
|
268
|
+
{
|
|
269
|
+
type: "confirm",
|
|
270
|
+
name: "useExisting",
|
|
271
|
+
message: `Found ${keys.length} existing Iterable API key${keys.length === 1 ? "" : "s"} in macOS Keychain. Use one of these?`,
|
|
272
|
+
default: true,
|
|
273
|
+
},
|
|
274
|
+
]);
|
|
275
|
+
if (useExisting) {
|
|
276
|
+
const { formatKeychainChoiceLabel } = await import("./utils/ui.js");
|
|
277
|
+
const { chosenId } = await inquirer.prompt([
|
|
278
|
+
{
|
|
279
|
+
type: "list",
|
|
280
|
+
name: "chosenId",
|
|
281
|
+
message: "Select a stored key:",
|
|
282
|
+
choices: keys.map((k) => ({
|
|
283
|
+
name: formatKeychainChoiceLabel(k.name, k.baseUrl, k.isActive, k.env),
|
|
284
|
+
value: k.id,
|
|
285
|
+
short: k.name,
|
|
286
|
+
})),
|
|
287
|
+
pageSize: Math.min(10, keys.length),
|
|
288
|
+
},
|
|
289
|
+
]);
|
|
290
|
+
try {
|
|
291
|
+
const meta = keys.find((k) => k.id === chosenId);
|
|
292
|
+
if (!meta)
|
|
293
|
+
throw new Error("Selected key metadata not found");
|
|
294
|
+
baseUrl = meta.baseUrl;
|
|
295
|
+
usedKeyName = meta.name;
|
|
296
|
+
usedExistingKey = true;
|
|
297
|
+
selectedExistingMeta = meta;
|
|
298
|
+
showSuccess(`Using existing key "${meta.name}"`);
|
|
299
|
+
}
|
|
300
|
+
catch (e) {
|
|
301
|
+
showError(e instanceof Error
|
|
302
|
+
? e.message
|
|
303
|
+
: "Failed to load selected key from Keychain");
|
|
304
|
+
process.exit(1);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
// 3) If neither env nor existing key chosen, prompt for a new key
|
|
310
|
+
if (!apiKey && !usedExistingKey) {
|
|
311
|
+
const { newApiKey } = await inquirer.prompt([
|
|
312
|
+
{
|
|
313
|
+
type: "password",
|
|
314
|
+
name: "newApiKey",
|
|
315
|
+
message: "Enter your Iterable API key:",
|
|
316
|
+
validate: (input) => {
|
|
317
|
+
if (!input)
|
|
318
|
+
return "API key is required";
|
|
319
|
+
if (!/^[a-f0-9]{32}$/.test(input))
|
|
320
|
+
return "API key must be a 32-character lowercase hexadecimal string";
|
|
321
|
+
return true;
|
|
322
|
+
},
|
|
323
|
+
mask: "*",
|
|
324
|
+
},
|
|
325
|
+
]);
|
|
326
|
+
apiKey = newApiKey;
|
|
327
|
+
}
|
|
328
|
+
// Step 2: API Endpoint Selection
|
|
329
|
+
console.log();
|
|
330
|
+
showSection("API Endpoint Configuration", icons.globe);
|
|
331
|
+
console.log();
|
|
332
|
+
if (!baseUrl) {
|
|
333
|
+
if (process.env.ITERABLE_BASE_URL) {
|
|
334
|
+
baseUrl = process.env.ITERABLE_BASE_URL;
|
|
335
|
+
showSuccess(`Using API endpoint from environment: ${baseUrl}`);
|
|
336
|
+
}
|
|
337
|
+
else {
|
|
338
|
+
const { promptForIterableBaseUrl } = await import("./utils/endpoint-prompt.js");
|
|
339
|
+
try {
|
|
340
|
+
baseUrl = await promptForIterableBaseUrl({
|
|
341
|
+
inquirer,
|
|
342
|
+
icons,
|
|
343
|
+
chalk,
|
|
344
|
+
showError,
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
catch {
|
|
348
|
+
process.exit(1);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
showSuccess(`Using API endpoint from selected key: ${baseUrl}`);
|
|
354
|
+
}
|
|
355
|
+
console.log();
|
|
356
|
+
console.log(formatKeyValue("Selected endpoint", baseUrl, chalk.cyan));
|
|
357
|
+
console.log();
|
|
358
|
+
// Step 3: Store the key securely (macOS only). On other platforms, use env vars.
|
|
359
|
+
let existingKeyWithValue = null;
|
|
360
|
+
if (process.platform === "darwin") {
|
|
361
|
+
const { getKeyManager } = await import("./key-manager.js");
|
|
362
|
+
const keyManager = getKeyManager();
|
|
363
|
+
spinner.start("Initializing secure key storage...");
|
|
364
|
+
await keyManager.initialize();
|
|
365
|
+
spinner.succeed("Key storage ready");
|
|
366
|
+
// Check if key already exists
|
|
367
|
+
if (!usedExistingKey && apiKey) {
|
|
368
|
+
spinner.start("Checking for existing keys...");
|
|
369
|
+
existingKeyWithValue = await keyManager.findKeyByValue(apiKey);
|
|
370
|
+
spinner.stop();
|
|
371
|
+
}
|
|
372
|
+
if (usedExistingKey) {
|
|
373
|
+
// Offer to activate the selected existing key if not already active
|
|
374
|
+
if (!selectedExistingMeta?.isActive) {
|
|
375
|
+
const { activateExisting } = await inquirer.prompt([
|
|
376
|
+
{
|
|
377
|
+
type: "confirm",
|
|
378
|
+
name: "activateExisting",
|
|
379
|
+
message: `Set "${selectedExistingMeta?.name}" as your active API key?`,
|
|
380
|
+
default: true,
|
|
381
|
+
},
|
|
382
|
+
]);
|
|
383
|
+
if (activateExisting) {
|
|
384
|
+
await keyManager.setActiveKey(selectedExistingMeta.id);
|
|
385
|
+
showSuccess(`"${selectedExistingMeta?.name}" is now your active key`);
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
showInfo("Keeping your current active key");
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
showSuccess(`"${selectedExistingMeta?.name}" is already your active key`);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
else if (existingKeyWithValue) {
|
|
396
|
+
showInfo(`This API key is already stored as "${existingKeyWithValue.name}"`);
|
|
397
|
+
usedKeyName = existingKeyWithValue.name;
|
|
398
|
+
if (!existingKeyWithValue.isActive) {
|
|
399
|
+
const { activateExisting } = await inquirer.prompt([
|
|
400
|
+
{
|
|
401
|
+
type: "confirm",
|
|
402
|
+
name: "activateExisting",
|
|
403
|
+
message: `Set "${existingKeyWithValue.name}" as your active API key?`,
|
|
404
|
+
default: true,
|
|
405
|
+
},
|
|
406
|
+
]);
|
|
407
|
+
if (activateExisting) {
|
|
408
|
+
await keyManager.setActiveKey(existingKeyWithValue.id);
|
|
409
|
+
showSuccess(`"${existingKeyWithValue.name}" is now your active key`);
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
showInfo("Keeping your current active key");
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
showSuccess(`"${existingKeyWithValue.name}" is already your active key`);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
// New key - prompt for name
|
|
421
|
+
const { newKeyName } = await inquirer.prompt([
|
|
422
|
+
{
|
|
423
|
+
type: "input",
|
|
424
|
+
name: "newKeyName",
|
|
425
|
+
message: "Enter a name for this API key:",
|
|
426
|
+
default: "default",
|
|
427
|
+
validate: (input) => {
|
|
428
|
+
if (input && input.length > 50)
|
|
429
|
+
return "Key name must be 50 characters or less";
|
|
430
|
+
return true;
|
|
431
|
+
},
|
|
432
|
+
},
|
|
433
|
+
]);
|
|
434
|
+
keyName = newKeyName || "default";
|
|
435
|
+
spinner.start("Storing API key securely in macOS Keychain...");
|
|
436
|
+
try {
|
|
437
|
+
const newKeyId = await keyManager.addKey(keyName, apiKey, baseUrl);
|
|
438
|
+
spinner.succeed("API key stored successfully!");
|
|
439
|
+
console.log();
|
|
440
|
+
console.log(formatKeyValue("Name", keyName, chalk.white.bold));
|
|
441
|
+
console.log(formatKeyValue("ID", newKeyId, chalk.gray));
|
|
442
|
+
console.log(formatKeyValue("Endpoint", baseUrl, chalk.cyan));
|
|
443
|
+
console.log();
|
|
444
|
+
usedKeyName = keyName;
|
|
445
|
+
// Check if we should activate this key
|
|
446
|
+
const allKeys = await keyManager.listKeys();
|
|
447
|
+
if (allKeys.length > 1) {
|
|
448
|
+
const { activateNew } = await inquirer.prompt([
|
|
449
|
+
{
|
|
450
|
+
type: "confirm",
|
|
451
|
+
name: "activateNew",
|
|
452
|
+
message: `Set "${keyName}" as your active API key?`,
|
|
453
|
+
default: true,
|
|
454
|
+
},
|
|
455
|
+
]);
|
|
456
|
+
if (activateNew) {
|
|
457
|
+
await keyManager.setActiveKey(newKeyId);
|
|
458
|
+
showSuccess(`"${keyName}" is now your active key`);
|
|
459
|
+
}
|
|
460
|
+
else {
|
|
461
|
+
showInfo(`Keeping your current active key. Run 'iterable-mcp keys activate "${keyName}"' to switch later.`);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
catch (error) {
|
|
466
|
+
const { sanitizeString } = await import("./utils/sanitize.js");
|
|
467
|
+
spinner.fail("Failed to store API key");
|
|
468
|
+
console.log();
|
|
469
|
+
const msg = error instanceof Error
|
|
470
|
+
? sanitizeString(error.message)
|
|
471
|
+
: "Unknown error";
|
|
472
|
+
showError(msg);
|
|
473
|
+
console.log();
|
|
474
|
+
showInfo("Your API key was not stored. You can re-run 'iterable-mcp setup' to try again.");
|
|
475
|
+
showInfo("If the problem persists, verify Keychain access and disk permissions.");
|
|
476
|
+
process.exit(1);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
else {
|
|
481
|
+
// Non-macOS: do not use Keychain; rely on environment variables instead
|
|
482
|
+
usedKeyName =
|
|
483
|
+
usedKeyName ||
|
|
484
|
+
(process.env.ITERABLE_API_KEY ? "Environment" : undefined);
|
|
485
|
+
}
|
|
486
|
+
// Step 4: Privacy & Security Settings (Basic by default; Advanced via --advanced)
|
|
487
|
+
console.log();
|
|
488
|
+
showSection("Privacy & Security Settings", icons.lock);
|
|
489
|
+
console.log();
|
|
490
|
+
// Default conservative env flags
|
|
491
|
+
let selectedEnv = {
|
|
492
|
+
ITERABLE_USER_PII: "false",
|
|
493
|
+
ITERABLE_ENABLE_WRITES: "false",
|
|
494
|
+
ITERABLE_ENABLE_SENDS: "false",
|
|
495
|
+
};
|
|
496
|
+
if (advanced) {
|
|
497
|
+
const { permFlags } = await inquirer.prompt([
|
|
498
|
+
{
|
|
499
|
+
type: "checkbox",
|
|
500
|
+
name: "permFlags",
|
|
501
|
+
message: "Select permissions to enable (default: none):",
|
|
502
|
+
choices: [
|
|
503
|
+
{ name: "View PII (access user data)", value: "pii" },
|
|
504
|
+
{
|
|
505
|
+
name: "Write operations (create/update/delete)",
|
|
506
|
+
value: "writes",
|
|
507
|
+
},
|
|
508
|
+
{ name: "Sends (campaigns/journeys/events)", value: "sends" },
|
|
509
|
+
],
|
|
510
|
+
pageSize: 6,
|
|
511
|
+
default: [],
|
|
512
|
+
},
|
|
513
|
+
]);
|
|
514
|
+
selectedEnv = {
|
|
515
|
+
ITERABLE_USER_PII: permFlags.includes("pii")
|
|
516
|
+
? "true"
|
|
517
|
+
: "false",
|
|
518
|
+
ITERABLE_ENABLE_WRITES: permFlags.includes("writes")
|
|
519
|
+
? "true"
|
|
520
|
+
: "false",
|
|
521
|
+
ITERABLE_ENABLE_SENDS: permFlags.includes("sends")
|
|
522
|
+
? "true"
|
|
523
|
+
: "false",
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
let mcpEnv = {
|
|
527
|
+
ITERABLE_USER_PII: selectedEnv.ITERABLE_USER_PII,
|
|
528
|
+
ITERABLE_ENABLE_WRITES: selectedEnv.ITERABLE_ENABLE_WRITES,
|
|
529
|
+
ITERABLE_ENABLE_SENDS: selectedEnv.ITERABLE_ENABLE_SENDS,
|
|
530
|
+
};
|
|
531
|
+
// On non-macOS, include API key and base URL in the written config
|
|
532
|
+
if (process.platform !== "darwin") {
|
|
533
|
+
if (apiKey)
|
|
534
|
+
mcpEnv.ITERABLE_API_KEY = apiKey;
|
|
535
|
+
if (baseUrl)
|
|
536
|
+
mcpEnv.ITERABLE_BASE_URL = baseUrl;
|
|
537
|
+
}
|
|
538
|
+
if (args.includes("--debug")) {
|
|
539
|
+
mcpEnv.ITERABLE_DEBUG = "true";
|
|
540
|
+
mcpEnv.LOG_LEVEL = "debug";
|
|
541
|
+
}
|
|
542
|
+
// Enforce permission invariants before displaying
|
|
543
|
+
mcpEnv = enforceSendsRequiresWrites(mcpEnv, (msg) => showWarning(msg));
|
|
544
|
+
console.log();
|
|
545
|
+
console.log(formatKeyValue("User PII Access", mcpEnv.ITERABLE_USER_PII === "true"
|
|
546
|
+
? chalk.green("Enabled")
|
|
547
|
+
: chalk.gray("Disabled")));
|
|
548
|
+
console.log(formatKeyValue("Write Operations", mcpEnv.ITERABLE_ENABLE_WRITES === "true"
|
|
549
|
+
? chalk.green("Enabled")
|
|
550
|
+
: chalk.gray("Disabled")));
|
|
551
|
+
console.log(formatKeyValue("Sends", mcpEnv.ITERABLE_ENABLE_SENDS === "true"
|
|
552
|
+
? chalk.green("Enabled")
|
|
553
|
+
: chalk.gray("Disabled")));
|
|
554
|
+
// Only show the default note when all flags are disabled
|
|
555
|
+
if (mcpEnv.ITERABLE_USER_PII !== "true" &&
|
|
556
|
+
mcpEnv.ITERABLE_ENABLE_WRITES !== "true" &&
|
|
557
|
+
mcpEnv.ITERABLE_ENABLE_SENDS !== "true") {
|
|
558
|
+
console.log();
|
|
559
|
+
console.log(chalk.gray("Note: These are disabled by default. If you absolutely need to enable these, run 'iterable-mcp setup --advanced'"));
|
|
560
|
+
}
|
|
561
|
+
else {
|
|
562
|
+
console.log();
|
|
563
|
+
showWarning("Warning: Elevated capabilities enabled. Sends, writes, and/or PII access may now occur.");
|
|
564
|
+
}
|
|
565
|
+
console.log();
|
|
566
|
+
// If we used an existing key, update its env overrides with chosen settings
|
|
567
|
+
if (usedKeyName && process.platform === "darwin") {
|
|
568
|
+
try {
|
|
569
|
+
const { getKeyManager } = await import("./key-manager.js");
|
|
570
|
+
const keyManager = getKeyManager();
|
|
571
|
+
await keyManager.initialize();
|
|
572
|
+
await keyManager.updateKeyEnv(usedKeyName, pickPersistablePermissionEnv(mcpEnv));
|
|
573
|
+
// Re-read the key metadata and prefer its persisted env when writing
|
|
574
|
+
// tool configurations. This addresses cases where the key was activated
|
|
575
|
+
// during setup and ensures configs reflect the updated values.
|
|
576
|
+
const keys = await keyManager.listKeys();
|
|
577
|
+
const updatedMeta = keys.find((k) => k.name === usedKeyName || k.id === usedKeyName);
|
|
578
|
+
if (updatedMeta?.env) {
|
|
579
|
+
mcpEnv = resolveFinalMcpEnv(mcpEnv, updatedMeta.env);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
catch (err) {
|
|
583
|
+
if (process.env.ITERABLE_DEBUG === "true") {
|
|
584
|
+
console.warn("Non-fatal: failed to persist per-key env settings", err);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
// Enforce again after merging persisted key env
|
|
589
|
+
mcpEnv = enforceSendsRequiresWrites(mcpEnv, (msg) => showWarning(msg));
|
|
590
|
+
// Step 5: Configure AI Tools
|
|
591
|
+
console.log();
|
|
592
|
+
showSection("Configuring AI Tools", icons.zap);
|
|
593
|
+
console.log();
|
|
594
|
+
const iterableMcpConfig = buildMcpConfig({
|
|
595
|
+
...(args.includes("--local") && { isLocal: true }),
|
|
596
|
+
env: mcpEnv,
|
|
597
|
+
});
|
|
598
|
+
// Preflight confirmation summary before applying changes
|
|
599
|
+
const prettyName = (t) => t === "cursor"
|
|
600
|
+
? "Cursor"
|
|
601
|
+
: t === "claude-desktop"
|
|
602
|
+
? "Claude Desktop"
|
|
603
|
+
: "Claude Code";
|
|
604
|
+
console.log(chalk.gray("Summary:"));
|
|
605
|
+
console.log(formatKeyValue("Tools", tools.map(prettyName).join(", "), chalk.white));
|
|
606
|
+
if (usedKeyName) {
|
|
607
|
+
console.log(formatKeyValue("API Key", usedKeyName, chalk.white));
|
|
608
|
+
}
|
|
609
|
+
console.log(formatKeyValue("Endpoint", baseUrl, chalk.cyan));
|
|
610
|
+
console.log(formatKeyValue("User PII", mcpEnv.ITERABLE_USER_PII === "true"
|
|
611
|
+
? chalk.green("Enabled")
|
|
612
|
+
: chalk.gray("Disabled")));
|
|
613
|
+
console.log(formatKeyValue("Writes", mcpEnv.ITERABLE_ENABLE_WRITES === "true"
|
|
614
|
+
? chalk.green("Enabled")
|
|
615
|
+
: chalk.gray("Disabled")));
|
|
616
|
+
console.log(formatKeyValue("Sends", mcpEnv.ITERABLE_ENABLE_SENDS === "true"
|
|
617
|
+
? chalk.green("Enabled")
|
|
618
|
+
: chalk.gray("Disabled")));
|
|
619
|
+
console.log();
|
|
620
|
+
const { proceed } = await inquirer.prompt([
|
|
621
|
+
{
|
|
622
|
+
type: "confirm",
|
|
623
|
+
name: "proceed",
|
|
624
|
+
message: "Proceed with configuration?",
|
|
625
|
+
default: true,
|
|
626
|
+
},
|
|
627
|
+
]);
|
|
628
|
+
if (!proceed) {
|
|
629
|
+
showInfo("Setup cancelled by user. No changes were made.");
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
const fileBasedTools = tools.filter((tool) => tool !== "claude-code");
|
|
633
|
+
const needsClaudeCode = tools.includes("claude-code");
|
|
634
|
+
if (fileBasedTools.length > 0) {
|
|
635
|
+
const { updateToolConfig } = await import("./utils/tool-config.js");
|
|
636
|
+
for (const tool of fileBasedTools) {
|
|
637
|
+
const configPath = TOOL_CONFIGS[tool];
|
|
638
|
+
const toolName = tool === "claude-desktop" ? "Claude Desktop" : "Cursor";
|
|
639
|
+
spinner.start(`Configuring ${toolName}...`);
|
|
640
|
+
try {
|
|
641
|
+
await updateToolConfig(configPath, iterableMcpConfig);
|
|
642
|
+
spinner.succeed(`${toolName} configured successfully`);
|
|
643
|
+
}
|
|
644
|
+
catch (error) {
|
|
645
|
+
spinner.fail(`Failed to configure ${toolName}`);
|
|
646
|
+
throw error;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
if (needsClaudeCode) {
|
|
651
|
+
spinner.start("Checking for Claude CLI...");
|
|
652
|
+
try {
|
|
653
|
+
await findCommand("claude");
|
|
654
|
+
spinner.succeed("Claude CLI found");
|
|
655
|
+
}
|
|
656
|
+
catch {
|
|
657
|
+
spinner.fail("Claude CLI not found");
|
|
658
|
+
showError("Please install Claude Code first: https://docs.claude.com/en/docs/claude-code/overview");
|
|
659
|
+
console.log();
|
|
660
|
+
showInfo("After installing, re-run: iterable-mcp setup --claude-code");
|
|
661
|
+
process.exit(1);
|
|
662
|
+
}
|
|
663
|
+
const configJson = JSON.stringify(iterableMcpConfig);
|
|
664
|
+
// Remove existing config
|
|
665
|
+
try {
|
|
666
|
+
await promisify(exec)("claude mcp remove iterable");
|
|
667
|
+
}
|
|
668
|
+
catch {
|
|
669
|
+
// Ignore errors
|
|
670
|
+
}
|
|
671
|
+
spinner.start("Configuring Claude Code...");
|
|
672
|
+
await new Promise((resolve, reject) => {
|
|
673
|
+
const child = spawn("claude", ["mcp", "add-json", "iterable", configJson], {
|
|
674
|
+
stdio: "inherit",
|
|
675
|
+
});
|
|
676
|
+
child.on("close", (code) => {
|
|
677
|
+
if (code === 0) {
|
|
678
|
+
resolve();
|
|
679
|
+
}
|
|
680
|
+
else {
|
|
681
|
+
reject(new Error(`claude mcp add-json exited with code ${code ?? "unknown"}`));
|
|
682
|
+
}
|
|
683
|
+
});
|
|
684
|
+
child.on("error", reject);
|
|
685
|
+
});
|
|
686
|
+
spinner.succeed("Claude Code configured successfully");
|
|
687
|
+
}
|
|
688
|
+
// Build configured tools list
|
|
689
|
+
const configuredTools = [];
|
|
690
|
+
if (fileBasedTools.includes("cursor"))
|
|
691
|
+
configuredTools.push("Cursor");
|
|
692
|
+
if (fileBasedTools.includes("claude-desktop"))
|
|
693
|
+
configuredTools.push("Claude Desktop");
|
|
694
|
+
if (needsClaudeCode)
|
|
695
|
+
configuredTools.push("Claude Code");
|
|
696
|
+
const toolsList = configuredTools.length === 1
|
|
697
|
+
? configuredTools[0]
|
|
698
|
+
: configuredTools.length === 2
|
|
699
|
+
? `${configuredTools[0]} and ${configuredTools[1]}`
|
|
700
|
+
: configuredTools.slice(0, -1).join(", ") +
|
|
701
|
+
", and " +
|
|
702
|
+
configuredTools[configuredTools.length - 1];
|
|
703
|
+
// Success!
|
|
704
|
+
console.log();
|
|
705
|
+
const nextSteps = [
|
|
706
|
+
`Restart ${toolsList} to load the new configuration`,
|
|
707
|
+
"Start using Iterable MCP tools in your conversations",
|
|
708
|
+
...(needsClaudeCode
|
|
709
|
+
? ["Run 'claude mcp list' to verify your setup"]
|
|
710
|
+
: []),
|
|
711
|
+
];
|
|
712
|
+
const tips = [
|
|
713
|
+
`Try: 'list my Iterable campaigns' in ${toolsList}`,
|
|
714
|
+
"Manage keys with 'iterable-mcp keys list'",
|
|
715
|
+
"Switch keys with 'iterable-mcp keys activate <name>'",
|
|
716
|
+
];
|
|
717
|
+
showCompletion("Setup Complete!", nextSteps, tips);
|
|
718
|
+
}
|
|
719
|
+
catch (error) {
|
|
720
|
+
spinner.stop();
|
|
721
|
+
showError(error instanceof Error ? error.message : "Setup failed");
|
|
722
|
+
process.exit(1);
|
|
723
|
+
}
|
|
724
|
+
};
|
|
725
|
+
//# sourceMappingURL=install.js.map
|