@evantahler/mcpx 0.18.3 → 0.18.6
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/package.json +63 -63
- package/src/cli.ts +46 -54
- package/src/client/browser.ts +36 -15
- package/src/client/debug-fetch.ts +64 -56
- package/src/client/elicitation.ts +279 -291
- package/src/client/http.ts +1 -1
- package/src/client/manager.ts +481 -514
- package/src/client/oauth.ts +272 -282
- package/src/client/sse.ts +1 -1
- package/src/client/stdio.ts +7 -7
- package/src/client/trace.ts +146 -152
- package/src/client/transport-options.ts +20 -20
- package/src/commands/add.ts +160 -165
- package/src/commands/allow.ts +141 -142
- package/src/commands/auth.ts +86 -90
- package/src/commands/check-update.ts +49 -53
- package/src/commands/deny.ts +114 -117
- package/src/commands/exec.ts +218 -222
- package/src/commands/index.ts +41 -41
- package/src/commands/info.ts +48 -50
- package/src/commands/list.ts +49 -49
- package/src/commands/ping.ts +47 -50
- package/src/commands/prompt.ts +40 -50
- package/src/commands/remove.ts +54 -56
- package/src/commands/resource.ts +31 -36
- package/src/commands/search.ts +35 -39
- package/src/commands/servers.ts +44 -48
- package/src/commands/skill.ts +89 -95
- package/src/commands/task.ts +50 -60
- package/src/commands/upgrade.ts +191 -208
- package/src/commands/with-command.ts +27 -29
- package/src/config/env.ts +26 -28
- package/src/config/loader.ts +103 -103
- package/src/config/schemas.ts +78 -87
- package/src/constants.ts +17 -17
- package/src/context.ts +51 -51
- package/src/lib/client-settings.ts +127 -140
- package/src/lib/input.ts +23 -26
- package/src/output/format-output.ts +12 -16
- package/src/output/format-table.ts +39 -42
- package/src/output/formatter.ts +794 -815
- package/src/output/logger.ts +140 -152
- package/src/sdk.ts +283 -291
- package/src/search/index.ts +50 -54
- package/src/search/indexer.ts +65 -65
- package/src/search/keyword.ts +54 -54
- package/src/search/semantic.ts +39 -39
- package/src/search/staleness.ts +3 -3
- package/src/search/types.ts +4 -4
- package/src/update/background.ts +51 -51
- package/src/update/cache.ts +21 -21
- package/src/update/checker.ts +81 -86
- package/src/validation/schema.ts +53 -58
package/src/commands/add.ts
CHANGED
|
@@ -1,179 +1,174 @@
|
|
|
1
1
|
import type { Command } from "commander";
|
|
2
|
-
import
|
|
2
|
+
import { resolveResourceUrl, tryOAuthIfSupported } from "../client/oauth.ts";
|
|
3
3
|
import { loadRawAuth, loadRawServers, saveServers } from "../config/loader.ts";
|
|
4
|
-
import {
|
|
4
|
+
import type { ServerConfig } from "../config/schemas.ts";
|
|
5
5
|
import { runIndex } from "./index.ts";
|
|
6
6
|
|
|
7
7
|
export function registerAddCommand(program: Command) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
8
|
+
program
|
|
9
|
+
.command("add <name>")
|
|
10
|
+
.description("add an MCP server to your config")
|
|
11
|
+
.option("--command <cmd>", "command to run (stdio server)")
|
|
12
|
+
.option("--args <args>", "comma-separated arguments for the command")
|
|
13
|
+
.option("--env <vars>", "comma-separated KEY=VAL environment variables")
|
|
14
|
+
.option("--cwd <dir>", "working directory for the command")
|
|
15
|
+
.option("--url <url>", "server URL (HTTP server)")
|
|
16
|
+
.option("--header <h>", "header in Key:Value format (repeatable)", collect, [])
|
|
17
|
+
.option("--transport <type>", 'transport for HTTP servers: "sse" or "streamable-http"')
|
|
18
|
+
.option("--allowed-tools <tools>", "comma-separated list of allowed tools")
|
|
19
|
+
.option("--disabled-tools <tools>", "comma-separated list of disabled tools")
|
|
20
|
+
.option("-f, --force", "overwrite if server already exists")
|
|
21
|
+
.option("--no-auth", "skip automatic OAuth authentication after adding an HTTP server")
|
|
22
|
+
.option("--no-index", "skip rebuilding the search index after adding")
|
|
23
|
+
.action(
|
|
24
|
+
async (
|
|
25
|
+
name: string,
|
|
26
|
+
options: {
|
|
27
|
+
command?: string;
|
|
28
|
+
args?: string;
|
|
29
|
+
env?: string;
|
|
30
|
+
cwd?: string;
|
|
31
|
+
url?: string;
|
|
32
|
+
header?: string[];
|
|
33
|
+
transport?: string;
|
|
34
|
+
allowedTools?: string;
|
|
35
|
+
disabledTools?: string;
|
|
36
|
+
force?: boolean;
|
|
37
|
+
auth?: boolean;
|
|
38
|
+
index?: boolean;
|
|
39
|
+
},
|
|
40
|
+
) => {
|
|
41
|
+
const hasCommand = !!options.command;
|
|
42
|
+
const hasUrl = !!options.url;
|
|
43
|
+
|
|
44
|
+
if (!hasCommand && !hasUrl) {
|
|
45
|
+
console.error("Must specify --command (stdio) or --url (http)");
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
if (hasCommand && hasUrl) {
|
|
49
|
+
console.error("Cannot specify both --command and --url");
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const configFlag = program.opts().config;
|
|
54
|
+
const { configDir, servers } = await loadRawServers(configFlag);
|
|
55
|
+
|
|
56
|
+
if (servers.mcpServers[name] && !options.force) {
|
|
57
|
+
console.error(`Server "${name}" already exists (use --force to overwrite)`);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let config: ServerConfig;
|
|
62
|
+
|
|
63
|
+
if (hasCommand) {
|
|
64
|
+
config = buildStdioConfig(options);
|
|
65
|
+
} else {
|
|
66
|
+
config = buildHttpConfig(options);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (hasUrl && options.transport) {
|
|
70
|
+
if (options.transport !== "sse" && options.transport !== "streamable-http") {
|
|
71
|
+
console.error('--transport must be "sse" or "streamable-http"');
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
(config as { transport: string }).transport = options.transport;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Common options
|
|
78
|
+
if (options.allowedTools) {
|
|
79
|
+
config.allowedTools = options.allowedTools.split(",").map((t) => t.trim());
|
|
80
|
+
}
|
|
81
|
+
if (options.disabledTools) {
|
|
82
|
+
config.disabledTools = options.disabledTools.split(",").map((t) => t.trim());
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// For HTTP servers, resolve the canonical resource URL before saving.
|
|
86
|
+
// Some servers (e.g. hf.co → huggingface.co) advertise a different canonical
|
|
87
|
+
// URL in their OAuth protected resource metadata, and the SDK enforces that the
|
|
88
|
+
// stored URL matches this canonical URL during the OAuth token flow.
|
|
89
|
+
let effectiveUrl = options.url!;
|
|
90
|
+
if (hasUrl && options.auth !== false) {
|
|
91
|
+
const canonical = await resolveResourceUrl(effectiveUrl);
|
|
92
|
+
if (canonical !== effectiveUrl) {
|
|
93
|
+
(config as { url: string }).url = canonical;
|
|
94
|
+
effectiveUrl = canonical;
|
|
95
|
+
console.log(`Resolved canonical URL: ${canonical}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
servers.mcpServers[name] = config;
|
|
100
|
+
await saveServers(configDir, servers);
|
|
101
|
+
console.log(`Added server "${name}" to ${configDir}/servers.json`);
|
|
102
|
+
|
|
103
|
+
// Auto-auth: probe for OAuth support and run the flow if supported
|
|
104
|
+
if (hasUrl && options.auth !== false) {
|
|
105
|
+
const auth = await loadRawAuth(configDir);
|
|
106
|
+
const formatOptions = {
|
|
107
|
+
json: !!program.opts().json,
|
|
108
|
+
verbose: !!program.opts().verbose,
|
|
109
|
+
showSecrets: false,
|
|
110
|
+
};
|
|
111
|
+
try {
|
|
112
|
+
await tryOAuthIfSupported(name, effectiveUrl, configDir, auth, formatOptions);
|
|
113
|
+
} catch {
|
|
114
|
+
console.error(`Warning: OAuth authentication failed. Run: mcpx auth ${name}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Commander treats --no-index as index=false (default true)
|
|
119
|
+
if (options.index !== false) {
|
|
120
|
+
await runIndex(program);
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
);
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
function collect(value: string, previous: string[]): string[] {
|
|
127
|
-
|
|
127
|
+
return previous.concat([value]);
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
-
function buildStdioConfig(options: {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
if (options.cwd) {
|
|
156
|
-
config.cwd = options.cwd;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
return config as unknown as ServerConfig;
|
|
130
|
+
function buildStdioConfig(options: { command?: string; args?: string; env?: string; cwd?: string }): ServerConfig {
|
|
131
|
+
const config: Record<string, unknown> = { command: options.command! };
|
|
132
|
+
|
|
133
|
+
if (options.args) {
|
|
134
|
+
config.args = options.args.split(",").map((a) => a.trim());
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (options.env) {
|
|
138
|
+
const env: Record<string, string> = {};
|
|
139
|
+
for (const pair of options.env.split(",")) {
|
|
140
|
+
const eqIdx = pair.indexOf("=");
|
|
141
|
+
if (eqIdx === -1) {
|
|
142
|
+
console.error(`Invalid env format "${pair}", expected KEY=VAL`);
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
env[pair.slice(0, eqIdx).trim()] = pair.slice(eqIdx + 1).trim();
|
|
146
|
+
}
|
|
147
|
+
config.env = env;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (options.cwd) {
|
|
151
|
+
config.cwd = options.cwd;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return config as unknown as ServerConfig;
|
|
160
155
|
}
|
|
161
156
|
|
|
162
157
|
function buildHttpConfig(options: { url?: string; header?: string[] }): ServerConfig {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
158
|
+
const config: Record<string, unknown> = { url: options.url! };
|
|
159
|
+
|
|
160
|
+
if (options.header && options.header.length > 0) {
|
|
161
|
+
const headers: Record<string, string> = {};
|
|
162
|
+
for (const h of options.header) {
|
|
163
|
+
const colonIdx = h.indexOf(":");
|
|
164
|
+
if (colonIdx === -1) {
|
|
165
|
+
console.error(`Invalid header format "${h}", expected Key:Value`);
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
168
|
+
headers[h.slice(0, colonIdx).trim()] = h.slice(colonIdx + 1).trim();
|
|
169
|
+
}
|
|
170
|
+
config.headers = headers;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return config as unknown as ServerConfig;
|
|
179
174
|
}
|
package/src/commands/allow.ts
CHANGED
|
@@ -1,163 +1,162 @@
|
|
|
1
|
+
import { bold, dim, green, yellow } from "ansis";
|
|
1
2
|
import type { Command } from "commander";
|
|
2
|
-
import { bold, cyan, dim, green, yellow } from "ansis";
|
|
3
3
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
4
|
+
addPatterns,
|
|
5
|
+
allExecPattern,
|
|
6
|
+
allowCommandPattern,
|
|
7
|
+
type Client,
|
|
8
|
+
denyCommandPattern,
|
|
9
|
+
execPattern,
|
|
10
|
+
getMcpxPatterns,
|
|
11
|
+
readClientSettings,
|
|
12
|
+
readOnlyPatterns,
|
|
13
|
+
resolveSettingsPath,
|
|
14
|
+
type Scope,
|
|
15
|
+
writeClientSettings,
|
|
16
16
|
} from "../lib/client-settings.ts";
|
|
17
17
|
import { formatOutput } from "../output/format-output.ts";
|
|
18
18
|
import type { FormatOptions } from "../output/formatter.ts";
|
|
19
19
|
|
|
20
20
|
export function registerAllowCommand(program: Command) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
21
|
+
program
|
|
22
|
+
.command("allow")
|
|
23
|
+
.description("add permission rules for mcpx commands (Claude Code or Cursor)")
|
|
24
|
+
.argument("[server]", "server name to allow")
|
|
25
|
+
.argument("[tools...]", "specific tool names to allow")
|
|
26
|
+
.option("--all", "allow all mcpx exec calls")
|
|
27
|
+
.option("--all-read", "allow read-only commands (search, info, list, servers, ping, etc.)")
|
|
28
|
+
.option("--list", "show current mcpx-related permissions")
|
|
29
|
+
.option("--cursor", "target Cursor settings instead of Claude Code")
|
|
30
|
+
.option("--local", "write to local settings (default)")
|
|
31
|
+
.option("--project", "write to project settings (shared)")
|
|
32
|
+
.option("--global", "write to global settings")
|
|
33
|
+
.option("--dry-run", "show patterns without writing")
|
|
34
|
+
.action(
|
|
35
|
+
async (
|
|
36
|
+
server: string | undefined,
|
|
37
|
+
tools: string[],
|
|
38
|
+
options: {
|
|
39
|
+
all?: boolean;
|
|
40
|
+
allRead?: boolean;
|
|
41
|
+
list?: boolean;
|
|
42
|
+
cursor?: boolean;
|
|
43
|
+
local?: boolean;
|
|
44
|
+
project?: boolean;
|
|
45
|
+
global?: boolean;
|
|
46
|
+
dryRun?: boolean;
|
|
47
|
+
},
|
|
48
|
+
) => {
|
|
49
|
+
const formatOptions: FormatOptions = { json: program.opts().json };
|
|
50
|
+
const client: Client = options.cursor ? "cursor" : "claude";
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const results: { scope: Scope; path: string; patterns: string[] }[] = [];
|
|
52
|
+
// --list mode: show current permissions across all scopes
|
|
53
|
+
if (options.list) {
|
|
54
|
+
// Cursor maps local and project to the same file, so only show unique scopes
|
|
55
|
+
const scopes: Scope[] = client === "cursor" ? ["local", "global"] : ["local", "project", "global"];
|
|
56
|
+
const results: { scope: Scope; path: string; patterns: string[] }[] = [];
|
|
58
57
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
58
|
+
for (const scope of scopes) {
|
|
59
|
+
const path = resolveSettingsPath(scope, client);
|
|
60
|
+
const settings = await readClientSettings(path);
|
|
61
|
+
const patterns = getMcpxPatterns(settings, client);
|
|
62
|
+
results.push({ scope, path, patterns });
|
|
63
|
+
}
|
|
65
64
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
65
|
+
console.log(
|
|
66
|
+
formatOutput(
|
|
67
|
+
results.map((r) => ({ scope: r.scope, path: r.path, patterns: r.patterns })),
|
|
68
|
+
() => {
|
|
69
|
+
const lines: string[] = [];
|
|
70
|
+
for (const r of results) {
|
|
71
|
+
lines.push(bold(`${r.scope}`) + dim(` (${r.path})`));
|
|
72
|
+
if (r.patterns.length === 0) {
|
|
73
|
+
lines.push(` ${dim("(none)")}`);
|
|
74
|
+
} else {
|
|
75
|
+
for (const p of r.patterns) {
|
|
76
|
+
lines.push(` ${green("✓")} ${p}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
lines.push("");
|
|
80
|
+
}
|
|
81
|
+
return lines.join("\n").trimEnd();
|
|
82
|
+
},
|
|
83
|
+
formatOptions,
|
|
84
|
+
),
|
|
85
|
+
);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
89
88
|
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
// Build the list of patterns to add
|
|
90
|
+
const patterns: string[] = [];
|
|
92
91
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
92
|
+
if (options.all) {
|
|
93
|
+
patterns.push(allExecPattern(client));
|
|
94
|
+
}
|
|
96
95
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
96
|
+
if (options.allRead) {
|
|
97
|
+
patterns.push(...readOnlyPatterns(client));
|
|
98
|
+
}
|
|
100
99
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
100
|
+
if (server && tools.length > 0) {
|
|
101
|
+
for (const tool of tools) {
|
|
102
|
+
patterns.push(execPattern(server, tool, client));
|
|
103
|
+
}
|
|
104
|
+
} else if (server) {
|
|
105
|
+
patterns.push(execPattern(server, undefined, client));
|
|
106
|
+
}
|
|
108
107
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
108
|
+
if (patterns.length === 0) {
|
|
109
|
+
console.error("error: specify a server, --all, or --all-read. See 'mcpx allow --help'.");
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
113
112
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
113
|
+
// Always include allow/deny command patterns so the agent can self-manage
|
|
114
|
+
patterns.push(allowCommandPattern(client));
|
|
115
|
+
patterns.push(denyCommandPattern(client));
|
|
117
116
|
|
|
118
|
-
|
|
119
|
-
|
|
117
|
+
const scope: Scope = options.global ? "global" : options.project ? "project" : "local";
|
|
118
|
+
const path = resolveSettingsPath(scope, client);
|
|
120
119
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
120
|
+
if (options.dryRun) {
|
|
121
|
+
console.log(
|
|
122
|
+
formatOutput(
|
|
123
|
+
{ scope, path, patterns },
|
|
124
|
+
() => {
|
|
125
|
+
const lines: string[] = [];
|
|
126
|
+
lines.push(bold("Dry run") + dim(` — would write to ${path}:`));
|
|
127
|
+
for (const p of patterns) {
|
|
128
|
+
lines.push(` ${yellow("+")} ${p}`);
|
|
129
|
+
}
|
|
130
|
+
return lines.join("\n");
|
|
131
|
+
},
|
|
132
|
+
formatOptions,
|
|
133
|
+
),
|
|
134
|
+
);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
138
137
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
138
|
+
const settings = await readClientSettings(path);
|
|
139
|
+
const { settings: updated, added } = addPatterns(settings, patterns);
|
|
140
|
+
await writeClientSettings(path, updated);
|
|
142
141
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
142
|
+
console.log(
|
|
143
|
+
formatOutput(
|
|
144
|
+
{ scope, path, added, total: (updated.permissions?.allow ?? []).length },
|
|
145
|
+
() => {
|
|
146
|
+
const lines: string[] = [];
|
|
147
|
+
if (added.length === 0) {
|
|
148
|
+
lines.push(dim("All patterns already present — no changes."));
|
|
149
|
+
} else {
|
|
150
|
+
lines.push(bold(`Added ${added.length} permission(s)`) + dim(` → ${path}`));
|
|
151
|
+
for (const p of added) {
|
|
152
|
+
lines.push(` ${green("+")} ${p}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return lines.join("\n");
|
|
156
|
+
},
|
|
157
|
+
formatOptions,
|
|
158
|
+
),
|
|
159
|
+
);
|
|
160
|
+
},
|
|
161
|
+
);
|
|
163
162
|
}
|