mcp-intervals 1.1.0 → 1.2.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 CHANGED
@@ -14,11 +14,14 @@ npx mcp-intervals init
14
14
  ```
15
15
 
16
16
  This will:
17
- 1. Detect installed MCP clients (Claude Code, Claude Desktop, Cursor, Windsurf)
18
- 2. Let you select which clients to configure
19
- 3. Prompt for your Intervals API token
20
- 4. Validate the token with your Intervals account
21
- 5. Save the configuration automatically
17
+ 1. Detect your shell (zsh, bash, or PowerShell)
18
+ 2. Store your API token securely in your shell profile (not in config files)
19
+ 3. Detect installed MCP clients (Claude Code, Claude Desktop, Cursor, Windsurf)
20
+ 4. Configure the selected clients
21
+
22
+ ### Security
23
+
24
+ The installer stores your API token as an environment variable in your shell profile (`~/.zshrc`, `~/.bashrc`, or PowerShell profile), keeping it out of project files that might be committed to git. MCP config files only contain the command reference, not the token.
22
25
 
23
26
  ## Manual Setup
24
27
 
@@ -28,37 +31,43 @@ This will:
28
31
  2. Go to **Options** (bottom-left) > **My Account** > **API Access**
29
32
  3. Copy your **API token**
30
33
 
31
- ### 2. Install in Claude Code
34
+ ### 2. Add the token to your shell profile
32
35
 
36
+ Add the following line to your shell profile:
37
+
38
+ **macOS/Linux (zsh)** - Add to `~/.zshrc`:
33
39
  ```bash
34
- # Available in all your projects (recommended)
35
- claude mcp add intervals --scope user -e INTERVALS_API_TOKEN=YOUR_TOKEN -- npx -y mcp-intervals
40
+ export INTERVALS_API_TOKEN="YOUR_TOKEN"
41
+ ```
36
42
 
37
- # Only in the current project, shared with team via .mcp.json (committed to git)
38
- claude mcp add intervals --scope project -e INTERVALS_API_TOKEN=YOUR_TOKEN -- npx -y mcp-intervals
43
+ **macOS/Linux (bash)** - Add to `~/.bashrc` or `~/.bash_profile`:
44
+ ```bash
45
+ export INTERVALS_API_TOKEN="YOUR_TOKEN"
46
+ ```
39
47
 
40
- # Only for you in the current project (default)
41
- claude mcp add intervals -e INTERVALS_API_TOKEN=YOUR_TOKEN -- npx -y mcp-intervals
48
+ **Windows (PowerShell)** - Add to your PowerShell profile:
49
+ ```powershell
50
+ $env:INTERVALS_API_TOKEN = "YOUR_TOKEN"
42
51
  ```
43
52
 
44
- Replace `YOUR_TOKEN` with your actual API token.
53
+ Then reload your shell or restart your terminal.
45
54
 
46
- ### 3. Install in Claude Desktop
55
+ ### 3. Configure MCP clients
47
56
 
48
- Add this to your config file:
57
+ Add this to your MCP config file (token is read from environment):
49
58
 
50
- - **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
51
- - **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
59
+ **Claude Code:**
60
+ ```bash
61
+ claude mcp add intervals --scope user -- npx -y mcp-intervals
62
+ ```
52
63
 
64
+ **Claude Desktop** (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
53
65
  ```json
54
66
  {
55
67
  "mcpServers": {
56
68
  "intervals": {
57
69
  "command": "npx",
58
- "args": ["-y", "mcp-intervals"],
59
- "env": {
60
- "INTERVALS_API_TOKEN": "YOUR_TOKEN"
61
- }
70
+ "args": ["-y", "mcp-intervals"]
62
71
  }
63
72
  }
64
73
  }
@@ -67,17 +76,14 @@ Add this to your config file:
67
76
  <details>
68
77
  <summary><strong>Cursor</strong></summary>
69
78
 
70
- Add to `.cursor/mcp.json` in your project or `~/.cursor/mcp.json` globally:
79
+ Add to `~/.cursor/mcp.json`:
71
80
 
