@archastro/intern 1.0.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/LICENSE +21 -0
- package/README.md +67 -0
- package/dist/args.d.ts +10 -0
- package/dist/args.js +101 -0
- package/dist/args.js.map +1 -0
- package/dist/config.d.ts +11 -0
- package/dist/config.js +42 -0
- package/dist/config.js.map +1 -0
- package/dist/credentials.d.ts +33 -0
- package/dist/credentials.js +179 -0
- package/dist/credentials.js.map +1 -0
- package/dist/errors.d.ts +6 -0
- package/dist/errors.js +14 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +249 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp.d.ts +23 -0
- package/dist/mcp.js +140 -0
- package/dist/mcp.js.map +1 -0
- package/dist/oauth.d.ts +9 -0
- package/dist/oauth.js +209 -0
- package/dist/oauth.js.map +1 -0
- package/dist/publish.d.ts +32 -0
- package/dist/publish.js +214 -0
- package/dist/publish.js.map +1 -0
- package/package.json +46 -0
- package/skills/intern/SKILL.md +204 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { parseArguments, flag, option, options, parseToolInput, } from "./args.js";
|
|
4
|
+
import { loadConfig } from "./config.js";
|
|
5
|
+
import { Credentials } from "./credentials.js";
|
|
6
|
+
import { InternError } from "./errors.js";
|
|
7
|
+
import { McpClient, ToolCallError } from "./mcp.js";
|
|
8
|
+
import { login } from "./oauth.js";
|
|
9
|
+
import { publishDirectory } from "./publish.js";
|
|
10
|
+
const version = createRequire(import.meta.url)("../package.json").version;
|
|
11
|
+
export async function run(argv) {
|
|
12
|
+
const args = parseArguments(argv);
|
|
13
|
+
if (flag(args, "version") || args.command === "version") {
|
|
14
|
+
process.stdout.write(`${version}\n`);
|
|
15
|
+
return 0;
|
|
16
|
+
}
|
|
17
|
+
if (flag(args, "help") || args.command === "help") {
|
|
18
|
+
process.stdout.write(HELP);
|
|
19
|
+
return 0;
|
|
20
|
+
}
|
|
21
|
+
const config = loadConfig({ origin: option(args, "origin") });
|
|
22
|
+
const credentials = new Credentials(config);
|
|
23
|
+
const client = new McpClient(config, credentials);
|
|
24
|
+
const compact = flag(args, "compact");
|
|
25
|
+
let output;
|
|
26
|
+
switch (args.command) {
|
|
27
|
+
case "login": {
|
|
28
|
+
requirePositionals(args.positionals, 0, "intern login");
|
|
29
|
+
await login(config, credentials, {
|
|
30
|
+
openBrowser: !flag(args, "no-open"),
|
|
31
|
+
notify: (message) => process.stderr.write(`${message}\n`),
|
|
32
|
+
});
|
|
33
|
+
output = await client.callTool("intern_auth_status", {});
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
case "logout": {
|
|
37
|
+
requirePositionals(args.positionals, 0, "intern logout");
|
|
38
|
+
const removed = await credentials.logout();
|
|
39
|
+
output = {
|
|
40
|
+
signedOut: !config.accessToken,
|
|
41
|
+
credentialsRemoved: removed,
|
|
42
|
+
environmentOverride: Boolean(config.accessToken),
|
|
43
|
+
};
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
case "status":
|
|
47
|
+
requirePositionals(args.positionals, 0, "intern status");
|
|
48
|
+
output = await authorizationStatus(client);
|
|
49
|
+
break;
|
|
50
|
+
case "auth":
|
|
51
|
+
if (args.positionals[0] !== "status" || args.positionals.length !== 1) {
|
|
52
|
+
throw usage("Usage: intern auth status");
|
|
53
|
+
}
|
|
54
|
+
output = await authorizationStatus(client);
|
|
55
|
+
break;
|
|
56
|
+
case "tools":
|
|
57
|
+
requirePositionals(args.positionals, 0, "intern tools");
|
|
58
|
+
output = { tools: await client.listTools() };
|
|
59
|
+
break;
|
|
60
|
+
case "call": {
|
|
61
|
+
const tool = args.positionals[0];
|
|
62
|
+
if (!tool || args.positionals.length > 2) {
|
|
63
|
+
throw usage("Usage: intern call <tool-name> [JSON] [--input-file PATH]");
|
|
64
|
+
}
|
|
65
|
+
output = await client.callTool(tool, await parseToolInput(args));
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
case "resources":
|
|
69
|
+
requirePositionals(args.positionals, 0, "intern resources");
|
|
70
|
+
output = await client.listResources();
|
|
71
|
+
break;
|
|
72
|
+
case "read": {
|
|
73
|
+
const uri = onlyPositional(args.positionals, "Usage: intern read <resource-uri>");
|
|
74
|
+
output = { contents: await client.readResource(uri) };
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
case "sites":
|
|
78
|
+
requirePositionals(args.positionals, 0, "intern sites");
|
|
79
|
+
output = await client.callTool("intern_list_sites", {});
|
|
80
|
+
break;
|
|
81
|
+
case "guide":
|
|
82
|
+
requirePositionals(args.positionals, 0, "intern guide");
|
|
83
|
+
output = await client.callTool("intern_get_authoring_guide", {});
|
|
84
|
+
break;
|
|
85
|
+
case "plugins": {
|
|
86
|
+
if (args.positionals.length > 1)
|
|
87
|
+
throw usage("Usage: intern plugins [site]");
|
|
88
|
+
const site = args.positionals[0];
|
|
89
|
+
output = site
|
|
90
|
+
? await client.callTool("intern_list_site_plugins", { site })
|
|
91
|
+
: await client.callTool("intern_list_available_plugins", {});
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
case "source": {
|
|
95
|
+
const site = onlyPositional(args.positionals, "Usage: intern source <site>");
|
|
96
|
+
output = await client.callTool("intern_get_site_source", { site });
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
case "fetch": {
|
|
100
|
+
const url = onlyPositional(args.positionals, "Usage: intern fetch <url>");
|
|
101
|
+
output = await client.callTool("intern_fetch_url", { url });
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
case "publish": {
|
|
105
|
+
if (args.positionals.length > 1) {
|
|
106
|
+
throw usage("Usage: intern publish [directory] --site <slug> [--message TEXT]");
|
|
107
|
+
}
|
|
108
|
+
const site = option(args, "site");
|
|
109
|
+
if (!site)
|
|
110
|
+
throw usage("intern publish requires --site <slug>");
|
|
111
|
+
output = await publishDirectory(client, {
|
|
112
|
+
site,
|
|
113
|
+
directory: args.positionals[0] ?? ".",
|
|
114
|
+
message: option(args, "message") ?? `Publish ${site} from Intern CLI`,
|
|
115
|
+
plugins: options(args, "plugin").map(parsePlugin),
|
|
116
|
+
});
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
case "visibility": {
|
|
120
|
+
if (args.positionals.length !== 2 ||
|
|
121
|
+
!["private", "public"].includes(args.positionals[1])) {
|
|
122
|
+
throw usage("Usage: intern visibility <site> private|public");
|
|
123
|
+
}
|
|
124
|
+
output = await client.callTool("intern_set_site_visibility", {
|
|
125
|
+
site: args.positionals[0],
|
|
126
|
+
visibility: args.positionals[1],
|
|
127
|
+
});
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
case "invite": {
|
|
131
|
+
if (args.positionals.length !== 2)
|
|
132
|
+
throw usage("Usage: intern invite <site> <email>");
|
|
133
|
+
output = await client.callTool("intern_invite_site_guest", {
|
|
134
|
+
site: args.positionals[0],
|
|
135
|
+
email: args.positionals[1],
|
|
136
|
+
});
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
case "guests": {
|
|
140
|
+
const site = onlyPositional(args.positionals, "Usage: intern guests <site>");
|
|
141
|
+
output = await client.callTool("intern_list_site_guests", { site });
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
case "revoke": {
|
|
145
|
+
if (args.positionals.length !== 2)
|
|
146
|
+
throw usage("Usage: intern revoke <site> <grant-id>");
|
|
147
|
+
output = await client.callTool("intern_revoke_site_guest", {
|
|
148
|
+
site: args.positionals[0],
|
|
149
|
+
grant: args.positionals[1],
|
|
150
|
+
});
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
case "delete": {
|
|
154
|
+
const site = onlyPositional(args.positionals, "Usage: intern delete <site> --yes");
|
|
155
|
+
if (!flag(args, "yes"))
|
|
156
|
+
throw usage("Permanent deletion requires --yes");
|
|
157
|
+
output = await client.callTool("intern_delete_site", { site });
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
default:
|
|
161
|
+
throw usage(`Unknown command ${args.command}. Run \`intern help\` for usage.`);
|
|
162
|
+
}
|
|
163
|
+
printJSON(output, compact);
|
|
164
|
+
return 0;
|
|
165
|
+
}
|
|
166
|
+
async function authorizationStatus(client) {
|
|
167
|
+
try {
|
|
168
|
+
return await client.callTool("intern_auth_status", {});
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
if (error instanceof InternError && error.code === "auth_required") {
|
|
172
|
+
return { authorized: false };
|
|
173
|
+
}
|
|
174
|
+
throw error;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
function parsePlugin(value) {
|
|
178
|
+
const [plugin, binding, ...rest] = value.split(":");
|
|
179
|
+
if (!plugin || rest.length > 0)
|
|
180
|
+
throw usage(`Invalid plugin ${value}; use key or key:binding`);
|
|
181
|
+
return { plugin, ...(binding ? { binding } : {}), config: {} };
|
|
182
|
+
}
|
|
183
|
+
function onlyPositional(values, message) {
|
|
184
|
+
if (values.length !== 1)
|
|
185
|
+
throw usage(message);
|
|
186
|
+
return values[0];
|
|
187
|
+
}
|
|
188
|
+
function requirePositionals(values, count, command) {
|
|
189
|
+
if (values.length !== count)
|
|
190
|
+
throw usage(`Usage: ${command}`);
|
|
191
|
+
}
|
|
192
|
+
function usage(message) {
|
|
193
|
+
return new InternError("usage", message);
|
|
194
|
+
}
|
|
195
|
+
function printJSON(value, compact) {
|
|
196
|
+
process.stdout.write(`${JSON.stringify(value, null, compact ? undefined : 2)}\n`);
|
|
197
|
+
}
|
|
198
|
+
const HELP = `Intern CLI ${version}
|
|
199
|
+
|
|
200
|
+
Usage:
|
|
201
|
+
intern login [--no-open] Connect through TryIntern browser OAuth
|
|
202
|
+
intern logout Remove stored credentials
|
|
203
|
+
intern status Show the current user and organization
|
|
204
|
+
intern publish [DIR] --site SLUG Create or update a site from static files
|
|
205
|
+
intern sites List sites
|
|
206
|
+
intern source SITE Read editable source
|
|
207
|
+
intern plugins [SITE] List available or installed plugins
|
|
208
|
+
intern guide Read the current authoring guide
|
|
209
|
+
intern fetch URL Read an authorized served Intern URL
|
|
210
|
+
intern visibility SITE private|public
|
|
211
|
+
intern invite SITE EMAIL
|
|
212
|
+
intern guests SITE
|
|
213
|
+
intern revoke SITE GRANT_ID
|
|
214
|
+
intern delete SITE --yes
|
|
215
|
+
|
|
216
|
+
Live MCP surface:
|
|
217
|
+
intern tools List every hosted Intern tool and schema
|
|
218
|
+
intern call TOOL [JSON] Call any hosted tool
|
|
219
|
+
intern call TOOL --input-file PATH Read tool arguments from a JSON file or -
|
|
220
|
+
intern resources List hosted resources and templates
|
|
221
|
+
intern read URI Read a hosted resource
|
|
222
|
+
|
|
223
|
+
Options:
|
|
224
|
+
--origin URL Override https://tryintern.dev (or set INTERN_BASE_URL)
|
|
225
|
+
--compact Emit compact JSON
|
|
226
|
+
--plugin KEY[:BINDING] Request a plugin while creating a site; repeatable
|
|
227
|
+
|
|
228
|
+
Credentials are stored in ~/.config/intern with mode 0600. Set
|
|
229
|
+
INTERN_ACCESS_TOKEN for an ephemeral bearer override. Command results are JSON.
|
|
230
|
+
`;
|
|
231
|
+
async function main() {
|
|
232
|
+
try {
|
|
233
|
+
process.exitCode = await run(process.argv.slice(2));
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
if (error instanceof ToolCallError) {
|
|
237
|
+
process.stderr.write(`${JSON.stringify(error.output, null, 2)}\n`);
|
|
238
|
+
process.exitCode = 1;
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const code = error instanceof InternError ? error.code : "internal_error";
|
|
242
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
243
|
+
const details = error instanceof InternError ? error.details : undefined;
|
|
244
|
+
process.stderr.write(`${JSON.stringify({ error: { code, message, ...(details === undefined ? {} : { details }) } }, null, 2)}\n`);
|
|
245
|
+
process.exitCode = code === "usage" ? 2 : 1;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
await main();
|
|
249
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/mcp.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { InternConfig } from "./config.js";
|
|
2
|
+
import { Credentials } from "./credentials.js";
|
|
3
|
+
import { InternError } from "./errors.js";
|
|
4
|
+
export declare class ToolCallError extends InternError {
|
|
5
|
+
readonly output: unknown;
|
|
6
|
+
constructor(output: unknown);
|
|
7
|
+
}
|
|
8
|
+
export declare class McpClient {
|
|
9
|
+
private readonly config;
|
|
10
|
+
private readonly credentials;
|
|
11
|
+
private readonly fetchFn;
|
|
12
|
+
private sequence;
|
|
13
|
+
constructor(config: InternConfig, credentials: Credentials, fetchFn?: typeof fetch);
|
|
14
|
+
listTools(): Promise<unknown>;
|
|
15
|
+
callTool(name: string, args: Record<string, unknown>): Promise<unknown>;
|
|
16
|
+
listResources(): Promise<{
|
|
17
|
+
resources: unknown;
|
|
18
|
+
resourceTemplates: unknown;
|
|
19
|
+
}>;
|
|
20
|
+
readResource(uri: string): Promise<unknown>;
|
|
21
|
+
private rpc;
|
|
22
|
+
private request;
|
|
23
|
+
}
|
package/dist/mcp.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { InternError } from "./errors.js";
|
|
2
|
+
export class ToolCallError extends InternError {
|
|
3
|
+
output;
|
|
4
|
+
constructor(output) {
|
|
5
|
+
const error = isRecord(output) && isRecord(output.error) ? output.error : undefined;
|
|
6
|
+
super(typeof error?.code === "string" ? error.code : "tool_failed", typeof error?.message === "string"
|
|
7
|
+
? error.message
|
|
8
|
+
: "Intern tool call failed", output);
|
|
9
|
+
this.output = output;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export class McpClient {
|
|
13
|
+
config;
|
|
14
|
+
credentials;
|
|
15
|
+
fetchFn;
|
|
16
|
+
sequence = 0;
|
|
17
|
+
constructor(config, credentials, fetchFn = fetch) {
|
|
18
|
+
this.config = config;
|
|
19
|
+
this.credentials = credentials;
|
|
20
|
+
this.fetchFn = fetchFn;
|
|
21
|
+
}
|
|
22
|
+
async listTools() {
|
|
23
|
+
return resultField(await this.rpc("tools/list", {}), "tools");
|
|
24
|
+
}
|
|
25
|
+
async callTool(name, args) {
|
|
26
|
+
const response = asRecord(await this.rpc("tools/call", { name, arguments: args }));
|
|
27
|
+
const output = response.structuredContent ?? textOutput(response);
|
|
28
|
+
if (response.isError === true)
|
|
29
|
+
throw new ToolCallError(output);
|
|
30
|
+
return output;
|
|
31
|
+
}
|
|
32
|
+
async listResources() {
|
|
33
|
+
const resources = resultField(await this.rpc("resources/list", {}), "resources");
|
|
34
|
+
const resourceTemplates = resultField(await this.rpc("resources/templates/list", {}), "resourceTemplates");
|
|
35
|
+
return { resources, resourceTemplates };
|
|
36
|
+
}
|
|
37
|
+
async readResource(uri) {
|
|
38
|
+
return resultField(await this.rpc("resources/read", { uri }), "contents");
|
|
39
|
+
}
|
|
40
|
+
async rpc(method, params) {
|
|
41
|
+
const id = ++this.sequence;
|
|
42
|
+
let token = await this.credentials.accessToken();
|
|
43
|
+
let response = await this.request(id, method, params, token);
|
|
44
|
+
if (response.status === 401 && !this.config.accessToken) {
|
|
45
|
+
token = await this.credentials.accessToken(true);
|
|
46
|
+
response = await this.request(id, method, params, token);
|
|
47
|
+
}
|
|
48
|
+
if (!response.ok) {
|
|
49
|
+
const body = await response.text();
|
|
50
|
+
throw new InternError(response.status === 401 ? "auth_required" : "mcp_http_error", response.status === 401
|
|
51
|
+
? "Intern authorization is no longer valid; run `intern login` again"
|
|
52
|
+
: `Intern service request failed with HTTP ${response.status}`, safeRemoteBody(body));
|
|
53
|
+
}
|
|
54
|
+
const envelope = parseEnvelope(await response.text(), response.headers.get("content-type"), id);
|
|
55
|
+
if (envelope.error) {
|
|
56
|
+
throw new InternError("mcp_protocol_error", envelope.error.message ?? "Intern service returned a protocol error", envelope.error);
|
|
57
|
+
}
|
|
58
|
+
if (!("result" in envelope)) {
|
|
59
|
+
throw new InternError("mcp_protocol_error", "Intern service response omitted a result");
|
|
60
|
+
}
|
|
61
|
+
return envelope.result;
|
|
62
|
+
}
|
|
63
|
+
request(id, method, params, token) {
|
|
64
|
+
return this.fetchFn(this.config.resource, {
|
|
65
|
+
method: "POST",
|
|
66
|
+
redirect: "error",
|
|
67
|
+
signal: AbortSignal.timeout(120_000),
|
|
68
|
+
headers: {
|
|
69
|
+
accept: "application/json, text/event-stream",
|
|
70
|
+
authorization: `Bearer ${token}`,
|
|
71
|
+
"content-type": "application/json",
|
|
72
|
+
"user-agent": "intern-cli/0.1",
|
|
73
|
+
},
|
|
74
|
+
body: JSON.stringify({ jsonrpc: "2.0", id, method, params }),
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function parseEnvelope(body, contentType, expectedId) {
|
|
79
|
+
let value;
|
|
80
|
+
if (contentType?.includes("text/event-stream")) {
|
|
81
|
+
const events = body
|
|
82
|
+
.split(/\r?\n\r?\n/)
|
|
83
|
+
.map((event) => event
|
|
84
|
+
.split(/\r?\n/)
|
|
85
|
+
.filter((line) => line.startsWith("data:"))
|
|
86
|
+
.map((line) => line.slice(5).trimStart())
|
|
87
|
+
.join("\n"))
|
|
88
|
+
.filter(Boolean);
|
|
89
|
+
const candidate = events.at(-1);
|
|
90
|
+
if (!candidate)
|
|
91
|
+
throw new InternError("mcp_protocol_error", "Intern service returned an empty event stream");
|
|
92
|
+
value = parseJSON(candidate);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
value = parseJSON(body);
|
|
96
|
+
}
|
|
97
|
+
if (!isRecord(value) || value.jsonrpc !== "2.0" || value.id !== expectedId) {
|
|
98
|
+
throw new InternError("mcp_protocol_error", "Intern service returned an invalid JSON-RPC response");
|
|
99
|
+
}
|
|
100
|
+
return value;
|
|
101
|
+
}
|
|
102
|
+
function parseJSON(value) {
|
|
103
|
+
try {
|
|
104
|
+
return JSON.parse(value);
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
throw new InternError("mcp_protocol_error", "Intern service returned invalid JSON");
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function textOutput(result) {
|
|
111
|
+
const text = result.content?.find((item) => item.type === "text")?.text;
|
|
112
|
+
if (!text)
|
|
113
|
+
return null;
|
|
114
|
+
try {
|
|
115
|
+
return JSON.parse(text);
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return text;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function resultField(value, field) {
|
|
122
|
+
const result = asRecord(value);
|
|
123
|
+
if (!(field in result)) {
|
|
124
|
+
throw new InternError("mcp_protocol_error", `Intern service response omitted ${field}`);
|
|
125
|
+
}
|
|
126
|
+
return result[field];
|
|
127
|
+
}
|
|
128
|
+
function asRecord(value) {
|
|
129
|
+
if (!isRecord(value))
|
|
130
|
+
throw new InternError("mcp_protocol_error", "Intern service returned an invalid result");
|
|
131
|
+
return value;
|
|
132
|
+
}
|
|
133
|
+
function safeRemoteBody(body) {
|
|
134
|
+
const trimmed = body.trim();
|
|
135
|
+
return trimmed.length > 0 && trimmed.length <= 2_000 ? trimmed : undefined;
|
|
136
|
+
}
|
|
137
|
+
function isRecord(value) {
|
|
138
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=mcp.js.map
|
package/dist/mcp.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp.js","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"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"}
|
package/dist/oauth.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type InternConfig } from "./config.js";
|
|
2
|
+
import { Credentials, type CredentialRecord } from "./credentials.js";
|
|
3
|
+
interface LoginOptions {
|
|
4
|
+
openBrowser?: boolean;
|
|
5
|
+
timeoutMs?: number;
|
|
6
|
+
notify?: (message: string) => void;
|
|
7
|
+
}
|
|
8
|
+
export declare function login(config: InternConfig, credentials: Credentials, options?: LoginOptions, fetchFn?: typeof fetch): Promise<CredentialRecord>;
|
|
9
|
+
export {};
|
package/dist/oauth.js
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import http from "node:http";
|
|
4
|
+
import { OAUTH_SCOPES } from "./config.js";
|
|
5
|
+
import { parseTokens, } from "./credentials.js";
|
|
6
|
+
import { InternError } from "./errors.js";
|
|
7
|
+
export async function login(config, credentials, options = {}, fetchFn = fetch) {
|
|
8
|
+
const metadata = await discover(config, fetchFn);
|
|
9
|
+
const state = randomBytes(32).toString("base64url");
|
|
10
|
+
const verifier = randomBytes(48).toString("base64url");
|
|
11
|
+
const challenge = createHash("sha256").update(verifier).digest("base64url");
|
|
12
|
+
const callback = await listenForCallback(state, metadata.issuer, options.timeoutMs ?? 5 * 60_000);
|
|
13
|
+
try {
|
|
14
|
+
const clientId = await registerClient(metadata.registrationEndpoint, callback.redirectUri, fetchFn);
|
|
15
|
+
const authorizationURL = new URL(metadata.authorizationEndpoint);
|
|
16
|
+
for (const [key, value] of Object.entries({
|
|
17
|
+
response_type: "code",
|
|
18
|
+
client_id: clientId,
|
|
19
|
+
redirect_uri: callback.redirectUri,
|
|
20
|
+
scope: OAUTH_SCOPES.join(" "),
|
|
21
|
+
state,
|
|
22
|
+
code_challenge: challenge,
|
|
23
|
+
code_challenge_method: "S256",
|
|
24
|
+
resource: config.resource,
|
|
25
|
+
}))
|
|
26
|
+
authorizationURL.searchParams.set(key, value);
|
|
27
|
+
options.notify?.(`Open this URL to connect Intern:\n${authorizationURL.toString()}`);
|
|
28
|
+
if (options.openBrowser !== false)
|
|
29
|
+
openBrowser(authorizationURL.toString());
|
|
30
|
+
const code = await callback.wait();
|
|
31
|
+
const response = await fetchFn(metadata.tokenEndpoint, {
|
|
32
|
+
method: "POST",
|
|
33
|
+
redirect: "error",
|
|
34
|
+
signal: AbortSignal.timeout(30_000),
|
|
35
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
36
|
+
body: new URLSearchParams({
|
|
37
|
+
grant_type: "authorization_code",
|
|
38
|
+
code,
|
|
39
|
+
client_id: clientId,
|
|
40
|
+
redirect_uri: callback.redirectUri,
|
|
41
|
+
code_verifier: verifier,
|
|
42
|
+
}),
|
|
43
|
+
});
|
|
44
|
+
const body = await response.json().catch(() => null);
|
|
45
|
+
if (!response.ok) {
|
|
46
|
+
throw new InternError("oauth_exchange_failed", `Intern authorization failed (${remoteError(body, response.status)})`);
|
|
47
|
+
}
|
|
48
|
+
const record = {
|
|
49
|
+
version: 1,
|
|
50
|
+
issuer: metadata.issuer,
|
|
51
|
+
resource: config.resource,
|
|
52
|
+
clientId,
|
|
53
|
+
tokenEndpoint: metadata.tokenEndpoint,
|
|
54
|
+
tokens: parseTokens(body),
|
|
55
|
+
};
|
|
56
|
+
await credentials.save(record);
|
|
57
|
+
return record;
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
await callback.close();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async function discover(config, fetchFn) {
|
|
64
|
+
const protectedResource = await fetchJSON(new URL("/.well-known/oauth-protected-resource/mcp", config.origin).toString(), fetchFn);
|
|
65
|
+
if (!isRecord(protectedResource) ||
|
|
66
|
+
protectedResource.resource !== config.resource ||
|
|
67
|
+
!Array.isArray(protectedResource.authorization_servers) ||
|
|
68
|
+
protectedResource.authorization_servers.length !== 1 ||
|
|
69
|
+
typeof protectedResource.authorization_servers[0] !== "string") {
|
|
70
|
+
throw new InternError("oauth_discovery_failed", "Intern returned invalid protected-resource metadata");
|
|
71
|
+
}
|
|
72
|
+
const issuer = sameOriginURL(protectedResource.authorization_servers[0], config.origin);
|
|
73
|
+
const authorization = await fetchJSON(new URL("/.well-known/oauth-authorization-server", issuer).toString(), fetchFn);
|
|
74
|
+
if (!isRecord(authorization) || authorization.issuer !== issuer) {
|
|
75
|
+
throw new InternError("oauth_discovery_failed", "Intern returned invalid authorization-server metadata");
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
issuer,
|
|
79
|
+
authorizationEndpoint: metadataEndpoint(authorization.authorization_endpoint, issuer),
|
|
80
|
+
tokenEndpoint: metadataEndpoint(authorization.token_endpoint, issuer),
|
|
81
|
+
registrationEndpoint: metadataEndpoint(authorization.registration_endpoint, issuer),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
async function registerClient(endpoint, redirectUri, fetchFn) {
|
|
85
|
+
const response = await fetchFn(endpoint, {
|
|
86
|
+
method: "POST",
|
|
87
|
+
redirect: "error",
|
|
88
|
+
signal: AbortSignal.timeout(30_000),
|
|
89
|
+
headers: { "content-type": "application/json" },
|
|
90
|
+
body: JSON.stringify({
|
|
91
|
+
client_name: "Intern CLI",
|
|
92
|
+
redirect_uris: [redirectUri],
|
|
93
|
+
grant_types: ["authorization_code", "refresh_token"],
|
|
94
|
+
response_types: ["code"],
|
|
95
|
+
token_endpoint_auth_method: "none",
|
|
96
|
+
}),
|
|
97
|
+
});
|
|
98
|
+
const body = await response.json().catch(() => null);
|
|
99
|
+
if (!response.ok || !isRecord(body) || typeof body.client_id !== "string") {
|
|
100
|
+
throw new InternError("oauth_registration_failed", `Intern could not register this CLI (${remoteError(body, response.status)})`);
|
|
101
|
+
}
|
|
102
|
+
return body.client_id;
|
|
103
|
+
}
|
|
104
|
+
async function listenForCallback(expectedState, expectedIssuer, timeoutMs) {
|
|
105
|
+
let settle;
|
|
106
|
+
let reject;
|
|
107
|
+
const result = new Promise((resolve, rejectResult) => {
|
|
108
|
+
settle = resolve;
|
|
109
|
+
reject = rejectResult;
|
|
110
|
+
});
|
|
111
|
+
let completed = false;
|
|
112
|
+
const server = http.createServer((request, response) => {
|
|
113
|
+
const url = new URL(request.url ?? "/", "http://127.0.0.1");
|
|
114
|
+
if (request.method !== "GET" || url.pathname !== "/callback") {
|
|
115
|
+
response.writeHead(404).end("Not found");
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const error = url.searchParams.get("error");
|
|
119
|
+
const state = url.searchParams.get("state");
|
|
120
|
+
const issuer = url.searchParams.get("iss");
|
|
121
|
+
const code = url.searchParams.get("code");
|
|
122
|
+
if (state !== expectedState || issuer !== expectedIssuer) {
|
|
123
|
+
response.writeHead(400, { "content-type": "text/plain; charset=utf-8" });
|
|
124
|
+
response.end("This authorization response did not match the Intern CLI request.");
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
completed = true;
|
|
128
|
+
if (error || !code) {
|
|
129
|
+
response.writeHead(400, { "content-type": "text/plain; charset=utf-8" });
|
|
130
|
+
response.end("Intern authorization was not approved. You can close this window.");
|
|
131
|
+
reject?.(new InternError("oauth_denied", `Intern authorization was denied (${error ?? "missing_code"})`));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
response.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
135
|
+
response.end("<!doctype html><title>Intern connected</title><p>Intern CLI is connected. You can close this window.</p>");
|
|
136
|
+
settle?.(code);
|
|
137
|
+
});
|
|
138
|
+
await new Promise((resolve, rejectListen) => {
|
|
139
|
+
server.once("error", rejectListen);
|
|
140
|
+
server.listen(0, "127.0.0.1", () => resolve());
|
|
141
|
+
});
|
|
142
|
+
const timer = setTimeout(() => {
|
|
143
|
+
if (!completed)
|
|
144
|
+
reject?.(new InternError("oauth_timeout", "Timed out waiting for Intern authorization"));
|
|
145
|
+
}, timeoutMs);
|
|
146
|
+
const address = server.address();
|
|
147
|
+
return {
|
|
148
|
+
redirectUri: `http://127.0.0.1:${address.port}/callback`,
|
|
149
|
+
wait: () => result,
|
|
150
|
+
close: async () => {
|
|
151
|
+
clearTimeout(timer);
|
|
152
|
+
if (!server.listening)
|
|
153
|
+
return;
|
|
154
|
+
await new Promise((resolve) => server.close(() => resolve()));
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
function openBrowser(url) {
|
|
159
|
+
const command = process.platform === "darwin"
|
|
160
|
+
? { file: "open", args: [url] }
|
|
161
|
+
: process.platform === "win32"
|
|
162
|
+
? { file: "cmd", args: ["/c", "start", "", url] }
|
|
163
|
+
: { file: "xdg-open", args: [url] };
|
|
164
|
+
try {
|
|
165
|
+
const child = spawn(command.file, command.args, {
|
|
166
|
+
detached: true,
|
|
167
|
+
stdio: "ignore",
|
|
168
|
+
});
|
|
169
|
+
child.on("error", () => { });
|
|
170
|
+
child.unref();
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
// The URL has already been printed for terminals without a browser opener.
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
async function fetchJSON(url, fetchFn) {
|
|
177
|
+
const response = await fetchFn(url, {
|
|
178
|
+
redirect: "error",
|
|
179
|
+
signal: AbortSignal.timeout(30_000),
|
|
180
|
+
headers: { accept: "application/json" },
|
|
181
|
+
});
|
|
182
|
+
const body = await response.json().catch(() => null);
|
|
183
|
+
if (!response.ok) {
|
|
184
|
+
throw new InternError("oauth_discovery_failed", `Intern OAuth discovery failed (${remoteError(body, response.status)})`);
|
|
185
|
+
}
|
|
186
|
+
return body;
|
|
187
|
+
}
|
|
188
|
+
function metadataEndpoint(value, issuer) {
|
|
189
|
+
if (typeof value !== "string") {
|
|
190
|
+
throw new InternError("oauth_discovery_failed", "Intern OAuth metadata omitted an endpoint");
|
|
191
|
+
}
|
|
192
|
+
return sameOriginURL(value, issuer);
|
|
193
|
+
}
|
|
194
|
+
function sameOriginURL(value, expectedOrigin) {
|
|
195
|
+
const url = new URL(value);
|
|
196
|
+
if (url.origin !== expectedOrigin || url.username || url.password) {
|
|
197
|
+
throw new InternError("oauth_discovery_failed", "Intern OAuth metadata pointed outside its issuer");
|
|
198
|
+
}
|
|
199
|
+
return url.toString().replace(/\/$/, "");
|
|
200
|
+
}
|
|
201
|
+
function remoteError(value, status) {
|
|
202
|
+
return isRecord(value) && typeof value.error === "string"
|
|
203
|
+
? value.error
|
|
204
|
+
: `HTTP ${status}`;
|
|
205
|
+
}
|
|
206
|
+
function isRecord(value) {
|
|
207
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
208
|
+
}
|
|
209
|
+
//# sourceMappingURL=oauth.js.map
|