toolstile 0.4.2 → 0.5.1

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,7 +7,7 @@ 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
  | --- | --- |
@@ -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
@@ -160,6 +160,7 @@ 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
 
@@ -182,7 +183,7 @@ reach the machine you are signing in, so it works over SSH, in containers and on
182
183
  servers without a browser.
183
184
 
184
185
  `mcp setup` lists the agents it finds (Claude Code, Claude Desktop, Cursor,
185
- 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
186
187
  off. Run it again any time to change the selection. Each enabled agent appears
187
188
  under this machine in the Toolstile dashboard with its own access, so you can
188
189
  grant, limit or revoke it there; then choose which MCP servers it can use.
@@ -243,6 +244,21 @@ works on its own and does not require a Toolstile account.
243
244
 
244
245
  Questions and reports: support@toolstile.com.
245
246
 
247
+ ## Development
248
+
249
+ Development uses [Vite+](https://viteplus.dev). Its `vp env` shims pick Node.js
250
+ from `.node-version` and pnpm from `packageManager`, so install Vite+, then run:
251
+
252
+ ```sh
253
+ pnpm install
254
+ pnpm typecheck
255
+ pnpm test # builds dist, then runs the tests with vp test
256
+ pnpm build
257
+ ```
258
+
259
+ Development needs Node.js 22.18+, 24.11+ or 26+. The published CLI still runs on
260
+ Node.js 18 and later.
261
+
246
262
  ## Releases
247
263
 
248
264
  The npm package is published by [GitHub Actions](.github/workflows/publish.yml)
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
 
@@ -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"));
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/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.2",
3
+ "version": "0.5.1",
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",
@@ -39,7 +39,7 @@
39
39
  "scripts": {
40
40
  "build": "rm -rf dist && tsc -p tsconfig.build.json && chmod +x dist/cli.js",
41
41
  "typecheck": "tsc --noEmit",
42
- "test": "pnpm build && vitest run",
42
+ "test": "pnpm build && vp test run",
43
43
  "prepublishOnly": "pnpm typecheck && pnpm test"
44
44
  },
45
45
  "dependencies": {
@@ -49,7 +49,7 @@
49
49
  "devDependencies": {
50
50
  "@types/node": "^22.19.0",
51
51
  "typescript": "^5.9.3",
52
- "vitest": "^4.0.0"
52
+ "vite-plus": "1.5.1"
53
53
  },
54
54
  "packageManager": "pnpm@11.20.0",
55
55
  "optionalDependencies": {