toolstile 0.4.1 → 0.5.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
@@ -7,13 +7,13 @@ npx toolstile audit
7
7
  ```
8
8
 
9
9
  It reads the configs of Claude Code, Claude Desktop, Cursor, Codex, VS Code,
10
- Windsurf, Gemini CLI and Cline and grades them for:
10
+ Windsurf, Devin Desktop, Gemini CLI and Cline and grades them for:
11
11
 
12
12
  | Check | Severity |
13
13
  | --- | --- |
14
14
  | API keys, tokens and passwords written into config files, URLs or arguments | high |
15
15
  | Remote servers reached over plain `http://` | high |
16
- | Containers run with `--privileged` or the Docker socket | high |
16
+ | Containers run with `--privileged`, the Docker socket or host networking (`--net=host`) | high |
17
17
  | The same credential pasted into several clients' configs | medium |
18
18
  | Config files holding secrets that other accounts can reach (file and folder permissions) | medium |
19
19
  | `npx`, `uvx` and `docker` servers that run whatever version is newest | medium |
@@ -73,7 +73,7 @@ you pick which to apply (space toggles, enter applies):
73
73
  | Rule that approves a whole server, or `enableAllProjectMcpServers` | Removes it. These start unselected, since the agent then asks before each tool. |
74
74
 
75
75
  Everything else (secrets in arguments or URLs, Claude Desktop and Cline, which
76
- cannot read variables, Windows, `http://` servers, Docker images, broad
76
+ cannot read variables, Devin Desktop, Windows, `http://` servers, Docker images, broad
77
77
  filesystem roots and auto-approve lists) is listed with what to change by hand.
78
78
 
79
79
  Each edited file is checked before it is written: it must parse to exactly the
@@ -84,8 +84,8 @@ them once your agents work. After moving secrets, open a new terminal and restar
84
84
  your agents so they start with the variables, and rotate any secret that sat in a
85
85
  shared or synced file.
86
86
 
87
- `--dry-run` only shows the changes; `--yes` applies the ones that start selected
88
- without asking.
87
+ `--dry-run` only shows the changes; `--yes` (or `-y`) applies the ones that start
88
+ selected without asking.
89
89
 
90
90
  ## The audit only reads
91
91
 
@@ -99,7 +99,7 @@ without asking.
99
99
  for example `mcpServers.github.env.GITHUB_TOKEN`, and the kind of secret.
100
100
  - Never quotes a config file: one that cannot be read or parsed is listed with a
101
101
  reason and position only, for example `invalid TOML at line 3, column 12`.