72
81
  ```json
73
82
  {
74
83
  "mcpServers": {
75
84
  "intervals": {
76
85
  "command": "npx",
77
- "args": ["-y", "mcp-intervals"],
78
- "env": {
79
- "INTERVALS_API_TOKEN": "YOUR_TOKEN"
80
- }
86
+ "args": ["-y", "mcp-intervals"]
81
87
  }
82
88
  }
83
89
  }
@@ -95,10 +101,7 @@ Add to `~/.codeium/windsurf/mcp_config.json`:
95
101
  "mcpServers": {
96
102
  "intervals": {
97
103
  "command": "npx",
98
- "args": ["-y", "mcp-intervals"],
99
- "env": {
100
- "INTERVALS_API_TOKEN": "YOUR_TOKEN"
101
- }
104
+ "args": ["-y", "mcp-intervals"]
102
105
  }
103
106
  }
104
107
  }
@@ -16,6 +16,6 @@ interface McpServerConfig {
16
16
  export declare function detectClients(): McpClient[];
17
17
  export declare function readConfig(configPath: string): McpConfig;
18
18
  export declare function writeConfig(configPath: string, config: McpConfig): void;
19
- export declare function configureClient(configPath: string, token: string): void;
19
+ export declare function configureClient(configPath: string): void;
20
20
  export declare function hasExistingConfig(configPath: string): boolean;
21
21
  export {};
@@ -111,17 +111,15 @@ export function writeConfig(configPath, config) {
111
111
  }
112
112
  fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
113
113
  }
114
- export function configureClient(configPath, token) {
114
+ export function configureClient(configPath) {
115
115
  const config = readConfig(configPath);
116
116
  if (!config.mcpServers) {
117
117
  config.mcpServers = {};
118
118
  }
119
+ // Don't include token in config - it's stored in shell profile
119
120
  config.mcpServers.intervals = {
120
121
  command: "npx",
121
122
  args: ["-y", "mcp-intervals"],
122
- env: {
123
- INTERVALS_API_TOKEN: token,
124
- },
125
123
  };
126
124
  writeConfig(configPath, config);
127
125
  }
package/dist/cli/init.js CHANGED
@@ -4,6 +4,7 @@ import pc from "picocolors";
4
4
  import { detectClients, configureClient, hasExistingConfig } from "./clients.js";
5
5
  import { validateToken } from "./api.js";
6
6
  import { searchMultiselect, cancelSymbol } from "./prompts/search-multiselect.js";
7
+ import { detectShell, getProfilePath, findExistingToken, saveTokenToProfile, getReloadCommand, getManualInstruction, } from "./shell.js";
7
8
  // Logo ASCII art with gradient grays (256-color)
8
9
  const LOGO_LINES = [
9
10
  "██╗███╗ ██╗████████╗███████╗██████╗ ██╗ ██╗ █████╗ ██╗ ███████╗",
@@ -36,12 +37,107 @@ function shortenPath(fullPath) {
36
37
  }
37
38
  return fullPath;
38
39
  }
40
+ function maskToken(token) {
41
+ if (token.length <= 6) {
42
+ return "***";
43
+ }
44
+ const start = token.slice(0, 4);
45
+ const end = token.slice(-3);
46
+ return `${start}***${end}`;
47
+ }
39
48
  async function main() {
40
49
  showLogo();
41
50
  console.log();
42
51
  p.intro(pc.bgCyan(pc.black(" intervals ")));
43
52
  const spinner = p.spinner();
44
- // Detect clients
53
+ // Step 1: Detect shell
54
+ spinner.start("Detecting shell...");
55
+ let shellInfo = detectShell();
56
+ spinner.stop(shellInfo.detected
57
+ ? `Detected ${shellInfo.type} shell`
58
+ : `Could not detect shell, assuming ${shellInfo.type}`);
59
+ // If shell not detected, ask user
60
+ if (!shellInfo.detected) {
61
+ console.log();
62
+ const selectedShell = await p.select({
63
+ message: "Which shell do you use?",
64
+ options: [
65
+ { value: "zsh", label: "zsh" },
66
+ { value: "bash", label: "bash" },
67
+ { value: "powershell", label: "PowerShell" },
68
+ ],
69
+ });
70
+ if (p.isCancel(selectedShell)) {
71
+ p.cancel("Installation cancelled");
72
+ process.exit(0);
73
+ }
74
+ shellInfo = {
75
+ type: selectedShell,
76
+ profilePath: getProfilePath(selectedShell),
77
+ detected: true,
78
+ };
79
+ }
80
+ // Step 2: Check for existing token
81
+ const existingToken = findExistingToken(shellInfo.profilePath, shellInfo.type);
82
+ let token;
83
+ if (existingToken.found && existingToken.value) {
84
+ console.log();
85
+ p.log.info(`Found existing token: ${pc.cyan(existingToken.partial)}`);
86
+ p.log.message(pc.dim(` in ${shortenPath(shellInfo.profilePath)}`));
87
+ const replaceToken = await p.select({
88
+ message: "What do you want to do?",
89
+ options: [
90
+ { value: "keep", label: "Keep existing token" },
91
+ { value: "replace", label: "Replace with new token" },
92
+ ],
93
+ });
94
+ if (p.isCancel(replaceToken)) {
95
+ p.cancel("Installation cancelled");
96
+ process.exit(0);
97
+ }
98
+ if (replaceToken === "keep") {
99
+ token = existingToken.value;
100
+ }
101
+ else {
102
+ console.log();
103
+ token = await p.password({
104
+ message: "Enter your new Intervals API token:",
105
+ });
106
+ }
107
+ }
108
+ else {
109
+ // No existing token, ask for one
110
+ console.log();
111
+ token = await p.password({
112
+ message: "Enter your Intervals API token:",
113
+ });
114
+ }
115
+ if (p.isCancel(token) || !token || token.trim() === "") {
116
+ p.cancel("No token provided");
117
+ process.exit(1);
118
+ }
119
+ const finalToken = token.trim();
120
+ // Step 3: Validate token
121
+ spinner.start("Validating token...");
122
+ const validation = await validateToken(finalToken);
123
+ if (!validation.valid) {
124
+ spinner.stop(pc.red("Token validation failed"));
125
+ p.log.error(validation.error || "Invalid token");
126
+ p.log.message(pc.dim(" Find your API token at: https://[subdomain].myintervals.com/account/api/"));
127
+ console.log();
128
+ const continueAnyway = await p.confirm({
129
+ message: "Save configuration anyway (without validation)?",
130
+ initialValue: false,
131
+ });
132
+ if (p.isCancel(continueAnyway) || !continueAnyway) {
133
+ p.cancel("Installation cancelled");
134
+ process.exit(1);
135
+ }
136
+ }
137
+ else {
138
+ spinner.stop(`Token valid! Connected to "${validation.workspace}"`);
139
+ }
140
+ // Step 4: Detect MCP clients
45
141
  spinner.start("Detecting MCP clients...");
46
142
  const clients = detectClients();
47
143
  const detectedClients = clients.filter((c) => c.detected);
@@ -60,13 +156,13 @@ async function main() {
60
156
  p.cancel("No MCP clients detected. Install Claude Code, Claude Desktop, Cursor, or Windsurf first.");
61
157
  process.exit(1);
62
158
  }
63
- // Select clients with search multiselect
159
+ // Step 5: Select clients
64
160
  const clientChoices = detectedClients.map((client) => {
65
161
  const hasExisting = hasExistingConfig(client.configPath);
66
162
  return {
67
163
  value: client.id,
68
164
  label: client.name,
69
- hint: shortenPath(client.configPath) + (hasExisting ? " - will overwrite" : ""),
165
+ hint: shortenPath(client.configPath) + (hasExisting ? " - will update" : ""),
70
166
  };
71
167
  });
72
168
  // Pre-select clients that don't have existing config
@@ -85,41 +181,14 @@ async function main() {
85
181
  process.exit(0);
86
182
  }
87
183
  const selectedClients = detectedClients.filter((c) => Array.isArray(selectedIds) && selectedIds.includes(c.id));
88
- // Get API token
184
+ // Step 6: Show summary and confirm
89
185
  console.log();
90
- const token = await p.password({
91
- message: "Enter your Intervals API token:",
92
- });
93
- if (p.isCancel(token) || !token || token.trim() === "") {
94
- p.cancel("No token provided");
95
- process.exit(1);
96
- }
97
- // Validate token
98
- spinner.start("Validating token...");
99
- const validation = await validateToken(token.trim());
100
- if (!validation.valid) {
101
- spinner.stop(pc.red("Token validation failed"));
102
- p.log.error(validation.error || "Invalid token");
103
- p.log.message(pc.dim(" Find your API token at: https://[subdomain].myintervals.com/account/api/"));
104
- console.log();
105
- const continueAnyway = await p.confirm({
106
- message: "Save configuration anyway (without validation)?",
107
- initialValue: false,
108
- });
109
- if (p.isCancel(continueAnyway) || !continueAnyway) {
110
- p.cancel("Installation cancelled");
111
- process.exit(1);
112
- }
113
- }
114
- else {
115
- spinner.stop(`Token valid! Connected to "${validation.workspace}"`);
116
- }
117
- // Show summary and confirm
118
- console.log();
119
- const summaryLines = [];
120
- for (const client of selectedClients) {
121
- summaryLines.push(`${pc.cyan(client.name)} ${pc.dim(shortenPath(client.configPath))}`);
122
- }
186
+ const summaryLines = [
187
+ `${pc.bold("Token:")} ${shortenPath(shellInfo.profilePath)}`,
188
+ "",
189
+ pc.bold("MCP clients:"),
190
+ ...selectedClients.map((c) => ` ${c.name} ${pc.dim(shortenPath(c.configPath))}`),
191
+ ];
123
192
  p.note(summaryLines.join("\n"), "Will configure");
124
193
  const confirmed = await p.confirm({
125
194
  message: "Proceed with configuration?",
@@ -129,26 +198,42 @@ async function main() {
129
198
  p.cancel("Installation cancelled");
130
199
  process.exit(0);
131
200
  }
132
- // Configure each client
133
- spinner.start("Saving configuration...");
201
+ // Step 7: Save token to shell profile
202
+ spinner.start("Saving token to shell profile...");
203
+ const tokenResult = saveTokenToProfile(shellInfo.profilePath, shellInfo.type, finalToken);
204
+ if (!tokenResult.success) {
205
+ spinner.stop(pc.red("Failed to save token"));
206
+ console.log();
207
+ p.log.error(`Cannot write to ${shortenPath(shellInfo.profilePath)}`);
208
+ p.log.message("");
209
+ p.log.message(pc.bold("Add this line manually to your shell profile:"));
210
+ p.log.message("");
211
+ p.log.message(` ${pc.cyan(getManualInstruction(finalToken, shellInfo.type))}`);
212
+ console.log();
213
+ }
214
+ else {
215
+ spinner.stop(`Token saved to ${shortenPath(shellInfo.profilePath)}`);
216
+ }
217
+ // Step 8: Configure MCP clients
218
+ spinner.start("Configuring MCP clients...");
134
219
  const results = [];
135
220
  for (const client of selectedClients) {
136
221
  try {
137
- configureClient(client.configPath, token.trim());
222
+ configureClient(client.configPath);
138
223
  results.push({ client: client.name, success: true });
139
224
  }
140
225
  catch (error) {
141
226
  results.push({
142
227
  client: client.name,
143
228
  success: false,
144
- error: error instanceof Error ? error.message : String(error)
229
+ error: error instanceof Error ? error.message : String(error),
145
230
  });
146
231
  }
147
232
  }
148
233
  const successful = results.filter((r) => r.success);
149
234
  const failed = results.filter((r) => !r.success);
150
235
  spinner.stop("Configuration complete");
151
- // Show results
236
+ // Step 9: Show results
152
237
  console.log();
153
238
  if (successful.length > 0) {
154
239
  const resultLines = successful.map((r) => {
@@ -164,6 +249,12 @@ async function main() {
164
249
  p.log.message(` ${pc.red("✗")} ${r.client}: ${pc.dim(r.error)}`);
165
250
  }
166
251
  }
252
+ // Step 10: Show reload instructions
253
+ if (tokenResult.success) {
254
+ console.log();
255
+ const reloadCmd = getReloadCommand(shellInfo.profilePath, shellInfo.type);
256
+ p.note(`${pc.bold("To activate the token, run:")}\n\n ${pc.cyan(reloadCmd)}\n\nOr restart your terminal.`, "Next step");
257
+ }
167
258
  console.log();
168
259
  p.outro(pc.green("Done! Restart your MCP clients to use mcp-intervals."));
169
260
  }
@@ -0,0 +1,39 @@
1
+ export type ShellType = "zsh" | "bash" | "powershell";
2
+ export interface ShellInfo {
3
+ type: ShellType;
4
+ profilePath: string;
5
+ detected: boolean;
6
+ }
7
+ export interface ExistingToken {
8
+ found: boolean;
9
+ value?: string;
10
+ partial?: string;
11
+ lineNumber?: number;
12
+ }
13
+ /**
14
+ * Detect the user's shell and return profile path
15
+ */
16
+ export declare function detectShell(): ShellInfo;
17
+ /**
18
+ * Get profile path for a specific shell type
19
+ */
20
+ export declare function getProfilePath(shellType: ShellType): string;
21
+ /**
22
+ * Check if token already exists in shell profile
23
+ */
24
+ export declare function findExistingToken(profilePath: string, shellType: ShellType): ExistingToken;
25
+ /**
26
+ * Save token to shell profile
27
+ */
28
+ export declare function saveTokenToProfile(profilePath: string, shellType: ShellType, token: string): {
29
+ success: boolean;
30
+ error?: string;
31
+ };
32
+ /**
33
+ * Get the command to reload the shell profile
34
+ */
35
+ export declare function getReloadCommand(profilePath: string, shellType: ShellType): string;
36
+ /**
37
+ * Get manual instruction line for when auto-save fails
38
+ */
39
+ export declare function getManualInstruction(token: string, shellType: ShellType): string;
@@ -0,0 +1,191 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import * as os from "os";
4
+ const TOKEN_VAR_NAME = "INTERVALS_API_TOKEN";
5
+ /**
6
+ * Detect the user's shell and return profile path
7
+ */
8
+ export function detectShell() {
9
+ const platform = os.platform();
10
+ const home = os.homedir();
11
+ if (platform === "win32") {
12
+ // Windows: assume PowerShell
13
+ const profilePath = process.env.USERPROFILE
14
+ ? path.join(process.env.USERPROFILE, "Documents", "PowerShell", "Microsoft.PowerShell_profile.ps1")
15
+ : path.join(home, "Documents", "PowerShell", "Microsoft.PowerShell_profile.ps1");
16
+ return {
17
+ type: "powershell",
18
+ profilePath,
19
+ detected: true,
20
+ };
21
+ }
22
+ // macOS/Linux: check $SHELL
23
+ const shellEnv = process.env.SHELL || "";
24
+ if (shellEnv.endsWith("zsh")) {
25
+ return {
26
+ type: "zsh",
27
+ profilePath: path.join(home, ".zshrc"),
28
+ detected: true,
29
+ };
30
+ }
31
+ if (shellEnv.endsWith("bash")) {
32
+ // On macOS, prefer .bash_profile for login shells
33
+ const bashProfile = path.join(home, ".bash_profile");
34
+ const bashrc = path.join(home, ".bashrc");
35
+ if (platform === "darwin" && fs.existsSync(bashProfile)) {
36
+ return {
37
+ type: "bash",
38
+ profilePath: bashProfile,
39
+ detected: true,
40
+ };
41
+ }
42
+ return {
43
+ type: "bash",
44
+ profilePath: bashrc,
45
+ detected: true,
46
+ };
47
+ }
48
+ // Default to zsh on macOS, bash on Linux
49
+ if (platform === "darwin") {
50
+ return {
51
+ type: "zsh",
52
+ profilePath: path.join(home, ".zshrc"),
53
+ detected: false,
54
+ };
55
+ }
56
+ return {
57
+ type: "bash",
58
+ profilePath: path.join(home, ".bashrc"),
59
+ detected: false,
60
+ };
61
+ }
62
+ /**
63
+ * Get profile path for a specific shell type
64
+ */
65
+ export function getProfilePath(shellType) {
66
+ const platform = os.platform();
67
+ const home = os.homedir();
68
+ switch (shellType) {
69
+ case "zsh":
70
+ return path.join(home, ".zshrc");
71
+ case "bash":
72
+ if (platform === "darwin") {
73
+ const bashProfile = path.join(home, ".bash_profile");
74
+ if (fs.existsSync(bashProfile)) {
75
+ return bashProfile;
76
+ }
77
+ }
78
+ return path.join(home, ".bashrc");
79
+ case "powershell":
80
+ return process.env.USERPROFILE
81
+ ? path.join(process.env.USERPROFILE, "Documents", "PowerShell", "Microsoft.PowerShell_profile.ps1")
82
+ : path.join(home, "Documents", "PowerShell", "Microsoft.PowerShell_profile.ps1");
83
+ }
84
+ }
85
+ /**
86
+ * Create partial token for display (e.g., "7ni6***chj")
87
+ */
88
+ function maskToken(token) {
89
+ if (token.length <= 6) {
90
+ return "***";
91
+ }
92
+ const start = token.slice(0, 4);
93
+ const end = token.slice(-3);
94
+ return `${start}***${end}`;
95
+ }
96
+ /**
97
+ * Check if token already exists in shell profile
98
+ */
99
+ export function findExistingToken(profilePath, shellType) {
100
+ if (!fs.existsSync(profilePath)) {
101
+ return { found: false };
102
+ }
103
+ const content = fs.readFileSync(profilePath, "utf-8");
104
+ const lines = content.split("\n");
105
+ // Pattern depends on shell type
106
+ const pattern = shellType === "powershell"
107
+ ? /\$env:INTERVALS_API_TOKEN\s*=\s*["']([^"']+)["']/
108
+ : /export\s+INTERVALS_API_TOKEN\s*=\s*["']?([^"'\s]+)["']?/;
109
+ for (let i = 0; i < lines.length; i++) {
110
+ const match = lines[i].match(pattern);
111
+ if (match) {
112
+ const value = match[1];
113
+ return {
114
+ found: true,
115
+ value,
116
+ partial: maskToken(value),
117
+ lineNumber: i + 1,
118
+ };
119
+ }
120
+ }
121
+ return { found: false };
122
+ }
123
+ /**
124
+ * Format the export line for the given shell
125
+ */
126
+ function formatExportLine(token, shellType) {
127
+ if (shellType === "powershell") {
128
+ return `$env:${TOKEN_VAR_NAME} = "${token}"`;
129
+ }
130
+ return `export ${TOKEN_VAR_NAME}="${token}"`;
131
+ }
132
+ /**
133
+ * Save token to shell profile
134
+ */
135
+ export function saveTokenToProfile(profilePath, shellType, token) {
136
+ try {
137
+ const dir = path.dirname(profilePath);
138
+ // Create directory if it doesn't exist (for PowerShell profile)
139
+ if (!fs.existsSync(dir)) {
140
+ fs.mkdirSync(dir, { recursive: true });
141
+ }
142
+ const exportLine = formatExportLine(token, shellType);
143
+ const existingToken = findExistingToken(profilePath, shellType);
144
+ if (!fs.existsSync(profilePath)) {
145
+ // Create new file
146
+ fs.writeFileSync(profilePath, exportLine + "\n", { mode: 0o644 });
147
+ return { success: true };
148
+ }
149
+ let content = fs.readFileSync(profilePath, "utf-8");
150
+ if (existingToken.found) {
151
+ // Replace existing line
152
+ const pattern = shellType === "powershell"
153
+ ? /\$env:INTERVALS_API_TOKEN\s*=\s*["'][^"']*["']/g
154
+ : /export\s+INTERVALS_API_TOKEN\s*=\s*["']?[^"'\s]*["']?/g;
155
+ content = content.replace(pattern, exportLine);
156
+ }
157
+ else {
158
+ // Append to file
159
+ const newline = content.endsWith("\n") ? "" : "\n";
160
+ content = content + newline + "\n# Intervals API token\n" + exportLine + "\n";
161
+ }
162
+ fs.writeFileSync(profilePath, content, { mode: 0o644 });
163
+ return { success: true };
164
+ }
165
+ catch (error) {
166
+ return {
167
+ success: false,
168
+ error: error instanceof Error ? error.message : String(error),
169
+ };
170
+ }
171
+ }
172
+ /**
173
+ * Get the command to reload the shell profile
174
+ */
175
+ export function getReloadCommand(profilePath, shellType) {
176
+ if (shellType === "powershell") {
177
+ return ". $PROFILE";
178
+ }
179
+ // Use ~ notation for display
180
+ const home = os.homedir();
181
+ const displayPath = profilePath.startsWith(home)
182
+ ? "~" + profilePath.slice(home.length)
183
+ : profilePath;
184
+ return `source ${displayPath}`;
185
+ }
186
+ /**
187
+ * Get manual instruction line for when auto-save fails
188
+ */
189
+ export function getManualInstruction(token, shellType) {
190
+ return formatExportLine(token, shellType);
191
+ }
package/dist/client.d.ts CHANGED
@@ -33,5 +33,7 @@ export declare class IntervalsClient {
33
33
  datebegin?: string;
34
34
  dateend?: string;
35
35
  }): Promise<Record<string, unknown>>;
36
- getMe(): Promise<Record<string, unknown>>;
36
+ getMe(): Promise<{
37
+ personid: number;
38
+ }>;
37
39
  }
package/dist/client.js CHANGED
@@ -113,6 +113,7 @@ export class IntervalsClient {
113
113
  // --- Me (current user) ---
114
114
  async getMe() {
115
115
  const data = await this.request(`/me/`);
116
- return data.me;
116
+ // Return the first user object with personid from the top-level response
117
+ return { ...data.me[0], personid: data.personid };
117
118
  }
118
119
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-intervals",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "MCP server for Intervals task management",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",