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 +20 -11
- package/dist/agents.js +3 -1
- package/dist/cli.js +3 -3
- package/dist/commands.d.ts +10 -0
- package/dist/commands.js +58 -14
- package/dist/fix.js +3 -1
- package/dist/report.js +1 -1
- package/dist/sources.d.ts +2 -0
- package/dist/sources.js +9 -1
- package/dist/state.d.ts +4 -0
- package/dist/state.js +1 -1
- package/dist/types.d.ts +1 -1
- package/package.json +1 -1
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
|
|
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
|
|
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
|
-
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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"));
|
package/dist/commands.d.ts
CHANGED
|
@@ -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
|
-
|
|
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 (
|
|
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
|
|
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 =
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
|
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.
|
|
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",
|