@obov/mext 0.0.0-stage → 0.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/dist/build-cli.d.ts +39 -0
- package/dist/build-cli.js +167 -0
- package/dist/build-cli.js.map +1 -0
- package/dist/build.d.ts +65 -0
- package/dist/build.js +252 -0
- package/dist/build.js.map +1 -0
- package/dist/cli/args.d.ts +107 -0
- package/dist/cli/args.js +621 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/bin.d.ts +2 -0
- package/dist/cli/bin.js +13 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/cli/build.d.ts +11 -0
- package/dist/cli/build.js +87 -0
- package/dist/cli/build.js.map +1 -0
- package/dist/cli/dev-lock.d.ts +23 -0
- package/dist/cli/dev-lock.js +68 -0
- package/dist/cli/dev-lock.js.map +1 -0
- package/dist/cli/dev-server.d.ts +20 -0
- package/dist/cli/dev-server.js +75 -0
- package/dist/cli/dev-server.js.map +1 -0
- package/dist/cli/dev.d.ts +21 -0
- package/dist/cli/dev.js +565 -0
- package/dist/cli/dev.js.map +1 -0
- package/dist/cli/help.d.ts +31 -0
- package/dist/cli/help.js +238 -0
- package/dist/cli/help.js.map +1 -0
- package/dist/cli/io.d.ts +26 -0
- package/dist/cli/io.js +74 -0
- package/dist/cli/io.js.map +1 -0
- package/dist/cli/login.d.ts +32 -0
- package/dist/cli/login.js +284 -0
- package/dist/cli/login.js.map +1 -0
- package/dist/cli/logout.d.ts +9 -0
- package/dist/cli/logout.js +105 -0
- package/dist/cli/logout.js.map +1 -0
- package/dist/cli/main.d.ts +19 -0
- package/dist/cli/main.js +77 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/mcp.d.ts +16 -0
- package/dist/cli/mcp.js +92 -0
- package/dist/cli/mcp.js.map +1 -0
- package/dist/cli/output.d.ts +78 -0
- package/dist/cli/output.js +229 -0
- package/dist/cli/output.js.map +1 -0
- package/dist/cli/remote.d.ts +17 -0
- package/dist/cli/remote.js +35 -0
- package/dist/cli/remote.js.map +1 -0
- package/dist/cli/run.d.ts +14 -0
- package/dist/cli/run.js +218 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/cli/start.d.ts +8 -0
- package/dist/cli/start.js +37 -0
- package/dist/cli/start.js.map +1 -0
- package/dist/client/cache.d.ts +104 -0
- package/dist/client/cache.js +208 -0
- package/dist/client/cache.js.map +1 -0
- package/dist/client/main.d.ts +31 -0
- package/dist/client/main.js +101 -0
- package/dist/client/main.js.map +1 -0
- package/dist/client/manifest.d.ts +19 -0
- package/dist/client/manifest.js +68 -0
- package/dist/client/manifest.js.map +1 -0
- package/dist/client/runner.d.ts +52 -0
- package/dist/client/runner.js +563 -0
- package/dist/client/runner.js.map +1 -0
- package/dist/command.d.ts +75 -0
- package/dist/command.js +44 -0
- package/dist/command.js.map +1 -0
- package/dist/config.d.ts +111 -0
- package/dist/config.js +197 -0
- package/dist/config.js.map +1 -0
- package/dist/errors.d.ts +74 -0
- package/dist/errors.js +38 -0
- package/dist/errors.js.map +1 -0
- package/dist/ids.d.ts +6 -0
- package/dist/ids.js +12 -0
- package/dist/ids.js.map +1 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/instrumentation.d.ts +97 -0
- package/dist/instrumentation.js +165 -0
- package/dist/instrumentation.js.map +1 -0
- package/dist/invoke.d.ts +64 -0
- package/dist/invoke.js +152 -0
- package/dist/invoke.js.map +1 -0
- package/dist/lookup.d.ts +9 -0
- package/dist/lookup.js +27 -0
- package/dist/lookup.js.map +1 -0
- package/dist/manifest.d.ts +68 -0
- package/dist/manifest.js +77 -0
- package/dist/manifest.js.map +1 -0
- package/dist/mcp/constants.d.ts +9 -0
- package/dist/mcp/constants.js +10 -0
- package/dist/mcp/constants.js.map +1 -0
- package/dist/mcp/dispatch.d.ts +66 -0
- package/dist/mcp/dispatch.js +249 -0
- package/dist/mcp/dispatch.js.map +1 -0
- package/dist/mcp/help.d.ts +57 -0
- package/dist/mcp/help.js +136 -0
- package/dist/mcp/help.js.map +1 -0
- package/dist/mcp/http.d.ts +96 -0
- package/dist/mcp/http.js +368 -0
- package/dist/mcp/http.js.map +1 -0
- package/dist/mcp/index.d.ts +15 -0
- package/dist/mcp/index.js +9 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/install.d.ts +17 -0
- package/dist/mcp/install.js +36 -0
- package/dist/mcp/install.js.map +1 -0
- package/dist/mcp/mrtr.d.ts +82 -0
- package/dist/mcp/mrtr.js +130 -0
- package/dist/mcp/mrtr.js.map +1 -0
- package/dist/mcp/server.d.ts +63 -0
- package/dist/mcp/server.js +210 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/stdio.d.ts +75 -0
- package/dist/mcp/stdio.js +222 -0
- package/dist/mcp/stdio.js.map +1 -0
- package/dist/principal.d.ts +16 -0
- package/dist/principal.js +24 -0
- package/dist/principal.js.map +1 -0
- package/dist/registry.d.ts +90 -0
- package/dist/registry.js +169 -0
- package/dist/registry.js.map +1 -0
- package/dist/remote/browser.d.ts +15 -0
- package/dist/remote/browser.js +47 -0
- package/dist/remote/browser.js.map +1 -0
- package/dist/remote/client.d.ts +113 -0
- package/dist/remote/client.js +312 -0
- package/dist/remote/client.js.map +1 -0
- package/dist/remote/credentials.d.ts +48 -0
- package/dist/remote/credentials.js +196 -0
- package/dist/remote/credentials.js.map +1 -0
- package/dist/remote/index.d.ts +8 -0
- package/dist/remote/index.js +5 -0
- package/dist/remote/index.js.map +1 -0
- package/dist/remote/oauth.d.ts +186 -0
- package/dist/remote/oauth.js +507 -0
- package/dist/remote/oauth.js.map +1 -0
- package/dist/scan.d.ts +34 -0
- package/dist/scan.js +155 -0
- package/dist/scan.js.map +1 -0
- package/dist/start.d.ts +63 -0
- package/dist/start.js +269 -0
- package/dist/start.js.map +1 -0
- package/dist/suggest.d.ts +7 -0
- package/dist/suggest.js +45 -0
- package/dist/suggest.js.map +1 -0
- package/dist/version.d.ts +4 -0
- package/dist/version.js +13 -0
- package/dist/version.js.map +1 -0
- package/package.json +49 -4
- package/README.md +0 -3
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { browserDecision, systemBrowserOpener } from "../remote/browser.js";
|
|
2
|
+
import { createRemoteClient, RemoteUnauthorizedError } from "../remote/client.js";
|
|
3
|
+
import { canonicalServerUrl, credentialStore, envPrefix } from "../remote/credentials.js";
|
|
4
|
+
import { authorizationCodeLogin, deviceLogin, discoverLoginTarget, OAuthFlowError, parseRedirectUrl, } from "../remote/oauth.js";
|
|
5
|
+
import { table } from "./help.js";
|
|
6
|
+
import { createLineReader } from "./io.js";
|
|
7
|
+
import { CliInterrupted, EXIT, formatJson } from "./output.js";
|
|
8
|
+
/**
|
|
9
|
+
* <bin> login — MCP authorization (OAuth 2.1 · PKCE) 로 토큰 발급 · 저장. 서버 URL 별 (키체인 · credentials.json).
|
|
10
|
+
* redirect = loopback (127.0.0.1) 또는 붙여넣은 redirect URL (TTY). device flow = 인가 서버가 지원할 때 (--device,
|
|
11
|
+
* 브라우저를 열지 않는 환경이면 자동). --with-token = stdin 한 줄을 API key 로 저장.
|
|
12
|
+
*/
|
|
13
|
+
export function loginCommand(options) {
|
|
14
|
+
return (argv, io) => runLoginWith(options, argv, io);
|
|
15
|
+
}
|
|
16
|
+
/** mext login */
|
|
17
|
+
export const runLogin = loginCommand({ bin: "mext" });
|
|
18
|
+
async function runLoginWith(options, argv, io) {
|
|
19
|
+
const opts = { browser: true, withToken: false, json: false };
|
|
20
|
+
for (let i = 0; i < argv.length; i++) {
|
|
21
|
+
const tok = argv[i];
|
|
22
|
+
const eq = tok.indexOf("=");
|
|
23
|
+
const name = tok.startsWith("--") && eq > 0 ? tok.slice(0, eq) : tok;
|
|
24
|
+
const value = () => (eq > 0 && tok.startsWith("--") ? tok.slice(eq + 1) : argv[++i]);
|
|
25
|
+
if (name === "-h" || name === "--help") {
|
|
26
|
+
io.stdout.write(loginHelp(options));
|
|
27
|
+
return EXIT.ok;
|
|
28
|
+
}
|
|
29
|
+
else if (name === "--json")
|
|
30
|
+
opts.json = true;
|
|
31
|
+
else if (name === "--no-browser")
|
|
32
|
+
opts.browser = false;
|
|
33
|
+
else if (name === "--device")
|
|
34
|
+
opts.device = true;
|
|
35
|
+
else if (name === "--no-device")
|
|
36
|
+
opts.device = false;
|
|
37
|
+
else if (name === "--with-token")
|
|
38
|
+
opts.withToken = true;
|
|
39
|
+
else if (name === "--url")
|
|
40
|
+
opts.url = value();
|
|
41
|
+
else if (name === "--client-id")
|
|
42
|
+
opts.clientId = value();
|
|
43
|
+
else if (name === "--scope")
|
|
44
|
+
opts.scope = value();
|
|
45
|
+
else if (name === "--port") {
|
|
46
|
+
const raw = value();
|
|
47
|
+
const port = Number(raw);
|
|
48
|
+
if (!Number.isInteger(port) || port < 0 || port > 65_535) {
|
|
49
|
+
return usage(io, options, `--port must be a port number (got "${raw}")`);
|
|
50
|
+
}
|
|
51
|
+
opts.port = port;
|
|
52
|
+
}
|
|
53
|
+
else
|
|
54
|
+
return usage(io, options, `unknown argument ${tok}`);
|
|
55
|
+
}
|
|
56
|
+
const url = resolveServerUrl(options, opts.url, io);
|
|
57
|
+
if (typeof url !== "string")
|
|
58
|
+
return usage(io, options, url.error);
|
|
59
|
+
const controller = new AbortController();
|
|
60
|
+
const onAbort = () => controller.abort(new CliInterrupted());
|
|
61
|
+
if (io.signal?.aborted)
|
|
62
|
+
onAbort();
|
|
63
|
+
io.signal?.addEventListener("abort", onAbort, { once: true });
|
|
64
|
+
const reader = createLineReader(io);
|
|
65
|
+
try {
|
|
66
|
+
const store = credentialStore({ env: io.env, bin: options.bin });
|
|
67
|
+
const server = canonicalServerUrl(url);
|
|
68
|
+
if (opts.withToken) {
|
|
69
|
+
const line = await reader.question(io.isTTY ? "Paste the token and press Enter: " : "", controller.signal);
|
|
70
|
+
if (controller.signal.aborted)
|
|
71
|
+
throw new CliInterrupted();
|
|
72
|
+
const token = line?.trim() ?? "";
|
|
73
|
+
if (token === "")
|
|
74
|
+
return usage(io, options, "--with-token reads the token from stdin (got nothing)");
|
|
75
|
+
// 저장 전에 확인 (거부된 토큰은 저장하지 않는다)
|
|
76
|
+
await createRemoteClient({ url, token: () => token }).listTools({ signal: controller.signal });
|
|
77
|
+
await store.set(url, { access_token: token, token_type: "Bearer" });
|
|
78
|
+
if (opts.json)
|
|
79
|
+
io.stdout.write(formatJson({ url: server, storage: store.location, flow: "token" }));
|
|
80
|
+
else
|
|
81
|
+
io.stderr.write(`Token saved for ${server} to ${store.location}.\n`);
|
|
82
|
+
return EXIT.ok;
|
|
83
|
+
}
|
|
84
|
+
// 토큰 없는 요청의 401 → WWW-Authenticate (resource_metadata · scope). 401 이 아니면 well-known 탐색
|
|
85
|
+
let challenge;
|
|
86
|
+
try {
|
|
87
|
+
await createRemoteClient({ url }).listTools({ signal: controller.signal });
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
if (error instanceof RemoteUnauthorizedError)
|
|
91
|
+
challenge = error.challenge;
|
|
92
|
+
else if (controller.signal.aborted)
|
|
93
|
+
throw new CliInterrupted();
|
|
94
|
+
else
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
const doFetch = options.fetch ?? fetch;
|
|
98
|
+
const target = await discoverLoginTarget({
|
|
99
|
+
serverUrl: url,
|
|
100
|
+
...(challenge ? { challenge } : {}),
|
|
101
|
+
...(opts.scope !== undefined ? { scope: opts.scope } : {}),
|
|
102
|
+
fetch: doFetch,
|
|
103
|
+
});
|
|
104
|
+
const decision = opts.browser
|
|
105
|
+
? browserDecision(io.env, options.platform ?? process.platform)
|
|
106
|
+
: { open: false };
|
|
107
|
+
const open = options.openBrowser ?? systemBrowserOpener(io.env, options.platform ?? process.platform);
|
|
108
|
+
const hasDevice = typeof target.as.device_authorization_endpoint === "string";
|
|
109
|
+
if (opts.device === true && !hasDevice) {
|
|
110
|
+
throw new OAuthFlowError(`${target.as.issuer} does not support the device authorization grant (no device_authorization_endpoint). ` +
|
|
111
|
+
"Run login without --device.");
|
|
112
|
+
}
|
|
113
|
+
// D5: 자동 device flow = 인가 서버 지원 + 환경 때문에 브라우저를 열지 않음 (SSH · display 없음) + --port · --no-device 없음.
|
|
114
|
+
// --no-browser 는 사용자 선택이라 loopback · 붙여넣기 유지
|
|
115
|
+
const useDevice = opts.device === true ||
|
|
116
|
+
(opts.device === undefined && hasDevice && decision.reason !== undefined && opts.port === undefined);
|
|
117
|
+
const common = {
|
|
118
|
+
clientName: options.bin,
|
|
119
|
+
signal: controller.signal,
|
|
120
|
+
fetch: doFetch,
|
|
121
|
+
...(opts.clientId !== undefined ? { clientId: opts.clientId } : {}),
|
|
122
|
+
};
|
|
123
|
+
let credentials;
|
|
124
|
+
if (useDevice) {
|
|
125
|
+
credentials = await deviceLogin(target, {
|
|
126
|
+
...common,
|
|
127
|
+
...(options.sleep ? { sleep: options.sleep } : {}),
|
|
128
|
+
onDeviceCode(device) {
|
|
129
|
+
printDeviceCode(io, opts.json, url, device, decision);
|
|
130
|
+
if (decision.open)
|
|
131
|
+
open(device.verification_uri_complete ?? device.verification_uri);
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
credentials = await authorizationCodeLogin(target, {
|
|
137
|
+
...common,
|
|
138
|
+
...(opts.port !== undefined ? { port: opts.port } : {}),
|
|
139
|
+
onAuthorizationUrl(authorizeUrl, loopback) {
|
|
140
|
+
io.stderr.write([
|
|
141
|
+
decision.open
|
|
142
|
+
? `Opening a browser to log in to ${url}. If it does not open, visit:`
|
|
143
|
+
: `Open this URL in a browser to log in to ${url}:`,
|
|
144
|
+
"",
|
|
145
|
+
` ${authorizeUrl}`,
|
|
146
|
+
"",
|
|
147
|
+
`Waiting for the redirect to ${loopback.redirectUri}`,
|
|
148
|
+
`(browser on another machine: ${io.isTTY ? "paste the redirect URL from its address bar below, or " : ""}` +
|
|
149
|
+
`ssh -L ${loopback.port}:127.0.0.1:${loopback.port} <this host> with --port)`,
|
|
150
|
+
"",
|
|
151
|
+
].join("\n"));
|
|
152
|
+
if (decision.open)
|
|
153
|
+
open(authorizeUrl);
|
|
154
|
+
},
|
|
155
|
+
// 붙여넣기는 TTY 에서만 (파이프 stdin 은 다른 용도일 수 있음)
|
|
156
|
+
...(io.isTTY
|
|
157
|
+
? {
|
|
158
|
+
async readRedirect(signal) {
|
|
159
|
+
for (;;) {
|
|
160
|
+
const line = await reader.question("Redirect URL: ", signal);
|
|
161
|
+
if (line === undefined) {
|
|
162
|
+
if (signal.aborted)
|
|
163
|
+
io.stderr.write("\n"); // loopback 이 먼저: 프롬프트 줄 닫기
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
const query = parseRedirectUrl(line);
|
|
167
|
+
if (query)
|
|
168
|
+
return query;
|
|
169
|
+
if (line.trim() !== "")
|
|
170
|
+
io.stderr.write("Not a redirect URL. Paste the full URL from the browser address bar.\n");
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
}
|
|
174
|
+
: {}),
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
await store.set(url, credentials);
|
|
178
|
+
// 발급 토큰으로 확인 요청
|
|
179
|
+
await createRemoteClient({ url, token: () => credentials.access_token }).listTools({ signal: controller.signal });
|
|
180
|
+
if (opts.json) {
|
|
181
|
+
const out = {
|
|
182
|
+
url: server,
|
|
183
|
+
storage: store.location,
|
|
184
|
+
issuer: credentials.issuer,
|
|
185
|
+
flow: useDevice ? "device" : "authorization_code",
|
|
186
|
+
};
|
|
187
|
+
if (credentials.scope !== undefined)
|
|
188
|
+
out.scope = credentials.scope;
|
|
189
|
+
if (credentials.expires_at !== undefined)
|
|
190
|
+
out.expires_at = new Date(credentials.expires_at).toISOString();
|
|
191
|
+
io.stdout.write(formatJson(out));
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
io.stderr.write(`Logged in to ${server}. Token saved to ${store.location}.\n`);
|
|
195
|
+
}
|
|
196
|
+
return EXIT.ok;
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
if (controller.signal.aborted || error instanceof CliInterrupted) {
|
|
200
|
+
io.stderr.write(opts.json ? formatJson({ code: "INTERRUPTED", message: "Interrupted." }) : "error: Interrupted.\n");
|
|
201
|
+
return EXIT.interrupted;
|
|
202
|
+
}
|
|
203
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
204
|
+
const unauthorized = error instanceof RemoteUnauthorizedError;
|
|
205
|
+
const code = error instanceof OAuthFlowError || unauthorized ? "LOGIN_FAILED" : "REMOTE_ERROR";
|
|
206
|
+
const text = unauthorized && opts.withToken ? `The server rejected the token (${message})` : message;
|
|
207
|
+
io.stderr.write(opts.json ? formatJson({ code, message: text }) : `error: login failed: ${text}\n`);
|
|
208
|
+
return unauthorized ? EXIT.auth : EXIT.error;
|
|
209
|
+
}
|
|
210
|
+
finally {
|
|
211
|
+
reader.close();
|
|
212
|
+
io.signal?.removeEventListener("abort", onAbort);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* device flow 안내 (D5). TTY 이고 --json 아님 = 사람용 텍스트, 그 외 = JSON 한 줄
|
|
217
|
+
* { verification_uri, verification_uri_complete?, user_code, expires_in } (stderr, stdout 은 결과 전용). 이후 polling 계속
|
|
218
|
+
*/
|
|
219
|
+
function printDeviceCode(io, json, url, device, decision) {
|
|
220
|
+
if (json || !io.isTTY) {
|
|
221
|
+
const line = { verification_uri: device.verification_uri };
|
|
222
|
+
if (device.verification_uri_complete !== undefined)
|
|
223
|
+
line.verification_uri_complete = device.verification_uri_complete;
|
|
224
|
+
line.user_code = device.user_code;
|
|
225
|
+
line.expires_in = device.expires_in;
|
|
226
|
+
io.stderr.write(`${JSON.stringify(line)}\n`);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
const minutes = Math.max(1, Math.round(device.expires_in / 60));
|
|
230
|
+
io.stderr.write([
|
|
231
|
+
`${decision.open ? "Opening a browser" : "Open this URL in a browser"} to log in to ${url}:`,
|
|
232
|
+
"",
|
|
233
|
+
` ${device.verification_uri_complete ?? device.verification_uri}`,
|
|
234
|
+
"",
|
|
235
|
+
`and confirm the code: ${device.user_code} (expires in ${minutes} min)`,
|
|
236
|
+
"Waiting for authorization...",
|
|
237
|
+
"",
|
|
238
|
+
].join("\n"));
|
|
239
|
+
}
|
|
240
|
+
/** 서버 URL = --url → 고정 URL (고객 CLI) → <BIN>_URL */
|
|
241
|
+
export function resolveServerUrl(options, flag, io) {
|
|
242
|
+
const envName = `${envPrefix(options.bin)}_URL`;
|
|
243
|
+
const url = flag ?? options.url ?? (io.env[envName] || undefined);
|
|
244
|
+
if (url === undefined || url === "")
|
|
245
|
+
return { error: `--url <url> is required (or set ${envName})` };
|
|
246
|
+
if (!/^https?:\/\//i.test(url))
|
|
247
|
+
return { error: `--url must be an http(s) URL (got "${url}")` };
|
|
248
|
+
return url;
|
|
249
|
+
}
|
|
250
|
+
function usage(io, options, message) {
|
|
251
|
+
io.stderr.write(`error: ${message}\n\n${loginHelp(options)}`);
|
|
252
|
+
return EXIT.usage;
|
|
253
|
+
}
|
|
254
|
+
function loginHelp(options) {
|
|
255
|
+
const { bin } = options;
|
|
256
|
+
const prefix = envPrefix(bin);
|
|
257
|
+
const rows = [
|
|
258
|
+
["--url <url>", options.url ? `MCP endpoint (default: ${options.url})` : `MCP endpoint (default: ${prefix}_URL)`],
|
|
259
|
+
["--client-id <id>", "Pre-registered OAuth client ID or Client ID Metadata Document URL (default: dynamic registration)"],
|
|
260
|
+
["--scope <scopes>", "Space-separated scopes (default: server challenge or scopes_supported)"],
|
|
261
|
+
["--port <port>", "Loopback redirect port on 127.0.0.1 (default: random)"],
|
|
262
|
+
["--no-browser", "Print the authorization URL without opening a browser"],
|
|
263
|
+
["--device", "Use the device authorization grant (if the authorization server supports it)"],
|
|
264
|
+
["--no-device", "Never switch to the device authorization grant automatically"],
|
|
265
|
+
["--with-token", "Read a token (API key) from stdin and store it"],
|
|
266
|
+
["--json", "Print the result as JSON"],
|
|
267
|
+
["-h, --help", "Show help"],
|
|
268
|
+
];
|
|
269
|
+
return [
|
|
270
|
+
"Log in to a remote server (OAuth 2.1 authorization code + PKCE, loopback redirect).",
|
|
271
|
+
`The token is stored per server URL (OS keychain, or ~/.config/${bin}/credentials.json; ${prefix}_NO_KEYRING=1 = file only).`,
|
|
272
|
+
"Over SSH or without a display the browser is not opened: paste the redirect URL, or the device flow is used",
|
|
273
|
+
"when the authorization server supports it.",
|
|
274
|
+
"",
|
|
275
|
+
"Usage:",
|
|
276
|
+
` ${bin} login${options.url ? "" : " --url <url>"} [flags]`,
|
|
277
|
+
` echo "$TOKEN" | ${bin} login${options.url ? "" : " --url <url>"} --with-token`,
|
|
278
|
+
"",
|
|
279
|
+
"Flags:",
|
|
280
|
+
...table(rows).map((l) => ` ${l}`),
|
|
281
|
+
"",
|
|
282
|
+
].join("\n");
|
|
283
|
+
}
|
|
284
|
+
//# sourceMappingURL=login.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/cli/login.ts"],"names":[],"mappings":"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","sourcesContent":["import { browserDecision, systemBrowserOpener, type BrowserDecision, type BrowserOpener } from \"../remote/browser.ts\"\nimport { createRemoteClient, RemoteUnauthorizedError, type BearerChallenge } from \"../remote/client.ts\"\nimport { canonicalServerUrl, credentialStore, envPrefix, type StoredCredentials } from \"../remote/credentials.ts\"\nimport {\n authorizationCodeLogin,\n deviceLogin,\n discoverLoginTarget,\n OAuthFlowError,\n parseRedirectUrl,\n type DeviceAuthorization,\n} from \"../remote/oauth.ts\"\nimport { table } from \"./help.ts\"\nimport { createLineReader, type CliIo } from \"./io.ts\"\nimport { CliInterrupted, EXIT, formatJson } from \"./output.ts\"\n\n/**\n * login · logout 명령 공통 설정. mext CLI = { bin: \"mext\" }, 고객 CLI = { bin, url } (설정의 cli.bin · cli.url).\n * bin = 안내 문구의 실행 파일 이름 · 환경변수 접두 (<BIN>_URL · <BIN>_CREDENTIALS_FILE · <BIN>_NO_KEYRING) ·\n * 자격 증명 위치 (~/.config/<bin> · 키체인 service \"<bin> credentials\") · 동적 등록 client_name.\n */\nexport interface AuthCommandOptions {\n bin: string\n /** 고정 서버 URL (고객 CLI). --url 로 덮어쓸 수 있다. 없으면 --url 또는 <BIN>_URL 필수 */\n url?: string\n /** 브라우저 열기 (테스트 주입). 기본 = BROWSER · open · xdg-open */\n openBrowser?: BrowserOpener\n /** 브라우저 판정용 (테스트 주입). 기본 process.platform */\n platform?: NodeJS.Platform\n /** 인가 서버 요청 (테스트 주입) */\n fetch?: typeof fetch\n /** device flow polling 대기 (테스트 주입) */\n sleep?: (ms: number, signal?: AbortSignal) => Promise<void>\n}\n\ninterface LoginFlags {\n url?: string\n clientId?: string\n scope?: string\n port?: number\n browser: boolean\n /** true = 강제, false = 끔, undefined = 자동 */\n device?: boolean\n withToken: boolean\n json: boolean\n}\n\n/**\n * <bin> login — MCP authorization (OAuth 2.1 · PKCE) 로 토큰 발급 · 저장. 서버 URL 별 (키체인 · credentials.json).\n * redirect = loopback (127.0.0.1) 또는 붙여넣은 redirect URL (TTY). device flow = 인가 서버가 지원할 때 (--device,\n * 브라우저를 열지 않는 환경이면 자동). --with-token = stdin 한 줄을 API key 로 저장.\n */\nexport function loginCommand(options: AuthCommandOptions): (argv: readonly string[], io: CliIo) => Promise<number> {\n return (argv, io) => runLoginWith(options, argv, io)\n}\n\n/** mext login */\nexport const runLogin = loginCommand({ bin: \"mext\" })\n\nasync function runLoginWith(options: AuthCommandOptions, argv: readonly string[], io: CliIo): Promise<number> {\n const opts: LoginFlags = { browser: true, withToken: false, json: false }\n for (let i = 0; i < argv.length; i++) {\n const tok = argv[i]!\n const eq = tok.indexOf(\"=\")\n const name = tok.startsWith(\"--\") && eq > 0 ? tok.slice(0, eq) : tok\n const value = (): string | undefined => (eq > 0 && tok.startsWith(\"--\") ? tok.slice(eq + 1) : argv[++i])\n if (name === \"-h\" || name === \"--help\") {\n io.stdout.write(loginHelp(options))\n return EXIT.ok\n } else if (name === \"--json\") opts.json = true\n else if (name === \"--no-browser\") opts.browser = false\n else if (name === \"--device\") opts.device = true\n else if (name === \"--no-device\") opts.device = false\n else if (name === \"--with-token\") opts.withToken = true\n else if (name === \"--url\") opts.url = value()\n else if (name === \"--client-id\") opts.clientId = value()\n else if (name === \"--scope\") opts.scope = value()\n else if (name === \"--port\") {\n const raw = value()\n const port = Number(raw)\n if (!Number.isInteger(port) || port < 0 || port > 65_535) {\n return usage(io, options, `--port must be a port number (got \"${raw}\")`)\n }\n opts.port = port\n } else return usage(io, options, `unknown argument ${tok}`)\n }\n const url = resolveServerUrl(options, opts.url, io)\n if (typeof url !== \"string\") return usage(io, options, url.error)\n\n const controller = new AbortController()\n const onAbort = (): void => controller.abort(new CliInterrupted())\n if (io.signal?.aborted) onAbort()\n io.signal?.addEventListener(\"abort\", onAbort, { once: true })\n const reader = createLineReader(io)\n try {\n const store = credentialStore({ env: io.env, bin: options.bin })\n const server = canonicalServerUrl(url)\n if (opts.withToken) {\n const line = await reader.question(io.isTTY ? \"Paste the token and press Enter: \" : \"\", controller.signal)\n if (controller.signal.aborted) throw new CliInterrupted()\n const token = line?.trim() ?? \"\"\n if (token === \"\") return usage(io, options, \"--with-token reads the token from stdin (got nothing)\")\n // 저장 전에 확인 (거부된 토큰은 저장하지 않는다)\n await createRemoteClient({ url, token: () => token }).listTools({ signal: controller.signal })\n await store.set(url, { access_token: token, token_type: \"Bearer\" })\n if (opts.json) io.stdout.write(formatJson({ url: server, storage: store.location, flow: \"token\" }))\n else io.stderr.write(`Token saved for ${server} to ${store.location}.\\n`)\n return EXIT.ok\n }\n\n // 토큰 없는 요청의 401 → WWW-Authenticate (resource_metadata · scope). 401 이 아니면 well-known 탐색\n let challenge: BearerChallenge | undefined\n try {\n await createRemoteClient({ url }).listTools({ signal: controller.signal })\n } catch (error) {\n if (error instanceof RemoteUnauthorizedError) challenge = error.challenge\n else if (controller.signal.aborted) throw new CliInterrupted()\n else throw error\n }\n const doFetch = options.fetch ?? fetch\n const target = await discoverLoginTarget({\n serverUrl: url,\n ...(challenge ? { challenge } : {}),\n ...(opts.scope !== undefined ? { scope: opts.scope } : {}),\n fetch: doFetch,\n })\n const decision: BrowserDecision = opts.browser\n ? browserDecision(io.env, options.platform ?? process.platform)\n : { open: false }\n const open = options.openBrowser ?? systemBrowserOpener(io.env, options.platform ?? process.platform)\n const hasDevice = typeof target.as.device_authorization_endpoint === \"string\"\n if (opts.device === true && !hasDevice) {\n throw new OAuthFlowError(\n `${target.as.issuer} does not support the device authorization grant (no device_authorization_endpoint). ` +\n \"Run login without --device.\",\n )\n }\n // D5: 자동 device flow = 인가 서버 지원 + 환경 때문에 브라우저를 열지 않음 (SSH · display 없음) + --port · --no-device 없음.\n // --no-browser 는 사용자 선택이라 loopback · 붙여넣기 유지\n const useDevice =\n opts.device === true ||\n (opts.device === undefined && hasDevice && decision.reason !== undefined && opts.port === undefined)\n const common = {\n clientName: options.bin,\n signal: controller.signal,\n fetch: doFetch,\n ...(opts.clientId !== undefined ? { clientId: opts.clientId } : {}),\n }\n\n let credentials: StoredCredentials\n if (useDevice) {\n credentials = await deviceLogin(target, {\n ...common,\n ...(options.sleep ? { sleep: options.sleep } : {}),\n onDeviceCode(device) {\n printDeviceCode(io, opts.json, url, device, decision)\n if (decision.open) open(device.verification_uri_complete ?? device.verification_uri)\n },\n })\n } else {\n credentials = await authorizationCodeLogin(target, {\n ...common,\n ...(opts.port !== undefined ? { port: opts.port } : {}),\n onAuthorizationUrl(authorizeUrl, loopback) {\n io.stderr.write(\n [\n decision.open\n ? `Opening a browser to log in to ${url}. If it does not open, visit:`\n : `Open this URL in a browser to log in to ${url}:`,\n \"\",\n ` ${authorizeUrl}`,\n \"\",\n `Waiting for the redirect to ${loopback.redirectUri}`,\n `(browser on another machine: ${io.isTTY ? \"paste the redirect URL from its address bar below, or \" : \"\"}` +\n `ssh -L ${loopback.port}:127.0.0.1:${loopback.port} <this host> with --port)`,\n \"\",\n ].join(\"\\n\"),\n )\n if (decision.open) open(authorizeUrl)\n },\n // 붙여넣기는 TTY 에서만 (파이프 stdin 은 다른 용도일 수 있음)\n ...(io.isTTY\n ? {\n async readRedirect(signal: AbortSignal) {\n for (;;) {\n const line = await reader.question(\"Redirect URL: \", signal)\n if (line === undefined) {\n if (signal.aborted) io.stderr.write(\"\\n\") // loopback 이 먼저: 프롬프트 줄 닫기\n return undefined\n }\n const query = parseRedirectUrl(line)\n if (query) return query\n if (line.trim() !== \"\") io.stderr.write(\"Not a redirect URL. Paste the full URL from the browser address bar.\\n\")\n }\n },\n }\n : {}),\n })\n }\n await store.set(url, credentials)\n // 발급 토큰으로 확인 요청\n await createRemoteClient({ url, token: () => credentials.access_token }).listTools({ signal: controller.signal })\n if (opts.json) {\n const out: Record<string, unknown> = {\n url: server,\n storage: store.location,\n issuer: credentials.issuer,\n flow: useDevice ? \"device\" : \"authorization_code\",\n }\n if (credentials.scope !== undefined) out.scope = credentials.scope\n if (credentials.expires_at !== undefined) out.expires_at = new Date(credentials.expires_at).toISOString()\n io.stdout.write(formatJson(out))\n } else {\n io.stderr.write(`Logged in to ${server}. Token saved to ${store.location}.\\n`)\n }\n return EXIT.ok\n } catch (error) {\n if (controller.signal.aborted || error instanceof CliInterrupted) {\n io.stderr.write(opts.json ? formatJson({ code: \"INTERRUPTED\", message: \"Interrupted.\" }) : \"error: Interrupted.\\n\")\n return EXIT.interrupted\n }\n const message = error instanceof Error ? error.message : String(error)\n const unauthorized = error instanceof RemoteUnauthorizedError\n const code = error instanceof OAuthFlowError || unauthorized ? \"LOGIN_FAILED\" : \"REMOTE_ERROR\"\n const text = unauthorized && opts.withToken ? `The server rejected the token (${message})` : message\n io.stderr.write(opts.json ? formatJson({ code, message: text }) : `error: login failed: ${text}\\n`)\n return unauthorized ? EXIT.auth : EXIT.error\n } finally {\n reader.close()\n io.signal?.removeEventListener(\"abort\", onAbort)\n }\n}\n\n/**\n * device flow 안내 (D5). TTY 이고 --json 아님 = 사람용 텍스트, 그 외 = JSON 한 줄\n * { verification_uri, verification_uri_complete?, user_code, expires_in } (stderr, stdout 은 결과 전용). 이후 polling 계속\n */\nfunction printDeviceCode(io: CliIo, json: boolean, url: string, device: DeviceAuthorization, decision: BrowserDecision): void {\n if (json || !io.isTTY) {\n const line: Record<string, unknown> = { verification_uri: device.verification_uri }\n if (device.verification_uri_complete !== undefined) line.verification_uri_complete = device.verification_uri_complete\n line.user_code = device.user_code\n line.expires_in = device.expires_in\n io.stderr.write(`${JSON.stringify(line)}\\n`)\n return\n }\n const minutes = Math.max(1, Math.round(device.expires_in / 60))\n io.stderr.write(\n [\n `${decision.open ? \"Opening a browser\" : \"Open this URL in a browser\"} to log in to ${url}:`,\n \"\",\n ` ${device.verification_uri_complete ?? device.verification_uri}`,\n \"\",\n `and confirm the code: ${device.user_code} (expires in ${minutes} min)`,\n \"Waiting for authorization...\",\n \"\",\n ].join(\"\\n\"),\n )\n}\n\n/** 서버 URL = --url → 고정 URL (고객 CLI) → <BIN>_URL */\nexport function resolveServerUrl(\n options: AuthCommandOptions,\n flag: string | undefined,\n io: CliIo,\n): string | { error: string } {\n const envName = `${envPrefix(options.bin)}_URL`\n const url = flag ?? options.url ?? (io.env[envName] || undefined)\n if (url === undefined || url === \"\") return { error: `--url <url> is required (or set ${envName})` }\n if (!/^https?:\\/\\//i.test(url)) return { error: `--url must be an http(s) URL (got \"${url}\")` }\n return url\n}\n\nfunction usage(io: CliIo, options: AuthCommandOptions, message: string): number {\n io.stderr.write(`error: ${message}\\n\\n${loginHelp(options)}`)\n return EXIT.usage\n}\n\nfunction loginHelp(options: AuthCommandOptions): string {\n const { bin } = options\n const prefix = envPrefix(bin)\n const rows = [\n [\"--url <url>\", options.url ? `MCP endpoint (default: ${options.url})` : `MCP endpoint (default: ${prefix}_URL)`],\n [\"--client-id <id>\", \"Pre-registered OAuth client ID or Client ID Metadata Document URL (default: dynamic registration)\"],\n [\"--scope <scopes>\", \"Space-separated scopes (default: server challenge or scopes_supported)\"],\n [\"--port <port>\", \"Loopback redirect port on 127.0.0.1 (default: random)\"],\n [\"--no-browser\", \"Print the authorization URL without opening a browser\"],\n [\"--device\", \"Use the device authorization grant (if the authorization server supports it)\"],\n [\"--no-device\", \"Never switch to the device authorization grant automatically\"],\n [\"--with-token\", \"Read a token (API key) from stdin and store it\"],\n [\"--json\", \"Print the result as JSON\"],\n [\"-h, --help\", \"Show help\"],\n ]\n return [\n \"Log in to a remote server (OAuth 2.1 authorization code + PKCE, loopback redirect).\",\n `The token is stored per server URL (OS keychain, or ~/.config/${bin}/credentials.json; ${prefix}_NO_KEYRING=1 = file only).`,\n \"Over SSH or without a display the browser is not opened: paste the redirect URL, or the device flow is used\",\n \"when the authorization server supports it.\",\n \"\",\n \"Usage:\",\n ` ${bin} login${options.url ? \"\" : \" --url <url>\"} [flags]`,\n ` echo \"$TOKEN\" | ${bin} login${options.url ? \"\" : \" --url <url>\"} --with-token`,\n \"\",\n \"Flags:\",\n ...table(rows).map((l) => ` ${l}`),\n \"\",\n ].join(\"\\n\")\n}\n"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { CliIo } from "./io.ts";
|
|
2
|
+
import { type AuthCommandOptions } from "./login.ts";
|
|
3
|
+
/**
|
|
4
|
+
* <bin> logout — 저장된 토큰 폐기 · 삭제. 인가 서버 메타데이터에 revocation_endpoint (RFC 7009) 가 있으면
|
|
5
|
+
* refresh token → access token 순서로 폐기 요청 (best effort: 실패는 경고, 삭제는 계속), 그다음 키체인 · 파일에서 삭제.
|
|
6
|
+
*/
|
|
7
|
+
export declare function logoutCommand(options: AuthCommandOptions): (argv: readonly string[], io: CliIo) => Promise<number>;
|
|
8
|
+
/** mext logout */
|
|
9
|
+
export declare const runLogout: (argv: readonly string[], io: CliIo) => Promise<number>;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { canonicalServerUrl, credentialStore, envPrefix } from "../remote/credentials.js";
|
|
2
|
+
import { revokeCredentials } from "../remote/oauth.js";
|
|
3
|
+
import { table } from "./help.js";
|
|
4
|
+
import { resolveServerUrl } from "./login.js";
|
|
5
|
+
import { EXIT, formatJson } from "./output.js";
|
|
6
|
+
/**
|
|
7
|
+
* <bin> logout — 저장된 토큰 폐기 · 삭제. 인가 서버 메타데이터에 revocation_endpoint (RFC 7009) 가 있으면
|
|
8
|
+
* refresh token → access token 순서로 폐기 요청 (best effort: 실패는 경고, 삭제는 계속), 그다음 키체인 · 파일에서 삭제.
|
|
9
|
+
*/
|
|
10
|
+
export function logoutCommand(options) {
|
|
11
|
+
return (argv, io) => runLogoutWith(options, argv, io);
|
|
12
|
+
}
|
|
13
|
+
/** mext logout */
|
|
14
|
+
export const runLogout = logoutCommand({ bin: "mext" });
|
|
15
|
+
async function runLogoutWith(options, argv, io) {
|
|
16
|
+
let flag;
|
|
17
|
+
let json = false;
|
|
18
|
+
for (let i = 0; i < argv.length; i++) {
|
|
19
|
+
const tok = argv[i];
|
|
20
|
+
const eq = tok.indexOf("=");
|
|
21
|
+
const name = tok.startsWith("--") && eq > 0 ? tok.slice(0, eq) : tok;
|
|
22
|
+
if (name === "-h" || name === "--help") {
|
|
23
|
+
io.stdout.write(logoutHelp(options));
|
|
24
|
+
return EXIT.ok;
|
|
25
|
+
}
|
|
26
|
+
else if (name === "--json")
|
|
27
|
+
json = true;
|
|
28
|
+
else if (name === "--url")
|
|
29
|
+
flag = eq > 0 ? tok.slice(eq + 1) : argv[++i];
|
|
30
|
+
else
|
|
31
|
+
return usage(io, options, `unknown argument ${tok}`);
|
|
32
|
+
}
|
|
33
|
+
const url = resolveServerUrl(options, flag, io);
|
|
34
|
+
if (typeof url !== "string")
|
|
35
|
+
return usage(io, options, url.error);
|
|
36
|
+
const server = canonicalServerUrl(url);
|
|
37
|
+
const store = credentialStore({ env: io.env, bin: options.bin });
|
|
38
|
+
let stored;
|
|
39
|
+
let readError;
|
|
40
|
+
try {
|
|
41
|
+
stored = await store.get(url);
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
readError = error instanceof Error ? error.message : String(error);
|
|
45
|
+
}
|
|
46
|
+
const revocation = stored
|
|
47
|
+
? await revokeCredentials(stored, options.fetch ? { fetch: options.fetch } : {})
|
|
48
|
+
: { supported: false, revoked: [], errors: [] };
|
|
49
|
+
if (readError !== undefined)
|
|
50
|
+
revocation.errors.push(`Cannot read stored credentials: ${readError}`);
|
|
51
|
+
try {
|
|
52
|
+
await store.delete(url);
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
const message = `Cannot remove stored credentials from ${store.location}: ${error instanceof Error ? error.message : String(error)}`;
|
|
56
|
+
io.stderr.write(json ? formatJson({ code: "LOGOUT_FAILED", message }) : `error: ${message}\n`);
|
|
57
|
+
return EXIT.error;
|
|
58
|
+
}
|
|
59
|
+
if (json) {
|
|
60
|
+
const out = {
|
|
61
|
+
url: server,
|
|
62
|
+
loggedIn: stored !== undefined,
|
|
63
|
+
storage: store.location,
|
|
64
|
+
revoked: revocation.revoked,
|
|
65
|
+
};
|
|
66
|
+
if (revocation.errors.length > 0)
|
|
67
|
+
out.errors = revocation.errors;
|
|
68
|
+
io.stdout.write(formatJson(out));
|
|
69
|
+
return EXIT.ok;
|
|
70
|
+
}
|
|
71
|
+
for (const e of revocation.errors)
|
|
72
|
+
io.stderr.write(`warning: ${e}\n`);
|
|
73
|
+
if (stored === undefined) {
|
|
74
|
+
io.stderr.write(`Not logged in to ${server}.\n`);
|
|
75
|
+
return EXIT.ok;
|
|
76
|
+
}
|
|
77
|
+
if (revocation.revoked.length > 0)
|
|
78
|
+
io.stderr.write(`Revoked ${revocation.revoked.join(" and ").replace(/_/g, " ")} at ${stored.issuer}.\n`);
|
|
79
|
+
io.stderr.write(`Logged out of ${server}. Removed credentials from ${store.location}.\n`);
|
|
80
|
+
return EXIT.ok;
|
|
81
|
+
}
|
|
82
|
+
function usage(io, options, message) {
|
|
83
|
+
io.stderr.write(`error: ${message}\n\n${logoutHelp(options)}`);
|
|
84
|
+
return EXIT.usage;
|
|
85
|
+
}
|
|
86
|
+
function logoutHelp(options) {
|
|
87
|
+
const { bin } = options;
|
|
88
|
+
const rows = [
|
|
89
|
+
["--url <url>", options.url ? `MCP endpoint (default: ${options.url})` : `MCP endpoint (default: ${envPrefix(bin)}_URL)`],
|
|
90
|
+
["--json", "Print the result as JSON"],
|
|
91
|
+
["-h, --help", "Show help"],
|
|
92
|
+
];
|
|
93
|
+
return [
|
|
94
|
+
"Log out of a remote server: revoke the stored tokens (if the authorization server supports RFC 7009)",
|
|
95
|
+
"and remove them from the OS keychain and credentials file.",
|
|
96
|
+
"",
|
|
97
|
+
"Usage:",
|
|
98
|
+
` ${bin} logout${options.url ? "" : " --url <url>"} [flags]`,
|
|
99
|
+
"",
|
|
100
|
+
"Flags:",
|
|
101
|
+
...table(rows).map((l) => ` ${l}`),
|
|
102
|
+
"",
|
|
103
|
+
].join("\n");
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=logout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logout.js","sourceRoot":"","sources":["../../src/cli/logout.ts"],"names":[],"mappings":"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","sourcesContent":["import { canonicalServerUrl, credentialStore, envPrefix, type StoredCredentials } from \"../remote/credentials.ts\"\nimport { revokeCredentials, type RevokeResult } from \"../remote/oauth.ts\"\nimport { table } from \"./help.ts\"\nimport type { CliIo } from \"./io.ts\"\nimport { resolveServerUrl, type AuthCommandOptions } from \"./login.ts\"\nimport { EXIT, formatJson } from \"./output.ts\"\n\n/**\n * <bin> logout — 저장된 토큰 폐기 · 삭제. 인가 서버 메타데이터에 revocation_endpoint (RFC 7009) 가 있으면\n * refresh token → access token 순서로 폐기 요청 (best effort: 실패는 경고, 삭제는 계속), 그다음 키체인 · 파일에서 삭제.\n */\nexport function logoutCommand(options: AuthCommandOptions): (argv: readonly string[], io: CliIo) => Promise<number> {\n return (argv, io) => runLogoutWith(options, argv, io)\n}\n\n/** mext logout */\nexport const runLogout = logoutCommand({ bin: \"mext\" })\n\nasync function runLogoutWith(options: AuthCommandOptions, argv: readonly string[], io: CliIo): Promise<number> {\n let flag: string | undefined\n let json = false\n for (let i = 0; i < argv.length; i++) {\n const tok = argv[i]!\n const eq = tok.indexOf(\"=\")\n const name = tok.startsWith(\"--\") && eq > 0 ? tok.slice(0, eq) : tok\n if (name === \"-h\" || name === \"--help\") {\n io.stdout.write(logoutHelp(options))\n return EXIT.ok\n } else if (name === \"--json\") json = true\n else if (name === \"--url\") flag = eq > 0 ? tok.slice(eq + 1) : argv[++i]\n else return usage(io, options, `unknown argument ${tok}`)\n }\n const url = resolveServerUrl(options, flag, io)\n if (typeof url !== \"string\") return usage(io, options, url.error)\n const server = canonicalServerUrl(url)\n const store = credentialStore({ env: io.env, bin: options.bin })\n\n let stored: StoredCredentials | undefined\n let readError: string | undefined\n try {\n stored = await store.get(url)\n } catch (error) {\n readError = error instanceof Error ? error.message : String(error)\n }\n const revocation: RevokeResult = stored\n ? await revokeCredentials(stored, options.fetch ? { fetch: options.fetch } : {})\n : { supported: false, revoked: [], errors: [] }\n if (readError !== undefined) revocation.errors.push(`Cannot read stored credentials: ${readError}`)\n try {\n await store.delete(url)\n } catch (error) {\n const message = `Cannot remove stored credentials from ${store.location}: ${error instanceof Error ? error.message : String(error)}`\n io.stderr.write(json ? formatJson({ code: \"LOGOUT_FAILED\", message }) : `error: ${message}\\n`)\n return EXIT.error\n }\n\n if (json) {\n const out: Record<string, unknown> = {\n url: server,\n loggedIn: stored !== undefined,\n storage: store.location,\n revoked: revocation.revoked,\n }\n if (revocation.errors.length > 0) out.errors = revocation.errors\n io.stdout.write(formatJson(out))\n return EXIT.ok\n }\n for (const e of revocation.errors) io.stderr.write(`warning: ${e}\\n`)\n if (stored === undefined) {\n io.stderr.write(`Not logged in to ${server}.\\n`)\n return EXIT.ok\n }\n if (revocation.revoked.length > 0) io.stderr.write(`Revoked ${revocation.revoked.join(\" and \").replace(/_/g, \" \")} at ${stored.issuer}.\\n`)\n io.stderr.write(`Logged out of ${server}. Removed credentials from ${store.location}.\\n`)\n return EXIT.ok\n}\n\nfunction usage(io: CliIo, options: AuthCommandOptions, message: string): number {\n io.stderr.write(`error: ${message}\\n\\n${logoutHelp(options)}`)\n return EXIT.usage\n}\n\nfunction logoutHelp(options: AuthCommandOptions): string {\n const { bin } = options\n const rows = [\n [\"--url <url>\", options.url ? `MCP endpoint (default: ${options.url})` : `MCP endpoint (default: ${envPrefix(bin)}_URL)`],\n [\"--json\", \"Print the result as JSON\"],\n [\"-h, --help\", \"Show help\"],\n ]\n return [\n \"Log out of a remote server: revoke the stored tokens (if the authorization server supports RFC 7009)\",\n \"and remove them from the OS keychain and credentials file.\",\n \"\",\n \"Usage:\",\n ` ${bin} logout${options.url ? \"\" : \" --url <url>\"} [flags]`,\n \"\",\n \"Flags:\",\n ...table(rows).map((l) => ` ${l}`),\n \"\",\n ].join(\"\\n\")\n}\n"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type CliIo } from "./io.ts";
|
|
2
|
+
/** mext 의 하위 명령 (run · dev · mcp install · login · logout · build · start) */
|
|
3
|
+
export interface CliSubcommand {
|
|
4
|
+
name: string;
|
|
5
|
+
/** mext help 한 줄 설명 */
|
|
6
|
+
summary: string;
|
|
7
|
+
/** 하위 명령 이름 뒤의 argv. 반환 = exit code */
|
|
8
|
+
run(argv: readonly string[], io: CliIo): Promise<number>;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* 하위 명령 표. 이후 단계가 항목을 추가한다 (mcp · mcp --stdio · mcp install · login · build · run --url).
|
|
12
|
+
* 순서 = help 표시 순서.
|
|
13
|
+
*/
|
|
14
|
+
export declare const SUBCOMMANDS: CliSubcommand[];
|
|
15
|
+
/**
|
|
16
|
+
* mext CLI 진입점. argv = 실행 파일 뒤 인자 (process.argv.slice(2)). 반환 = exit code.
|
|
17
|
+
* io 는 테스트용 주입 (stdin · stdout · stderr · isTTY · env · cwd · signal), 생략 = process.
|
|
18
|
+
*/
|
|
19
|
+
export declare function runCli(argv: readonly string[], io?: Partial<CliIo>): Promise<number>;
|
package/dist/cli/main.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { closest } from "../suggest.js";
|
|
3
|
+
import { table } from "./help.js";
|
|
4
|
+
import { resolveIo } from "./io.js";
|
|
5
|
+
import { EXIT } from "./output.js";
|
|
6
|
+
import { runBuild } from "./build.js";
|
|
7
|
+
import { runLogin } from "./login.js";
|
|
8
|
+
import { devCommand } from "./dev.js";
|
|
9
|
+
import { runLogout } from "./logout.js";
|
|
10
|
+
import { mcpCommand } from "./mcp.js";
|
|
11
|
+
import { runCommand } from "./run.js";
|
|
12
|
+
import { startCommand } from "./start.js";
|
|
13
|
+
/**
|
|
14
|
+
* 하위 명령 표. 이후 단계가 항목을 추가한다 (mcp · mcp --stdio · mcp install · login · build · run --url).
|
|
15
|
+
* 순서 = help 표시 순서.
|
|
16
|
+
*/
|
|
17
|
+
export const SUBCOMMANDS = [
|
|
18
|
+
{ name: "run", summary: "Run a command locally, or remotely with --url (mext run help)", run: runCommand },
|
|
19
|
+
{ name: "dev", summary: "Development MCP server with file watching (mext dev, mext dev --stdio)", run: devCommand },
|
|
20
|
+
{ name: "mcp", summary: "Print MCP client registration commands (mext mcp install)", run: mcpCommand },
|
|
21
|
+
{ name: "login", summary: "Log in to a remote server (mext login --url <url>)", run: runLogin },
|
|
22
|
+
{ name: "logout", summary: "Revoke and remove stored credentials (mext logout --url <url>)", run: runLogout },
|
|
23
|
+
{ name: "build", summary: "Build .mext/ for production: bundle, manifest, server.js (mext build --help)", run: (argv, io) => runBuild(argv, io) },
|
|
24
|
+
{ name: "start", summary: "Production MCP server from .mext/ (mext start, mext start --stdio)", run: startCommand },
|
|
25
|
+
];
|
|
26
|
+
/**
|
|
27
|
+
* mext CLI 진입점. argv = 실행 파일 뒤 인자 (process.argv.slice(2)). 반환 = exit code.
|
|
28
|
+
* io 는 테스트용 주입 (stdin · stdout · stderr · isTTY · env · cwd · signal), 생략 = process.
|
|
29
|
+
*/
|
|
30
|
+
export async function runCli(argv, io = {}) {
|
|
31
|
+
const resolved = resolveIo(io);
|
|
32
|
+
const subcommands = SUBCOMMANDS;
|
|
33
|
+
const [first, ...rest] = argv;
|
|
34
|
+
if (first === undefined) {
|
|
35
|
+
resolved.stderr.write(rootHelp(subcommands));
|
|
36
|
+
return EXIT.usage;
|
|
37
|
+
}
|
|
38
|
+
if (first === "help" || first === "--help" || first === "-h") {
|
|
39
|
+
resolved.stdout.write(rootHelp(subcommands));
|
|
40
|
+
return EXIT.ok;
|
|
41
|
+
}
|
|
42
|
+
if (first === "--version" || first === "-v") {
|
|
43
|
+
resolved.stdout.write(`${packageVersion()}\n`);
|
|
44
|
+
return EXIT.ok;
|
|
45
|
+
}
|
|
46
|
+
const sub = subcommands.find((s) => s.name === first);
|
|
47
|
+
if (!sub) {
|
|
48
|
+
const suggested = closest(first, subcommands.map((s) => [s.name, s.name]));
|
|
49
|
+
const hint = suggested.length > 0 ? ` Did you mean "mext ${suggested[0]}"?` : "";
|
|
50
|
+
resolved.stderr.write(`error: unknown command "${first}".${hint}\nRun "mext help" for available commands.\n`);
|
|
51
|
+
return EXIT.usage;
|
|
52
|
+
}
|
|
53
|
+
return sub.run(rest, resolved);
|
|
54
|
+
}
|
|
55
|
+
function rootHelp(subcommands) {
|
|
56
|
+
const lines = [
|
|
57
|
+
"mext — file-based commands as a CLI and an MCP server",
|
|
58
|
+
"",
|
|
59
|
+
"Usage:",
|
|
60
|
+
" mext <command> [args]",
|
|
61
|
+
"",
|
|
62
|
+
"Commands:",
|
|
63
|
+
...table(subcommands.map((s) => [s.name, s.summary])).map((l) => ` ${l}`),
|
|
64
|
+
"",
|
|
65
|
+
"Flags:",
|
|
66
|
+
...table([
|
|
67
|
+
["-h, --help", "Show help"],
|
|
68
|
+
["-v, --version", "Print the version"],
|
|
69
|
+
]).map((l) => ` ${l}`),
|
|
70
|
+
];
|
|
71
|
+
return `${lines.join("\n")}\n`;
|
|
72
|
+
}
|
|
73
|
+
function packageVersion() {
|
|
74
|
+
const pkg = JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf8"));
|
|
75
|
+
return pkg.version ?? "0.0.0";
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=main.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.js","sourceRoot":"","sources":["../../src/cli/main.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFileSync } from \"node:fs\"\nimport { closest } from \"../suggest.ts\"\nimport { table } from \"./help.ts\"\nimport { resolveIo, type CliIo } from \"./io.ts\"\nimport { EXIT } from \"./output.ts\"\nimport { runBuild } from \"./build.ts\"\nimport { runLogin } from \"./login.ts\"\nimport { devCommand } from \"./dev.ts\"\nimport { runLogout } from \"./logout.ts\"\nimport { mcpCommand } from \"./mcp.ts\"\nimport { runCommand } from \"./run.ts\"\nimport { startCommand } from \"./start.ts\"\n\n/** mext 의 하위 명령 (run · dev · mcp install · login · logout · build · start) */\nexport interface CliSubcommand {\n name: string\n /** mext help 한 줄 설명 */\n summary: string\n /** 하위 명령 이름 뒤의 argv. 반환 = exit code */\n run(argv: readonly string[], io: CliIo): Promise<number>\n}\n\n/**\n * 하위 명령 표. 이후 단계가 항목을 추가한다 (mcp · mcp --stdio · mcp install · login · build · run --url).\n * 순서 = help 표시 순서.\n */\nexport const SUBCOMMANDS: CliSubcommand[] = [\n { name: \"run\", summary: \"Run a command locally, or remotely with --url (mext run help)\", run: runCommand },\n { name: \"dev\", summary: \"Development MCP server with file watching (mext dev, mext dev --stdio)\", run: devCommand },\n { name: \"mcp\", summary: \"Print MCP client registration commands (mext mcp install)\", run: mcpCommand },\n { name: \"login\", summary: \"Log in to a remote server (mext login --url <url>)\", run: runLogin },\n { name: \"logout\", summary: \"Revoke and remove stored credentials (mext logout --url <url>)\", run: runLogout },\n { name: \"build\", summary: \"Build .mext/ for production: bundle, manifest, server.js (mext build --help)\", run: (argv, io) => runBuild(argv, io) },\n { name: \"start\", summary: \"Production MCP server from .mext/ (mext start, mext start --stdio)\", run: startCommand },\n]\n\n/**\n * mext CLI 진입점. argv = 실행 파일 뒤 인자 (process.argv.slice(2)). 반환 = exit code.\n * io 는 테스트용 주입 (stdin · stdout · stderr · isTTY · env · cwd · signal), 생략 = process.\n */\nexport async function runCli(argv: readonly string[], io: Partial<CliIo> = {}): Promise<number> {\n const resolved = resolveIo(io)\n const subcommands = SUBCOMMANDS\n const [first, ...rest] = argv\n if (first === undefined) {\n resolved.stderr.write(rootHelp(subcommands))\n return EXIT.usage\n }\n if (first === \"help\" || first === \"--help\" || first === \"-h\") {\n resolved.stdout.write(rootHelp(subcommands))\n return EXIT.ok\n }\n if (first === \"--version\" || first === \"-v\") {\n resolved.stdout.write(`${packageVersion()}\\n`)\n return EXIT.ok\n }\n const sub = subcommands.find((s) => s.name === first)\n if (!sub) {\n const suggested = closest(first, subcommands.map((s): [string, string] => [s.name, s.name]))\n const hint = suggested.length > 0 ? ` Did you mean \"mext ${suggested[0]}\"?` : \"\"\n resolved.stderr.write(`error: unknown command \"${first}\".${hint}\\nRun \"mext help\" for available commands.\\n`)\n return EXIT.usage\n }\n return sub.run(rest, resolved)\n}\n\nfunction rootHelp(subcommands: readonly CliSubcommand[]): string {\n const lines = [\n \"mext — file-based commands as a CLI and an MCP server\",\n \"\",\n \"Usage:\",\n \" mext <command> [args]\",\n \"\",\n \"Commands:\",\n ...table(subcommands.map((s) => [s.name, s.summary])).map((l) => ` ${l}`),\n \"\",\n \"Flags:\",\n ...table([\n [\"-h, --help\", \"Show help\"],\n [\"-v, --version\", \"Print the version\"],\n ]).map((l) => ` ${l}`),\n ]\n return `${lines.join(\"\\n\")}\\n`\n}\n\nfunction packageVersion(): string {\n const pkg = JSON.parse(readFileSync(new URL(\"../../package.json\", import.meta.url), \"utf8\")) as { version?: string }\n return pkg.version ?? \"0.0.0\"\n}\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { CliIo } from "./io.ts";
|
|
2
|
+
export declare const MCP_PROG = "mext mcp";
|
|
3
|
+
/**
|
|
4
|
+
* mext mcp install [--name] [--url] MCP 클라이언트 등록 안내 출력 (설정 파일은 쓰지 않음)
|
|
5
|
+
* 서버 형태 (mext mcp · mext mcp --stdio) 는 제거: mext dev · mext dev --stdio (운영 = mext start)
|
|
6
|
+
*/
|
|
7
|
+
export declare function mcpCommand(argv: readonly string[], io: CliIo): Promise<number>;
|
|
8
|
+
interface McpArgs {
|
|
9
|
+
help: boolean;
|
|
10
|
+
name?: string;
|
|
11
|
+
url?: string;
|
|
12
|
+
}
|
|
13
|
+
/** 반환 문자열 = 사용법 오류 메시지 */
|
|
14
|
+
export declare function parseMcpArgs(argv: readonly string[]): McpArgs | string;
|
|
15
|
+
export declare function mcpHelp(): string;
|
|
16
|
+
export {};
|