@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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ArchAstro Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # Intern CLI
2
+
3
+ `intern` gives coding agents and people direct access to TryIntern's hosted
4
+ tools, without requiring MCP support in the coding agent. It talks directly to
5
+ `https://tryintern.dev/mcp`.
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ npm install --global --@archastro:registry=https://registry.npmjs.org @archastro/intern
11
+ intern login
12
+ ```
13
+
14
+ Login opens TryIntern in your browser and stores the resulting OAuth refresh
15
+ session in `~/.config/intern/credentials.json` with mode `0600`.
16
+
17
+ ## Publish a site
18
+
19
+ Point `publish` at a deploy-ready static directory. New slugs are created and
20
+ published in one call. Existing slugs are updated against their exact current
21
+ revision, uploading changed files and deleting files absent from the directory.
22
+
23
+ ```sh
24
+ intern publish ./dist --site launch-room
25
+ ```
26
+
27
+ Sites stay private unless visibility is changed explicitly:
28
+
29
+ ```sh
30
+ intern visibility launch-room public
31
+ intern invite launch-room teammate@example.com
32
+ ```
33
+
34
+ `.git`, `.intern`, `node_modules`, local environment files, private keys, and
35
+ symlinks are not published. Source limits are checked before any remote write.
36
+
37
+ ## Use from a coding agent
38
+
39
+ All output is JSON. The live remote MCP schemas are authoritative:
40
+
41
+ ```sh
42
+ intern tools
43
+ intern call intern_auth_status '{}'
44
+ intern call intern_create_site --input-file request.json
45
+ printf '%s' '{"site":"launch-room"}' | intern call intern_get_site_source
46
+ ```
47
+
48
+ `intern call` exposes every hosted tool, including tools added after this CLI
49
+ was installed. `intern resources` and `intern read` expose the MCP resource
50
+ surface as well.
51
+
52
+ Common shortcuts:
53
+
54
+ ```text
55
+ intern status
56
+ intern sites
57
+ intern source SITE
58
+ intern plugins [SITE]
59
+ intern guide
60
+ intern fetch URL
61
+ intern guests SITE
62
+ intern revoke SITE GRANT_ID
63
+ intern delete SITE --yes
64
+ ```
65
+
66
+ Use `INTERN_ACCESS_TOKEN` for an ephemeral bearer override. Development and
67
+ self-hosted tests can set `INTERN_BASE_URL` or pass `--origin`.
package/dist/args.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ export interface ParsedArguments {
2
+ command: string;
3
+ positionals: string[];
4
+ options: Map<string, string[]>;
5
+ }
6
+ export declare function parseArguments(argv: string[]): ParsedArguments;
7
+ export declare function option(args: ParsedArguments, name: string): string | undefined;
8
+ export declare function options(args: ParsedArguments, name: string): string[];
9
+ export declare function flag(args: ParsedArguments, name: string): boolean;
10
+ export declare function parseToolInput(args: ParsedArguments): Promise<Record<string, unknown>>;
package/dist/args.js ADDED
@@ -0,0 +1,101 @@
1
+ import fs from "node:fs/promises";
2
+ import { InternError } from "./errors.js";
3
+ const valueOptions = new Set([
4
+ "origin",
5
+ "input",
6
+ "input-file",
7
+ "site",
8
+ "message",
9
+ "plugin",
10
+ ]);
11
+ const booleanOptions = new Set([
12
+ "compact",
13
+ "no-open",
14
+ "yes",
15
+ "help",
16
+ "version",
17
+ ]);
18
+ export function parseArguments(argv) {
19
+ const positionals = [];
20
+ const options = new Map();
21
+ let optionsEnded = false;
22
+ for (let index = 0; index < argv.length; index += 1) {
23
+ const argument = argv[index];
24
+ if (!optionsEnded && argument === "--") {
25
+ optionsEnded = true;
26
+ continue;
27
+ }
28
+ if (!optionsEnded && argument.startsWith("--")) {
29
+ const equal = argument.indexOf("=");
30
+ const name = argument.slice(2, equal === -1 ? undefined : equal);
31
+ if (!valueOptions.has(name) && !booleanOptions.has(name)) {
32
+ throw new InternError("usage", `Unknown option --${name}`);
33
+ }
34
+ if (booleanOptions.has(name)) {
35
+ if (equal !== -1)
36
+ throw new InternError("usage", `--${name} does not take a value`);
37
+ append(options, name, "true");
38
+ continue;
39
+ }
40
+ const value = equal === -1 ? argv[++index] : argument.slice(equal + 1);
41
+ if (value === undefined)
42
+ throw new InternError("usage", `--${name} requires a value`);
43
+ append(options, name, value);
44
+ continue;
45
+ }
46
+ positionals.push(argument);
47
+ }
48
+ return {
49
+ command: positionals.shift() ?? "help",
50
+ positionals,
51
+ options,
52
+ };
53
+ }
54
+ export function option(args, name) {
55
+ return args.options.get(name)?.at(-1);
56
+ }
57
+ export function options(args, name) {
58
+ return args.options.get(name) ?? [];
59
+ }
60
+ export function flag(args, name) {
61
+ return args.options.has(name);
62
+ }
63
+ export async function parseToolInput(args) {
64
+ const inline = option(args, "input") ?? args.positionals[1];
65
+ const inputFile = option(args, "input-file");
66
+ if (inline !== undefined && inputFile !== undefined) {
67
+ throw new InternError("usage", "Use only one of inline JSON and --input-file");
68
+ }
69
+ let raw;
70
+ if (inline !== undefined)
71
+ raw = inline;
72
+ else if (inputFile !== undefined)
73
+ raw =
74
+ inputFile === "-"
75
+ ? await readStdin()
76
+ : await fs.readFile(inputFile, "utf8");
77
+ else if (!process.stdin.isTTY)
78
+ raw = await readStdin();
79
+ if (raw === undefined || !raw.trim())
80
+ return {};
81
+ try {
82
+ const value = JSON.parse(raw);
83
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
84
+ throw new Error("not an object");
85
+ }
86
+ return value;
87
+ }
88
+ catch {
89
+ throw new InternError("invalid_json", "Tool input must be a JSON object");
90
+ }
91
+ }
92
+ function append(map, name, value) {
93
+ map.set(name, [...(map.get(name) ?? []), value]);
94
+ }
95
+ async function readStdin() {
96
+ const chunks = [];
97
+ for await (const chunk of process.stdin)
98
+ chunks.push(Buffer.from(chunk));
99
+ return Buffer.concat(chunks).toString("utf8");
100
+ }
101
+ //# sourceMappingURL=args.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"args.js","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ export declare const DEFAULT_INTERN_ORIGIN = "https://tryintern.dev";
2
+ export declare const OAUTH_SCOPES: readonly ["profile", "offline_access", "sites:read", "sites:write", "entitlements:read"];
3
+ export interface InternConfig {
4
+ origin: string;
5
+ resource: string;
6
+ configRoot: string;
7
+ accessToken?: string;
8
+ }
9
+ export declare function loadConfig(overrides?: {
10
+ origin?: string;
11
+ }): InternConfig;
package/dist/config.js ADDED
@@ -0,0 +1,42 @@
1
+ import os from "node:os";
2
+ import path from "node:path";
3
+ export const DEFAULT_INTERN_ORIGIN = "https://tryintern.dev";
4
+ export const OAUTH_SCOPES = [
5
+ "profile",
6
+ "offline_access",
7
+ "sites:read",
8
+ "sites:write",
9
+ "entitlements:read",
10
+ ];
11
+ export function loadConfig(overrides = {}) {
12
+ const rawOrigin = overrides.origin ?? process.env.INTERN_BASE_URL ?? DEFAULT_INTERN_ORIGIN;
13
+ const origin = normalizeOrigin(rawOrigin);
14
+ return {
15
+ origin,
16
+ resource: new URL("/mcp", origin).toString(),
17
+ configRoot: process.env.INTERN_CONFIG_DIR ??
18
+ path.join(process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), ".config"), "intern"),
19
+ accessToken: process.env.INTERN_ACCESS_TOKEN?.trim() || undefined,
20
+ };
21
+ }
22
+ function normalizeOrigin(value) {
23
+ const url = new URL(value);
24
+ if (url.username ||
25
+ url.password ||
26
+ url.pathname !== "/" ||
27
+ url.search ||
28
+ url.hash) {
29
+ throw new Error("Intern origin must contain only a scheme, host, and optional port");
30
+ }
31
+ if (url.protocol !== "https:" && !isLoopback(url)) {
32
+ throw new Error("Intern origin must use HTTPS unless it is a loopback test server");
33
+ }
34
+ return url.origin;
35
+ }
36
+ function isLoopback(url) {
37
+ return (url.protocol === "http:" &&
38
+ (url.hostname === "127.0.0.1" ||
39
+ url.hostname === "[::1]" ||
40
+ url.hostname === "localhost"));
41
+ }
42
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,MAAM,CAAC,MAAM,qBAAqB,GAAG,uBAAuB,CAAC;AAC7D,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,SAAS;IACT,gBAAgB;IAChB,YAAY;IACZ,aAAa;IACb,mBAAmB;CACX,CAAC;AASX,MAAM,UAAU,UAAU,CAAC,YAAiC,EAAE;IAC5D,MAAM,SAAS,GACb,SAAS,CAAC,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,qBAAqB,CAAC;IAC3E,MAAM,MAAM,GAAG,eAAe,CAAC,SAAS,CAAC,CAAC;IAC1C,OAAO;QACL,MAAM;QACN,QAAQ,EAAE,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,QAAQ,EAAE;QAC5C,UAAU,EACR,OAAO,CAAC,GAAG,CAAC,iBAAiB;YAC7B,IAAI,CAAC,IAAI,CACP,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,EACjE,QAAQ,CACT;QACH,WAAW,EAAE,OAAO,CAAC,GAAG,CAAC,mBAAmB,EAAE,IAAI,EAAE,IAAI,SAAS;KAClE,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CAAC,KAAa;IACpC,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;IAC3B,IACE,GAAG,CAAC,QAAQ;QACZ,GAAG,CAAC,QAAQ;QACZ,GAAG,CAAC,QAAQ,KAAK,GAAG;QACpB,GAAG,CAAC,MAAM;QACV,GAAG,CAAC,IAAI,EACR,CAAC;QACD,MAAM,IAAI,KAAK,CACb,mEAAmE,CACpE,CAAC;IACJ,CAAC;IACD,IAAI,GAAG,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAClD,MAAM,IAAI,KAAK,CACb,kEAAkE,CACnE,CAAC;IACJ,CAAC;IACD,OAAO,GAAG,CAAC,MAAM,CAAC;AACpB,CAAC;AAED,SAAS,UAAU,CAAC,GAAQ;IAC1B,OAAO,CACL,GAAG,CAAC,QAAQ,KAAK,OAAO;QACxB,CAAC,GAAG,CAAC,QAAQ,KAAK,WAAW;YAC3B,GAAG,CAAC,QAAQ,KAAK,OAAO;YACxB,GAAG,CAAC,QAAQ,KAAK,WAAW,CAAC,CAChC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,33 @@
1
+ import type { InternConfig } from "./config.js";
2
+ export interface OAuthTokens {
3
+ accessToken: string;
4
+ refreshToken: string;
5
+ expiresAtMs: number;
6
+ scope: string;
7
+ }
8
+ export interface CredentialRecord {
9
+ version: 1;
10
+ issuer: string;
11
+ resource: string;
12
+ clientId: string;
13
+ tokenEndpoint: string;
14
+ tokens: OAuthTokens;
15
+ }
16
+ export declare class CredentialStore {
17
+ private readonly config;
18
+ constructor(config: InternConfig);
19
+ read(): Promise<CredentialRecord | null>;
20
+ write(record: CredentialRecord): Promise<void>;
21
+ remove(): Promise<boolean>;
22
+ private file;
23
+ }
24
+ export declare class Credentials {
25
+ private readonly config;
26
+ private readonly fetchFn;
27
+ private readonly store;
28
+ constructor(config: InternConfig, fetchFn?: typeof fetch);
29
+ accessToken(forceRefresh?: boolean): Promise<string>;
30
+ save(record: CredentialRecord): Promise<void>;
31
+ logout(): Promise<boolean>;
32
+ }
33
+ export declare function parseTokens(value: unknown): OAuthTokens;
@@ -0,0 +1,179 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { InternError } from "./errors.js";
5
+ const refreshWindowMs = 60_000;
6
+ export class CredentialStore {
7
+ config;
8
+ constructor(config) {
9
+ this.config = config;
10
+ }
11
+ async read() {
12
+ let parsed;
13
+ try {
14
+ parsed = JSON.parse(await fs.readFile(this.file(), "utf8"));
15
+ }
16
+ catch (error) {
17
+ if (hasCode(error, "ENOENT"))
18
+ return null;
19
+ throw new InternError("credentials_invalid", "Stored Intern credentials are unreadable; run `intern login` again");
20
+ }
21
+ const record = parseRecord(parsed);
22
+ if (!record ||
23
+ record.issuer !== this.config.origin ||
24
+ record.resource !== this.config.resource ||
25
+ !isIssuerEndpoint(record.tokenEndpoint, record.issuer)) {
26
+ throw new InternError("credentials_invalid", "Stored Intern credentials do not belong to this Intern origin; run `intern login` again");
27
+ }
28
+ return record;
29
+ }
30
+ async write(record) {
31
+ if (!parseRecord(record) ||
32
+ record.issuer !== this.config.origin ||
33
+ record.resource !== this.config.resource ||
34
+ !isIssuerEndpoint(record.tokenEndpoint, record.issuer)) {
35
+ throw new Error("Refusing to store invalid Intern credentials");
36
+ }
37
+ await fs.mkdir(this.config.configRoot, { recursive: true, mode: 0o700 });
38
+ await fs.chmod(this.config.configRoot, 0o700);
39
+ const temporary = path.join(this.config.configRoot, `.credentials.${process.pid}.${randomUUID()}`);
40
+ try {
41
+ await fs.writeFile(temporary, `${JSON.stringify(record, null, 2)}\n`, {
42
+ flag: "wx",
43
+ mode: 0o600,
44
+ });
45
+ await fs.chmod(temporary, 0o600);
46
+ await fs.rename(temporary, this.file());
47
+ }
48
+ catch (error) {
49
+ await fs.rm(temporary, { force: true }).catch(() => { });
50
+ throw error;
51
+ }
52
+ }
53
+ async remove() {
54
+ try {
55
+ await fs.rm(this.file());
56
+ return true;
57
+ }
58
+ catch (error) {
59
+ if (hasCode(error, "ENOENT"))
60
+ return false;
61
+ throw error;
62
+ }
63
+ }
64
+ file() {
65
+ return path.join(this.config.configRoot, "credentials.json");
66
+ }
67
+ }
68
+ export class Credentials {
69
+ config;
70
+ fetchFn;
71
+ store;
72
+ constructor(config, fetchFn = fetch) {
73
+ this.config = config;
74
+ this.fetchFn = fetchFn;
75
+ this.store = new CredentialStore(config);
76
+ }
77
+ async accessToken(forceRefresh = false) {
78
+ if (this.config.accessToken)
79
+ return this.config.accessToken;
80
+ const record = await this.store.read();
81
+ if (!record) {
82
+ throw new InternError("auth_required", "Intern is not connected; run `intern login`");
83
+ }
84
+ if (!forceRefresh &&
85
+ record.tokens.expiresAtMs > Date.now() + refreshWindowMs) {
86
+ return record.tokens.accessToken;
87
+ }
88
+ const response = await this.fetchFn(record.tokenEndpoint, {
89
+ method: "POST",
90
+ redirect: "error",
91
+ signal: AbortSignal.timeout(30_000),
92
+ headers: { "content-type": "application/x-www-form-urlencoded" },
93
+ body: new URLSearchParams({
94
+ grant_type: "refresh_token",
95
+ client_id: record.clientId,
96
+ refresh_token: record.tokens.refreshToken,
97
+ }),
98
+ });
99
+ const body = await responseJSON(response);
100
+ if (!response.ok) {
101
+ throw new InternError("auth_required", `Intern session refresh failed; run \`intern login\` again (${oauthError(body, response.status)})`);
102
+ }
103
+ const tokens = parseTokens(body);
104
+ const updated = { ...record, tokens };
105
+ await this.store.write(updated);
106
+ return tokens.accessToken;
107
+ }
108
+ save(record) {
109
+ return this.store.write(record);
110
+ }
111
+ logout() {
112
+ return this.store.remove();
113
+ }
114
+ }
115
+ export function parseTokens(value) {
116
+ if (!isRecord(value))
117
+ throw new InternError("oauth_invalid_response", "OAuth server returned invalid JSON");
118
+ const accessToken = value.access_token;
119
+ const refreshToken = value.refresh_token;
120
+ const expiresIn = value.expires_in;
121
+ const scope = value.scope;
122
+ if (typeof accessToken !== "string" ||
123
+ !accessToken ||
124
+ typeof refreshToken !== "string" ||
125
+ !refreshToken ||
126
+ typeof expiresIn !== "number" ||
127
+ !Number.isFinite(expiresIn) ||
128
+ expiresIn <= 0 ||
129
+ typeof scope !== "string") {
130
+ throw new InternError("oauth_invalid_response", "OAuth server returned incomplete credentials");
131
+ }
132
+ return {
133
+ accessToken,
134
+ refreshToken,
135
+ expiresAtMs: Date.now() + expiresIn * 1_000,
136
+ scope,
137
+ };
138
+ }
139
+ function parseRecord(value) {
140
+ if (!isRecord(value) || value.version !== 1 || !isRecord(value.tokens))
141
+ return null;
142
+ if (typeof value.issuer !== "string" ||
143
+ typeof value.resource !== "string" ||
144
+ typeof value.clientId !== "string" ||
145
+ typeof value.tokenEndpoint !== "string" ||
146
+ typeof value.tokens.accessToken !== "string" ||
147
+ typeof value.tokens.refreshToken !== "string" ||
148
+ typeof value.tokens.expiresAtMs !== "number" ||
149
+ typeof value.tokens.scope !== "string")
150
+ return null;
151
+ return value;
152
+ }
153
+ function oauthError(value, status) {
154
+ return isRecord(value) && typeof value.error === "string"
155
+ ? value.error
156
+ : `HTTP ${status}`;
157
+ }
158
+ async function responseJSON(response) {
159
+ return response.json().catch(() => null);
160
+ }
161
+ function isRecord(value) {
162
+ return typeof value === "object" && value !== null && !Array.isArray(value);
163
+ }
164
+ function isIssuerEndpoint(value, issuer) {
165
+ try {
166
+ const url = new URL(value);
167
+ return url.origin === issuer && !url.username && !url.password;
168
+ }
169
+ catch {
170
+ return false;
171
+ }
172
+ }
173
+ function hasCode(error, code) {
174
+ return (typeof error === "object" &&
175
+ error !== null &&
176
+ "code" in error &&
177
+ error.code === code);
178
+ }
179
+ //# sourceMappingURL=credentials.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credentials.js","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export declare class InternError extends Error {
2
+ readonly code: string;
3
+ readonly details?: unknown | undefined;
4
+ constructor(code: string, message: string, details?: unknown | undefined);
5
+ }
6
+ export declare function errorMessage(error: unknown): string;
package/dist/errors.js ADDED
@@ -0,0 +1,14 @@
1
+ export class InternError extends Error {
2
+ code;
3
+ details;
4
+ constructor(code, message, details) {
5
+ super(message);
6
+ this.code = code;
7
+ this.details = details;
8
+ this.name = "InternError";
9
+ }
10
+ }
11
+ export function errorMessage(error) {
12
+ return error instanceof Error ? error.message : String(error);
13
+ }
14
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,WAAY,SAAQ,KAAK;IAEzB;IAEA;IAHX,YACW,IAAY,EACrB,OAAe,EACN,OAAiB;QAE1B,KAAK,CAAC,OAAO,CAAC,CAAC;QAJN,SAAI,GAAJ,IAAI,CAAQ;QAEZ,YAAO,GAAP,OAAO,CAAU;QAG1B,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;IAC5B,CAAC;CACF;AAED,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAChE,CAAC"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export declare function run(argv: string[]): Promise<number>;