@alpha-clients/terminal 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -0
- package/dist/commandRunner.d.ts +23 -0
- package/dist/commandRunner.d.ts.map +1 -0
- package/dist/commandRunner.js +163 -0
- package/dist/commandRunner.js.map +1 -0
- package/dist/config.d.ts +22 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +178 -0
- package/dist/config.js.map +1 -0
- package/dist/constants.d.ts +10 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +9 -0
- package/dist/constants.js.map +1 -0
- package/dist/deviceInfo.d.ts +3 -0
- package/dist/deviceInfo.d.ts.map +1 -0
- package/dist/deviceInfo.js +14 -0
- package/dist/deviceInfo.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +128 -0
- package/dist/index.js.map +1 -0
- package/dist/macosSandbox.d.ts +15 -0
- package/dist/macosSandbox.d.ts.map +1 -0
- package/dist/macosSandbox.js +89 -0
- package/dist/macosSandbox.js.map +1 -0
- package/dist/tokenStore.d.ts +3 -0
- package/dist/tokenStore.d.ts.map +1 -0
- package/dist/tokenStore.js +28 -0
- package/dist/tokenStore.js.map +1 -0
- package/dist/tools.d.ts +5 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +116 -0
- package/dist/tools.js.map +1 -0
- package/package.json +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# @alpha-clients/terminal
|
|
2
|
+
|
|
3
|
+
Terminal client for Alpha client-side MCP tools.
|
|
4
|
+
|
|
5
|
+
Run it directly with `npx`:
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npx @alpha-clients/terminal
|
|
9
|
+
npx @alpha-clients/terminal -- --env test
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
When installed globally or linked locally, the executable name is `terminal`.
|
|
13
|
+
`device-mcp-terminal` is also installed as a compatibility alias.
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
terminal --env test
|
|
17
|
+
terminal --base-url http://localhost:3000
|
|
18
|
+
terminal --scope ~/Downloads/projects --allowed git --allowed node
|
|
19
|
+
terminal --include ~/Downloads/projects --allow bash
|
|
20
|
+
terminal --clear-tokens
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The client defaults to the live portal and unrestricted command access.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { TerminalConfig } from "./config.js";
|
|
2
|
+
export interface RunCommandInput {
|
|
3
|
+
readonly command: string;
|
|
4
|
+
readonly args?: readonly string[];
|
|
5
|
+
readonly cwd?: string;
|
|
6
|
+
readonly timeoutMs?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface RunCommandResult {
|
|
9
|
+
readonly command: string;
|
|
10
|
+
readonly args: readonly string[];
|
|
11
|
+
readonly cwd: string;
|
|
12
|
+
readonly exitCode: number | null;
|
|
13
|
+
readonly signal: string | null;
|
|
14
|
+
readonly stdout: string;
|
|
15
|
+
readonly stderr: string;
|
|
16
|
+
readonly durationMs: number;
|
|
17
|
+
readonly timedOut: boolean;
|
|
18
|
+
}
|
|
19
|
+
export declare function resolveCommandCwd(config: Pick<TerminalConfig, "scopes">, requestedCwd?: string): string;
|
|
20
|
+
export declare function assertCommandAllowed(command: string, allowedCommands: readonly string[]): void;
|
|
21
|
+
export declare function executeCommand(config: TerminalConfig, input: RunCommandInput): Promise<RunCommandResult>;
|
|
22
|
+
export declare function isWithinScope(candidatePath: string, scopeRoot: string): boolean;
|
|
23
|
+
//# sourceMappingURL=commandRunner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commandRunner.d.ts","sourceRoot":"","sources":["../src/commandRunner.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAKD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,QAAQ,CAAC,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAavG;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAS9F;AAED,wBAAsB,cAAc,CAAC,MAAM,EAAE,cAAc,EAAE,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA4H9G;AAED,wBAAgB,aAAa,CAAC,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAK/E"}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { realpathSync } from "node:fs";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { DEFAULT_TIMEOUT_MS, MAX_TIMEOUT_MS } from "./constants.js";
|
|
5
|
+
import { createSandboxedExecution } from "./macosSandbox.js";
|
|
6
|
+
const WINDOWS_EXECUTABLE_EXTENSIONS = new Set([".exe", ".cmd", ".bat", ".com"]);
|
|
7
|
+
const STREAM_DRAIN_GRACE_MS = 100;
|
|
8
|
+
export function resolveCommandCwd(config, requestedCwd) {
|
|
9
|
+
const baseCwd = requestedCwd ?? config.scopes[0] ?? process.cwd();
|
|
10
|
+
const resolvedPath = path.resolve(baseCwd);
|
|
11
|
+
const canonicalPath = canonicalizePathForComparison(resolvedPath);
|
|
12
|
+
if (config.scopes.length > 0) {
|
|
13
|
+
const isAllowed = config.scopes.some((scopeRoot) => isWithinScope(canonicalPath, scopeRoot));
|
|
14
|
+
if (!isAllowed) {
|
|
15
|
+
throw new Error(`Working directory is outside allowed scopes: ${resolvedPath}`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return resolvedPath;
|
|
19
|
+
}
|
|
20
|
+
export function assertCommandAllowed(command, allowedCommands) {
|
|
21
|
+
const requestedCommand = normalizeCommandName(path.basename(command));
|
|
22
|
+
if (allowedCommands.length > 0 &&
|
|
23
|
+
!allowedCommands.some((candidate) => normalizeCommandName(path.basename(candidate)) === requestedCommand)) {
|
|
24
|
+
throw new Error(`Command is not allowed: ${command}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export async function executeCommand(config, input) {
|
|
28
|
+
if (!input.command.trim()) {
|
|
29
|
+
throw new Error("command must be a non-empty string.");
|
|
30
|
+
}
|
|
31
|
+
assertCommandAllowed(input.command, config.allowedCommands);
|
|
32
|
+
const cwd = resolveCommandCwd(config, input.cwd);
|
|
33
|
+
const args = [...(input.args ?? [])];
|
|
34
|
+
const timeoutMs = input.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
35
|
+
if (timeoutMs > MAX_TIMEOUT_MS) {
|
|
36
|
+
throw new Error(`timeoutMs must be at most ${MAX_TIMEOUT_MS}.`);
|
|
37
|
+
}
|
|
38
|
+
if (timeoutMs <= 0) {
|
|
39
|
+
throw new Error("timeoutMs must be greater than 0.");
|
|
40
|
+
}
|
|
41
|
+
const sandboxedExecution = createSandboxedExecution(config.scopes, cwd, input.command, args);
|
|
42
|
+
const startedAt = Date.now();
|
|
43
|
+
let timedOut = false;
|
|
44
|
+
let settled = false;
|
|
45
|
+
let stdout = "";
|
|
46
|
+
let stderr = "";
|
|
47
|
+
let exitCode = null;
|
|
48
|
+
let signal = null;
|
|
49
|
+
let exited = false;
|
|
50
|
+
let stdoutEnded = false;
|
|
51
|
+
let stderrEnded = false;
|
|
52
|
+
let forceKillTimer;
|
|
53
|
+
let streamDrainGraceTimer;
|
|
54
|
+
const child = spawn(sandboxedExecution?.command ?? input.command, sandboxedExecution?.args ?? args, {
|
|
55
|
+
cwd,
|
|
56
|
+
env: sandboxedExecution?.env ?? process.env,
|
|
57
|
+
shell: false,
|
|
58
|
+
windowsHide: true,
|
|
59
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
60
|
+
});
|
|
61
|
+
child.stdout.setEncoding("utf8");
|
|
62
|
+
child.stderr.setEncoding("utf8");
|
|
63
|
+
return await new Promise((resolve, reject) => {
|
|
64
|
+
const timeoutHandle = setTimeout(() => {
|
|
65
|
+
timedOut = true;
|
|
66
|
+
child.kill();
|
|
67
|
+
forceKillTimer = setTimeout(() => {
|
|
68
|
+
if (!child.killed) {
|
|
69
|
+
child.kill("SIGKILL");
|
|
70
|
+
}
|
|
71
|
+
}, 5_000);
|
|
72
|
+
}, timeoutMs);
|
|
73
|
+
child.stdout.on("data", (chunk) => {
|
|
74
|
+
stdout += chunk;
|
|
75
|
+
});
|
|
76
|
+
child.stdout.once("end", () => {
|
|
77
|
+
stdoutEnded = true;
|
|
78
|
+
settleIfReady();
|
|
79
|
+
});
|
|
80
|
+
child.stderr.on("data", (chunk) => {
|
|
81
|
+
stderr += chunk;
|
|
82
|
+
});
|
|
83
|
+
child.stderr.once("end", () => {
|
|
84
|
+
stderrEnded = true;
|
|
85
|
+
settleIfReady();
|
|
86
|
+
});
|
|
87
|
+
child.once("error", (error) => {
|
|
88
|
+
cleanup();
|
|
89
|
+
if (!settled) {
|
|
90
|
+
settled = true;
|
|
91
|
+
reject(new Error(`Failed to start command "${input.command}": ${error.message}`));
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
child.once("exit", (childExitCode, childSignal) => {
|
|
95
|
+
exited = true;
|
|
96
|
+
exitCode = childExitCode;
|
|
97
|
+
signal = childSignal;
|
|
98
|
+
streamDrainGraceTimer = setTimeout(() => {
|
|
99
|
+
child.stdout.destroy();
|
|
100
|
+
child.stderr.destroy();
|
|
101
|
+
stdoutEnded = true;
|
|
102
|
+
stderrEnded = true;
|
|
103
|
+
settleIfReady();
|
|
104
|
+
}, STREAM_DRAIN_GRACE_MS);
|
|
105
|
+
settleIfReady();
|
|
106
|
+
});
|
|
107
|
+
function cleanup() {
|
|
108
|
+
clearTimeout(timeoutHandle);
|
|
109
|
+
if (forceKillTimer) {
|
|
110
|
+
clearTimeout(forceKillTimer);
|
|
111
|
+
}
|
|
112
|
+
if (streamDrainGraceTimer) {
|
|
113
|
+
clearTimeout(streamDrainGraceTimer);
|
|
114
|
+
}
|
|
115
|
+
sandboxedExecution?.cleanup();
|
|
116
|
+
}
|
|
117
|
+
function settleIfReady() {
|
|
118
|
+
if (!exited || settled || !stdoutEnded || !stderrEnded) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
cleanup();
|
|
122
|
+
settled = true;
|
|
123
|
+
resolve({
|
|
124
|
+
command: input.command,
|
|
125
|
+
args,
|
|
126
|
+
cwd,
|
|
127
|
+
exitCode,
|
|
128
|
+
signal,
|
|
129
|
+
stdout,
|
|
130
|
+
stderr,
|
|
131
|
+
durationMs: Date.now() - startedAt,
|
|
132
|
+
timedOut
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
export function isWithinScope(candidatePath, scopeRoot) {
|
|
138
|
+
const canonicalCandidatePath = canonicalizePathForComparison(candidatePath);
|
|
139
|
+
const canonicalScopeRoot = canonicalizePathForComparison(scopeRoot);
|
|
140
|
+
const relativePath = path.relative(canonicalScopeRoot, canonicalCandidatePath);
|
|
141
|
+
return relativePath === "" || (!relativePath.startsWith("..") && !path.isAbsolute(relativePath));
|
|
142
|
+
}
|
|
143
|
+
function canonicalizePathForComparison(inputPath) {
|
|
144
|
+
const resolvedPath = path.resolve(inputPath);
|
|
145
|
+
try {
|
|
146
|
+
return realpathSync.native(resolvedPath);
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return resolvedPath;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function normalizeCommandName(command) {
|
|
153
|
+
const normalized = process.platform === "win32" ? command.toLowerCase() : command;
|
|
154
|
+
if (process.platform !== "win32") {
|
|
155
|
+
return normalized;
|
|
156
|
+
}
|
|
157
|
+
const extension = path.extname(normalized);
|
|
158
|
+
if (WINDOWS_EXECUTABLE_EXTENSIONS.has(extension)) {
|
|
159
|
+
return normalized.slice(0, -extension.length);
|
|
160
|
+
}
|
|
161
|
+
return normalized;
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=commandRunner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commandRunner.js","sourceRoot":"","sources":["../src/commandRunner.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type PortalEnv } from "./constants.js";
|
|
2
|
+
export interface TerminalConfig {
|
|
3
|
+
readonly baseUrl: string;
|
|
4
|
+
readonly env?: PortalEnv;
|
|
5
|
+
readonly name: string;
|
|
6
|
+
readonly scopes: readonly string[];
|
|
7
|
+
readonly allowedCommands: readonly string[];
|
|
8
|
+
readonly clearTokens: boolean;
|
|
9
|
+
readonly tokenPath: string;
|
|
10
|
+
}
|
|
11
|
+
interface ParseCliArgsOptions {
|
|
12
|
+
readonly homeDir?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare class HelpRequestedError extends Error {
|
|
15
|
+
readonly helpText: string;
|
|
16
|
+
constructor(helpText: string);
|
|
17
|
+
}
|
|
18
|
+
export declare function parseCliArgs(argv: readonly string[], options?: ParseCliArgsOptions): TerminalConfig;
|
|
19
|
+
export declare function formatCliHelp(): string;
|
|
20
|
+
export declare function formatStartupSummary(config: TerminalConfig): string[];
|
|
21
|
+
export {};
|
|
22
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAKA,OAAO,EAAmC,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEjF,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,GAAG,CAAC,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,QAAQ,CAAC,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5C,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,UAAU,mBAAmB;IAC3B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;gBAEd,QAAQ,EAAE,MAAM;CAK7B;AAgBD,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,GAAE,mBAAwB,GAAG,cAAc,CA2DvG;AAED,wBAAgB,aAAa,IAAI,MAAM,CAsBtC;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,EAAE,CAQrE"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { parseArgs } from "node:util";
|
|
6
|
+
import { DEFAULT_PORTAL_ENV, PORTAL_ENVS } from "./constants.js";
|
|
7
|
+
export class HelpRequestedError extends Error {
|
|
8
|
+
helpText;
|
|
9
|
+
constructor(helpText) {
|
|
10
|
+
super("CLI help requested.");
|
|
11
|
+
this.name = "HelpRequestedError";
|
|
12
|
+
this.helpText = helpText;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
const VALID_OPTION_NAMES = new Set([
|
|
16
|
+
"help",
|
|
17
|
+
"env",
|
|
18
|
+
"base-url",
|
|
19
|
+
"baseUrl",
|
|
20
|
+
"scope",
|
|
21
|
+
"include",
|
|
22
|
+
"allowed",
|
|
23
|
+
"allow",
|
|
24
|
+
"name",
|
|
25
|
+
"clear-tokens",
|
|
26
|
+
"clearTokens"
|
|
27
|
+
]);
|
|
28
|
+
export function parseCliArgs(argv, options = {}) {
|
|
29
|
+
const normalizedArgv = argv[0] === "--" ? argv.slice(1) : argv;
|
|
30
|
+
const parsed = parseArgs({
|
|
31
|
+
args: [...normalizedArgv],
|
|
32
|
+
strict: false,
|
|
33
|
+
allowPositionals: true,
|
|
34
|
+
tokens: true,
|
|
35
|
+
options: {
|
|
36
|
+
help: { type: "boolean", short: "h" },
|
|
37
|
+
env: { type: "string" },
|
|
38
|
+
"base-url": { type: "string" },
|
|
39
|
+
baseUrl: { type: "string" },
|
|
40
|
+
scope: { type: "string", multiple: true },
|
|
41
|
+
include: { type: "string", multiple: true },
|
|
42
|
+
allowed: { type: "string", multiple: true },
|
|
43
|
+
allow: { type: "string", multiple: true },
|
|
44
|
+
name: { type: "string" },
|
|
45
|
+
"clear-tokens": { type: "boolean" },
|
|
46
|
+
clearTokens: { type: "boolean" }
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
if (parsed.values.help) {
|
|
50
|
+
throw new HelpRequestedError(formatCliHelp());
|
|
51
|
+
}
|
|
52
|
+
const unknownOptions = parsed.tokens.flatMap((token) => token.kind === "option" && !VALID_OPTION_NAMES.has(token.name) ? [token.rawName] : []);
|
|
53
|
+
if (unknownOptions.length > 0) {
|
|
54
|
+
throw new Error(`Unknown option(s): ${unknownOptions.join(", ")}`);
|
|
55
|
+
}
|
|
56
|
+
const positionalEnv = parsed.positionals[0];
|
|
57
|
+
if (parsed.positionals.length > 1 || (positionalEnv && !isPortalEnv(positionalEnv))) {
|
|
58
|
+
throw new Error(`Unexpected positional argument(s): ${parsed.positionals.join(", ")}`);
|
|
59
|
+
}
|
|
60
|
+
const envValue = parseEnvValue(coerceOptionalString(parsed.values.env) ?? positionalEnv ?? DEFAULT_PORTAL_ENV);
|
|
61
|
+
const explicitBaseUrl = coerceOptionalString(parsed.values["base-url"]) ?? coerceOptionalString(parsed.values.baseUrl);
|
|
62
|
+
const baseUrl = normalizeBaseUrl(explicitBaseUrl ?? PORTAL_ENVS[envValue]);
|
|
63
|
+
const scopes = normalizePathList([...coerceStringList(parsed.values.scope), ...coerceStringList(parsed.values.include)]);
|
|
64
|
+
const allowedCommands = normalizeCommandList([
|
|
65
|
+
...coerceStringList(parsed.values.allowed),
|
|
66
|
+
...coerceStringList(parsed.values.allow)
|
|
67
|
+
]);
|
|
68
|
+
const name = coerceOptionalString(parsed.values.name) ?? `Device MCP Terminal (${os.hostname()})`;
|
|
69
|
+
const clearTokens = Boolean(parsed.values["clear-tokens"] || parsed.values.clearTokens);
|
|
70
|
+
const tokenPath = buildTokenPath(baseUrl, options.homeDir ?? os.homedir());
|
|
71
|
+
return {
|
|
72
|
+
baseUrl,
|
|
73
|
+
env: explicitBaseUrl ? undefined : envValue,
|
|
74
|
+
name,
|
|
75
|
+
scopes,
|
|
76
|
+
allowedCommands,
|
|
77
|
+
clearTokens,
|
|
78
|
+
tokenPath
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
export function formatCliHelp() {
|
|
82
|
+
return [
|
|
83
|
+
"Usage: terminal [options]",
|
|
84
|
+
" npx @alpha-clients/terminal -- [options]",
|
|
85
|
+
"",
|
|
86
|
+
"Options:",
|
|
87
|
+
" -h, --help Print this help and exit",
|
|
88
|
+
" --env <test|staging|live> Portal environment. Defaults to live",
|
|
89
|
+
" --base-url <url> Explicit portal base URL",
|
|
90
|
+
" --scope <path> Repeatable. Restrict run_command to allowed root directories",
|
|
91
|
+
" --include <path> Alias for --scope",
|
|
92
|
+
" --allowed <command> Repeatable. Restrict run_command.command to allowed executables",
|
|
93
|
+
" --allow <command> Alias for --allowed",
|
|
94
|
+
" --name <name> Client display name",
|
|
95
|
+
" --clear-tokens Clear stored credentials before connecting",
|
|
96
|
+
"",
|
|
97
|
+
"Examples:",
|
|
98
|
+
" npx @alpha-clients/terminal",
|
|
99
|
+
" npx @alpha-clients/terminal -- --env test",
|
|
100
|
+
` terminal --scope ${path.join(os.homedir(), "Downloads")} --allowed git --allowed node`,
|
|
101
|
+
" terminal --base-url http://localhost:3000 --clear-tokens"
|
|
102
|
+
].join("\n");
|
|
103
|
+
}
|
|
104
|
+
export function formatStartupSummary(config) {
|
|
105
|
+
return [
|
|
106
|
+
`Device MCP terminal client: ${config.name}`,
|
|
107
|
+
`Portal: ${config.baseUrl}`,
|
|
108
|
+
`Token store: ${config.tokenPath}`,
|
|
109
|
+
`Access scope: ${config.scopes.length > 0 ? config.scopes.join(", ") : "unrestricted"}`,
|
|
110
|
+
`Allowed commands: ${config.allowedCommands.length > 0 ? config.allowedCommands.join(", ") : "all"}`
|
|
111
|
+
];
|
|
112
|
+
}
|
|
113
|
+
function parseEnvValue(value) {
|
|
114
|
+
if (!isPortalEnv(value)) {
|
|
115
|
+
throw new Error(`Unknown environment: ${value}. Expected one of: ${Object.keys(PORTAL_ENVS).join(", ")}`);
|
|
116
|
+
}
|
|
117
|
+
return value;
|
|
118
|
+
}
|
|
119
|
+
function isPortalEnv(value) {
|
|
120
|
+
return Object.hasOwn(PORTAL_ENVS, value);
|
|
121
|
+
}
|
|
122
|
+
function coerceOptionalString(value) {
|
|
123
|
+
if (value === undefined || value === false) {
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
127
|
+
throw new Error("Expected a non-empty string value for the provided option.");
|
|
128
|
+
}
|
|
129
|
+
return value.trim();
|
|
130
|
+
}
|
|
131
|
+
function coerceStringList(value) {
|
|
132
|
+
if (value === undefined || value === false) {
|
|
133
|
+
return [];
|
|
134
|
+
}
|
|
135
|
+
const values = Array.isArray(value) ? value : [value];
|
|
136
|
+
return values.map((entry) => {
|
|
137
|
+
if (typeof entry !== "string" || entry.trim().length === 0) {
|
|
138
|
+
throw new Error("Expected a non-empty string value for the provided option.");
|
|
139
|
+
}
|
|
140
|
+
return entry.trim();
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
function normalizePathList(values) {
|
|
144
|
+
return [...new Set(values.map((entry) => resolveExistingDirectory(entry)))];
|
|
145
|
+
}
|
|
146
|
+
function normalizeCommandList(values) {
|
|
147
|
+
return [...new Set(values.map((entry) => entry.trim()).filter(Boolean))];
|
|
148
|
+
}
|
|
149
|
+
function resolveExistingDirectory(inputPath) {
|
|
150
|
+
const resolvedPath = path.resolve(inputPath);
|
|
151
|
+
if (!existsSync(resolvedPath)) {
|
|
152
|
+
throw new Error(`Path does not exist: ${inputPath}`);
|
|
153
|
+
}
|
|
154
|
+
try {
|
|
155
|
+
return realpathSync.native(resolvedPath);
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return resolvedPath;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function normalizeBaseUrl(value) {
|
|
162
|
+
const trimmed = value.trim().replace(/\/+$/, "");
|
|
163
|
+
if (/^https?:\/\//i.test(trimmed)) {
|
|
164
|
+
return trimmed;
|
|
165
|
+
}
|
|
166
|
+
if (/^ws:\/\//i.test(trimmed)) {
|
|
167
|
+
return `http://${trimmed.slice("ws://".length)}`;
|
|
168
|
+
}
|
|
169
|
+
if (/^wss:\/\//i.test(trimmed)) {
|
|
170
|
+
return `https://${trimmed.slice("wss://".length)}`;
|
|
171
|
+
}
|
|
172
|
+
return `https://${trimmed}`;
|
|
173
|
+
}
|
|
174
|
+
function buildTokenPath(baseUrl, homeDir) {
|
|
175
|
+
const digest = createHash("sha256").update(baseUrl).digest("hex").slice(0, 16);
|
|
176
|
+
return path.join(homeDir, ".device-mcp", "terminal-tokens", `${digest}.json`);
|
|
177
|
+
}
|
|
178
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAkB,MAAM,gBAAgB,CAAC;AAgBjF,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAClC,QAAQ,CAAS;IAE1B,YAAY,QAAgB;QAC1B,KAAK,CAAC,qBAAqB,CAAC,CAAC;QAC7B,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;QACjC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;CACF;AAED,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC;IACjC,MAAM;IACN,KAAK;IACL,UAAU;IACV,SAAS;IACT,OAAO;IACP,SAAS;IACT,SAAS;IACT,OAAO;IACP,MAAM;IACN,cAAc;IACd,aAAa;CACd,CAAC,CAAC;AAEH,MAAM,UAAU,YAAY,CAAC,IAAuB,EAAE,UAA+B,EAAE;IACrF,MAAM,cAAc,GAAG,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC/D,MAAM,MAAM,GAAG,SAAS,CAAC;QACvB,IAAI,EAAE,CAAC,GAAG,cAAc,CAAC;QACzB,MAAM,EAAE,KAAK;QACb,gBAAgB,EAAE,IAAI;QACtB,MAAM,EAAE,IAAI;QACZ,OAAO,EAAE;YACP,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE;YACrC,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;YACvB,UAAU,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;YAC9B,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;YAC3B,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE;YACzC,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE;YAC3C,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE;YAC3C,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE;YACzC,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;YACxB,cAAc,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;YACnC,WAAW,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;SACjC;KACF,CAAC,CAAC;IAEH,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QACvB,MAAM,IAAI,kBAAkB,CAAC,aAAa,EAAE,CAAC,CAAC;IAChD,CAAC;IAED,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CACrD,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CACtF,CAAC;IACF,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CAAC,sBAAsB,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACrE,CAAC;IAED,MAAM,aAAa,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IAC5C,IAAI,MAAM,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,aAAa,IAAI,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC;QACpF,MAAM,IAAI,KAAK,CAAC,sCAAsC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACzF,CAAC;IAED,MAAM,QAAQ,GAAG,aAAa,CAAC,oBAAoB,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,aAAa,IAAI,kBAAkB,CAAC,CAAC;IAC/G,MAAM,eAAe,GAAG,oBAAoB,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,IAAI,oBAAoB,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACvH,MAAM,OAAO,GAAG,gBAAgB,CAAC,eAAe,IAAI,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC3E,MAAM,MAAM,GAAG,iBAAiB,CAAC,CAAC,GAAG,gBAAgB,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,GAAG,gBAAgB,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IACzH,MAAM,eAAe,GAAG,oBAAoB,CAAC;QAC3C,GAAG,gBAAgB,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC;QAC1C,GAAG,gBAAgB,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC;KACzC,CAAC,CAAC;IACH,MAAM,IAAI,GAAG,oBAAoB,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,wBAAwB,EAAE,CAAC,QAAQ,EAAE,GAAG,CAAC;IAClG,MAAM,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IACxF,MAAM,SAAS,GAAG,cAAc,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;IAE3E,OAAO;QACL,OAAO;QACP,GAAG,EAAE,eAAe,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ;QAC3C,IAAI;QACJ,MAAM;QACN,eAAe;QACf,WAAW;QACX,SAAS;KACV,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,OAAO;QACL,2BAA2B;QAC3B,iDAAiD;QACjD,EAAE;QACF,UAAU;QACV,wDAAwD;QACxD,oEAAoE;QACpE,wDAAwD;QACxD,4FAA4F;QAC5F,iDAAiD;QACjD,+FAA+F;QAC/F,mDAAmD;QACnD,mDAAmD;QACnD,0EAA0E;QAC1E,EAAE;QACF,WAAW;QACX,+BAA+B;QAC/B,6CAA6C;QAC7C,sBAAsB,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,WAAW,CAAC,+BAA+B;QACzF,4DAA4D;KAC7D,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,MAAsB;IACzD,OAAO;QACL,+BAA+B,MAAM,CAAC,IAAI,EAAE;QAC5C,WAAW,MAAM,CAAC,OAAO,EAAE;QAC3B,gBAAgB,MAAM,CAAC,SAAS,EAAE;QAClC,iBAAiB,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,cAAc,EAAE;QACvF,qBAAqB,MAAM,CAAC,eAAe,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE;KACrG,CAAC;AACJ,CAAC;AAED,SAAS,aAAa,CAAC,KAAa;IAClC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CAAC,wBAAwB,KAAK,sBAAsB,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC5G,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,WAAW,CAAC,KAAa;IAChC,OAAO,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;AAC3C,CAAC;AAED,SAAS,oBAAoB,CAAC,KAAc;IAC1C,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,KAAK,EAAE,CAAC;QAC3C,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3D,MAAM,IAAI,KAAK,CAAC,4DAA4D,CAAC,CAAC;IAChF,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC;AACtB,CAAC;AAED,SAAS,gBAAgB,CAAC,KAAc;IACtC,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,KAAK,EAAE,CAAC;QAC3C,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IACtD,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QAC1B,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC3D,MAAM,IAAI,KAAK,CAAC,4DAA4D,CAAC,CAAC;QAChF,CAAC;QAED,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC;IACtB,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,iBAAiB,CAAC,MAAyB;IAClD,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,wBAAwB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AAC9E,CAAC;AAED,SAAS,oBAAoB,CAAC,MAAyB;IACrD,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AAC3E,CAAC;AAED,SAAS,wBAAwB,CAAC,SAAiB;IACjD,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAC7C,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CAAC,wBAAwB,SAAS,EAAE,CAAC,CAAC;IACvD,CAAC;IAED,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;IAC3C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,YAAY,CAAC;IACtB,CAAC;AACH,CAAC;AAED,SAAS,gBAAgB,CAAC,KAAa;IACrC,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACjD,IAAI,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QAClC,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,IAAI,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QAC9B,OAAO,UAAU,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;IACnD,CAAC;IAED,IAAI,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QAC/B,OAAO,WAAW,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;IACrD,CAAC;IAED,OAAO,WAAW,OAAO,EAAE,CAAC;AAC9B,CAAC;AAED,SAAS,cAAc,CAAC,OAAe,EAAE,OAAe;IACtD,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC/E,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,GAAG,MAAM,OAAO,CAAC,CAAC;AAChF,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const PORTAL_ENVS: {
|
|
2
|
+
readonly test: "https://ai.test.insea.io";
|
|
3
|
+
readonly staging: "https://ai.staging.insea.io";
|
|
4
|
+
readonly live: "https://ai.insea.io";
|
|
5
|
+
};
|
|
6
|
+
export type PortalEnv = keyof typeof PORTAL_ENVS;
|
|
7
|
+
export declare const DEFAULT_PORTAL_ENV: PortalEnv;
|
|
8
|
+
export declare const DEFAULT_TIMEOUT_MS = 30000;
|
|
9
|
+
export declare const MAX_TIMEOUT_MS = 900000;
|
|
10
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,WAAW;;;;CAId,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG,MAAM,OAAO,WAAW,CAAC;AAEjD,eAAO,MAAM,kBAAkB,EAAE,SAAkB,CAAC;AACpD,eAAO,MAAM,kBAAkB,QAAS,CAAC;AACzC,eAAO,MAAM,cAAc,SAAU,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const PORTAL_ENVS = {
|
|
2
|
+
test: "https://ai.test.insea.io",
|
|
3
|
+
staging: "https://ai.staging.insea.io",
|
|
4
|
+
live: "https://ai.insea.io"
|
|
5
|
+
};
|
|
6
|
+
export const DEFAULT_PORTAL_ENV = "live";
|
|
7
|
+
export const DEFAULT_TIMEOUT_MS = 30_000;
|
|
8
|
+
export const MAX_TIMEOUT_MS = 900_000;
|
|
9
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,IAAI,EAAE,0BAA0B;IAChC,OAAO,EAAE,6BAA6B;IACtC,IAAI,EAAE,qBAAqB;CACnB,CAAC;AAIX,MAAM,CAAC,MAAM,kBAAkB,GAAc,MAAM,CAAC;AACpD,MAAM,CAAC,MAAM,kBAAkB,GAAG,MAAM,CAAC;AACzC,MAAM,CAAC,MAAM,cAAc,GAAG,OAAO,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deviceInfo.d.ts","sourceRoot":"","sources":["../src/deviceInfo.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kCAAkC,CAAC;AAEnE,wBAAgB,aAAa,IAAI,UAAU,CAW1C"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
export function getDeviceInfo() {
|
|
3
|
+
return {
|
|
4
|
+
platform: process.platform,
|
|
5
|
+
type: os.type(),
|
|
6
|
+
release: os.release(),
|
|
7
|
+
version: os.version(),
|
|
8
|
+
arch: process.arch,
|
|
9
|
+
hostname: os.hostname(),
|
|
10
|
+
nodeVersion: process.version,
|
|
11
|
+
cwd: process.cwd()
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=deviceInfo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deviceInfo.js","sourceRoot":"","sources":["../src/deviceInfo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AAGzB,MAAM,UAAU,aAAa;IAC3B,OAAO;QACL,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE;QACf,OAAO,EAAE,EAAE,CAAC,OAAO,EAAE;QACrB,OAAO,EAAE,EAAE,CAAC,OAAO,EAAE;QACrB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,QAAQ,EAAE,EAAE,CAAC,QAAQ,EAAE;QACvB,WAAW,EAAE,OAAO,CAAC,OAAO;QAC5B,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE;KACnB,CAAC;AACJ,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
import WebSocket from "ws";
|
|
4
|
+
import { CLIENT_TYPE_DESKTOP, DeviceMcpClient } from "@alpha-clients/client-connection";
|
|
5
|
+
import { HelpRequestedError, formatStartupSummary, parseCliArgs } from "./config.js";
|
|
6
|
+
import { createFileTokenStore } from "./tokenStore.js";
|
|
7
|
+
import { getDeviceInfo } from "./deviceInfo.js";
|
|
8
|
+
import { createTerminalTools } from "./tools.js";
|
|
9
|
+
const RECONNECT_DELAY_MS = 2_000;
|
|
10
|
+
async function main() {
|
|
11
|
+
const config = parseCliArgs(process.argv.slice(2));
|
|
12
|
+
const tokenStore = createFileTokenStore(config.tokenPath);
|
|
13
|
+
if (config.clearTokens) {
|
|
14
|
+
await tokenStore.clear();
|
|
15
|
+
}
|
|
16
|
+
for (const line of formatStartupSummary(config)) {
|
|
17
|
+
console.log(line);
|
|
18
|
+
}
|
|
19
|
+
let stopped = false;
|
|
20
|
+
let activeClient;
|
|
21
|
+
const shutdown = (signal) => {
|
|
22
|
+
stopped = true;
|
|
23
|
+
console.log(`Received ${signal}, shutting down.`);
|
|
24
|
+
activeClient?.disconnect(1000, signal);
|
|
25
|
+
process.exit(0);
|
|
26
|
+
};
|
|
27
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
28
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
29
|
+
while (!stopped) {
|
|
30
|
+
activeClient = new DeviceMcpClient({
|
|
31
|
+
baseUrl: config.baseUrl,
|
|
32
|
+
clientType: CLIENT_TYPE_DESKTOP,
|
|
33
|
+
name: config.name,
|
|
34
|
+
deviceInfo: getDeviceInfo(),
|
|
35
|
+
authorizationMode: "header",
|
|
36
|
+
tokenStore,
|
|
37
|
+
fetch,
|
|
38
|
+
webSocketFactory(url, options) {
|
|
39
|
+
console.log(`[device-mcp-terminal] connecting ${url}`);
|
|
40
|
+
return new WebSocket(url, {
|
|
41
|
+
headers: options?.headers
|
|
42
|
+
});
|
|
43
|
+
},
|
|
44
|
+
onAuthSession(session) {
|
|
45
|
+
console.log(`[device-mcp-terminal] auth session ${session.authSessionId} expiresAt=${session.expiresAt}`);
|
|
46
|
+
},
|
|
47
|
+
openLoginUrl(loginUrl) {
|
|
48
|
+
console.log("");
|
|
49
|
+
console.log("Open this URL in your browser and complete login:");
|
|
50
|
+
console.log(loginUrl);
|
|
51
|
+
console.log("");
|
|
52
|
+
},
|
|
53
|
+
logger: consoleLogger(),
|
|
54
|
+
tools: createTerminalTools(config)
|
|
55
|
+
});
|
|
56
|
+
try {
|
|
57
|
+
const result = await activeClient.connect();
|
|
58
|
+
console.log(`[device-mcp-terminal] connected clientId=${result.client.clientId}`);
|
|
59
|
+
await waitForSocketClose(result.socket);
|
|
60
|
+
if (!stopped) {
|
|
61
|
+
console.warn(`[device-mcp-terminal] connection closed; reconnecting in ${RECONNECT_DELAY_MS}ms.`);
|
|
62
|
+
await sleep(RECONNECT_DELAY_MS);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
if (stopped) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
console.error("[device-mcp-terminal] connection failed");
|
|
70
|
+
console.error(formatLogData(error));
|
|
71
|
+
await sleep(RECONNECT_DELAY_MS);
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
activeClient.disconnect();
|
|
75
|
+
activeClient = undefined;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
main().catch((error) => {
|
|
80
|
+
if (error instanceof HelpRequestedError) {
|
|
81
|
+
console.log(error.helpText);
|
|
82
|
+
process.exit(0);
|
|
83
|
+
}
|
|
84
|
+
console.error(formatLogData(error));
|
|
85
|
+
process.exit(1);
|
|
86
|
+
});
|
|
87
|
+
function waitForSocketClose(socket) {
|
|
88
|
+
return new Promise((resolve) => {
|
|
89
|
+
if (socket.readyState === 3) {
|
|
90
|
+
resolve();
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (socket.addEventListener) {
|
|
94
|
+
socket.addEventListener("close", () => resolve());
|
|
95
|
+
socket.addEventListener("error", () => resolve());
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
socket.once?.("close", () => resolve());
|
|
99
|
+
socket.once?.("error", () => resolve());
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
function sleep(ms) {
|
|
103
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
104
|
+
}
|
|
105
|
+
function consoleLogger() {
|
|
106
|
+
return {
|
|
107
|
+
debug: (message, data) => log("debug", message, data),
|
|
108
|
+
info: (message, data) => log("info", message, data),
|
|
109
|
+
warn: (message, data) => log("warn", message, data),
|
|
110
|
+
error: (message, data) => log("error", message, data)
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function log(level, message, data) {
|
|
114
|
+
const suffix = data === undefined ? "" : ` ${formatLogData(data)}`;
|
|
115
|
+
console[level](`[device-mcp-terminal] ${message}${suffix}`);
|
|
116
|
+
}
|
|
117
|
+
function formatLogData(data) {
|
|
118
|
+
if (data instanceof Error) {
|
|
119
|
+
return data.stack ?? data.message;
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
return JSON.stringify(data);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return String(data);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface SandboxedExecution {
|
|
2
|
+
readonly command: string;
|
|
3
|
+
readonly args: readonly string[];
|
|
4
|
+
readonly env: NodeJS.ProcessEnv;
|
|
5
|
+
cleanup(): void;
|
|
6
|
+
}
|
|
7
|
+
export declare function createSandboxedExecution(scopes: readonly string[], cwd: string, command: string, args: readonly string[]): SandboxedExecution | undefined;
|
|
8
|
+
interface MacOsSeatbeltProfileOptions {
|
|
9
|
+
readonly scopeRoots: readonly string[];
|
|
10
|
+
readonly extraReadRoots: readonly string[];
|
|
11
|
+
readonly commandTempDir: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function createMacOsSeatbeltProfile(options: MacOsSeatbeltProfileOptions): string;
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=macosSandbox.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"macosSandbox.d.ts","sourceRoot":"","sources":["../src/macosSandbox.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IAChC,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,SAAS,MAAM,EAAE,EACzB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,SAAS,MAAM,EAAE,GACtB,kBAAkB,GAAG,SAAS,CA0BhC;AAED,UAAU,2BAA2B;IACnC,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,2BAA2B,GAAG,MAAM,CAqBvF"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, realpathSync } from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
const SANDBOX_EXECUTABLE = "/usr/bin/sandbox-exec";
|
|
5
|
+
const SYSTEM_READ_PATHS = ["/bin", "/dev", "/etc", "/Library", "/opt", "/private/etc", "/System", "/usr", "/usr/local"];
|
|
6
|
+
const SYSTEM_TEMP_PATHS = ["/tmp", "/private/tmp"];
|
|
7
|
+
export function createSandboxedExecution(scopes, cwd, command, args) {
|
|
8
|
+
if (process.platform !== "darwin" || scopes.length === 0 || !existsSync(SANDBOX_EXECUTABLE)) {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
const commandTempDir = path.join(cwd, ".device-mcp-tmp");
|
|
12
|
+
mkdirSync(commandTempDir, { recursive: true });
|
|
13
|
+
const profile = createMacOsSeatbeltProfile({
|
|
14
|
+
scopeRoots: scopes.map((scopeRoot) => canonicalizePath(scopeRoot)),
|
|
15
|
+
extraReadRoots: resolveExecutableSearchPaths(process.env.PATH),
|
|
16
|
+
commandTempDir
|
|
17
|
+
});
|
|
18
|
+
return {
|
|
19
|
+
command: SANDBOX_EXECUTABLE,
|
|
20
|
+
args: ["-p", profile, command, ...args],
|
|
21
|
+
env: {
|
|
22
|
+
...process.env,
|
|
23
|
+
HOME: cwd,
|
|
24
|
+
TEMP: commandTempDir,
|
|
25
|
+
TMP: commandTempDir,
|
|
26
|
+
TMPDIR: commandTempDir
|
|
27
|
+
},
|
|
28
|
+
cleanup() { }
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export function createMacOsSeatbeltProfile(options) {
|
|
32
|
+
const allowedReadRoots = dedupePaths([
|
|
33
|
+
...SYSTEM_READ_PATHS,
|
|
34
|
+
...SYSTEM_TEMP_PATHS,
|
|
35
|
+
...options.extraReadRoots,
|
|
36
|
+
...options.scopeRoots,
|
|
37
|
+
options.commandTempDir
|
|
38
|
+
]);
|
|
39
|
+
const allowedWriteRoots = dedupePaths([...options.scopeRoots, ...SYSTEM_TEMP_PATHS, options.commandTempDir]);
|
|
40
|
+
const metadataRoots = dedupePaths(collectAncestorPaths([...allowedReadRoots, ...allowedWriteRoots]));
|
|
41
|
+
const lines = ["(version 1)", "(deny default)", '(import "system.sb")', "(allow network*)", "(allow process*)"];
|
|
42
|
+
if (metadataRoots.length > 0) {
|
|
43
|
+
lines.push(formatRule("allow", "file-read-metadata", "literal", metadataRoots));
|
|
44
|
+
}
|
|
45
|
+
lines.push(formatRule("allow", "file-read*", "subpath", allowedReadRoots));
|
|
46
|
+
lines.push(formatRule("allow", "file-write*", "subpath", allowedWriteRoots));
|
|
47
|
+
return lines.join("\n");
|
|
48
|
+
}
|
|
49
|
+
function formatRule(action, operation, matcher, paths) {
|
|
50
|
+
return [`(${action} ${operation}`, ...paths.map((entry) => ` (${matcher} ${JSON.stringify(entry)})`), ")"].join("\n");
|
|
51
|
+
}
|
|
52
|
+
function collectAncestorPaths(paths) {
|
|
53
|
+
const ancestors = new Set();
|
|
54
|
+
for (const inputPath of paths) {
|
|
55
|
+
const resolvedPath = canonicalizePath(inputPath);
|
|
56
|
+
let currentPath = path.dirname(resolvedPath);
|
|
57
|
+
while (currentPath !== path.dirname(currentPath)) {
|
|
58
|
+
ancestors.add(currentPath);
|
|
59
|
+
currentPath = path.dirname(currentPath);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return [...ancestors];
|
|
63
|
+
}
|
|
64
|
+
function resolveExecutableSearchPaths(pathValue) {
|
|
65
|
+
if (!pathValue) {
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
const homeDirectory = os.homedir();
|
|
69
|
+
return dedupePaths(pathValue
|
|
70
|
+
.split(path.delimiter)
|
|
71
|
+
.map((entry) => entry.trim())
|
|
72
|
+
.filter(Boolean)
|
|
73
|
+
.map((entry) => (entry.startsWith("~/") ? path.join(homeDirectory, entry.slice(2)) : entry))
|
|
74
|
+
.filter((entry) => path.isAbsolute(entry))
|
|
75
|
+
.map((entry) => canonicalizePath(entry)));
|
|
76
|
+
}
|
|
77
|
+
function dedupePaths(paths) {
|
|
78
|
+
return [...new Set(paths.map((entry) => canonicalizePath(entry)))];
|
|
79
|
+
}
|
|
80
|
+
function canonicalizePath(inputPath) {
|
|
81
|
+
const resolvedPath = path.resolve(inputPath);
|
|
82
|
+
try {
|
|
83
|
+
return realpathSync.native(resolvedPath);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return resolvedPath;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=macosSandbox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"macosSandbox.js","sourceRoot":"","sources":["../src/macosSandbox.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokenStore.d.ts","sourceRoot":"","sources":["../src/tokenStore.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAe,UAAU,EAAE,MAAM,kCAAkC,CAAC;AAEhF,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAqB7D"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
export function createFileTokenStore(path) {
|
|
4
|
+
return {
|
|
5
|
+
async load() {
|
|
6
|
+
try {
|
|
7
|
+
return JSON.parse(await readFile(path, "utf8"));
|
|
8
|
+
}
|
|
9
|
+
catch (error) {
|
|
10
|
+
if (isNodeError(error) && error.code === "ENOENT") {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
throw error;
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
async save(tokens) {
|
|
17
|
+
await mkdir(dirname(path), { recursive: true });
|
|
18
|
+
await writeFile(path, `${JSON.stringify(tokens, null, 2)}\n`, "utf8");
|
|
19
|
+
},
|
|
20
|
+
async clear() {
|
|
21
|
+
await rm(path, { force: true });
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function isNodeError(error) {
|
|
26
|
+
return error instanceof Error;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=tokenStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokenStore.js","sourceRoot":"","sources":["../src/tokenStore.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAClE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGpC,MAAM,UAAU,oBAAoB,CAAC,IAAY;IAC/C,OAAO;QACL,KAAK,CAAC,IAAI;YACR,IAAI,CAAC;gBACH,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAgB,CAAC;YACjE,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,WAAW,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;oBAClD,OAAO,IAAI,CAAC;gBACd,CAAC;gBAED,MAAM,KAAK,CAAC;YACd,CAAC;QACH,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,MAAM;YACf,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAChD,MAAM,SAAS,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACxE,CAAC;QACD,KAAK,CAAC,KAAK;YACT,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAClC,CAAC;KACF,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,KAAc;IACjC,OAAO,KAAK,YAAY,KAAK,CAAC;AAChC,CAAC"}
|
package/dist/tools.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ClientTool, JsonObject } from "@alpha-clients/client-connection";
|
|
2
|
+
import type { TerminalConfig } from "./config.js";
|
|
3
|
+
export declare function createTerminalTools(config: TerminalConfig): ClientTool[];
|
|
4
|
+
export declare function createInfoPayload(config: TerminalConfig): JsonObject;
|
|
5
|
+
//# sourceMappingURL=tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkB,UAAU,EAAE,UAAU,EAAa,MAAM,kCAAkC,CAAC;AAI1G,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,cAAc,GAAG,UAAU,EAAE,CAExE;AA+DD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,cAAc,GAAG,UAAU,CAgBpE"}
|
package/dist/tools.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { DEFAULT_TIMEOUT_MS, MAX_TIMEOUT_MS } from "./constants.js";
|
|
2
|
+
import { executeCommand } from "./commandRunner.js";
|
|
3
|
+
import { getDeviceInfo } from "./deviceInfo.js";
|
|
4
|
+
export function createTerminalTools(config) {
|
|
5
|
+
return [createGetInfoTool(config), createRunCommandTool(config)];
|
|
6
|
+
}
|
|
7
|
+
function createGetInfoTool(config) {
|
|
8
|
+
return {
|
|
9
|
+
definition: {
|
|
10
|
+
name: "get_info",
|
|
11
|
+
description: "Get device info, access scope, and command whitelist for this terminal client.",
|
|
12
|
+
inputSchema: {
|
|
13
|
+
type: "object",
|
|
14
|
+
properties: {},
|
|
15
|
+
additionalProperties: false
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
handler() {
|
|
19
|
+
const structuredContent = createInfoPayload(config);
|
|
20
|
+
return jsonResult(structuredContent);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function createRunCommandTool(config) {
|
|
25
|
+
return {
|
|
26
|
+
definition: {
|
|
27
|
+
name: "run_command",
|
|
28
|
+
description: "Run a terminal command and return stdout and stderr.",
|
|
29
|
+
inputSchema: {
|
|
30
|
+
type: "object",
|
|
31
|
+
properties: {
|
|
32
|
+
command: {
|
|
33
|
+
type: "string",
|
|
34
|
+
description: "Command executable to run."
|
|
35
|
+
},
|
|
36
|
+
args: {
|
|
37
|
+
type: "array",
|
|
38
|
+
items: {
|
|
39
|
+
type: "string"
|
|
40
|
+
},
|
|
41
|
+
description: "Optional command arguments."
|
|
42
|
+
},
|
|
43
|
+
cwd: {
|
|
44
|
+
type: "string",
|
|
45
|
+
description: "Working directory for the command."
|
|
46
|
+
},
|
|
47
|
+
timeoutMs: {
|
|
48
|
+
type: "number",
|
|
49
|
+
description: `Optional timeout in milliseconds. Defaults to ${DEFAULT_TIMEOUT_MS}; max ${MAX_TIMEOUT_MS}.`
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
required: ["command"],
|
|
53
|
+
additionalProperties: false
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
async handler(args) {
|
|
57
|
+
const input = parseRunCommandArgs(args);
|
|
58
|
+
const result = await executeCommand(config, input);
|
|
59
|
+
return jsonResult({
|
|
60
|
+
...result,
|
|
61
|
+
args: [...result.args]
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
export function createInfoPayload(config) {
|
|
67
|
+
return {
|
|
68
|
+
deviceInfo: getDeviceInfo(),
|
|
69
|
+
runtime: {
|
|
70
|
+
defaultTimeoutMs: DEFAULT_TIMEOUT_MS,
|
|
71
|
+
maxTimeoutMs: MAX_TIMEOUT_MS
|
|
72
|
+
},
|
|
73
|
+
accessScope: {
|
|
74
|
+
unrestricted: config.scopes.length === 0,
|
|
75
|
+
roots: [...config.scopes]
|
|
76
|
+
},
|
|
77
|
+
commandWhitelist: {
|
|
78
|
+
unrestricted: config.allowedCommands.length === 0,
|
|
79
|
+
commands: [...config.allowedCommands]
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function parseRunCommandArgs(args) {
|
|
84
|
+
if (typeof args.command !== "string" || args.command.trim().length === 0) {
|
|
85
|
+
throw new Error("command must be a non-empty string.");
|
|
86
|
+
}
|
|
87
|
+
const commandArgs = args.args;
|
|
88
|
+
if (commandArgs !== undefined && (!Array.isArray(commandArgs) || !commandArgs.every((entry) => typeof entry === "string"))) {
|
|
89
|
+
throw new Error("args must be an array of strings.");
|
|
90
|
+
}
|
|
91
|
+
if (args.cwd !== undefined && typeof args.cwd !== "string") {
|
|
92
|
+
throw new Error("cwd must be a string.");
|
|
93
|
+
}
|
|
94
|
+
const timeoutMs = args.timeoutMs;
|
|
95
|
+
if (timeoutMs !== undefined && (typeof timeoutMs !== "number" || !Number.isInteger(timeoutMs) || timeoutMs <= 0)) {
|
|
96
|
+
throw new Error("timeoutMs must be a positive integer.");
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
command: args.command,
|
|
100
|
+
...(commandArgs === undefined ? {} : { args: commandArgs }),
|
|
101
|
+
...(args.cwd === undefined ? {} : { cwd: args.cwd }),
|
|
102
|
+
...(timeoutMs === undefined ? {} : { timeoutMs })
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function jsonResult(structuredContent) {
|
|
106
|
+
return {
|
|
107
|
+
structuredContent,
|
|
108
|
+
content: [
|
|
109
|
+
{
|
|
110
|
+
type: "text",
|
|
111
|
+
text: JSON.stringify(structuredContent, null, 2)
|
|
112
|
+
}
|
|
113
|
+
]
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@alpha-clients/terminal",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Terminal client for Alpha client-side MCP tools.",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"terminal": "./dist/index.js",
|
|
10
|
+
"device-mcp-terminal": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"publishConfig": {
|
|
13
|
+
"access": "public"
|
|
14
|
+
},
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=20"
|
|
17
|
+
},
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"import": "./dist/index.js"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist",
|
|
26
|
+
"README.md"
|
|
27
|
+
],
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsc -p tsconfig.build.json",
|
|
30
|
+
"start": "node dist/index.js",
|
|
31
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
32
|
+
"test": "vitest run"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@alpha-clients/client-connection": "workspace:^",
|
|
36
|
+
"ws": "^8.18.3"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/ws": "^8.18.1"
|
|
40
|
+
}
|
|
41
|
+
}
|