102
- - About 3,500 lines of TypeScript with two runtime dependencies
102
+ - Plain TypeScript with two runtime dependencies
103
103
  ([smol-toml](https://github.com/squirrelchat/smol-toml), for Codex's TOML
104
104
  config, and [jsonc-parser](https://github.com/microsoft/node-jsonc-parser), so
105
105
  `--fix` keeps comments and layout in JSON files) and an optional one ([resvg-js](https://github.com/thx/resvg-js), only
@@ -109,7 +109,7 @@ without asking.
109
109
 
110
110
  ```text
111
111
  --fix Suggest fixes and apply the ones you select
112
- --yes With --fix: apply the fixes that start selected, without asking
112
+ -y, --yes With --fix: apply the fixes that start selected, without asking
113
113
  --dry-run With --fix: only show the changes
114
114
  --json Print results as JSON
115
115
  --card [file] Write a shareable grade card (default toolstile-audit.png;
@@ -160,13 +160,15 @@ values) and prints a ready-to-post link. To post the text report instead, add
160
160
  | Codex | `$CODEX_HOME/config.toml` (default `~/.codex`) | `.codex/config.toml` |
161
161
  | VS Code | user `settings.json` and `mcp.json` | `.vscode/mcp.json` |
162
162
  | Windsurf | `~/.codeium/windsurf/mcp_config.json` | |
163
+ | Devin Desktop | `~/.config/devin/mcp_config.json` (`%APPDATA%\devin` on Windows) | `.devin/mcp_config.json`, `.devin/mcp_config.local.json` |
163
164
  | Gemini CLI | `~/.gemini/settings.json` | `.gemini/settings.json` |
164
165
  | Cline | `cline_mcp_settings.json` in VS Code or Cursor storage | |
165
166
 
166
167
  ## Connect your agents to Toolstile
167
168
 
168
- If you use [Toolstile](https://www.toolstile.com), the CLI can also sign in and set
169
- up the agents on a machine for you:
169
+ If you use the managed [Toolstile](https://www.toolstile.com) service, the CLI can
170
+ also sign in and set up the agents on a machine for you (self-hosted instances
171
+ use each agent app's own sign-in instead):
170
172
 
171
173
  ```sh
172
174
  npx toolstile auth login
@@ -181,7 +183,7 @@ reach the machine you are signing in, so it works over SSH, in containers and on
181
183
  servers without a browser.
182
184
 
183
185
  `mcp setup` lists the agents it finds (Claude Code, Claude Desktop, Cursor,
184
- Codex, VS Code, Windsurf, Gemini CLI, Cline) and lets you turn each one on or
186
+ Codex, VS Code, Windsurf, Devin Desktop, Gemini CLI, Cline) and lets you turn each one on or
185
187
  off. Run it again any time to change the selection. Each enabled agent appears
186
188
  under this machine in the Toolstile dashboard with its own access, so you can
187
189
  grant, limit or revoke it there; then choose which MCP servers it can use.
@@ -205,6 +207,11 @@ readable by you alone; `%APPDATA%\toolstile` on Windows). No token is written
205
207
  into the agent's config, so `toolstile audit` has nothing to flag. JSON configs
206
208
  with comments are not rewritten; setup prints the entry to add yourself.
207
209
 
210
+ The CLI keeps the credentials file, `--fix`'s `secrets.env` and its backups in one
211
+ folder: `$TOOLSTILE_CONFIG_DIR` when set, otherwise `%APPDATA%\toolstile` on
212
+ Windows and `$XDG_CONFIG_HOME/toolstile` (default `~/.config/toolstile`)
213
+ elsewhere.
214
+
208
215
  `npx toolstile auth logout` ends the CLI's sign-in. Agents you enabled keep
209
216
  working until you turn them off with `mcp setup` or delete the device in the
210
217
  dashboard.
@@ -231,7 +238,9 @@ const { grade, findings } = audit();
231
238
  Built by [Akshit Kr Nagpal](https://akshit.io) as part of
232
239
  [Toolstile](https://www.toolstile.com), an MCP gateway that keeps upstream
233
240
  credentials off your machines and lets you choose which tools each agent can use.
234
- The audit works on its own and does not require a Toolstile account.
241
+ Toolstile is available as a managed service and as an open-source self-hosted
242
+ edition at [toolstile/toolstile](https://github.com/toolstile/toolstile). The audit
243
+ works on its own and does not require a Toolstile account.
235
244
 
236
245
  Questions and reports: support@toolstile.com.
237
246
 
package/dist/agents.js CHANGED
@@ -4,7 +4,7 @@ import { chmodSync, existsSync, mkdirSync, readFileSync, realpathSync, renameSyn
4
4
  import { dirname, join } from "node:path";
5
5
  import { parse as parseToml } from "smol-toml";
6
6
  import { parseJsonc } from "./parse.js";
7
- import { appData, CLIENT_NAMES } from "./sources.js";
7
+ import { appData, CLIENT_NAMES, devinDir } from "./sources.js";
8
8
  /** The server name `toolstile mcp setup` adds to each agent's config. */
9
9
  export const ENTRY = "toolstile";
10
10
  export function targets(opts) {
@@ -21,6 +21,8 @@ export function targets(opts) {
21
21
  ["codex", join(codexHome, "config.toml"), "codex", codexHome],
22
22
  ["vscode", join(code, "mcp.json"), "vscode", code],
23
23
  ["windsurf", join(home, ".codeium", "windsurf", "mcp_config.json"), "mcpServers", join(home, ".codeium", "windsurf")],
24
+ // Devin Desktop, which replaced the Windsurf editor, reads its own user config.
25
+ ["devin", join(devinDir(opts), "mcp_config.json"), "mcpServers", devinDir(opts)],
24
26
  ["gemini", join(home, ".gemini", "settings.json"), "mcpServers", join(home, ".gemini")],
25
27
  ["cline", join(cline, "settings", "cline_mcp_settings.json"), "mcpServers", cline],
26
28
  ];
package/dist/cli.js CHANGED
@@ -27,7 +27,7 @@ Usage
27
27
  npx toolstile audit [options]
28
28
 
29
29
  Checks Claude Code, Claude Desktop, Cursor, Codex, VS Code, Windsurf,
30
- Gemini CLI and Cline configs for plaintext secrets, credentials copied
30
+ Devin Desktop, Gemini CLI and Cline configs for plaintext secrets, credentials copied
31
31
  between clients, unpinned packages, auto-approved tools, broad
32
32
  filesystem access and unencrypted remote servers.
33
33
 
@@ -47,7 +47,7 @@ safely are listed with what to do by hand.
47
47
 
48
48
  Options
49
49
  --fix Suggest fixes and apply the ones you select
50
- --yes With --fix: apply the fixes that start selected, without asking
50
+ -y, --yes With --fix: apply the fixes that start selected, without asking
51
51
  --dry-run With --fix: only show the changes
52
52
  --json Print results as JSON
53
53
  --card [file] Write a shareable grade card (default toolstile-audit.png;
@@ -89,7 +89,7 @@ Options for setup
89
89
  --workspace <slug> Workspace the agents use (default: the last one, or personal)
90
90
  --device-name <name> Name for this machine (default: its hostname)
91
91
 
92
- Agents: claude-code, claude-desktop, cursor, codex, vscode, windsurf, gemini, cline
92
+ Agents: claude-code, claude-desktop, cursor, codex, vscode, windsurf, devin, gemini, cline
93
93
  `;
94
94
  function version() {
95
95
  const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
@@ -25,6 +25,16 @@ export declare class UserError extends Error {
25
25
  export declare function normalizeServer(value: string): string;
26
26
  /** A gateway URL the server reported: HTTPS (or loopback HTTP) with no credentials, else undefined. */
27
27
  export declare function gatewayUrl(value: unknown): string | undefined;
28
+ /**
29
+ * A sign-in page link the server sent, if it is safe to show and open: a URL on the server's
30
+ * own origin (so HTTPS, or HTTP for a loopback server) with no credentials, else undefined.
31
+ */
32
+ export declare function signInUrl(value: unknown, server: string): URL | undefined;
33
+ /**
34
+ * The program and arguments that open `url` in the default browser. Never a shell string:
35
+ * Windows uses rundll32 rather than `cmd /c start`, where `&` in a URL would end the command.
36
+ */
37
+ export declare function browserCommand(platform: NodeJS.Platform, url: string): [string, string[]];
28
38
  export declare function login(ctx: Context, options: {
29
39
  server?: string;
30
40
  browser: boolean;
package/dist/commands.js CHANGED
@@ -24,6 +24,9 @@ async function compensate(error, cleanup, remedy) {
24
24
  }
25
25
  throw error;
26
26
  }
27
+ const LOOPBACK = /^(localhost|127\.\d+\.\d+\.\d+|\[::1\])$/;
28
+ /** HTTPS, or HTTP to a loopback host for local development. */
29
+ const secureScheme = (url) => url.protocol === "https:" || (url.protocol === "http:" && LOOPBACK.test(url.hostname));
27
30
  /** HTTPS, except a loopback server for local development. */
28
31
  export function normalizeServer(value) {
29
32
  let url;
@@ -33,8 +36,7 @@ export function normalizeServer(value) {
33
36
  catch {
34
37
  throw new UserError(`--server must be a URL, for example https://app.toolstile.com`);
35
38
  }
36
- const loopback = /^(localhost|127\.\d+\.\d+\.\d+|\[::1\])$/.test(url.hostname);
37
- if (url.protocol !== "https:" && !(loopback && url.protocol === "http:"))
39
+ if (!secureScheme(url))
38
40
  throw new UserError("--server must use https://");
39
41
  return url.origin;
40
42
  }
@@ -49,13 +51,41 @@ export function gatewayUrl(value) {
49
51
  catch {
50
52
  return undefined;
51
53
  }
52
- const loopback = /^(localhost|127\.\d+\.\d+\.\d+|\[::1\])$/.test(url.hostname);
53
54
  if (url.username || url.password || url.search || url.hash)
54
55
  return undefined;
55
- if (url.protocol !== "https:" && !(loopback && url.protocol === "http:"))
56
+ if (!secureScheme(url))
56
57
  return undefined;
57
58
  return url.href;
58
59
  }
60
+ /**
61
+ * A sign-in page link the server sent, if it is safe to show and open: a URL on the server's
62
+ * own origin (so HTTPS, or HTTP for a loopback server) with no credentials, else undefined.
63
+ */
64
+ export function signInUrl(value, server) {
65
+ if (typeof value !== "string")
66
+ return undefined;
67
+ let url;
68
+ try {
69
+ url = new URL(value);
70
+ }
71
+ catch {
72
+ return undefined;
73
+ }
74
+ if (!secureScheme(url) || url.username || url.password || url.origin !== server)
75
+ return undefined;
76
+ return url;
77
+ }
78
+ /**
79
+ * The program and arguments that open `url` in the default browser. Never a shell string:
80
+ * Windows uses rundll32 rather than `cmd /c start`, where `&` in a URL would end the command.
81
+ */
82
+ export function browserCommand(platform, url) {
83
+ if (platform === "darwin")
84
+ return ["open", [url]];
85
+ if (platform === "win32")
86
+ return ["rundll32", ["url.dll,FileProtocolHandler", url]];
87
+ return ["xdg-open", [url]];
88
+ }
59
89
  function defaultOpenBrowser(ctx) {
60
90
  return (url) => {
61
91
  // Over SSH or without a display the URL and code are enough; nothing to open.
@@ -63,9 +93,9 @@ function defaultOpenBrowser(ctx) {
63
93
  return;
64
94
  if (ctx.platform === "linux" && !ctx.env.DISPLAY && !ctx.env.WAYLAND_DISPLAY)
65
95
  return;
66
- const [command, args] = ctx.platform === "darwin" ? ["open", [url]] : ctx.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
96
+ const [command, args] = browserCommand(ctx.platform, url);
67
97
  try {
68
- spawn(command, args, { stdio: "ignore", detached: true }).on("error", () => { }).unref();
98
+ spawn(command, args, { stdio: "ignore", detached: true, shell: false }).on("error", () => { }).unref();
69
99
  }
70
100
  catch { }
71
101
  };
@@ -81,15 +111,29 @@ export async function login(ctx, options) {
81
111
  const code = await api.deviceCode();
82
112
  // The page fills in the code and this machine's name; the name can be edited there and
83
113
  // becomes the device this machine's agents are listed under.
84
- const link = new URL(code.verification_uri_complete);
85
- link.searchParams.set("device_name", machineName(ctx));
86
- print(ctx, `Open this link on any device with a browser and approve the sign-in:`);
87
- print(ctx);
88
- print(ctx, ` ${s.bold(link.href)}`);
89
- print(ctx);
90
- print(ctx, `Check that the page shows this code: ${s.bold(formatCode(code.user_code))}`);
114
+ const link = signInUrl(code.verification_uri_complete, server);
115
+ if (link) {
116
+ link.searchParams.set("device_name", machineName(ctx));
117
+ print(ctx, `Open this link on any device with a browser and approve the sign-in:`);
118
+ print(ctx);
119
+ print(ctx, ` ${s.bold(link.href)}`);
120
+ print(ctx);
121
+ print(ctx, `Check that the page shows this code: ${s.bold(formatCode(code.user_code))}`);
122
+ }
123
+ else {
124
+ // Never show or open a link that points anywhere but the server being signed in to.
125
+ const page = signInUrl(code.verification_uri, server);
126
+ if (!page)
127
+ throw new UserError(`${server} sent a sign-in link for a different address. Check --server and TOOLSTILE_URL.`);
128
+ ctx.stderr.write(`The sign-in link from ${server} was not valid, so no browser was opened.\n`);
129
+ print(ctx, `Open this page on any device with a browser, enter the code and approve the sign-in:`);
130
+ print(ctx);
131
+ print(ctx, ` ${s.bold(page.href)}`);
132
+ print(ctx);
133
+ print(ctx, `Code: ${s.bold(formatCode(code.user_code))}`);
134
+ }
91
135
  print(ctx, s.dim(`Waiting for approval. The code expires in ${Math.round(code.expires_in / 60)} minutes.`));
92
- if (options.browser)
136
+ if (link && options.browser)
93
137
  (ctx.openBrowser ?? defaultOpenBrowser(ctx))(link.href);
94
138
  const sleep = ctx.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
95
139
  let interval = Math.max(code.interval, 1) * 1000;
package/dist/fix.js CHANGED
@@ -156,7 +156,9 @@ function moveSecret(sec, names) {
156
156
  }
157
157
  const ref = REFERENCE[s.client];
158
158
  if (!ref)
159
- return `${CLIENT_NAMES[s.client]} does not read environment variables in its config`;
159
+ return s.client === "devin"
160
+ ? "--fix does not move Devin Desktop secrets yet; replace the value with an ${env:NAME} reference by hand"
161
+ : `${CLIENT_NAMES[s.client]} does not read environment variables in its config`;
160
162
  if (where === "env" && key) {
161
163
  const name = names.pick([key, `${server}_${key}`], sec.value);
162
164
  if (!name)
package/dist/report.js CHANGED
@@ -120,7 +120,7 @@ export function renderReport(r, home, s, redacted = false, columns = 80) {
120
120
  const lines = [];
121
121
  lines.push("", `${s.bold("toolstile audit")} ${s.dim("· local MCP config check · nothing leaves this machine")}`, "");
122
122
  if (r.files.length === 0 && r.unreadable.length === 0) {
123
- lines.push("No MCP client configs found.", s.dim("Looked for Claude Code, Claude Desktop, Cursor, Codex, VS Code, Windsurf, Gemini CLI and Cline."), "");
123
+ lines.push("No MCP client configs found.", s.dim("Looked for Claude Code, Claude Desktop, Cursor, Codex, VS Code, Windsurf, Devin Desktop, Gemini CLI and Cline."), "");
124
124
  return lines.join("\n");
125
125
  }
126
126
  if (r.grade !== null) {
package/dist/sources.d.ts CHANGED
@@ -7,6 +7,8 @@ export interface ScanOptions {
7
7
  env: Record<string, string | undefined>;
8
8
  }
9
9
  export declare const CLIENT_NAMES: Record<ClientId, string>;
10
+ /** Devin Desktop's user config folder: `%APPDATA%\\devin` on Windows, `~/.config/devin` elsewhere. */
11
+ export declare function devinDir({ home, platform, env }: Omit<ScanOptions, "cwd">): string;
10
12
  export declare function appData({ home, platform, env }: Omit<ScanOptions, "cwd">): string;
11
13
  /** Appends a key to a pointer, quoting keys that are not plain identifiers. */
12
14
  export declare function pointerJoin(base: string, key: string): string;
package/dist/sources.js CHANGED
@@ -8,9 +8,16 @@ export const CLIENT_NAMES = {
8
8
  codex: "Codex",
9
9
  vscode: "VS Code",
10
10
  windsurf: "Windsurf",
11
+ devin: "Devin Desktop",
11
12
  gemini: "Gemini CLI",
12
13
  cline: "Cline",
13
14
  };
15
+ /** Devin Desktop's user config folder: `%APPDATA%\\devin` on Windows, `~/.config/devin` elsewhere. */
16
+ export function devinDir({ home, platform, env }) {
17
+ if (platform === "win32")
18
+ return join(env.APPDATA ?? join(home, "AppData", "Roaming"), "devin");
19
+ return join(home, ".config", "devin");
20
+ }
14
21
  export function appData({ home, platform, env }) {
15
22
  if (platform === "darwin")
16
23
  return join(home, "Library", "Application Support");
@@ -30,6 +37,7 @@ function candidates(opts) {
30
37
  { client: "cursor", path: join(home, ".cursor", "mcp.json"), kind: "mcp" },
31
38
  { client: "codex", path: join(codexHome, "config.toml"), kind: "codex" },
32
39
  { client: "windsurf", path: join(home, ".codeium", "windsurf", "mcp_config.json"), kind: "mcp" },
40
+ { client: "devin", path: join(devinDir(opts), "mcp_config.json"), kind: "mcp" },
33
41
  { client: "gemini", path: join(home, ".gemini", "settings.json"), kind: "mcp" },
34
42
  ];
35
43
  for (const edition of ["Code", "Code - Insiders"]) {
@@ -38,7 +46,7 @@ function candidates(opts) {
38
46
  list.push({ client: "cline", path: join(data, "Cursor", "User", ...clineSettings), kind: "mcp" });
39
47
  if (cwd) {
40
48
  const dir = resolve(cwd);
41
- list.push({ client: "claude-code", path: join(dir, ".mcp.json"), kind: "mcp" }, { client: "claude-code", path: join(dir, ".claude", "settings.json"), kind: "claude-settings" }, { client: "claude-code", path: join(dir, ".claude", "settings.local.json"), kind: "claude-settings" }, { client: "cursor", path: join(dir, ".cursor", "mcp.json"), kind: "mcp" }, { client: "vscode", path: join(dir, ".vscode", "mcp.json"), kind: "vscode-mcp" }, { client: "gemini", path: join(dir, ".gemini", "settings.json"), kind: "mcp" }, { client: "codex", path: join(dir, ".codex", "config.toml"), kind: "codex" });
49
+ list.push({ client: "claude-code", path: join(dir, ".mcp.json"), kind: "mcp" }, { client: "claude-code", path: join(dir, ".claude", "settings.json"), kind: "claude-settings" }, { client: "claude-code", path: join(dir, ".claude", "settings.local.json"), kind: "claude-settings" }, { client: "cursor", path: join(dir, ".cursor", "mcp.json"), kind: "mcp" }, { client: "vscode", path: join(dir, ".vscode", "mcp.json"), kind: "vscode-mcp" }, { client: "gemini", path: join(dir, ".gemini", "settings.json"), kind: "mcp" }, { client: "codex", path: join(dir, ".codex", "config.toml"), kind: "codex" }, { client: "devin", path: join(dir, ".devin", "mcp_config.json"), kind: "mcp" }, { client: "devin", path: join(dir, ".devin", "mcp_config.local.json"), kind: "mcp" });
42
50
  }
43
51
  const seen = new Set();
44
52
  return list.filter((c) => (seen.has(c.path) ? false : (seen.add(c.path), true)));
package/dist/state.d.ts CHANGED
@@ -43,6 +43,10 @@ export interface StateEnv {
43
43
  /** `TOOLSTILE_CONFIG_DIR`, else the platform's per-user config folder. */
44
44
  export declare function stateDir({ home, env, platform }?: StateEnv): string;
45
45
  export declare const statePath: (opts?: StateEnv) => string;
46
+ /**
47
+ * The saved server always wins over `TOOLSTILE_URL`, which only picks the server for a new
48
+ * `auth login`: an environment variable set by a project must not redirect saved tokens.
49
+ */
46
50
  export declare function loadState(opts?: StateEnv): State;
47
51
  /** Hold the credential revision stable during a synchronous config mutation. */
48
52
  export declare function withCurrentState<T>(state: State, opts: StateEnv | undefined, run: () => T): T;
package/dist/state.js CHANGED
@@ -17,11 +17,11 @@ export function stateDir({ home = homedir(), env = process.env, platform = proce
17
17
  return join(env.XDG_CONFIG_HOME ?? join(home, ".config"), "toolstile");
18
18
  }
19
19
  export const statePath = (opts) => join(stateDir(opts), "credentials.json");
20
+ const revisions = new WeakMap();
20
21
  /**
21
22
  * The saved server always wins over `TOOLSTILE_URL`, which only picks the server for a new
22
23
  * `auth login`: an environment variable set by a project must not redirect saved tokens.
23
24
  */
24
- const revisions = new WeakMap();
25
25
  export function loadState(opts) {
26
26
  const fallback = { server: DEFAULT_SERVER, agents: {} };
27
27
  let text;
package/dist/types.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /** `info` findings are context only and do not affect the score. */
2
2
  export type Severity = "high" | "medium" | "low" | "info";
3
- export type ClientId = "claude-code" | "claude-desktop" | "cursor" | "codex" | "vscode" | "windsurf" | "gemini" | "cline";
3
+ export type ClientId = "claude-code" | "claude-desktop" | "cursor" | "codex" | "vscode" | "windsurf" | "devin" | "gemini" | "cline";
4
4
  export interface Location {
5
5
  /** Absolute path of the config file. */
6
6
  file: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolstile",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "Audit the MCP server configs your AI agents use for plaintext secrets, unpinned packages, auto-approved tools and duplicated setups, and connect those agents to Toolstile without putting tokens in their configs.",
5
5
  "keywords": [
6
6
  "mcp",