terminal-commands 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +102 -0
- package/dist/agent.js +634 -122
- package/dist/cli.js +883 -138
- package/dist/index.js +1281 -364
- package/dist/native/GO-LICENSE +27 -0
- package/dist/native/darwin-arm64/fs-helper +0 -0
- package/dist/native/darwin-x64/fs-helper +0 -0
- package/dist/native/linux-arm64/fs-helper +0 -0
- package/dist/native/linux-x64/fs-helper +0 -0
- package/dist/native/win32-arm64/fs-helper.exe +0 -0
- package/dist/native/win32-x64/fs-helper.exe +0 -0
- package/dist/stdio.js +1001 -186
- package/package.json +6 -5
package/dist/cli.js
CHANGED
|
@@ -4155,13 +4155,14 @@ var require_fast_uri = __commonJS({
|
|
|
4155
4155
|
if (!malformedIPLiteral) {
|
|
4156
4156
|
malformedHost = canonicalizeHost(parsed, options, schemeHandler, isIP);
|
|
4157
4157
|
}
|
|
4158
|
-
if (
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
parsed.host = reescapeHostDelimiters(host, isIP);
|
|
4163
|
-
}
|
|
4158
|
+
if (uri.indexOf("%") !== -1 && parsed.host !== void 0 && !malformedIPLiteral) {
|
|
4159
|
+
let host = isIP ? parsed.host : normalizePercentEncoding(parsed.host, true);
|
|
4160
|
+
if (!isIP) {
|
|
4161
|
+
host = normalizePercentEncoding(host.toLowerCase());
|
|
4164
4162
|
}
|
|
4163
|
+
parsed.host = reescapeHostDelimiters(host, isIP);
|
|
4164
|
+
}
|
|
4165
|
+
if (!schemeHandler || schemeHandler && !schemeHandler.skipNormalize) {
|
|
4165
4166
|
if (parsed.path) {
|
|
4166
4167
|
parsed.path = normalizePathEncoding(parsed.path);
|
|
4167
4168
|
}
|
|
@@ -9548,7 +9549,7 @@ var require_websocket = __commonJS({
|
|
|
9548
9549
|
var http = __require("http");
|
|
9549
9550
|
var net = __require("net");
|
|
9550
9551
|
var tls = __require("tls");
|
|
9551
|
-
var { randomBytes, createHash } = __require("crypto");
|
|
9552
|
+
var { randomBytes: randomBytes3, createHash } = __require("crypto");
|
|
9552
9553
|
var { Duplex, Readable } = __require("stream");
|
|
9553
9554
|
var { URL: URL2 } = __require("url");
|
|
9554
9555
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -10086,7 +10087,7 @@ var require_websocket = __commonJS({
|
|
|
10086
10087
|
}
|
|
10087
10088
|
}
|
|
10088
10089
|
const defaultPort = isSecure ? 443 : 80;
|
|
10089
|
-
const key =
|
|
10090
|
+
const key = randomBytes3(16).toString("base64");
|
|
10090
10091
|
const request = isSecure ? https.request : http.request;
|
|
10091
10092
|
const protocolSet = /* @__PURE__ */ new Set();
|
|
10092
10093
|
let perMessageDeflate;
|
|
@@ -10981,7 +10982,9 @@ var require_websocket_server = __commonJS({
|
|
|
10981
10982
|
|
|
10982
10983
|
// src/cli.ts
|
|
10983
10984
|
import { spawn } from "node:child_process";
|
|
10985
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
10984
10986
|
import { fileURLToPath } from "node:url";
|
|
10987
|
+
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
10985
10988
|
import os5 from "node:os";
|
|
10986
10989
|
import path3 from "node:path";
|
|
10987
10990
|
|
|
@@ -32309,6 +32312,9 @@ var Protocol = class {
|
|
|
32309
32312
|
this.setRequestHandler(GetTaskPayloadRequestSchema, async (request, extra) => {
|
|
32310
32313
|
const handleTaskResult = async () => {
|
|
32311
32314
|
const taskId = request.params.taskId;
|
|
32315
|
+
if (!await this._taskStore.getTask(taskId, extra.sessionId)) {
|
|
32316
|
+
throw new McpError(ErrorCode.InvalidParams, `Task not found: ${taskId}`);
|
|
32317
|
+
}
|
|
32312
32318
|
if (this._taskMessageQueue) {
|
|
32313
32319
|
let queuedMessage;
|
|
32314
32320
|
while (queuedMessage = await this._taskMessageQueue.dequeue(taskId, extra.sessionId)) {
|
|
@@ -32339,12 +32345,12 @@ var Protocol = class {
|
|
|
32339
32345
|
throw new McpError(ErrorCode.InvalidParams, `Task not found: ${taskId}`);
|
|
32340
32346
|
}
|
|
32341
32347
|
if (!isTerminal(task.status)) {
|
|
32342
|
-
await this._waitForTaskUpdate(taskId, extra.signal);
|
|
32348
|
+
await this._waitForTaskUpdate(taskId, extra.signal, extra.sessionId);
|
|
32343
32349
|
return await handleTaskResult();
|
|
32344
32350
|
}
|
|
32345
32351
|
if (isTerminal(task.status)) {
|
|
32346
32352
|
const result = await this._taskStore.getTaskResult(taskId, extra.sessionId);
|
|
32347
|
-
this._clearTaskQueue(taskId);
|
|
32353
|
+
this._clearTaskQueue(taskId, extra.sessionId);
|
|
32348
32354
|
return {
|
|
32349
32355
|
...result,
|
|
32350
32356
|
_meta: {
|
|
@@ -32381,7 +32387,7 @@ var Protocol = class {
|
|
|
32381
32387
|
throw new McpError(ErrorCode.InvalidParams, `Cannot cancel task in terminal status: ${task.status}`);
|
|
32382
32388
|
}
|
|
32383
32389
|
await this._taskStore.updateTaskStatus(request.params.taskId, "cancelled", "Client cancelled task execution.", extra.sessionId);
|
|
32384
|
-
this._clearTaskQueue(request.params.taskId);
|
|
32390
|
+
this._clearTaskQueue(request.params.taskId, extra.sessionId);
|
|
32385
32391
|
const cancelledTask = await this._taskStore.getTask(request.params.taskId, extra.sessionId);
|
|
32386
32392
|
if (!cancelledTask) {
|
|
32387
32393
|
throw new McpError(ErrorCode.InvalidParams, `Task not found after cancellation: ${request.params.taskId}`);
|
|
@@ -32509,6 +32515,19 @@ var Protocol = class {
|
|
|
32509
32515
|
const handler = this._requestHandlers.get(request.method) ?? this.fallbackRequestHandler;
|
|
32510
32516
|
const capturedTransport = this._transport;
|
|
32511
32517
|
const relatedTaskId = request.params?._meta?.[RELATED_TASK_META_KEY]?.taskId;
|
|
32518
|
+
const sessionId = capturedTransport?.sessionId;
|
|
32519
|
+
const store = this._taskStore;
|
|
32520
|
+
let relatedTaskFound = true;
|
|
32521
|
+
let relatedTaskLookup;
|
|
32522
|
+
if (relatedTaskId && store && this._taskMessageQueue && sessionId !== void 0) {
|
|
32523
|
+
relatedTaskFound = false;
|
|
32524
|
+
relatedTaskLookup = (async () => {
|
|
32525
|
+
if (!await store.getTask(relatedTaskId, sessionId)) {
|
|
32526
|
+
throw new McpError(ErrorCode.InvalidParams, `Task not found: ${relatedTaskId}`);
|
|
32527
|
+
}
|
|
32528
|
+
relatedTaskFound = true;
|
|
32529
|
+
})();
|
|
32530
|
+
}
|
|
32512
32531
|
if (handler === void 0) {
|
|
32513
32532
|
const errorResponse = {
|
|
32514
32533
|
jsonrpc: "2.0",
|
|
@@ -32518,7 +32537,10 @@ var Protocol = class {
|
|
|
32518
32537
|
message: "Method not found"
|
|
32519
32538
|
}
|
|
32520
32539
|
};
|
|
32521
|
-
if (relatedTaskId &&
|
|
32540
|
+
if (relatedTaskId && relatedTaskLookup) {
|
|
32541
|
+
const queuedError = { type: "error", message: errorResponse, timestamp: Date.now() };
|
|
32542
|
+
relatedTaskLookup.then(() => this._enqueueTaskMessage(relatedTaskId, queuedError, sessionId), () => capturedTransport?.send(errorResponse)).catch((error62) => this._onerror(new Error(`Failed to send an error response: ${error62}`)));
|
|
32543
|
+
} else if (relatedTaskId && this._taskMessageQueue) {
|
|
32522
32544
|
this._enqueueTaskMessage(relatedTaskId, {
|
|
32523
32545
|
type: "error",
|
|
32524
32546
|
message: errorResponse,
|
|
@@ -32569,7 +32591,10 @@ var Protocol = class {
|
|
|
32569
32591
|
closeSSEStream: extra?.closeSSEStream,
|
|
32570
32592
|
closeStandaloneSSEStream: extra?.closeStandaloneSSEStream
|
|
32571
32593
|
};
|
|
32572
|
-
Promise.resolve().then(() => {
|
|
32594
|
+
(relatedTaskLookup ?? Promise.resolve()).then(() => {
|
|
32595
|
+
if (relatedTaskLookup && abortController.signal.aborted) {
|
|
32596
|
+
throw new McpError(ErrorCode.ConnectionClosed, "Request was cancelled");
|
|
32597
|
+
}
|
|
32573
32598
|
if (taskCreationParams) {
|
|
32574
32599
|
this.assertTaskHandlerCapability(request.method);
|
|
32575
32600
|
}
|
|
@@ -32604,7 +32629,7 @@ var Protocol = class {
|
|
|
32604
32629
|
...error62["data"] !== void 0 && { data: error62["data"] }
|
|
32605
32630
|
}
|
|
32606
32631
|
};
|
|
32607
|
-
if (relatedTaskId && this._taskMessageQueue) {
|
|
32632
|
+
if (relatedTaskId && this._taskMessageQueue && relatedTaskFound) {
|
|
32608
32633
|
await this._enqueueTaskMessage(relatedTaskId, {
|
|
32609
32634
|
type: "error",
|
|
32610
32635
|
message: errorResponse,
|
|
@@ -33084,7 +33109,7 @@ var Protocol = class {
|
|
|
33084
33109
|
throw new Error("Cannot enqueue task message: taskStore and taskMessageQueue are not configured");
|
|
33085
33110
|
}
|
|
33086
33111
|
const maxQueueSize = this._options?.maxTaskQueueSize;
|
|
33087
|
-
await this._taskMessageQueue.enqueue(taskId, message, sessionId, maxQueueSize);
|
|
33112
|
+
await this._taskMessageQueue.enqueue(taskId, message, sessionId ?? this._transport?.sessionId, maxQueueSize);
|
|
33088
33113
|
}
|
|
33089
33114
|
/**
|
|
33090
33115
|
* Clears the message queue for a task and rejects any pending request resolvers.
|
|
@@ -33113,12 +33138,13 @@ var Protocol = class {
|
|
|
33113
33138
|
* Uses polling to check for updates at the task's configured poll interval.
|
|
33114
33139
|
* @param taskId The task ID to wait for
|
|
33115
33140
|
* @param signal Abort signal to cancel the wait
|
|
33141
|
+
* @param sessionId Session of the request that waits, passed to the task store
|
|
33116
33142
|
* @returns Promise that resolves when an update occurs or rejects if aborted
|
|
33117
33143
|
*/
|
|
33118
|
-
async _waitForTaskUpdate(taskId, signal) {
|
|
33144
|
+
async _waitForTaskUpdate(taskId, signal, sessionId) {
|
|
33119
33145
|
let interval = this._options?.defaultTaskPollInterval ?? 1e3;
|
|
33120
33146
|
try {
|
|
33121
|
-
const task = await this._taskStore?.getTask(taskId);
|
|
33147
|
+
const task = await this._taskStore?.getTask(taskId, sessionId);
|
|
33122
33148
|
if (task?.pollInterval) {
|
|
33123
33149
|
interval = task.pollInterval;
|
|
33124
33150
|
}
|
|
@@ -34033,6 +34059,68 @@ function createFetchWithInit(baseFetch = fetch, baseInit) {
|
|
|
34033
34059
|
return baseFetch(url2, mergedInit);
|
|
34034
34060
|
};
|
|
34035
34061
|
}
|
|
34062
|
+
var MAX_REDIRECTS = 5;
|
|
34063
|
+
var REDIRECT_STATUSES = [301, 302, 303, 307, 308];
|
|
34064
|
+
function isWithinOrigin(from, to) {
|
|
34065
|
+
if (from.protocol === to.protocol && from.hostname === to.hostname && from.port === to.port) {
|
|
34066
|
+
return true;
|
|
34067
|
+
}
|
|
34068
|
+
return from.hostname === to.hostname && from.protocol === "http:" && from.port === "" && to.protocol === "https:" && to.port === "";
|
|
34069
|
+
}
|
|
34070
|
+
function redirectTarget(response, requestUrl) {
|
|
34071
|
+
const location = REDIRECT_STATUSES.includes(response.status) ? response.headers.get("location") : null;
|
|
34072
|
+
try {
|
|
34073
|
+
return location ? new URL(location, requestUrl) : void 0;
|
|
34074
|
+
} catch {
|
|
34075
|
+
return void 0;
|
|
34076
|
+
}
|
|
34077
|
+
}
|
|
34078
|
+
function followWithinOrigin(baseFetch, url2, init, response, followed = 0) {
|
|
34079
|
+
const target = redirectTarget(response, url2);
|
|
34080
|
+
const keepsMethod = response.status === 307 || response.status === 308 || (init?.method ?? "GET").toUpperCase() === "GET";
|
|
34081
|
+
if (!target || followed === MAX_REDIRECTS || !keepsMethod) {
|
|
34082
|
+
return void 0;
|
|
34083
|
+
}
|
|
34084
|
+
const from = new URL(url2);
|
|
34085
|
+
const addsUserinfo = (target.username || target.password) && (target.username !== from.username || target.password !== from.password);
|
|
34086
|
+
if (addsUserinfo || !isWithinOrigin(from, target)) {
|
|
34087
|
+
return void 0;
|
|
34088
|
+
}
|
|
34089
|
+
return Promise.resolve(response.body?.cancel()).then(() => baseFetch(target, { ...init, redirect: "manual" })).then((next) => followWithinOrigin(baseFetch, target, init, next, followed + 1) ?? next);
|
|
34090
|
+
}
|
|
34091
|
+
var followingFetches = /* @__PURE__ */ new WeakSet();
|
|
34092
|
+
function fetchLeavingRedirects(baseFetch) {
|
|
34093
|
+
const following = (url2, init) => (baseFetch ?? fetch)(url2, init);
|
|
34094
|
+
followingFetches.add(following);
|
|
34095
|
+
return following;
|
|
34096
|
+
}
|
|
34097
|
+
function fetchWithinOrigin(baseFetch = fetch) {
|
|
34098
|
+
if (followingFetches.has(baseFetch)) {
|
|
34099
|
+
return baseFetch;
|
|
34100
|
+
}
|
|
34101
|
+
return (url2, init) => {
|
|
34102
|
+
if (init?.redirect === "error" || init?.redirect === "manual") {
|
|
34103
|
+
return baseFetch(url2, init);
|
|
34104
|
+
}
|
|
34105
|
+
return Promise.resolve(baseFetch(url2, { ...init, redirect: "manual" })).then((response) => followWithinOrigin(baseFetch, url2, init, response) ?? response);
|
|
34106
|
+
};
|
|
34107
|
+
}
|
|
34108
|
+
function unfollowedRedirect(response, requestUrl) {
|
|
34109
|
+
if (response.type === "opaqueredirect") {
|
|
34110
|
+
return "Redirect not followed: this runtime does not expose the redirect target";
|
|
34111
|
+
}
|
|
34112
|
+
const from = response.url || requestUrl;
|
|
34113
|
+
const target = redirectTarget(response, from);
|
|
34114
|
+
if (!target) {
|
|
34115
|
+
return void 0;
|
|
34116
|
+
}
|
|
34117
|
+
target.username = target.password = target.search = target.hash = "";
|
|
34118
|
+
if (target.protocol === "http:" && new URL(from).protocol === "https:") {
|
|
34119
|
+
target.protocol = "https:";
|
|
34120
|
+
return `Redirect to plain http not followed; try ${target.href} instead`;
|
|
34121
|
+
}
|
|
34122
|
+
return `Redirect to ${target.href} not followed`;
|
|
34123
|
+
}
|
|
34036
34124
|
|
|
34037
34125
|
// node_modules/pkce-challenge/dist/index.node.js
|
|
34038
34126
|
var crypto;
|
|
@@ -34047,8 +34135,8 @@ async function random(size) {
|
|
|
34047
34135
|
const evenDistCutoff = Math.pow(2, 8) - Math.pow(2, 8) % mask.length;
|
|
34048
34136
|
let result = "";
|
|
34049
34137
|
while (result.length < size) {
|
|
34050
|
-
const
|
|
34051
|
-
for (const randomByte of
|
|
34138
|
+
const randomBytes3 = await getRandomValues(size - result.length);
|
|
34139
|
+
for (const randomByte of randomBytes3) {
|
|
34052
34140
|
if (randomByte < evenDistCutoff) {
|
|
34053
34141
|
result += mask[randomByte % mask.length];
|
|
34054
34142
|
}
|
|
@@ -34179,7 +34267,12 @@ var OAuthTokensSchema = object2({
|
|
|
34179
34267
|
token_type: string2(),
|
|
34180
34268
|
expires_in: coerce_exports.number().optional(),
|
|
34181
34269
|
scope: string2().optional(),
|
|
34182
|
-
refresh_token: string2().optional()
|
|
34270
|
+
refresh_token: string2().optional(),
|
|
34271
|
+
/**
|
|
34272
|
+
* Not part of the wire format: the authorization server this value was obtained from, added by
|
|
34273
|
+
* the client's `auth()` before it is stored and compared when it is read back.
|
|
34274
|
+
*/
|
|
34275
|
+
issuer: string2().optional().catch(void 0)
|
|
34183
34276
|
}).strip();
|
|
34184
34277
|
var OAuthErrorResponseSchema = object2({
|
|
34185
34278
|
error: string2(),
|
|
@@ -34209,7 +34302,12 @@ var OAuthClientInformationSchema = object2({
|
|
|
34209
34302
|
client_id: string2(),
|
|
34210
34303
|
client_secret: string2().optional(),
|
|
34211
34304
|
client_id_issued_at: number2().optional(),
|
|
34212
|
-
client_secret_expires_at: number2().optional()
|
|
34305
|
+
client_secret_expires_at: number2().optional(),
|
|
34306
|
+
/**
|
|
34307
|
+
* Not part of the wire format: the authorization server this value was obtained from, added by
|
|
34308
|
+
* the client's `auth()` before it is stored and compared when it is read back.
|
|
34309
|
+
*/
|
|
34310
|
+
issuer: string2().optional().catch(void 0)
|
|
34213
34311
|
}).strip();
|
|
34214
34312
|
var OAuthClientInformationFullSchema = OAuthClientMetadataSchema.merge(OAuthClientInformationSchema);
|
|
34215
34313
|
var OAuthClientRegistrationErrorSchema = object2({
|
|
@@ -34407,10 +34505,31 @@ async function parseErrorResponse(input2) {
|
|
|
34407
34505
|
const errorClass = OAUTH_ERRORS[error62] || ServerError;
|
|
34408
34506
|
return new errorClass(error_description || "", error_uri);
|
|
34409
34507
|
} catch (error62) {
|
|
34410
|
-
const
|
|
34508
|
+
const redirect = input2 instanceof Response ? unfollowedRedirect(input2, input2.url) : void 0;
|
|
34509
|
+
const errorMessage = `${statusCode ? `HTTP ${statusCode}: ` : ""}${redirect ?? `Invalid OAuth error response: ${error62}. Raw body: ${body}`}`;
|
|
34411
34510
|
return new ServerError(errorMessage);
|
|
34412
34511
|
}
|
|
34413
34512
|
}
|
|
34513
|
+
function issuersMatch(a, b) {
|
|
34514
|
+
let [x, y] = [a, b];
|
|
34515
|
+
try {
|
|
34516
|
+
[x, y] = [new URL(a).href, new URL(b).href];
|
|
34517
|
+
} catch {
|
|
34518
|
+
}
|
|
34519
|
+
return x === y || x.endsWith("/") && x.slice(0, -1) === y || y.endsWith("/") && y.slice(0, -1) === x;
|
|
34520
|
+
}
|
|
34521
|
+
function discardIfIssuerMismatch(stored, issuer) {
|
|
34522
|
+
if (!stored)
|
|
34523
|
+
return void 0;
|
|
34524
|
+
if (typeof stored.issuer !== "string")
|
|
34525
|
+
return stored.issuer == null ? stored : { ...stored, issuer: void 0 };
|
|
34526
|
+
return issuersMatch(stored.issuer, issuer) ? stored : void 0;
|
|
34527
|
+
}
|
|
34528
|
+
var TokenResponseSchema = OAuthTokensSchema.omit({ issuer: true });
|
|
34529
|
+
var RegistrationResponseSchema = OAuthClientInformationFullSchema.omit({ issuer: true });
|
|
34530
|
+
function boundElsewhereError(stored, issuer) {
|
|
34531
|
+
return new Error(`OAuth client information is bound to authorization server ${stored.issuer} and is not presented to ${issuer}. Clear the stored client information, or correct \`expectedIssuer\`, if the authorization server has moved.`);
|
|
34532
|
+
}
|
|
34414
34533
|
async function auth(provider, options) {
|
|
34415
34534
|
try {
|
|
34416
34535
|
return await authInternal(provider, options);
|
|
@@ -34464,9 +34583,24 @@ async function authInternal(provider, { serverUrl, authorizationCode, scope, res
|
|
|
34464
34583
|
authorizationServerMetadata: metadata
|
|
34465
34584
|
});
|
|
34466
34585
|
}
|
|
34467
|
-
const
|
|
34586
|
+
const issuer = String(authorizationServerUrl);
|
|
34587
|
+
const selectedResource = await selectResourceURL(serverUrl, provider, resourceMetadata);
|
|
34588
|
+
const resource = selectedResource && resourceMetadata && !provider.validateResourceURL ? resourceMetadata.resource : selectedResource;
|
|
34468
34589
|
const resolvedScope = scope || resourceMetadata?.scopes_supported?.join(" ") || provider.clientMetadata.scope;
|
|
34469
|
-
|
|
34590
|
+
const storedClientInformation = await Promise.resolve(provider.clientInformation());
|
|
34591
|
+
let clientInformation = discardIfIssuerMismatch(storedClientInformation, issuer);
|
|
34592
|
+
const canRegisterAgain = provider.saveClientInformation !== void 0 && !!provider.redirectUrl;
|
|
34593
|
+
if (storedClientInformation && !clientInformation && !canRegisterAgain) {
|
|
34594
|
+
throw boundElsewhereError(storedClientInformation, issuer);
|
|
34595
|
+
}
|
|
34596
|
+
const unstampedClientInformation = clientInformation?.issuer == null ? clientInformation : void 0;
|
|
34597
|
+
const bindClientInformation = async () => {
|
|
34598
|
+
try {
|
|
34599
|
+
if (unstampedClientInformation)
|
|
34600
|
+
await provider.saveClientInformation?.({ ...unstampedClientInformation, issuer });
|
|
34601
|
+
} catch {
|
|
34602
|
+
}
|
|
34603
|
+
};
|
|
34470
34604
|
if (!clientInformation) {
|
|
34471
34605
|
if (authorizationCode !== void 0) {
|
|
34472
34606
|
throw new Error("Existing OAuth client information is required when exchanging an authorization code");
|
|
@@ -34478,9 +34612,7 @@ async function authInternal(provider, { serverUrl, authorizationCode, scope, res
|
|
|
34478
34612
|
}
|
|
34479
34613
|
const shouldUseUrlBasedClientId = supportsUrlBasedClientId && clientMetadataUrl;
|
|
34480
34614
|
if (shouldUseUrlBasedClientId) {
|
|
34481
|
-
clientInformation = {
|
|
34482
|
-
client_id: clientMetadataUrl
|
|
34483
|
-
};
|
|
34615
|
+
clientInformation = { client_id: clientMetadataUrl, issuer };
|
|
34484
34616
|
await provider.saveClientInformation?.(clientInformation);
|
|
34485
34617
|
} else {
|
|
34486
34618
|
if (!provider.saveClientInformation) {
|
|
@@ -34492,8 +34624,8 @@ async function authInternal(provider, { serverUrl, authorizationCode, scope, res
|
|
|
34492
34624
|
scope: resolvedScope,
|
|
34493
34625
|
fetchFn
|
|
34494
34626
|
});
|
|
34495
|
-
|
|
34496
|
-
clientInformation
|
|
34627
|
+
clientInformation = { ...fullInformation, issuer };
|
|
34628
|
+
await provider.saveClientInformation(clientInformation);
|
|
34497
34629
|
}
|
|
34498
34630
|
}
|
|
34499
34631
|
const nonInteractiveFlow = !provider.redirectUrl;
|
|
@@ -34504,10 +34636,14 @@ async function authInternal(provider, { serverUrl, authorizationCode, scope, res
|
|
|
34504
34636
|
authorizationCode,
|
|
34505
34637
|
fetchFn
|
|
34506
34638
|
});
|
|
34507
|
-
await
|
|
34639
|
+
await bindClientInformation();
|
|
34640
|
+
await provider.saveTokens({ ...tokens2, issuer });
|
|
34508
34641
|
return "AUTHORIZED";
|
|
34509
34642
|
}
|
|
34510
|
-
const tokens = await provider.tokens();
|
|
34643
|
+
const tokens = discardIfIssuerMismatch(await provider.tokens(), issuer);
|
|
34644
|
+
if (tokens?.refresh_token && tokens.issuer == null) {
|
|
34645
|
+
console.warn("[mcp-sdk] stored OAuth tokens have no 'issuer' property (saved by an earlier version, or by a provider that does not keep it) and are used as-is; make sure your OAuthClientProvider stores what saveTokens() and saveClientInformation() receive unchanged.");
|
|
34646
|
+
}
|
|
34511
34647
|
if (tokens?.refresh_token) {
|
|
34512
34648
|
try {
|
|
34513
34649
|
const newTokens = await refreshAuthorization(authorizationServerUrl, {
|
|
@@ -34518,7 +34654,8 @@ async function authInternal(provider, { serverUrl, authorizationCode, scope, res
|
|
|
34518
34654
|
addClientAuthentication: provider.addClientAuthentication,
|
|
34519
34655
|
fetchFn
|
|
34520
34656
|
});
|
|
34521
|
-
await
|
|
34657
|
+
await bindClientInformation();
|
|
34658
|
+
await provider.saveTokens({ ...newTokens, issuer });
|
|
34522
34659
|
return "AUTHORIZED";
|
|
34523
34660
|
} catch (error62) {
|
|
34524
34661
|
if (!(error62 instanceof OAuthError) || error62 instanceof ServerError) {
|
|
@@ -34617,7 +34754,7 @@ async function discoverOAuthProtectedResourceMetadata(serverUrl, opts, fetchFn =
|
|
|
34617
34754
|
}
|
|
34618
34755
|
async function fetchWithCorsRetry(url2, headers, fetchFn = fetch) {
|
|
34619
34756
|
try {
|
|
34620
|
-
return await fetchFn(url2, { headers });
|
|
34757
|
+
return await fetchWithinOrigin(fetchFn)(url2, { headers });
|
|
34621
34758
|
} catch (error62) {
|
|
34622
34759
|
if (error62 instanceof TypeError) {
|
|
34623
34760
|
if (headers) {
|
|
@@ -34642,7 +34779,7 @@ async function tryMetadataDiscovery(url2, protocolVersion, fetchFn = fetch) {
|
|
|
34642
34779
|
return await fetchWithCorsRetry(url2, headers, fetchFn);
|
|
34643
34780
|
}
|
|
34644
34781
|
function shouldAttemptFallback(response, pathname) {
|
|
34645
|
-
return !response || response.
|
|
34782
|
+
return !response || !response.ok && response.status < 500 && pathname !== "/";
|
|
34646
34783
|
}
|
|
34647
34784
|
async function discoverMetadataWithFallback(serverUrl, wellKnownType, fetchFn, opts) {
|
|
34648
34785
|
const issuer = new URL(serverUrl);
|
|
@@ -34708,7 +34845,7 @@ async function discoverAuthorizationServerMetadata(authorizationServerUrl, { fet
|
|
|
34708
34845
|
}
|
|
34709
34846
|
if (!response.ok) {
|
|
34710
34847
|
await response.body?.cancel();
|
|
34711
|
-
if (response.status
|
|
34848
|
+
if (response.status < 500) {
|
|
34712
34849
|
continue;
|
|
34713
34850
|
}
|
|
34714
34851
|
throw new Error(`HTTP ${response.status} trying to load ${type === "oauth" ? "OAuth" : "OpenID provider"} metadata from ${endpointUrl}`);
|
|
@@ -34741,6 +34878,9 @@ async function discoverOAuthServerInfo(serverUrl, opts) {
|
|
|
34741
34878
|
resourceMetadata
|
|
34742
34879
|
};
|
|
34743
34880
|
}
|
|
34881
|
+
function resourceIndicatorToString(resource) {
|
|
34882
|
+
return typeof resource === "string" ? resource : resource.href;
|
|
34883
|
+
}
|
|
34744
34884
|
async function startAuthorization(authorizationServerUrl, { metadata, clientInformation, redirectUrl, scope, state, resource }) {
|
|
34745
34885
|
let authorizationUrl;
|
|
34746
34886
|
if (metadata) {
|
|
@@ -34772,7 +34912,7 @@ async function startAuthorization(authorizationServerUrl, { metadata, clientInfo
|
|
|
34772
34912
|
authorizationUrl.searchParams.append("prompt", "consent");
|
|
34773
34913
|
}
|
|
34774
34914
|
if (resource) {
|
|
34775
|
-
authorizationUrl.searchParams.set("resource", resource
|
|
34915
|
+
authorizationUrl.searchParams.set("resource", resourceIndicatorToString(resource));
|
|
34776
34916
|
}
|
|
34777
34917
|
return { authorizationUrl, codeVerifier };
|
|
34778
34918
|
}
|
|
@@ -34791,7 +34931,7 @@ async function executeTokenRequest(authorizationServerUrl, { metadata, tokenRequ
|
|
|
34791
34931
|
Accept: "application/json"
|
|
34792
34932
|
});
|
|
34793
34933
|
if (resource) {
|
|
34794
|
-
tokenRequestParams.set("resource", resource
|
|
34934
|
+
tokenRequestParams.set("resource", resourceIndicatorToString(resource));
|
|
34795
34935
|
}
|
|
34796
34936
|
if (addClientAuthentication) {
|
|
34797
34937
|
await addClientAuthentication(headers, tokenRequestParams, tokenUrl, metadata);
|
|
@@ -34800,7 +34940,7 @@ async function executeTokenRequest(authorizationServerUrl, { metadata, tokenRequ
|
|
|
34800
34940
|
const authMethod = selectClientAuthMethod(clientInformation, supportedMethods);
|
|
34801
34941
|
applyClientAuthentication(authMethod, clientInformation, headers, tokenRequestParams);
|
|
34802
34942
|
}
|
|
34803
|
-
const response = await (fetchFn ?? fetch)(tokenUrl, {
|
|
34943
|
+
const response = await fetchWithinOrigin(fetchFn ?? fetch)(tokenUrl, {
|
|
34804
34944
|
method: "POST",
|
|
34805
34945
|
headers,
|
|
34806
34946
|
body: tokenRequestParams
|
|
@@ -34808,7 +34948,7 @@ async function executeTokenRequest(authorizationServerUrl, { metadata, tokenRequ
|
|
|
34808
34948
|
if (!response.ok) {
|
|
34809
34949
|
throw await parseErrorResponse(response);
|
|
34810
34950
|
}
|
|
34811
|
-
return
|
|
34951
|
+
return TokenResponseSchema.parse(await response.json());
|
|
34812
34952
|
}
|
|
34813
34953
|
async function refreshAuthorization(authorizationServerUrl, { metadata, clientInformation, refreshToken, resource, addClientAuthentication, fetchFn }) {
|
|
34814
34954
|
const tokenRequestParams = new URLSearchParams({
|
|
@@ -34826,6 +34966,15 @@ async function refreshAuthorization(authorizationServerUrl, { metadata, clientIn
|
|
|
34826
34966
|
return { refresh_token: refreshToken, ...tokens };
|
|
34827
34967
|
}
|
|
34828
34968
|
async function fetchToken(provider, authorizationServerUrl, { metadata, resource, authorizationCode, fetchFn } = {}) {
|
|
34969
|
+
const readClientInformation = async () => {
|
|
34970
|
+
const storedClientInformation = await provider.clientInformation();
|
|
34971
|
+
const checked = discardIfIssuerMismatch(storedClientInformation, String(authorizationServerUrl));
|
|
34972
|
+
if (storedClientInformation && !checked) {
|
|
34973
|
+
throw boundElsewhereError(storedClientInformation, String(authorizationServerUrl));
|
|
34974
|
+
}
|
|
34975
|
+
return checked;
|
|
34976
|
+
};
|
|
34977
|
+
let clientInformation = await readClientInformation();
|
|
34829
34978
|
const scope = provider.clientMetadata.scope;
|
|
34830
34979
|
let tokenRequestParams;
|
|
34831
34980
|
if (provider.prepareTokenRequest) {
|
|
@@ -34841,11 +34990,11 @@ async function fetchToken(provider, authorizationServerUrl, { metadata, resource
|
|
|
34841
34990
|
const codeVerifier = await provider.codeVerifier();
|
|
34842
34991
|
tokenRequestParams = prepareAuthorizationCodeRequest(authorizationCode, codeVerifier, provider.redirectUrl);
|
|
34843
34992
|
}
|
|
34844
|
-
|
|
34993
|
+
clientInformation ?? (clientInformation = await readClientInformation());
|
|
34845
34994
|
return executeTokenRequest(authorizationServerUrl, {
|
|
34846
34995
|
metadata,
|
|
34847
34996
|
tokenRequestParams,
|
|
34848
|
-
clientInformation
|
|
34997
|
+
clientInformation,
|
|
34849
34998
|
addClientAuthentication: provider.addClientAuthentication,
|
|
34850
34999
|
resource,
|
|
34851
35000
|
fetchFn
|
|
@@ -34861,7 +35010,7 @@ async function registerClient(authorizationServerUrl, { metadata, clientMetadata
|
|
|
34861
35010
|
} else {
|
|
34862
35011
|
registrationUrl = new URL("/register", authorizationServerUrl);
|
|
34863
35012
|
}
|
|
34864
|
-
const response = await (fetchFn ?? fetch)(registrationUrl, {
|
|
35013
|
+
const response = await fetchWithinOrigin(fetchFn ?? fetch)(registrationUrl, {
|
|
34865
35014
|
method: "POST",
|
|
34866
35015
|
headers: {
|
|
34867
35016
|
"Content-Type": "application/json"
|
|
@@ -34874,7 +35023,7 @@ async function registerClient(authorizationServerUrl, { metadata, clientMetadata
|
|
|
34874
35023
|
if (!response.ok) {
|
|
34875
35024
|
throw await parseErrorResponse(response);
|
|
34876
35025
|
}
|
|
34877
|
-
return
|
|
35026
|
+
return RegistrationResponseSchema.parse(await response.json());
|
|
34878
35027
|
}
|
|
34879
35028
|
|
|
34880
35029
|
// node_modules/eventsource-parser/dist/index.js
|
|
@@ -35101,9 +35250,19 @@ var StreamableHTTPClientTransport = class {
|
|
|
35101
35250
|
this._authProvider = opts?.authProvider;
|
|
35102
35251
|
this._fetch = opts?.fetch;
|
|
35103
35252
|
this._fetchWithInit = createFetchWithInit(opts?.fetch, opts?.requestInit);
|
|
35253
|
+
this._followRedirects = opts?.redirectPolicy === "follow";
|
|
35254
|
+
if (this._followRedirects) {
|
|
35255
|
+
this._fetch = fetchLeavingRedirects(this._fetch);
|
|
35256
|
+
this._fetchWithInit = fetchLeavingRedirects(this._fetchWithInit);
|
|
35257
|
+
}
|
|
35104
35258
|
this._sessionId = opts?.sessionId;
|
|
35105
35259
|
this._reconnectionOptions = opts?.reconnectionOptions ?? DEFAULT_STREAMABLE_HTTP_RECONNECTION_OPTIONS;
|
|
35106
35260
|
}
|
|
35261
|
+
/** Error text for a redirect `response` that was not followed, or `undefined` for any other response. */
|
|
35262
|
+
_unfollowedRedirect(response, url2) {
|
|
35263
|
+
const text = unfollowedRedirect(response, url2);
|
|
35264
|
+
return text && !this._followRedirects ? `${text} (redirectPolicy: 'same-origin')` : text;
|
|
35265
|
+
}
|
|
35107
35266
|
async _authThenStart() {
|
|
35108
35267
|
if (!this._authProvider) {
|
|
35109
35268
|
throw new UnauthorizedError("No auth provider");
|
|
@@ -35153,11 +35312,13 @@ var StreamableHTTPClientTransport = class {
|
|
|
35153
35312
|
if (resumptionToken) {
|
|
35154
35313
|
headers.set("last-event-id", resumptionToken);
|
|
35155
35314
|
}
|
|
35156
|
-
const
|
|
35157
|
-
|
|
35158
|
-
|
|
35159
|
-
|
|
35160
|
-
|
|
35315
|
+
const fetchFn = this._fetch ?? fetch;
|
|
35316
|
+
const init = { method: "GET", headers, signal: this._abortController?.signal };
|
|
35317
|
+
let response = await fetchFn(this._url, this._followRedirects ? init : { ...init, redirect: "manual" });
|
|
35318
|
+
const followed = this._followRedirects ? void 0 : followWithinOrigin(fetchFn, this._url, init, response);
|
|
35319
|
+
if (followed) {
|
|
35320
|
+
response = await followed;
|
|
35321
|
+
}
|
|
35161
35322
|
if (!response.ok) {
|
|
35162
35323
|
await response.body?.cancel();
|
|
35163
35324
|
if (response.status === 401 && this._authProvider) {
|
|
@@ -35166,7 +35327,7 @@ var StreamableHTTPClientTransport = class {
|
|
|
35166
35327
|
if (response.status === 405) {
|
|
35167
35328
|
return;
|
|
35168
35329
|
}
|
|
35169
|
-
throw new StreamableHTTPError(response.status, `Failed to open SSE stream: ${response.statusText}`);
|
|
35330
|
+
throw new StreamableHTTPError(response.status, `Failed to open SSE stream: ${this._unfollowedRedirect(response, this._url) ?? response.statusText}`);
|
|
35170
35331
|
}
|
|
35171
35332
|
this._handleSseStream(response.body, options, true);
|
|
35172
35333
|
} catch (error62) {
|
|
@@ -35329,7 +35490,7 @@ var StreamableHTTPClientTransport = class {
|
|
|
35329
35490
|
body: JSON.stringify(message),
|
|
35330
35491
|
signal: this._abortController?.signal
|
|
35331
35492
|
};
|
|
35332
|
-
const response = await (this._fetch ?? fetch)(this._url, init);
|
|
35493
|
+
const response = await fetchWithinOrigin(this._fetch ?? fetch)(this._url, init);
|
|
35333
35494
|
const sessionId = response.headers.get("mcp-session-id");
|
|
35334
35495
|
if (sessionId) {
|
|
35335
35496
|
this._sessionId = sessionId;
|
|
@@ -35381,7 +35542,7 @@ var StreamableHTTPClientTransport = class {
|
|
|
35381
35542
|
return this.send(message);
|
|
35382
35543
|
}
|
|
35383
35544
|
}
|
|
35384
|
-
throw new StreamableHTTPError(response.status, `Error POSTing to endpoint: ${text}`);
|
|
35545
|
+
throw new StreamableHTTPError(response.status, `Error POSTing to endpoint: ${this._unfollowedRedirect(response, this._url) ?? text}`);
|
|
35385
35546
|
}
|
|
35386
35547
|
this._hasCompletedAuthFlow = false;
|
|
35387
35548
|
this._lastUpscopingHeader = void 0;
|
|
@@ -35443,10 +35604,10 @@ var StreamableHTTPClientTransport = class {
|
|
|
35443
35604
|
headers,
|
|
35444
35605
|
signal: this._abortController?.signal
|
|
35445
35606
|
};
|
|
35446
|
-
const response = await (this._fetch ?? fetch)(this._url, init);
|
|
35607
|
+
const response = await fetchWithinOrigin(this._fetch ?? fetch)(this._url, init);
|
|
35447
35608
|
await response.body?.cancel();
|
|
35448
35609
|
if (!response.ok && response.status !== 405) {
|
|
35449
|
-
throw new StreamableHTTPError(response.status, `Failed to terminate session: ${response.statusText}`);
|
|
35610
|
+
throw new StreamableHTTPError(response.status, `Failed to terminate session: ${this._unfollowedRedirect(response, this._url) ?? response.statusText}`);
|
|
35450
35611
|
}
|
|
35451
35612
|
this._sessionId = void 0;
|
|
35452
35613
|
} catch (error62) {
|
|
@@ -35486,81 +35647,394 @@ var import_websocket = __toESM(require_websocket(), 1);
|
|
|
35486
35647
|
var import_websocket_server = __toESM(require_websocket_server(), 1);
|
|
35487
35648
|
var wrapper_default = import_websocket.default;
|
|
35488
35649
|
|
|
35650
|
+
// src/process-event-forwarder.ts
|
|
35651
|
+
var eventSchema = external_exports.object({
|
|
35652
|
+
sessionId: external_exports.string().uuid(),
|
|
35653
|
+
taskId: external_exports.string().uuid().optional(),
|
|
35654
|
+
sequence: external_exports.number().int().positive(),
|
|
35655
|
+
kind: external_exports.enum(["started", "stdout", "stderr", "finished"]),
|
|
35656
|
+
offset: external_exports.number().int().nonnegative().optional(),
|
|
35657
|
+
data: external_exports.string().max(4096).optional(),
|
|
35658
|
+
exitCode: external_exports.number().int().nullable().optional(),
|
|
35659
|
+
signal: external_exports.string().nullable().optional(),
|
|
35660
|
+
timedOut: external_exports.boolean().optional()
|
|
35661
|
+
});
|
|
35662
|
+
var batchSchema = external_exports.object({
|
|
35663
|
+
sessionId: external_exports.string().uuid(),
|
|
35664
|
+
events: external_exports.array(eventSchema).max(32),
|
|
35665
|
+
nextSequence: external_exports.number().int().nonnegative(),
|
|
35666
|
+
missedEvents: external_exports.boolean(),
|
|
35667
|
+
finished: external_exports.boolean()
|
|
35668
|
+
});
|
|
35669
|
+
var processListSchema = external_exports.object({
|
|
35670
|
+
processes: external_exports.array(
|
|
35671
|
+
external_exports.object({
|
|
35672
|
+
sessionId: external_exports.string().uuid(),
|
|
35673
|
+
taskId: external_exports.string().uuid().optional()
|
|
35674
|
+
})
|
|
35675
|
+
)
|
|
35676
|
+
});
|
|
35677
|
+
function structuredContent(response) {
|
|
35678
|
+
if (!response || typeof response !== "object") {
|
|
35679
|
+
throw new Error("Local process tool returned an invalid response.");
|
|
35680
|
+
}
|
|
35681
|
+
const result = response;
|
|
35682
|
+
if (result.isError || result.structuredContent === void 0) {
|
|
35683
|
+
throw new Error("Local process event tool failed or returned no content.");
|
|
35684
|
+
}
|
|
35685
|
+
return result.structuredContent;
|
|
35686
|
+
}
|
|
35687
|
+
var ProcessEventForwarder = class {
|
|
35688
|
+
constructor(callLocalTool, send, log, acknowledged = /* @__PURE__ */ new Map(), ackTimeoutMs = 3e3, onFatal) {
|
|
35689
|
+
this.callLocalTool = callLocalTool;
|
|
35690
|
+
this.send = send;
|
|
35691
|
+
this.log = log;
|
|
35692
|
+
this.acknowledged = acknowledged;
|
|
35693
|
+
this.ackTimeoutMs = ackTimeoutMs;
|
|
35694
|
+
this.onFatal = onFatal;
|
|
35695
|
+
}
|
|
35696
|
+
callLocalTool;
|
|
35697
|
+
send;
|
|
35698
|
+
log;
|
|
35699
|
+
acknowledged;
|
|
35700
|
+
ackTimeoutMs;
|
|
35701
|
+
onFatal;
|
|
35702
|
+
#watchers = /* @__PURE__ */ new Map();
|
|
35703
|
+
#ackWaiters = /* @__PURE__ */ new Map();
|
|
35704
|
+
#lastSent = /* @__PURE__ */ new Map();
|
|
35705
|
+
#closed = false;
|
|
35706
|
+
acknowledge(sessionId, sequence) {
|
|
35707
|
+
if (!external_exports.string().uuid().safeParse(sessionId).success || !Number.isSafeInteger(sequence) || sequence < 1)
|
|
35708
|
+
return;
|
|
35709
|
+
const maximum = this.#lastSent.get(sessionId) ?? 0;
|
|
35710
|
+
if (sequence > maximum) return;
|
|
35711
|
+
const current = this.acknowledged.get(sessionId) ?? 0;
|
|
35712
|
+
if (sequence <= current) return;
|
|
35713
|
+
this.acknowledged.set(sessionId, sequence);
|
|
35714
|
+
for (const wake of this.#ackWaiters.get(sessionId) ?? []) wake();
|
|
35715
|
+
}
|
|
35716
|
+
async #waitForAck(sessionId, target, signal) {
|
|
35717
|
+
if ((this.acknowledged.get(sessionId) ?? 0) >= target) return true;
|
|
35718
|
+
if (signal.aborted) return false;
|
|
35719
|
+
return new Promise((resolve) => {
|
|
35720
|
+
const listeners = this.#ackWaiters.get(sessionId) ?? /* @__PURE__ */ new Set();
|
|
35721
|
+
this.#ackWaiters.set(sessionId, listeners);
|
|
35722
|
+
let timer;
|
|
35723
|
+
const finish = () => {
|
|
35724
|
+
clearTimeout(timer);
|
|
35725
|
+
signal.removeEventListener("abort", finish);
|
|
35726
|
+
listeners.delete(wake);
|
|
35727
|
+
if (listeners.size === 0) this.#ackWaiters.delete(sessionId);
|
|
35728
|
+
resolve((this.acknowledged.get(sessionId) ?? 0) >= target);
|
|
35729
|
+
};
|
|
35730
|
+
const wake = () => {
|
|
35731
|
+
if ((this.acknowledged.get(sessionId) ?? 0) >= target) finish();
|
|
35732
|
+
};
|
|
35733
|
+
listeners.add(wake);
|
|
35734
|
+
signal.addEventListener("abort", finish, { once: true });
|
|
35735
|
+
timer = setTimeout(finish, this.ackTimeoutMs);
|
|
35736
|
+
wake();
|
|
35737
|
+
});
|
|
35738
|
+
}
|
|
35739
|
+
async recover() {
|
|
35740
|
+
if (this.#closed) return;
|
|
35741
|
+
const result = await this.callLocalTool("list_processes", {});
|
|
35742
|
+
const parsed = processListSchema.parse(structuredContent(result));
|
|
35743
|
+
for (const process2 of parsed.processes) {
|
|
35744
|
+
if (process2.taskId) this.watch(process2.sessionId);
|
|
35745
|
+
}
|
|
35746
|
+
}
|
|
35747
|
+
watch(sessionId) {
|
|
35748
|
+
if (this.#closed || this.#watchers.has(sessionId)) return;
|
|
35749
|
+
if (!external_exports.string().uuid().safeParse(sessionId).success) return;
|
|
35750
|
+
const controller = new AbortController();
|
|
35751
|
+
this.#watchers.set(sessionId, controller);
|
|
35752
|
+
void this.#pump(sessionId, controller.signal).catch((error62) => {
|
|
35753
|
+
const failure2 = error62 instanceof Error ? error62 : new Error("Unknown process event failure.");
|
|
35754
|
+
this.log?.("Process event watch failed: " + failure2.message);
|
|
35755
|
+
if (!controller.signal.aborted) this.onFatal?.(failure2);
|
|
35756
|
+
}).finally(() => {
|
|
35757
|
+
if (this.#watchers.get(sessionId) === controller) {
|
|
35758
|
+
this.#watchers.delete(sessionId);
|
|
35759
|
+
}
|
|
35760
|
+
});
|
|
35761
|
+
}
|
|
35762
|
+
async #pump(sessionId, signal) {
|
|
35763
|
+
let afterSequence = this.acknowledged.get(sessionId) ?? 0;
|
|
35764
|
+
while (!signal.aborted) {
|
|
35765
|
+
const raw = await this.callLocalTool("watch_process_events", {
|
|
35766
|
+
sessionId,
|
|
35767
|
+
afterSequence,
|
|
35768
|
+
timeoutMs: 1e4,
|
|
35769
|
+
maxEvents: 32
|
|
35770
|
+
});
|
|
35771
|
+
if (signal.aborted) break;
|
|
35772
|
+
const batch = batchSchema.parse(structuredContent(raw));
|
|
35773
|
+
if (batch.sessionId !== sessionId) {
|
|
35774
|
+
throw new Error("Process event watch returned a different session ID.");
|
|
35775
|
+
}
|
|
35776
|
+
if (batch.nextSequence < afterSequence) {
|
|
35777
|
+
throw new Error("Process event cursor moved backwards.");
|
|
35778
|
+
}
|
|
35779
|
+
if (batch.events.length === 0 && batch.finished) break;
|
|
35780
|
+
if (batch.events.length === 0) continue;
|
|
35781
|
+
let confirmed = false;
|
|
35782
|
+
for (let attempt = 0; attempt < 5 && !signal.aborted; attempt++) {
|
|
35783
|
+
for (const [index, event] of batch.events.entries()) {
|
|
35784
|
+
if (signal.aborted) break;
|
|
35785
|
+
if (event.sessionId !== sessionId) {
|
|
35786
|
+
throw new Error("Process event belongs to another session.");
|
|
35787
|
+
}
|
|
35788
|
+
this.#lastSent.set(
|
|
35789
|
+
sessionId,
|
|
35790
|
+
Math.max(this.#lastSent.get(sessionId) ?? 0, event.sequence)
|
|
35791
|
+
);
|
|
35792
|
+
this.send({
|
|
35793
|
+
type: "process_event",
|
|
35794
|
+
event,
|
|
35795
|
+
missedEvents: index === 0 && batch.missedEvents
|
|
35796
|
+
});
|
|
35797
|
+
}
|
|
35798
|
+
confirmed = await this.#waitForAck(
|
|
35799
|
+
sessionId,
|
|
35800
|
+
batch.nextSequence,
|
|
35801
|
+
signal
|
|
35802
|
+
);
|
|
35803
|
+
if (confirmed) break;
|
|
35804
|
+
}
|
|
35805
|
+
if (!confirmed) {
|
|
35806
|
+
if (signal.aborted) break;
|
|
35807
|
+
throw new Error(
|
|
35808
|
+
"PROCESS_EVENT_ACK_TIMEOUT: Gateway did not acknowledge event batch."
|
|
35809
|
+
);
|
|
35810
|
+
}
|
|
35811
|
+
afterSequence = batch.nextSequence;
|
|
35812
|
+
if (batch.finished && batch.events.length < 32) break;
|
|
35813
|
+
}
|
|
35814
|
+
}
|
|
35815
|
+
close() {
|
|
35816
|
+
this.#closed = true;
|
|
35817
|
+
for (const controller of this.#watchers.values()) controller.abort();
|
|
35818
|
+
this.#watchers.clear();
|
|
35819
|
+
for (const wake of this.#ackWaiters.values()) {
|
|
35820
|
+
for (const notify of wake) notify();
|
|
35821
|
+
}
|
|
35822
|
+
this.#ackWaiters.clear();
|
|
35823
|
+
}
|
|
35824
|
+
};
|
|
35825
|
+
|
|
35489
35826
|
// src/agent-core.ts
|
|
35490
|
-
var
|
|
35491
|
-
|
|
35827
|
+
var supportedAgentTools = [
|
|
35828
|
+
"get_system_info",
|
|
35829
|
+
"list_directory",
|
|
35830
|
+
"read_text_file",
|
|
35831
|
+
"start_process",
|
|
35832
|
+
"read_process_output",
|
|
35833
|
+
"watch_process_events",
|
|
35834
|
+
"terminate_process",
|
|
35835
|
+
"list_processes",
|
|
35836
|
+
"write_text_file"
|
|
35837
|
+
];
|
|
35838
|
+
var destructiveTools = /* @__PURE__ */ new Set([
|
|
35839
|
+
"start_process",
|
|
35840
|
+
"terminate_process",
|
|
35841
|
+
"write_text_file"
|
|
35842
|
+
]);
|
|
35843
|
+
var requestEnvelope = external_exports.object({
|
|
35492
35844
|
type: external_exports.literal("request"),
|
|
35493
|
-
requestId: external_exports.string().uuid()
|
|
35494
|
-
|
|
35495
|
-
|
|
35496
|
-
|
|
35497
|
-
|
|
35498
|
-
|
|
35499
|
-
|
|
35500
|
-
|
|
35501
|
-
|
|
35502
|
-
|
|
35503
|
-
|
|
35845
|
+
requestId: external_exports.string().uuid()
|
|
35846
|
+
});
|
|
35847
|
+
var requestSchema = requestEnvelope.extend({
|
|
35848
|
+
tool: external_exports.enum(supportedAgentTools),
|
|
35849
|
+
arguments: external_exports.record(external_exports.string(), external_exports.unknown()),
|
|
35850
|
+
deadlineAt: external_exports.number().int().positive().optional()
|
|
35851
|
+
});
|
|
35852
|
+
var cancelSchema = external_exports.object({
|
|
35853
|
+
type: external_exports.literal("cancel"),
|
|
35854
|
+
requestId: external_exports.string().uuid()
|
|
35855
|
+
});
|
|
35856
|
+
var ackSchema = external_exports.object({
|
|
35857
|
+
type: external_exports.literal("process_event_ack"),
|
|
35858
|
+
sessionId: external_exports.string().uuid(),
|
|
35859
|
+
sequence: external_exports.number().int().positive()
|
|
35860
|
+
});
|
|
35861
|
+
var DEFAULT_REQUEST_LIFETIME_MS = 6e4;
|
|
35862
|
+
function assertActive(signal) {
|
|
35863
|
+
if (signal?.aborted) {
|
|
35864
|
+
throw new Error(
|
|
35865
|
+
"REQUEST_EXPIRED: The operation was cancelled or its approval deadline expired."
|
|
35866
|
+
);
|
|
35867
|
+
}
|
|
35868
|
+
}
|
|
35869
|
+
async function authorizeToolCall(tool, args, allowDestructive, approve, signal) {
|
|
35870
|
+
assertActive(signal);
|
|
35504
35871
|
if (!destructiveTools.has(tool)) return;
|
|
35505
35872
|
if (!allowDestructive) {
|
|
35506
35873
|
throw new Error(
|
|
35507
35874
|
"This action requires local approval. Commands and writes are disabled unless explicitly enabled."
|
|
35508
35875
|
);
|
|
35509
35876
|
}
|
|
35510
|
-
if (!approve || !await approve(tool, args)) {
|
|
35877
|
+
if (!approve || !await approve(tool, args, signal)) {
|
|
35878
|
+
assertActive(signal);
|
|
35511
35879
|
throw new Error("The action was not approved on the connected machine.");
|
|
35512
35880
|
}
|
|
35881
|
+
assertActive(signal);
|
|
35513
35882
|
}
|
|
35514
|
-
async function connectOnce(options) {
|
|
35883
|
+
async function connectOnce(options, acknowledged) {
|
|
35515
35884
|
const { config: config2, signal } = options;
|
|
35885
|
+
if (!config2.MACHINE_TERMINAL_DEVICE_SECRET) {
|
|
35886
|
+
throw new Error(
|
|
35887
|
+
"DEVICE_PAIRING_REQUIRED: Connect using the CLI or set MACHINE_TERMINAL_DEVICE_SECRET."
|
|
35888
|
+
);
|
|
35889
|
+
}
|
|
35516
35890
|
const client = new Client({
|
|
35517
35891
|
name: "terminal-commands-cli",
|
|
35518
|
-
version: "0.
|
|
35519
|
-
});
|
|
35520
|
-
await client.connect(
|
|
35521
|
-
new StreamableHTTPClientTransport(
|
|
35522
|
-
new URL(config2.MACHINE_TERMINAL_LOCAL_MCP_URL)
|
|
35523
|
-
)
|
|
35524
|
-
);
|
|
35525
|
-
const token = options.getToken ? await options.getToken() : config2.MACHINE_TERMINAL_DEVICE_TOKEN;
|
|
35526
|
-
const socket = new wrapper_default(config2.MACHINE_TERMINAL_GATEWAY_URL, {
|
|
35527
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
35892
|
+
version: "0.2.0"
|
|
35528
35893
|
});
|
|
35529
|
-
|
|
35530
|
-
|
|
35531
|
-
|
|
35532
|
-
|
|
35533
|
-
|
|
35534
|
-
|
|
35535
|
-
|
|
35536
|
-
|
|
35537
|
-
|
|
35538
|
-
|
|
35894
|
+
let disposeSocket;
|
|
35895
|
+
try {
|
|
35896
|
+
await client.connect(
|
|
35897
|
+
new StreamableHTTPClientTransport(
|
|
35898
|
+
new URL(config2.MACHINE_TERMINAL_LOCAL_MCP_URL),
|
|
35899
|
+
{
|
|
35900
|
+
requestInit: {
|
|
35901
|
+
headers: config2.MACHINE_TERMINAL_LOCAL_AGENT_TOKEN ? {
|
|
35902
|
+
"X-Terminal-Commands-Agent-Token": config2.MACHINE_TERMINAL_LOCAL_AGENT_TOKEN
|
|
35903
|
+
} : {}
|
|
35904
|
+
}
|
|
35905
|
+
}
|
|
35906
|
+
)
|
|
35539
35907
|
);
|
|
35540
|
-
|
|
35541
|
-
|
|
35542
|
-
|
|
35543
|
-
|
|
35544
|
-
|
|
35545
|
-
|
|
35546
|
-
|
|
35547
|
-
|
|
35548
|
-
|
|
35908
|
+
const token = options.getToken ? await options.getToken() : config2.MACHINE_TERMINAL_DEVICE_TOKEN;
|
|
35909
|
+
const socket = new wrapper_default(config2.MACHINE_TERMINAL_GATEWAY_URL, {
|
|
35910
|
+
headers: { Authorization: "Bearer " + token }
|
|
35911
|
+
});
|
|
35912
|
+
disposeSocket = () => socket.terminate();
|
|
35913
|
+
const pending = /* @__PURE__ */ new Map();
|
|
35914
|
+
const forwarder = new ProcessEventForwarder(
|
|
35915
|
+
async (name, args) => client.callTool({ name, arguments: args }),
|
|
35916
|
+
(message) => {
|
|
35917
|
+
if (socket.readyState !== wrapper_default.OPEN) return;
|
|
35918
|
+
if (socket.bufferedAmount > 512e3) {
|
|
35919
|
+
socket.close(1013, "Process event backpressure");
|
|
35920
|
+
throw new Error(
|
|
35921
|
+
"Process event WebSocket backpressure limit reached."
|
|
35922
|
+
);
|
|
35923
|
+
}
|
|
35924
|
+
socket.send(JSON.stringify(message));
|
|
35925
|
+
},
|
|
35926
|
+
options.log,
|
|
35927
|
+
acknowledged,
|
|
35928
|
+
3e3,
|
|
35929
|
+
() => {
|
|
35930
|
+
if (socket.readyState === wrapper_default.OPEN) {
|
|
35931
|
+
socket.close(1013, "Process event delivery stalled");
|
|
35932
|
+
}
|
|
35933
|
+
}
|
|
35934
|
+
);
|
|
35935
|
+
const stop = () => socket.close(1e3, "Client stopping.");
|
|
35936
|
+
signal?.addEventListener("abort", stop, { once: true });
|
|
35937
|
+
socket.on("open", () => {
|
|
35938
|
+
socket.send(
|
|
35939
|
+
JSON.stringify({
|
|
35940
|
+
type: "hello",
|
|
35941
|
+
deviceId: config2.MACHINE_TERMINAL_DEVICE_ID,
|
|
35942
|
+
deviceSecret: config2.MACHINE_TERMINAL_DEVICE_SECRET,
|
|
35943
|
+
name: config2.MACHINE_TERMINAL_DEVICE_NAME,
|
|
35944
|
+
platform: os.platform() + "-" + os.arch(),
|
|
35945
|
+
protocolVersion: 2,
|
|
35946
|
+
capabilities: supportedAgentTools
|
|
35947
|
+
})
|
|
35948
|
+
);
|
|
35949
|
+
});
|
|
35950
|
+
socket.on("message", (raw) => {
|
|
35951
|
+
try {
|
|
35952
|
+
const message = JSON.parse(raw.toString());
|
|
35953
|
+
const cancelled = cancelSchema.safeParse(message);
|
|
35954
|
+
if (cancelled.success) {
|
|
35955
|
+
pending.get(cancelled.data.requestId)?.abort();
|
|
35956
|
+
return;
|
|
35957
|
+
}
|
|
35958
|
+
const ack = ackSchema.safeParse(message);
|
|
35959
|
+
if (ack.success) {
|
|
35960
|
+
forwarder.acknowledge(ack.data.sessionId, ack.data.sequence);
|
|
35961
|
+
return;
|
|
35962
|
+
}
|
|
35963
|
+
if (message && typeof message === "object" && "type" in message && message.type === "ready") {
|
|
35964
|
+
void forwarder.recover().catch(
|
|
35965
|
+
(error62) => options.log?.(
|
|
35966
|
+
"Could not recover local process streams: " + (error62 instanceof Error ? error62.message : "unknown error")
|
|
35967
|
+
)
|
|
35968
|
+
);
|
|
35969
|
+
return;
|
|
35970
|
+
}
|
|
35971
|
+
} catch {
|
|
35972
|
+
options.log?.("Ignoring a malformed gateway message.");
|
|
35973
|
+
return;
|
|
35974
|
+
}
|
|
35975
|
+
void handleMessage(options, client, socket, raw, pending, forwarder);
|
|
35976
|
+
});
|
|
35977
|
+
socket.on("close", () => {
|
|
35978
|
+
forwarder.close();
|
|
35979
|
+
for (const controller of pending.values()) controller.abort();
|
|
35980
|
+
});
|
|
35981
|
+
await new Promise((resolve, reject) => {
|
|
35982
|
+
socket.once("close", resolve);
|
|
35983
|
+
socket.once("error", reject);
|
|
35984
|
+
}).finally(() => signal?.removeEventListener("abort", stop));
|
|
35985
|
+
} finally {
|
|
35986
|
+
disposeSocket?.();
|
|
35987
|
+
await client.close().catch(() => void 0);
|
|
35988
|
+
}
|
|
35549
35989
|
}
|
|
35550
|
-
async function handleMessage(options, client, socket, raw) {
|
|
35990
|
+
async function handleMessage(options, client, socket, raw, pending, forwarder) {
|
|
35551
35991
|
let requestId;
|
|
35992
|
+
let controller;
|
|
35993
|
+
let deadlineTimer;
|
|
35552
35994
|
try {
|
|
35553
|
-
const
|
|
35554
|
-
|
|
35995
|
+
const payload = JSON.parse(raw.toString());
|
|
35996
|
+
const envelope = requestEnvelope.safeParse(payload);
|
|
35997
|
+
if (!envelope.success) {
|
|
35998
|
+
options.log?.("Ignoring a request with an invalid envelope.");
|
|
35999
|
+
return;
|
|
36000
|
+
}
|
|
36001
|
+
requestId = envelope.data.requestId;
|
|
36002
|
+
const parsed = requestSchema.safeParse(payload);
|
|
36003
|
+
if (!parsed.success) {
|
|
36004
|
+
const tool = payload && typeof payload === "object" && "tool" in payload ? payload.tool : void 0;
|
|
36005
|
+
if (typeof tool === "string" && !supportedAgentTools.includes(tool)) {
|
|
36006
|
+
throw new Error(
|
|
36007
|
+
"UNSUPPORTED_TOOL: " + tool + ". Update the local companion."
|
|
36008
|
+
);
|
|
36009
|
+
}
|
|
36010
|
+
throw new Error("INVALID_REQUEST: Invalid tool arguments or deadline.");
|
|
36011
|
+
}
|
|
36012
|
+
if (pending.has(requestId)) {
|
|
36013
|
+
throw new Error(
|
|
36014
|
+
"DUPLICATE_REQUEST: This request is already in progress."
|
|
36015
|
+
);
|
|
36016
|
+
}
|
|
36017
|
+
const deadlineAt = parsed.data.deadlineAt ?? Date.now() + DEFAULT_REQUEST_LIFETIME_MS;
|
|
36018
|
+
if (deadlineAt <= Date.now()) {
|
|
36019
|
+
throw new Error("REQUEST_EXPIRED: The request deadline has passed.");
|
|
36020
|
+
}
|
|
36021
|
+
controller = new AbortController();
|
|
36022
|
+
pending.set(requestId, controller);
|
|
36023
|
+
deadlineTimer = setTimeout(
|
|
36024
|
+
() => controller?.abort(),
|
|
36025
|
+
Math.max(0, deadlineAt - Date.now())
|
|
36026
|
+
);
|
|
35555
36027
|
await authorizeToolCall(
|
|
35556
|
-
parsed.tool,
|
|
35557
|
-
parsed.arguments,
|
|
36028
|
+
parsed.data.tool,
|
|
36029
|
+
parsed.data.arguments,
|
|
35558
36030
|
options.config.MACHINE_TERMINAL_AGENT_ALLOW_DESTRUCTIVE === "1",
|
|
35559
|
-
options.approve
|
|
36031
|
+
options.approve,
|
|
36032
|
+
controller.signal
|
|
35560
36033
|
);
|
|
36034
|
+
assertActive(controller.signal);
|
|
35561
36035
|
const result = await client.callTool({
|
|
35562
|
-
name: parsed.tool,
|
|
35563
|
-
arguments: parsed.arguments
|
|
36036
|
+
name: parsed.data.tool,
|
|
36037
|
+
arguments: parsed.data.arguments
|
|
35564
36038
|
});
|
|
35565
36039
|
if (result.isError) {
|
|
35566
36040
|
const content = Array.isArray(result.content) ? result.content : [];
|
|
@@ -35577,6 +36051,12 @@ async function handleMessage(options, client, socket, raw) {
|
|
|
35577
36051
|
result: result.structuredContent ?? { content: result.content }
|
|
35578
36052
|
})
|
|
35579
36053
|
);
|
|
36054
|
+
if (parsed.data.tool === "start_process") {
|
|
36055
|
+
const snapshot = result.structuredContent;
|
|
36056
|
+
if (snapshot && typeof snapshot === "object" && "sessionId" in snapshot && typeof snapshot.sessionId === "string" && "taskId" in snapshot && typeof snapshot.taskId === "string") {
|
|
36057
|
+
forwarder.watch(snapshot.sessionId);
|
|
36058
|
+
}
|
|
36059
|
+
}
|
|
35580
36060
|
}
|
|
35581
36061
|
} catch (error62) {
|
|
35582
36062
|
if (!requestId || socket.readyState !== wrapper_default.OPEN) return;
|
|
@@ -35587,18 +36067,24 @@ async function handleMessage(options, client, socket, raw) {
|
|
|
35587
36067
|
error: error62 instanceof Error ? error62.message : "Local request failed."
|
|
35588
36068
|
})
|
|
35589
36069
|
);
|
|
36070
|
+
} finally {
|
|
36071
|
+
if (deadlineTimer) clearTimeout(deadlineTimer);
|
|
36072
|
+
if (requestId && controller && pending.get(requestId) === controller) {
|
|
36073
|
+
pending.delete(requestId);
|
|
36074
|
+
}
|
|
35590
36075
|
}
|
|
35591
36076
|
}
|
|
35592
36077
|
async function runAgent(options) {
|
|
35593
36078
|
let reconnectDelayMs = 1e3;
|
|
36079
|
+
const acknowledged = /* @__PURE__ */ new Map();
|
|
35594
36080
|
while (!options.signal?.aborted) {
|
|
35595
36081
|
try {
|
|
35596
|
-
await connectOnce(options);
|
|
36082
|
+
await connectOnce(options, acknowledged);
|
|
35597
36083
|
reconnectDelayMs = 1e3;
|
|
35598
36084
|
} catch (error62) {
|
|
35599
36085
|
if (options.signal?.aborted) break;
|
|
35600
36086
|
options.log?.(
|
|
35601
|
-
|
|
36087
|
+
"Connection failed: " + (error62 instanceof Error ? error62.message : "unknown error")
|
|
35602
36088
|
);
|
|
35603
36089
|
}
|
|
35604
36090
|
if (options.signal?.aborted) break;
|
|
@@ -35617,12 +36103,120 @@ async function runAgent(options) {
|
|
|
35617
36103
|
}
|
|
35618
36104
|
}
|
|
35619
36105
|
|
|
36106
|
+
// src/local-agent-auth.ts
|
|
36107
|
+
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
36108
|
+
function localHealthProof(secret, challenge) {
|
|
36109
|
+
if (!secret || !/^[a-f0-9]{64}$/i.test(secret)) return null;
|
|
36110
|
+
if (!challenge || !/^[a-f0-9]{64}$/i.test(challenge)) return null;
|
|
36111
|
+
return createHmac("sha256", Buffer.from(secret, "hex")).update(challenge, "ascii").digest("hex");
|
|
36112
|
+
}
|
|
36113
|
+
|
|
36114
|
+
// src/device-management.ts
|
|
36115
|
+
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]", "::1"]);
|
|
36116
|
+
function validDeviceId(id) {
|
|
36117
|
+
return /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(id);
|
|
36118
|
+
}
|
|
36119
|
+
function managementUrl(connectionUrl) {
|
|
36120
|
+
const url2 = new URL(connectionUrl);
|
|
36121
|
+
if (url2.username || url2.password || url2.pathname !== "/device/connect") {
|
|
36122
|
+
throw new Error(
|
|
36123
|
+
"Expected a credential-free Terminal Commands gateway URL."
|
|
36124
|
+
);
|
|
36125
|
+
}
|
|
36126
|
+
if (url2.protocol === "wss:") {
|
|
36127
|
+
url2.protocol = "https:";
|
|
36128
|
+
} else if (url2.protocol === "ws:" && LOOPBACK_HOSTS.has(url2.hostname)) {
|
|
36129
|
+
url2.protocol = "http:";
|
|
36130
|
+
} else {
|
|
36131
|
+
throw new Error("Device revocation requires HTTPS, or loopback test HTTP.");
|
|
36132
|
+
}
|
|
36133
|
+
url2.search = "";
|
|
36134
|
+
url2.hash = "";
|
|
36135
|
+
return url2;
|
|
36136
|
+
}
|
|
36137
|
+
function deviceRevocationUrl(connectionUrl, deviceId) {
|
|
36138
|
+
if (!validDeviceId(deviceId)) throw new Error("Invalid device identifier.");
|
|
36139
|
+
const url2 = managementUrl(connectionUrl);
|
|
36140
|
+
url2.pathname = "/devices/" + encodeURIComponent(deviceId) + "/revoke";
|
|
36141
|
+
return url2.toString();
|
|
36142
|
+
}
|
|
36143
|
+
function pairedDevicesUrl(connectionUrl, cursor) {
|
|
36144
|
+
if (cursor !== void 0 && !validDeviceId(cursor)) {
|
|
36145
|
+
throw new Error("Invalid pairing-list cursor.");
|
|
36146
|
+
}
|
|
36147
|
+
const url2 = managementUrl(connectionUrl);
|
|
36148
|
+
url2.pathname = "/devices";
|
|
36149
|
+
if (cursor !== void 0) url2.searchParams.set("after", cursor);
|
|
36150
|
+
return url2.toString();
|
|
36151
|
+
}
|
|
36152
|
+
async function fetchPairedDevices(connectionUrl, managementToken2, fetchImpl = fetch) {
|
|
36153
|
+
if (!managementToken2 || /\s/.test(managementToken2)) {
|
|
36154
|
+
throw new Error("Invalid device-management access token.");
|
|
36155
|
+
}
|
|
36156
|
+
const devices = [];
|
|
36157
|
+
const seen = /* @__PURE__ */ new Set();
|
|
36158
|
+
let cursor;
|
|
36159
|
+
for (let page = 0; page < 10; page++) {
|
|
36160
|
+
const response = await fetchImpl(pairedDevicesUrl(connectionUrl, cursor), {
|
|
36161
|
+
headers: { Authorization: "Bearer " + managementToken2 },
|
|
36162
|
+
redirect: "error",
|
|
36163
|
+
signal: AbortSignal.timeout(15e3)
|
|
36164
|
+
});
|
|
36165
|
+
if (!response.ok) {
|
|
36166
|
+
throw new Error(
|
|
36167
|
+
"Could not list paired devices (" + response.status + ")."
|
|
36168
|
+
);
|
|
36169
|
+
}
|
|
36170
|
+
const payload = await response.json();
|
|
36171
|
+
if (!Array.isArray(payload.devices) || payload.devices.length > 100) {
|
|
36172
|
+
throw new Error("Invalid paired-device response.");
|
|
36173
|
+
}
|
|
36174
|
+
for (const entry of payload.devices) {
|
|
36175
|
+
const item = entry;
|
|
36176
|
+
if (typeof item.deviceId !== "string" || !validDeviceId(item.deviceId) || typeof item.pairedAt !== "string" || !(typeof item.revokedAt === "string" || item.revokedAt === null) || typeof item.connected !== "boolean" || seen.has(item.deviceId)) {
|
|
36177
|
+
throw new Error("Invalid or repeated device in pairing inventory.");
|
|
36178
|
+
}
|
|
36179
|
+
seen.add(item.deviceId);
|
|
36180
|
+
devices.push(item);
|
|
36181
|
+
}
|
|
36182
|
+
if (payload.nextCursor === null) return { devices, truncated: false };
|
|
36183
|
+
if (typeof payload.nextCursor !== "string" || !validDeviceId(payload.nextCursor) || cursor === payload.nextCursor) {
|
|
36184
|
+
throw new Error("Invalid device-list continuation cursor.");
|
|
36185
|
+
}
|
|
36186
|
+
cursor = payload.nextCursor;
|
|
36187
|
+
}
|
|
36188
|
+
return { devices, truncated: true };
|
|
36189
|
+
}
|
|
36190
|
+
async function requestDeviceRevocation(connectionUrl, deviceId, managementToken2, fetchImpl = fetch) {
|
|
36191
|
+
if (!managementToken2 || /\s/.test(managementToken2)) {
|
|
36192
|
+
throw new Error("Invalid device-management access token.");
|
|
36193
|
+
}
|
|
36194
|
+
const endpoint2 = deviceRevocationUrl(connectionUrl, deviceId);
|
|
36195
|
+
const response = await fetchImpl(endpoint2, {
|
|
36196
|
+
method: "POST",
|
|
36197
|
+
headers: { Authorization: "Bearer " + managementToken2 },
|
|
36198
|
+
redirect: "error",
|
|
36199
|
+
signal: AbortSignal.timeout(15e3)
|
|
36200
|
+
});
|
|
36201
|
+
if (!response.ok) {
|
|
36202
|
+
throw new Error(
|
|
36203
|
+
"Device revocation was not confirmed (" + response.status + ")."
|
|
36204
|
+
);
|
|
36205
|
+
}
|
|
36206
|
+
const result = await response.json();
|
|
36207
|
+
if (result.deviceId !== deviceId || result.revoked !== true) {
|
|
36208
|
+
throw new Error("Gateway did not confirm this device's revocation.");
|
|
36209
|
+
}
|
|
36210
|
+
}
|
|
36211
|
+
|
|
35620
36212
|
// src/agent-config.ts
|
|
35621
36213
|
import os2 from "node:os";
|
|
35622
36214
|
var schema = external_exports.object({
|
|
35623
36215
|
MACHINE_TERMINAL_GATEWAY_URL: external_exports.string().url(),
|
|
35624
36216
|
MACHINE_TERMINAL_DEVICE_TOKEN: external_exports.string().min(1),
|
|
36217
|
+
MACHINE_TERMINAL_LOCAL_AGENT_TOKEN: external_exports.string().regex(/^[a-f0-9]{64}$/i).optional(),
|
|
35625
36218
|
MACHINE_TERMINAL_DEVICE_ID: external_exports.string().min(1).max(128),
|
|
36219
|
+
MACHINE_TERMINAL_DEVICE_SECRET: external_exports.string().regex(/^[a-f0-9]{64}$/i).optional(),
|
|
35626
36220
|
MACHINE_TERMINAL_DEVICE_NAME: external_exports.string().min(1).max(128).default(os2.hostname()),
|
|
35627
36221
|
MACHINE_TERMINAL_LOCAL_MCP_URL: external_exports.string().url().default("http://127.0.0.1:3333/mcp"),
|
|
35628
36222
|
MACHINE_TERMINAL_AGENT_ALLOW_DESTRUCTIVE: external_exports.enum(["0", "1"]).default("0"),
|
|
@@ -35686,52 +36280,78 @@ function keyValueBlock(rows) {
|
|
|
35686
36280
|
}
|
|
35687
36281
|
|
|
35688
36282
|
// src/approval.ts
|
|
36283
|
+
var DEFAULT_APPROVAL_TIMEOUT_MS = 3e4;
|
|
36284
|
+
function safeLabel(value) {
|
|
36285
|
+
return JSON.stringify(String(value));
|
|
36286
|
+
}
|
|
35689
36287
|
function approvalSummary(tool, args) {
|
|
35690
36288
|
if (tool === "write_text_file") {
|
|
35691
36289
|
const bytes = Buffer.byteLength(String(args.contents ?? ""));
|
|
35692
|
-
return
|
|
36290
|
+
return "Write " + bytes + " bytes to " + safeLabel(args.path ?? "an unknown path") + (args.overwrite ? " (overwrite)" : "");
|
|
35693
36291
|
}
|
|
35694
|
-
if (tool === "
|
|
36292
|
+
if (tool === "start_process") {
|
|
35695
36293
|
const command = [
|
|
35696
|
-
|
|
35697
|
-
...Array.isArray(args.args) ? args.args.map(
|
|
36294
|
+
safeLabel(args.program ?? ""),
|
|
36295
|
+
...Array.isArray(args.args) ? args.args.map(safeLabel) : []
|
|
35698
36296
|
];
|
|
35699
|
-
return
|
|
36297
|
+
return "Start: " + command.join(" ") + " (cwd: " + safeLabel(args.cwd ?? "configured root") + ")";
|
|
36298
|
+
}
|
|
36299
|
+
if (tool === "terminate_process") {
|
|
36300
|
+
return "Terminate process session " + safeLabel(args.sessionId ?? "");
|
|
35700
36301
|
}
|
|
35701
|
-
return
|
|
36302
|
+
return "Run destructive tool: " + safeLabel(tool);
|
|
35702
36303
|
}
|
|
35703
36304
|
function createAutoApprover() {
|
|
35704
|
-
return (tool, args) => {
|
|
36305
|
+
return (tool, args, signal) => {
|
|
36306
|
+
if (signal?.aborted) return Promise.resolve(false);
|
|
35705
36307
|
console.log(
|
|
35706
|
-
|
|
35707
|
-
${warn("Approval requested:")} ${approvalSummary(tool, args)} ${dim("(auto-approved)")}`
|
|
36308
|
+
"\n" + warn("Approval requested:") + " " + approvalSummary(tool, args) + " " + dim("(auto-approved)")
|
|
35708
36309
|
);
|
|
35709
36310
|
return Promise.resolve(true);
|
|
35710
36311
|
};
|
|
35711
36312
|
}
|
|
35712
|
-
function createTerminalApprover() {
|
|
35713
|
-
|
|
35714
|
-
|
|
35715
|
-
|
|
35716
|
-
|
|
36313
|
+
function createTerminalApprover(timeoutMs = DEFAULT_APPROVAL_TIMEOUT_MS) {
|
|
36314
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs < 1) {
|
|
36315
|
+
throw new Error("Approval timeout must be a positive integer.");
|
|
36316
|
+
}
|
|
36317
|
+
let queue = Promise.resolve();
|
|
36318
|
+
return (tool, args, signal) => {
|
|
36319
|
+
const next = queue.then(async () => {
|
|
36320
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY || signal?.aborted) {
|
|
36321
|
+
return false;
|
|
36322
|
+
}
|
|
36323
|
+
const controller = new AbortController();
|
|
36324
|
+
const abort = () => controller.abort();
|
|
36325
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
36326
|
+
const timer = setTimeout(abort, timeoutMs);
|
|
35717
36327
|
const prompt = createInterface({
|
|
35718
36328
|
input: process.stdin,
|
|
35719
36329
|
output: process.stdout
|
|
35720
36330
|
});
|
|
35721
36331
|
try {
|
|
36332
|
+
if (signal?.aborted) return false;
|
|
35722
36333
|
console.log(
|
|
35723
|
-
|
|
35724
|
-
${warn("Approval requested:")} ${approvalSummary(tool, args)}`
|
|
36334
|
+
"\n" + warn("Approval requested:") + " " + approvalSummary(tool, args)
|
|
35725
36335
|
);
|
|
35726
36336
|
const answer = await prompt.question(
|
|
35727
|
-
|
|
36337
|
+
bold("Approve this action?") + " [y/N] ",
|
|
36338
|
+
{ signal: controller.signal }
|
|
35728
36339
|
);
|
|
35729
|
-
return (/* @__PURE__ */ new Set(["y", "yes"])).has(answer.trim().toLowerCase());
|
|
36340
|
+
return !controller.signal.aborted && (/* @__PURE__ */ new Set(["y", "yes"])).has(answer.trim().toLowerCase());
|
|
36341
|
+
} catch (error62) {
|
|
36342
|
+
if (controller.signal.aborted) return false;
|
|
36343
|
+
throw error62;
|
|
35730
36344
|
} finally {
|
|
36345
|
+
clearTimeout(timer);
|
|
36346
|
+
signal?.removeEventListener("abort", abort);
|
|
35731
36347
|
prompt.close();
|
|
35732
36348
|
}
|
|
35733
36349
|
});
|
|
35734
|
-
|
|
36350
|
+
queue = next.then(
|
|
36351
|
+
() => void 0,
|
|
36352
|
+
() => void 0
|
|
36353
|
+
);
|
|
36354
|
+
return next;
|
|
35735
36355
|
};
|
|
35736
36356
|
}
|
|
35737
36357
|
|
|
@@ -35744,15 +36364,26 @@ var COMMANDS = [
|
|
|
35744
36364
|
"connect",
|
|
35745
36365
|
"status",
|
|
35746
36366
|
"logout",
|
|
36367
|
+
"revoke-device",
|
|
36368
|
+
"paired-devices",
|
|
35747
36369
|
"help"
|
|
35748
36370
|
];
|
|
35749
36371
|
var COMMAND_SUMMARIES = {
|
|
35750
36372
|
login: "Sign in using a URL and one-time code",
|
|
35751
36373
|
connect: "Connect this machine until stopped",
|
|
35752
36374
|
status: "Show sign-in state and the settings connect would use",
|
|
35753
|
-
logout: "Remove locally saved credentials"
|
|
36375
|
+
logout: "Remove locally saved credentials",
|
|
36376
|
+
"revoke-device": "Revoke one paired device using fresh owner authentication",
|
|
36377
|
+
"paired-devices": "List offline, connected and revoked device identities"
|
|
35754
36378
|
};
|
|
35755
|
-
var AUTH_COMMANDS = [
|
|
36379
|
+
var AUTH_COMMANDS = [
|
|
36380
|
+
"login",
|
|
36381
|
+
"connect",
|
|
36382
|
+
"status",
|
|
36383
|
+
"logout",
|
|
36384
|
+
"revoke-device",
|
|
36385
|
+
"paired-devices"
|
|
36386
|
+
];
|
|
35756
36387
|
var OPTIONS = [
|
|
35757
36388
|
{
|
|
35758
36389
|
name: "root",
|
|
@@ -35777,8 +36408,8 @@ var OPTIONS = [
|
|
|
35777
36408
|
kind: "string",
|
|
35778
36409
|
env: "MACHINE_TERMINAL_DEVICE_ID",
|
|
35779
36410
|
placeholder: "<id>",
|
|
35780
|
-
commands: ["connect", "status"],
|
|
35781
|
-
description: "Device identifier
|
|
36411
|
+
commands: ["connect", "status", "revoke-device"],
|
|
36412
|
+
description: "Device identifier. Required explicitly for revoke-device; defaults to saved ID for connect/status."
|
|
35782
36413
|
},
|
|
35783
36414
|
{
|
|
35784
36415
|
name: "port",
|
|
@@ -35801,7 +36432,7 @@ var OPTIONS = [
|
|
|
35801
36432
|
kind: "string",
|
|
35802
36433
|
env: "MACHINE_TERMINAL_GATEWAY_URL",
|
|
35803
36434
|
placeholder: "<url>",
|
|
35804
|
-
commands: ["connect", "status"],
|
|
36435
|
+
commands: ["connect", "status", "revoke-device", "paired-devices"],
|
|
35805
36436
|
description: "Gateway WebSocket URL. Defaults to wss://mcp.terminalcommands.fr/device/connect."
|
|
35806
36437
|
},
|
|
35807
36438
|
{
|
|
@@ -36109,6 +36740,8 @@ function renderHelp(command = "help") {
|
|
|
36109
36740
|
" terminal-commands connect --root ~/Documents/GitHub --root ~/work",
|
|
36110
36741
|
" terminal-commands connect --root . --dry-run",
|
|
36111
36742
|
" terminal-commands connect --root ~/code --name laptop --allow-destructive",
|
|
36743
|
+
" terminal-commands paired-devices",
|
|
36744
|
+
" terminal-commands revoke-device --device-id <paired-device-id>",
|
|
36112
36745
|
" npm run cli -- connect --root ~/Documents/GitHub --allowed-program git,node"
|
|
36113
36746
|
);
|
|
36114
36747
|
return lines.join("\n");
|
|
@@ -36116,7 +36749,7 @@ function renderHelp(command = "help") {
|
|
|
36116
36749
|
|
|
36117
36750
|
// src/credential-store.ts
|
|
36118
36751
|
import { promises as fs2 } from "node:fs";
|
|
36119
|
-
import { randomUUID } from "node:crypto";
|
|
36752
|
+
import { randomBytes, randomUUID } from "node:crypto";
|
|
36120
36753
|
import os4 from "node:os";
|
|
36121
36754
|
import path2 from "node:path";
|
|
36122
36755
|
var storedSchema = external_exports.object({
|
|
@@ -36189,6 +36822,23 @@ async function loadOrCreateDeviceId(environment = process.env) {
|
|
|
36189
36822
|
});
|
|
36190
36823
|
return (await fs2.readFile(target, "utf8")).trim();
|
|
36191
36824
|
}
|
|
36825
|
+
async function loadOrCreateDeviceSecret(environment = process.env) {
|
|
36826
|
+
const directory = configDirectory(environment);
|
|
36827
|
+
const target = path2.join(directory, "device-secret");
|
|
36828
|
+
await fs2.mkdir(directory, { recursive: true, mode: 448 });
|
|
36829
|
+
const generated = randomBytes(32).toString("hex");
|
|
36830
|
+
try {
|
|
36831
|
+
await fs2.writeFile(target, generated + "\n", { mode: 384, flag: "wx" });
|
|
36832
|
+
} catch (error62) {
|
|
36833
|
+
if (error62.code !== "EEXIST") throw error62;
|
|
36834
|
+
}
|
|
36835
|
+
const value = (await fs2.readFile(target, "utf8")).trim();
|
|
36836
|
+
if (!/^[a-f0-9]{64}$/i.test(value)) {
|
|
36837
|
+
throw new Error("DEVICE_SECRET_INVALID: Re-pair this machine securely.");
|
|
36838
|
+
}
|
|
36839
|
+
if (process.platform !== "win32") await fs2.chmod(target, 384);
|
|
36840
|
+
return value;
|
|
36841
|
+
}
|
|
36192
36842
|
|
|
36193
36843
|
// src/device-auth.ts
|
|
36194
36844
|
var deviceCodeSchema = external_exports.object({
|
|
@@ -36439,14 +37089,22 @@ async function validCredentials(auth2) {
|
|
|
36439
37089
|
await saveCredentials(refreshed);
|
|
36440
37090
|
return refreshed;
|
|
36441
37091
|
}
|
|
36442
|
-
async function waitForHealth(url2, child) {
|
|
37092
|
+
async function waitForHealth(url2, child, agentToken) {
|
|
37093
|
+
const challenge = randomBytes2(32).toString("hex");
|
|
37094
|
+
const expectedProof = localHealthProof(agentToken, challenge);
|
|
37095
|
+
if (!expectedProof)
|
|
37096
|
+
throw new Error("Invalid local agent startup capability.");
|
|
36443
37097
|
const deadline = Date.now() + 1e4;
|
|
36444
37098
|
while (Date.now() < deadline) {
|
|
36445
|
-
if (child.exitCode !== null)
|
|
37099
|
+
if (child.exitCode !== null || child.signalCode !== null)
|
|
36446
37100
|
throw new Error("The local terminal service could not start.");
|
|
36447
37101
|
try {
|
|
36448
|
-
const response = await fetch(url2
|
|
36449
|
-
|
|
37102
|
+
const response = await fetch(url2, {
|
|
37103
|
+
headers: { "X-Terminal-Commands-Health-Challenge": challenge },
|
|
37104
|
+
signal: AbortSignal.timeout(1e3)
|
|
37105
|
+
});
|
|
37106
|
+
const health = await response.json();
|
|
37107
|
+
if (response.ok && health?.agentProof === expectedProof) return;
|
|
36450
37108
|
} catch {
|
|
36451
37109
|
}
|
|
36452
37110
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
@@ -36465,6 +37123,7 @@ async function connect(environment, parsed) {
|
|
|
36465
37123
|
);
|
|
36466
37124
|
return;
|
|
36467
37125
|
}
|
|
37126
|
+
const deviceSecret = environment.MACHINE_TERMINAL_DEVICE_SECRET ?? await loadOrCreateDeviceSecret();
|
|
36468
37127
|
const auth2 = resolveAuth(environment);
|
|
36469
37128
|
const firstCredentials = await validCredentials(auth2);
|
|
36470
37129
|
console.log(
|
|
@@ -36477,11 +37136,14 @@ async function connect(environment, parsed) {
|
|
|
36477
37136
|
)
|
|
36478
37137
|
);
|
|
36479
37138
|
}
|
|
37139
|
+
const localAgentToken = randomBytes2(32).toString("hex");
|
|
36480
37140
|
const childEnvironment = {
|
|
36481
37141
|
...environment,
|
|
37142
|
+
MACHINE_TERMINAL_LOCAL_AGENT_TOKEN: localAgentToken,
|
|
36482
37143
|
MACHINE_TERMINAL_HOST: settings.host,
|
|
36483
37144
|
MACHINE_TERMINAL_PORT: String(settings.port),
|
|
36484
|
-
MACHINE_TERMINAL_ROOTS: settings.roots.join(path3.delimiter)
|
|
37145
|
+
MACHINE_TERMINAL_ROOTS: settings.roots.join(path3.delimiter),
|
|
37146
|
+
MACHINE_TERMINAL_TASK_RECEIPTS_FILE: environment.MACHINE_TERMINAL_TASK_RECEIPTS_FILE ?? path3.join(configDirectory(environment), "task-receipts.json")
|
|
36485
37147
|
};
|
|
36486
37148
|
const serverEntry = fileURLToPath(new URL("./index.js", import.meta.url));
|
|
36487
37149
|
const local = spawn(process.execPath, [serverEntry], {
|
|
@@ -36493,12 +37155,14 @@ async function connect(environment, parsed) {
|
|
|
36493
37155
|
process.once("SIGINT", stop);
|
|
36494
37156
|
process.once("SIGTERM", stop);
|
|
36495
37157
|
try {
|
|
36496
|
-
await waitForHealth(`${settings.localUrl}/health`, local);
|
|
37158
|
+
await waitForHealth(`${settings.localUrl}/health`, local, localAgentToken);
|
|
36497
37159
|
const config2 = loadAgentConfig({
|
|
36498
37160
|
...childEnvironment,
|
|
36499
37161
|
MACHINE_TERMINAL_GATEWAY_URL: settings.gatewayUrl,
|
|
37162
|
+
MACHINE_TERMINAL_LOCAL_AGENT_TOKEN: localAgentToken,
|
|
36500
37163
|
MACHINE_TERMINAL_DEVICE_TOKEN: firstCredentials.accessToken,
|
|
36501
37164
|
MACHINE_TERMINAL_DEVICE_ID: deviceId,
|
|
37165
|
+
MACHINE_TERMINAL_DEVICE_SECRET: deviceSecret,
|
|
36502
37166
|
MACHINE_TERMINAL_DEVICE_NAME: settings.deviceName,
|
|
36503
37167
|
MACHINE_TERMINAL_LOCAL_MCP_URL: `${settings.localUrl}/mcp`,
|
|
36504
37168
|
MACHINE_TERMINAL_AGENT_ALLOW_DESTRUCTIVE: settings.allowDestructive ? "1" : "0"
|
|
@@ -36573,6 +37237,81 @@ async function status(environment, parsed) {
|
|
|
36573
37237
|
].join("\n")
|
|
36574
37238
|
);
|
|
36575
37239
|
}
|
|
37240
|
+
async function managementToken(environment, purpose) {
|
|
37241
|
+
const management = {
|
|
37242
|
+
...resolveAuth(environment),
|
|
37243
|
+
scope: "openid profile email terminal:devices:revoke"
|
|
37244
|
+
};
|
|
37245
|
+
const code = await requestDeviceCode(management);
|
|
37246
|
+
console.log("\n" + bold("Authorize " + purpose + " in your browser:"));
|
|
37247
|
+
console.log(info(code.verification_uri_complete ?? code.verification_uri));
|
|
37248
|
+
console.log("\n" + bold("One-time code: ") + info(code.user_code));
|
|
37249
|
+
const credentials = await pollForDeviceToken(
|
|
37250
|
+
management,
|
|
37251
|
+
code.device_code,
|
|
37252
|
+
code.expires_in,
|
|
37253
|
+
code.interval
|
|
37254
|
+
);
|
|
37255
|
+
return credentials.accessToken;
|
|
37256
|
+
}
|
|
37257
|
+
async function pairedDevices(environment) {
|
|
37258
|
+
const gateway = environment.MACHINE_TERMINAL_GATEWAY_URL ?? DEFAULTS.gatewayUrl;
|
|
37259
|
+
pairedDevicesUrl(gateway);
|
|
37260
|
+
const token = await managementToken(environment, "paired-device inventory");
|
|
37261
|
+
const result = await fetchPairedDevices(gateway, token);
|
|
37262
|
+
if (result.devices.length === 0) {
|
|
37263
|
+
console.log(info("No paired devices found for this account."));
|
|
37264
|
+
return;
|
|
37265
|
+
}
|
|
37266
|
+
for (const device of result.devices) {
|
|
37267
|
+
const status2 = device.revokedAt ? "revoked" : device.connected ? "connected" : "offline";
|
|
37268
|
+
console.log(
|
|
37269
|
+
device.deviceId + " \u2014 " + status2 + " (paired " + device.pairedAt + ")"
|
|
37270
|
+
);
|
|
37271
|
+
}
|
|
37272
|
+
if (result.truncated) {
|
|
37273
|
+
console.log(warn("More than 1,000 device records: list was truncated."));
|
|
37274
|
+
}
|
|
37275
|
+
}
|
|
37276
|
+
async function revokeDevice(environment, parsed) {
|
|
37277
|
+
const supplied = parsed.options.get("device-id");
|
|
37278
|
+
const deviceId = Array.isArray(supplied) ? supplied[0] : void 0;
|
|
37279
|
+
if (!deviceId) {
|
|
37280
|
+
throw new Error("Specify --device-id of the pairing to revoke.");
|
|
37281
|
+
}
|
|
37282
|
+
const gateway = environment.MACHINE_TERMINAL_GATEWAY_URL ?? DEFAULTS.gatewayUrl;
|
|
37283
|
+
deviceRevocationUrl(gateway, deviceId);
|
|
37284
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
37285
|
+
throw new Error("Revocation needs an interactive confirmation terminal.");
|
|
37286
|
+
}
|
|
37287
|
+
console.log(
|
|
37288
|
+
warn(
|
|
37289
|
+
"Device revocation is permanent for this device ID. It disconnects the companion, but cannot stop already launched OS processes or invalidate Auth0 tokens."
|
|
37290
|
+
)
|
|
37291
|
+
);
|
|
37292
|
+
const prompt = createInterface2({
|
|
37293
|
+
input: process.stdin,
|
|
37294
|
+
output: process.stdout
|
|
37295
|
+
});
|
|
37296
|
+
let answer;
|
|
37297
|
+
try {
|
|
37298
|
+
answer = await prompt.question(
|
|
37299
|
+
"Type REVOKE " + deviceId + " to continue: "
|
|
37300
|
+
);
|
|
37301
|
+
} finally {
|
|
37302
|
+
prompt.close();
|
|
37303
|
+
}
|
|
37304
|
+
if (answer.trim() !== "REVOKE " + deviceId) {
|
|
37305
|
+
throw new Error("Device revocation cancelled.");
|
|
37306
|
+
}
|
|
37307
|
+
const token = await managementToken(environment, "device revocation");
|
|
37308
|
+
await requestDeviceRevocation(gateway, deviceId, token);
|
|
37309
|
+
console.log(
|
|
37310
|
+
ok(
|
|
37311
|
+
"Device " + deviceId + " was revoked and its existing pairing disabled."
|
|
37312
|
+
)
|
|
37313
|
+
);
|
|
37314
|
+
}
|
|
36576
37315
|
async function main() {
|
|
36577
37316
|
const parsed = parseCommandLine(process.argv.slice(2));
|
|
36578
37317
|
if (parsed.help) {
|
|
@@ -36586,6 +37325,12 @@ async function main() {
|
|
|
36586
37325
|
if (parsed.command === "login") return login(environment);
|
|
36587
37326
|
if (parsed.command === "connect") return connect(environment, parsed);
|
|
36588
37327
|
if (parsed.command === "status") return status(environment, parsed);
|
|
37328
|
+
if (parsed.command === "revoke-device") {
|
|
37329
|
+
return revokeDevice(environment, parsed);
|
|
37330
|
+
}
|
|
37331
|
+
if (parsed.command === "paired-devices") {
|
|
37332
|
+
return pairedDevices(environment);
|
|
37333
|
+
}
|
|
36589
37334
|
if (parsed.command === "logout") {
|
|
36590
37335
|
await clearCredentials();
|
|
36591
37336
|
console.log(ok("Signed out and removed locally saved credentials."));
|