Skip to content

Commit 0ab3194

Browse files
authored
Update keychain references (#9)
1 parent 0a09da3 commit 0ab3194

File tree

6 files changed

+98
-16
lines changed

6 files changed

+98
-16
lines changed

src/install.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { fileURLToPath } from "url";
99
import { promisify } from "util";
1010

1111
import { getKeyManager } from "./key-manager.js";
12+
import { getKeyStorageMessage } from "./utils/formatting.js";
1213

1314
const { dirname, join } = path;
1415

@@ -341,11 +342,7 @@ export const setupMcpServer = async (): Promise<void> => {
341342
"Security Features",
342343
[
343344
"• API keys prompted interactively (never in shell history)",
344-
process.platform === "darwin"
345-
? "• Keys are stored securely in the macOS Keychain"
346-
: process.platform === "win32"
347-
? "• Keys are stored in ~/.iterable-mcp/keys.json"
348-
: "• Keys are stored in ~/.iterable-mcp/keys.json with restricted permissions",
345+
getKeyStorageMessage(true),
349346
"• Each key coupled to its endpoint (US/EU/custom)",
350347
],
351348
{ icon: icons.lock, theme: "info", padding: 1 }

src/keys-cli.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { promisify } from "util";
1414
const { dirname, join } = path;
1515

1616
import { getSpinner, loadUi } from "./utils/cli-env.js";
17+
import { getKeyStorageMessage } from "./utils/formatting.js";
1718
import { promptForApiKey } from "./utils/password-prompt.js";
1819

1920
const execFileAsync = promisify(execFile);
@@ -149,7 +150,7 @@ export async function handleKeysCommand(): Promise<void> {
149150
chalk.cyan("keys activate <name>") +
150151
" to switch between keys",
151152
"Use " + chalk.cyan("keys add") + " to add a new API key",
152-
"Keys are stored securely in macOS Keychain",
153+
getKeyStorageMessage(),
153154
];
154155

155156
showBox("Quick Tips", tips, {
@@ -316,7 +317,7 @@ export async function handleKeysCommand(): Promise<void> {
316317
);
317318
console.log();
318319

319-
showSuccess("Your API key is now stored securely in macOS Keychain");
320+
showSuccess(`Your API key "${name}" is now stored`);
320321

321322
// Offer to set newly added key as active
322323
const { activateNow } = await inquirer.prompt([
@@ -656,7 +657,7 @@ export async function handleKeysCommand(): Promise<void> {
656657
console.log(formatKeyValue("ID", resolved, chalk.gray));
657658
console.log();
658659

659-
showSuccess("Key removed from macOS Keychain");
660+
showSuccess("Key removed successfully");
660661
} catch (error) {
661662
spinner.fail("Failed to delete key");
662663
showError(error instanceof Error ? error.message : "Unknown error");
@@ -716,9 +717,7 @@ export async function handleKeysCommand(): Promise<void> {
716717
const tips = [
717718
"API keys are prompted interactively - never stored in shell history",
718719
"Each API key is tightly coupled to its endpoint (US/EU/custom)",
719-
process.platform === "darwin"
720-
? "Keys are stored securely in macOS Keychain"
721-
: "Keys are stored in ~/.iterable-mcp/keys.json with restricted permissions",
720+
getKeyStorageMessage(),
722721
"Use 'keys list' to see all your keys and their details",
723722
"The active key (● ACTIVE) is what your AI tools will use",
724723
"To update a key: delete the old one and add a new one",

src/utils/formatting.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Test-friendly, chalk-free formatter for Keychain choice labels.
2+
* Test-friendly, chalk-free formatter for stored key choice labels.
33
* Production coloring/wrapping is applied in ui.ts.
44
*/
55
export function formatKeychainChoiceLabelPlain(
@@ -18,3 +18,17 @@ export function formatKeychainChoiceLabelPlain(
1818
: "";
1919
return `${activeBadge}${name} ${endpoint}${flags}`;
2020
}
21+
22+
/**
23+
* Get platform-specific storage description for tips/help text
24+
* @param bulletPoint - Whether to include a bullet point prefix (default: false)
25+
*/
26+
export function getKeyStorageMessage(bulletPoint = false): string {
27+
const prefix = bulletPoint ? "• " : "";
28+
const message =
29+
process.platform === "darwin"
30+
? "Keys are stored securely in macOS Keychain"
31+
: "Keys are stored in ~/.iterable-mcp/keys.json" +
32+
(process.platform === "win32" ? "" : " with restricted permissions");
33+
return prefix + message;
34+
}

src/utils/ui.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,7 @@ export function showProgress(message: string, done = false): void {
441441
}
442442

443443
/**
444-
* Format a macOS Keychain entry label for selection lists
444+
* Format a stored key entry label for selection lists
445445
*/
446446
export function formatKeychainChoiceLabel(
447447
name: string,

tests/integration/mcp-protocol.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ describe("MCP Protocol Integration Tests", () => {
7070

7171
if (!isValidApiKey(resolvedApiKey)) {
7272
throw new Error(
73-
"No valid API key found. Set ITERABLE_API_KEY or add/activate a key in macOS Keychain."
73+
"No valid API key found. Set ITERABLE_API_KEY or add/activate a key using 'iterable-mcp keys'."
7474
);
7575
}
7676

tests/unit/ui-formatting.test.ts

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
/* eslint-disable simple-import-sort/imports */
2-
import { describe, it, expect } from "@jest/globals";
2+
import { describe, it, expect, beforeEach, afterEach } from "@jest/globals";
33

4-
import { formatKeychainChoiceLabelPlain } from "../../src/utils/formatting";
4+
import {
5+
formatKeychainChoiceLabelPlain,
6+
getKeyStorageMessage,
7+
} from "../../src/utils/formatting";
58

69
describe("formatKeychainChoiceLabel", () => {
710
it("includes name and endpoint, excludes id", () => {
@@ -49,3 +52,72 @@ describe("formatKeychainChoiceLabel", () => {
4952
expect(label).toContain("Sends: Off");
5053
});
5154
});
55+
56+
describe("getKeyStorageMessage", () => {
57+
let originalPlatform: string;
58+
59+
beforeEach(() => {
60+
originalPlatform = process.platform;
61+
});
62+
63+
afterEach(() => {
64+
Object.defineProperty(process, "platform", {
65+
value: originalPlatform,
66+
writable: true,
67+
configurable: true,
68+
});
69+
});
70+
71+
it("returns macOS Keychain message on darwin", () => {
72+
Object.defineProperty(process, "platform", {
73+
value: "darwin",
74+
writable: true,
75+
configurable: true,
76+
});
77+
expect(getKeyStorageMessage()).toBe(
78+
"Keys are stored securely in macOS Keychain"
79+
);
80+
});
81+
82+
it("returns file message on win32", () => {
83+
Object.defineProperty(process, "platform", {
84+
value: "win32",
85+
writable: true,
86+
configurable: true,
87+
});
88+
expect(getKeyStorageMessage()).toBe(
89+
"Keys are stored in ~/.iterable-mcp/keys.json"
90+
);
91+
});
92+
93+
it("returns file with permissions message on linux", () => {
94+
Object.defineProperty(process, "platform", {
95+
value: "linux",
96+
writable: true,
97+
configurable: true,
98+
});
99+
expect(getKeyStorageMessage()).toBe(
100+
"Keys are stored in ~/.iterable-mcp/keys.json with restricted permissions"
101+
);
102+
});
103+
104+
it("adds bullet point prefix when requested", () => {
105+
Object.defineProperty(process, "platform", {
106+
value: "darwin",
107+
writable: true,
108+
configurable: true,
109+
});
110+
expect(getKeyStorageMessage(true)).toBe(
111+
"• Keys are stored securely in macOS Keychain"
112+
);
113+
});
114+
115+
it("omits bullet point by default", () => {
116+
Object.defineProperty(process, "platform", {
117+
value: "darwin",
118+
writable: true,
119+
configurable: true,
120+
});
121+
expect(getKeyStorageMessage()).not.toContain("• ");
122+
});
123+
});

0 commit comments

Comments
 (0)