terminal-commands 0.1.2 → 0.1.3
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 +5 -0
- package/dist/agent.js +14 -4
- package/dist/cli.js +14 -4
- package/dist/index.js +270 -60
- package/dist/stdio.js +269 -59
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -18,6 +18,11 @@ approval prompts are enabled.
|
|
|
18
18
|
|
|
19
19
|
Command-line programs still execute with your operating-system user permissions. A program can access more than the dedicated filesystem tools, so review every command approval carefully.
|
|
20
20
|
|
|
21
|
+
The MCP command surface exposes process lifecycle operations separately: `start_process` starts
|
|
22
|
+
an approved executable and returns a session ID, `read_process_output` reads bounded output,
|
|
23
|
+
`list_processes` lists sessions, and `terminate_process` stops a session. This keeps terminal
|
|
24
|
+
workflows available while making each model-callable operation explicit.
|
|
25
|
+
|
|
21
26
|
## Run
|
|
22
27
|
|
|
23
28
|
```bash
|
package/dist/agent.js
CHANGED
|
@@ -35480,7 +35480,11 @@ var import_websocket_server = __toESM(require_websocket_server(), 1);
|
|
|
35480
35480
|
var wrapper_default = import_websocket.default;
|
|
35481
35481
|
|
|
35482
35482
|
// src/agent-core.ts
|
|
35483
|
-
var destructiveTools = /* @__PURE__ */ new Set([
|
|
35483
|
+
var destructiveTools = /* @__PURE__ */ new Set([
|
|
35484
|
+
"start_process",
|
|
35485
|
+
"terminate_process",
|
|
35486
|
+
"write_text_file"
|
|
35487
|
+
]);
|
|
35484
35488
|
var requestSchema = external_exports.object({
|
|
35485
35489
|
type: external_exports.literal("request"),
|
|
35486
35490
|
requestId: external_exports.string().uuid(),
|
|
@@ -35488,7 +35492,10 @@ var requestSchema = external_exports.object({
|
|
|
35488
35492
|
"get_system_info",
|
|
35489
35493
|
"list_directory",
|
|
35490
35494
|
"read_text_file",
|
|
35491
|
-
"
|
|
35495
|
+
"start_process",
|
|
35496
|
+
"read_process_output",
|
|
35497
|
+
"terminate_process",
|
|
35498
|
+
"list_processes",
|
|
35492
35499
|
"write_text_file"
|
|
35493
35500
|
]),
|
|
35494
35501
|
arguments: external_exports.record(external_exports.string(), external_exports.unknown())
|
|
@@ -35668,12 +35675,15 @@ function approvalSummary(tool, args) {
|
|
|
35668
35675
|
const bytes = Buffer.byteLength(String(args.contents ?? ""));
|
|
35669
35676
|
return `Write ${bytes} bytes to ${String(args.path ?? "an unknown path")}${args.overwrite ? " (overwrite)" : ""}`;
|
|
35670
35677
|
}
|
|
35671
|
-
if (tool === "
|
|
35678
|
+
if (tool === "start_process") {
|
|
35672
35679
|
const command = [
|
|
35673
35680
|
String(args.program ?? ""),
|
|
35674
35681
|
...Array.isArray(args.args) ? args.args.map(String) : []
|
|
35675
35682
|
];
|
|
35676
|
-
return `
|
|
35683
|
+
return `Start: ${command.join(" ")}`;
|
|
35684
|
+
}
|
|
35685
|
+
if (tool === "terminate_process") {
|
|
35686
|
+
return `Terminate process session ${String(args.sessionId ?? "")}`;
|
|
35677
35687
|
}
|
|
35678
35688
|
return `Run destructive tool: ${tool}`;
|
|
35679
35689
|
}
|
package/dist/cli.js
CHANGED
|
@@ -35487,7 +35487,11 @@ var import_websocket_server = __toESM(require_websocket_server(), 1);
|
|
|
35487
35487
|
var wrapper_default = import_websocket.default;
|
|
35488
35488
|
|
|
35489
35489
|
// src/agent-core.ts
|
|
35490
|
-
var destructiveTools = /* @__PURE__ */ new Set([
|
|
35490
|
+
var destructiveTools = /* @__PURE__ */ new Set([
|
|
35491
|
+
"start_process",
|
|
35492
|
+
"terminate_process",
|
|
35493
|
+
"write_text_file"
|
|
35494
|
+
]);
|
|
35491
35495
|
var requestSchema = external_exports.object({
|
|
35492
35496
|
type: external_exports.literal("request"),
|
|
35493
35497
|
requestId: external_exports.string().uuid(),
|
|
@@ -35495,7 +35499,10 @@ var requestSchema = external_exports.object({
|
|
|
35495
35499
|
"get_system_info",
|
|
35496
35500
|
"list_directory",
|
|
35497
35501
|
"read_text_file",
|
|
35498
|
-
"
|
|
35502
|
+
"start_process",
|
|
35503
|
+
"read_process_output",
|
|
35504
|
+
"terminate_process",
|
|
35505
|
+
"list_processes",
|
|
35499
35506
|
"write_text_file"
|
|
35500
35507
|
]),
|
|
35501
35508
|
arguments: external_exports.record(external_exports.string(), external_exports.unknown())
|
|
@@ -35691,12 +35698,15 @@ function approvalSummary(tool, args) {
|
|
|
35691
35698
|
const bytes = Buffer.byteLength(String(args.contents ?? ""));
|
|
35692
35699
|
return `Write ${bytes} bytes to ${String(args.path ?? "an unknown path")}${args.overwrite ? " (overwrite)" : ""}`;
|
|
35693
35700
|
}
|
|
35694
|
-
if (tool === "
|
|
35701
|
+
if (tool === "start_process") {
|
|
35695
35702
|
const command = [
|
|
35696
35703
|
String(args.program ?? ""),
|
|
35697
35704
|
...Array.isArray(args.args) ? args.args.map(String) : []
|
|
35698
35705
|
];
|
|
35699
|
-
return `
|
|
35706
|
+
return `Start: ${command.join(" ")}`;
|
|
35707
|
+
}
|
|
35708
|
+
if (tool === "terminate_process") {
|
|
35709
|
+
return `Terminate process session ${String(args.sessionId ?? "")}`;
|
|
35700
35710
|
}
|
|
35701
35711
|
return `Run destructive tool: ${tool}`;
|
|
35702
35712
|
}
|
package/dist/index.js
CHANGED
|
@@ -62498,9 +62498,30 @@ var EMPTY_COMPLETION_RESULT = {
|
|
|
62498
62498
|
}
|
|
62499
62499
|
};
|
|
62500
62500
|
|
|
62501
|
-
// src/
|
|
62502
|
-
import {
|
|
62503
|
-
|
|
62501
|
+
// src/file-reader.ts
|
|
62502
|
+
import { open as open2 } from "node:fs/promises";
|
|
62503
|
+
async function readBoundedTextFile(filename, maxCharacters) {
|
|
62504
|
+
const handle = await open2(filename, "r");
|
|
62505
|
+
try {
|
|
62506
|
+
const stats = await handle.stat();
|
|
62507
|
+
const buffer = Buffer.allocUnsafe(maxCharacters * 4 + 4);
|
|
62508
|
+
const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
|
|
62509
|
+
const hasUnreadBytes = bytesRead < stats.size;
|
|
62510
|
+
const decoded = new TextDecoder().decode(buffer.subarray(0, bytesRead), {
|
|
62511
|
+
stream: hasUnreadBytes
|
|
62512
|
+
});
|
|
62513
|
+
return {
|
|
62514
|
+
contents: decoded.slice(0, maxCharacters),
|
|
62515
|
+
truncated: hasUnreadBytes || decoded.length > maxCharacters
|
|
62516
|
+
};
|
|
62517
|
+
} finally {
|
|
62518
|
+
await handle.close();
|
|
62519
|
+
}
|
|
62520
|
+
}
|
|
62521
|
+
|
|
62522
|
+
// src/process-manager.ts
|
|
62523
|
+
import { randomUUID } from "node:crypto";
|
|
62524
|
+
import { spawn } from "node:child_process";
|
|
62504
62525
|
|
|
62505
62526
|
// src/security.ts
|
|
62506
62527
|
import { constants as constants2 } from "node:fs";
|
|
@@ -62590,59 +62611,144 @@ function assertProgramAllowed(program) {
|
|
|
62590
62611
|
}
|
|
62591
62612
|
}
|
|
62592
62613
|
|
|
62593
|
-
// src/
|
|
62594
|
-
|
|
62595
|
-
|
|
62596
|
-
|
|
62597
|
-
try {
|
|
62598
|
-
const { stdout, stderr } = await execFileAsync(
|
|
62599
|
-
options.program,
|
|
62600
|
-
options.args,
|
|
62601
|
-
{
|
|
62602
|
-
cwd: options.cwd,
|
|
62603
|
-
env: safeEnvironment(),
|
|
62604
|
-
encoding: "utf8",
|
|
62605
|
-
maxBuffer: options.maxOutputChars,
|
|
62606
|
-
timeout: options.timeoutMs,
|
|
62607
|
-
windowsHide: true
|
|
62608
|
-
}
|
|
62609
|
-
);
|
|
62610
|
-
return { exitCode: 0, signal: null, stdout, stderr, timedOut: false };
|
|
62611
|
-
} catch (error62) {
|
|
62612
|
-
const failure2 = error62;
|
|
62613
|
-
return {
|
|
62614
|
-
exitCode: typeof failure2.code === "number" ? failure2.code : null,
|
|
62615
|
-
signal: failure2.signal ?? null,
|
|
62616
|
-
stdout: failure2.stdout ?? "",
|
|
62617
|
-
stderr: failure2.stderr ?? failure2.message,
|
|
62618
|
-
timedOut: failure2.killed === true && failure2.signal === "SIGTERM"
|
|
62619
|
-
};
|
|
62620
|
-
}
|
|
62614
|
+
// src/process-manager.ts
|
|
62615
|
+
function appendBounded(current, value, limit) {
|
|
62616
|
+
if (current.length >= limit) return current;
|
|
62617
|
+
return `${current}${value.toString("utf8")}`.slice(0, limit);
|
|
62621
62618
|
}
|
|
62622
|
-
|
|
62623
|
-
|
|
62624
|
-
|
|
62625
|
-
|
|
62626
|
-
|
|
62627
|
-
|
|
62628
|
-
|
|
62629
|
-
|
|
62630
|
-
|
|
62631
|
-
|
|
62632
|
-
|
|
62633
|
-
|
|
62619
|
+
function snapshot(session) {
|
|
62620
|
+
return {
|
|
62621
|
+
sessionId: session.sessionId,
|
|
62622
|
+
program: session.program,
|
|
62623
|
+
args: session.args,
|
|
62624
|
+
cwd: session.cwd,
|
|
62625
|
+
pid: session.child.pid ?? null,
|
|
62626
|
+
running: session.running,
|
|
62627
|
+
exitCode: session.exitCode,
|
|
62628
|
+
signal: session.signal,
|
|
62629
|
+
stdout: session.stdout,
|
|
62630
|
+
stderr: session.stderr,
|
|
62631
|
+
timedOut: session.timedOut
|
|
62632
|
+
};
|
|
62633
|
+
}
|
|
62634
|
+
var ProcessManager = class {
|
|
62635
|
+
#sessions = /* @__PURE__ */ new Map();
|
|
62636
|
+
start(options) {
|
|
62637
|
+
assertProgramAllowed(options.program);
|
|
62638
|
+
const child = spawn(options.program, options.args, {
|
|
62639
|
+
cwd: options.cwd,
|
|
62640
|
+
env: safeEnvironment(),
|
|
62641
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
62642
|
+
windowsHide: true
|
|
62643
|
+
});
|
|
62644
|
+
const session = {
|
|
62645
|
+
sessionId: randomUUID(),
|
|
62646
|
+
program: options.program,
|
|
62647
|
+
args: [...options.args],
|
|
62648
|
+
cwd: options.cwd,
|
|
62649
|
+
child,
|
|
62650
|
+
maxOutputCharacters: options.maxOutputCharacters,
|
|
62651
|
+
stdout: "",
|
|
62652
|
+
stderr: "",
|
|
62653
|
+
running: true,
|
|
62654
|
+
exitCode: null,
|
|
62655
|
+
signal: null,
|
|
62656
|
+
timedOut: false
|
|
62657
|
+
};
|
|
62658
|
+
this.#sessions.set(session.sessionId, session);
|
|
62659
|
+
child.stdout.on("data", (chunk) => {
|
|
62660
|
+
session.stdout = appendBounded(
|
|
62661
|
+
session.stdout,
|
|
62662
|
+
chunk,
|
|
62663
|
+
session.maxOutputCharacters
|
|
62664
|
+
);
|
|
62665
|
+
});
|
|
62666
|
+
child.stderr.on("data", (chunk) => {
|
|
62667
|
+
session.stderr = appendBounded(
|
|
62668
|
+
session.stderr,
|
|
62669
|
+
chunk,
|
|
62670
|
+
session.maxOutputCharacters
|
|
62671
|
+
);
|
|
62634
62672
|
});
|
|
62673
|
+
child.once("error", (error62) => {
|
|
62674
|
+
session.stderr = appendBounded(
|
|
62675
|
+
session.stderr,
|
|
62676
|
+
error62.message,
|
|
62677
|
+
session.maxOutputCharacters
|
|
62678
|
+
);
|
|
62679
|
+
session.running = false;
|
|
62680
|
+
session.exitCode = null;
|
|
62681
|
+
session.signal = null;
|
|
62682
|
+
if (session.timeout) clearTimeout(session.timeout);
|
|
62683
|
+
});
|
|
62684
|
+
child.once("close", (exitCode, signal) => {
|
|
62685
|
+
session.running = false;
|
|
62686
|
+
session.exitCode = exitCode;
|
|
62687
|
+
session.signal = signal;
|
|
62688
|
+
if (session.timeout) clearTimeout(session.timeout);
|
|
62689
|
+
});
|
|
62690
|
+
session.timeout = setTimeout(() => {
|
|
62691
|
+
if (!session.running) return;
|
|
62692
|
+
session.timedOut = true;
|
|
62693
|
+
child.kill("SIGTERM");
|
|
62694
|
+
}, options.timeoutMs);
|
|
62695
|
+
const result = snapshot(session);
|
|
62635
62696
|
return {
|
|
62636
|
-
|
|
62637
|
-
|
|
62697
|
+
...result,
|
|
62698
|
+
nextStdoutOffset: result.stdout.length,
|
|
62699
|
+
nextStderrOffset: result.stderr.length
|
|
62700
|
+
};
|
|
62701
|
+
}
|
|
62702
|
+
read(sessionId, stdoutOffset, stderrOffset, maxOutputCharacters) {
|
|
62703
|
+
const session = this.#sessions.get(sessionId);
|
|
62704
|
+
if (!session)
|
|
62705
|
+
throw new Error("The requested process session was not found.");
|
|
62706
|
+
const safeStdoutOffset = Math.max(
|
|
62707
|
+
0,
|
|
62708
|
+
Math.min(stdoutOffset, session.stdout.length)
|
|
62709
|
+
);
|
|
62710
|
+
const safeStderrOffset = Math.max(
|
|
62711
|
+
0,
|
|
62712
|
+
Math.min(stderrOffset, session.stderr.length)
|
|
62713
|
+
);
|
|
62714
|
+
const stdout = session.stdout.slice(
|
|
62715
|
+
safeStdoutOffset,
|
|
62716
|
+
safeStdoutOffset + maxOutputCharacters
|
|
62717
|
+
);
|
|
62718
|
+
const stderr = session.stderr.slice(
|
|
62719
|
+
safeStderrOffset,
|
|
62720
|
+
safeStderrOffset + maxOutputCharacters
|
|
62721
|
+
);
|
|
62722
|
+
return {
|
|
62723
|
+
...snapshot(session),
|
|
62724
|
+
stdout,
|
|
62725
|
+
stderr,
|
|
62726
|
+
nextStdoutOffset: safeStdoutOffset + stdout.length,
|
|
62727
|
+
nextStderrOffset: safeStderrOffset + stderr.length
|
|
62638
62728
|
};
|
|
62639
|
-
} finally {
|
|
62640
|
-
await handle.close();
|
|
62641
62729
|
}
|
|
62642
|
-
|
|
62730
|
+
terminate(sessionId) {
|
|
62731
|
+
const session = this.#sessions.get(sessionId);
|
|
62732
|
+
if (!session)
|
|
62733
|
+
throw new Error("The requested process session was not found.");
|
|
62734
|
+
if (session.running) session.child.kill("SIGTERM");
|
|
62735
|
+
return snapshot(session);
|
|
62736
|
+
}
|
|
62737
|
+
list() {
|
|
62738
|
+
return [...this.#sessions.values()].map(snapshot);
|
|
62739
|
+
}
|
|
62740
|
+
close() {
|
|
62741
|
+
for (const session of this.#sessions.values()) {
|
|
62742
|
+
if (session.timeout) clearTimeout(session.timeout);
|
|
62743
|
+
if (session.running) session.child.kill("SIGTERM");
|
|
62744
|
+
}
|
|
62745
|
+
this.#sessions.clear();
|
|
62746
|
+
}
|
|
62747
|
+
};
|
|
62643
62748
|
|
|
62644
62749
|
// src/server.ts
|
|
62645
62750
|
var roots = configuredRoots();
|
|
62751
|
+
var processManager = new ProcessManager();
|
|
62646
62752
|
function textResult(value) {
|
|
62647
62753
|
return {
|
|
62648
62754
|
content: [{ type: "text", text: JSON.stringify(value, null, 2) }],
|
|
@@ -62782,10 +62888,10 @@ function createServer() {
|
|
|
62782
62888
|
}
|
|
62783
62889
|
);
|
|
62784
62890
|
server.registerTool(
|
|
62785
|
-
"
|
|
62891
|
+
"start_process",
|
|
62786
62892
|
{
|
|
62787
|
-
title: "
|
|
62788
|
-
description: "
|
|
62893
|
+
title: "Start a process",
|
|
62894
|
+
description: "Start one approved executable without a shell on this machine and return a session ID for reading its output.",
|
|
62789
62895
|
inputSchema: {
|
|
62790
62896
|
program: external_exports.string().min(1),
|
|
62791
62897
|
args: external_exports.array(external_exports.string()).max(100).default([]),
|
|
@@ -62794,14 +62900,19 @@ function createServer() {
|
|
|
62794
62900
|
maxOutputCharacters: external_exports.number().int().min(1e3).max(2e5).default(5e4)
|
|
62795
62901
|
},
|
|
62796
62902
|
outputSchema: {
|
|
62903
|
+
sessionId: external_exports.string(),
|
|
62797
62904
|
program: external_exports.string(),
|
|
62798
62905
|
args: external_exports.array(external_exports.string()),
|
|
62799
62906
|
cwd: external_exports.string(),
|
|
62907
|
+
pid: external_exports.number().int().nullable(),
|
|
62908
|
+
running: external_exports.boolean(),
|
|
62800
62909
|
exitCode: external_exports.number().int().nullable(),
|
|
62801
62910
|
signal: external_exports.string().nullable(),
|
|
62802
62911
|
stdout: external_exports.string(),
|
|
62803
62912
|
stderr: external_exports.string(),
|
|
62804
|
-
timedOut: external_exports.boolean()
|
|
62913
|
+
timedOut: external_exports.boolean(),
|
|
62914
|
+
nextStdoutOffset: external_exports.number().int().nonnegative(),
|
|
62915
|
+
nextStderrOffset: external_exports.number().int().nonnegative()
|
|
62805
62916
|
},
|
|
62806
62917
|
annotations: {
|
|
62807
62918
|
readOnlyHint: false,
|
|
@@ -62814,16 +62925,114 @@ function createServer() {
|
|
|
62814
62925
|
cwd ?? roots[0],
|
|
62815
62926
|
roots
|
|
62816
62927
|
);
|
|
62817
|
-
|
|
62818
|
-
|
|
62819
|
-
|
|
62820
|
-
|
|
62821
|
-
|
|
62822
|
-
|
|
62823
|
-
|
|
62824
|
-
|
|
62928
|
+
return textResult(
|
|
62929
|
+
processManager.start({
|
|
62930
|
+
program,
|
|
62931
|
+
args,
|
|
62932
|
+
cwd: workingDirectory,
|
|
62933
|
+
timeoutMs,
|
|
62934
|
+
maxOutputCharacters
|
|
62935
|
+
})
|
|
62936
|
+
);
|
|
62825
62937
|
}
|
|
62826
62938
|
);
|
|
62939
|
+
server.registerTool(
|
|
62940
|
+
"read_process_output",
|
|
62941
|
+
{
|
|
62942
|
+
title: "Read process output",
|
|
62943
|
+
description: "Read new stdout and stderr from a previously started process session.",
|
|
62944
|
+
inputSchema: {
|
|
62945
|
+
sessionId: external_exports.string().uuid(),
|
|
62946
|
+
stdoutOffset: external_exports.number().int().min(0).default(0),
|
|
62947
|
+
stderrOffset: external_exports.number().int().min(0).default(0),
|
|
62948
|
+
maxOutputCharacters: external_exports.number().int().min(1e3).max(2e5).default(5e4)
|
|
62949
|
+
},
|
|
62950
|
+
outputSchema: {
|
|
62951
|
+
sessionId: external_exports.string(),
|
|
62952
|
+
program: external_exports.string(),
|
|
62953
|
+
args: external_exports.array(external_exports.string()),
|
|
62954
|
+
cwd: external_exports.string(),
|
|
62955
|
+
pid: external_exports.number().int().nullable(),
|
|
62956
|
+
running: external_exports.boolean(),
|
|
62957
|
+
exitCode: external_exports.number().int().nullable(),
|
|
62958
|
+
signal: external_exports.string().nullable(),
|
|
62959
|
+
stdout: external_exports.string(),
|
|
62960
|
+
stderr: external_exports.string(),
|
|
62961
|
+
timedOut: external_exports.boolean(),
|
|
62962
|
+
nextStdoutOffset: external_exports.number().int().nonnegative(),
|
|
62963
|
+
nextStderrOffset: external_exports.number().int().nonnegative()
|
|
62964
|
+
},
|
|
62965
|
+
annotations: {
|
|
62966
|
+
readOnlyHint: true,
|
|
62967
|
+
destructiveHint: false,
|
|
62968
|
+
openWorldHint: false
|
|
62969
|
+
}
|
|
62970
|
+
},
|
|
62971
|
+
async ({ sessionId, stdoutOffset, stderrOffset, maxOutputCharacters }) => textResult(
|
|
62972
|
+
processManager.read(
|
|
62973
|
+
sessionId,
|
|
62974
|
+
stdoutOffset,
|
|
62975
|
+
stderrOffset,
|
|
62976
|
+
maxOutputCharacters
|
|
62977
|
+
)
|
|
62978
|
+
)
|
|
62979
|
+
);
|
|
62980
|
+
server.registerTool(
|
|
62981
|
+
"terminate_process",
|
|
62982
|
+
{
|
|
62983
|
+
title: "Terminate a process",
|
|
62984
|
+
description: "Stop a previously started process session on this machine.",
|
|
62985
|
+
inputSchema: { sessionId: external_exports.string().uuid() },
|
|
62986
|
+
outputSchema: {
|
|
62987
|
+
sessionId: external_exports.string(),
|
|
62988
|
+
program: external_exports.string(),
|
|
62989
|
+
args: external_exports.array(external_exports.string()),
|
|
62990
|
+
cwd: external_exports.string(),
|
|
62991
|
+
pid: external_exports.number().int().nullable(),
|
|
62992
|
+
running: external_exports.boolean(),
|
|
62993
|
+
exitCode: external_exports.number().int().nullable(),
|
|
62994
|
+
signal: external_exports.string().nullable(),
|
|
62995
|
+
stdout: external_exports.string(),
|
|
62996
|
+
stderr: external_exports.string(),
|
|
62997
|
+
timedOut: external_exports.boolean()
|
|
62998
|
+
},
|
|
62999
|
+
annotations: {
|
|
63000
|
+
readOnlyHint: false,
|
|
63001
|
+
destructiveHint: true,
|
|
63002
|
+
openWorldHint: false
|
|
63003
|
+
}
|
|
63004
|
+
},
|
|
63005
|
+
async ({ sessionId }) => textResult(processManager.terminate(sessionId))
|
|
63006
|
+
);
|
|
63007
|
+
server.registerTool(
|
|
63008
|
+
"list_processes",
|
|
63009
|
+
{
|
|
63010
|
+
title: "List process sessions",
|
|
63011
|
+
description: "List process sessions started through Terminal Commands.",
|
|
63012
|
+
inputSchema: {},
|
|
63013
|
+
outputSchema: {
|
|
63014
|
+
processes: external_exports.array(
|
|
63015
|
+
external_exports.object({
|
|
63016
|
+
sessionId: external_exports.string(),
|
|
63017
|
+
program: external_exports.string(),
|
|
63018
|
+
args: external_exports.array(external_exports.string()),
|
|
63019
|
+
cwd: external_exports.string(),
|
|
63020
|
+
pid: external_exports.number().int().nullable(),
|
|
63021
|
+
running: external_exports.boolean(),
|
|
63022
|
+
exitCode: external_exports.number().int().nullable(),
|
|
63023
|
+
signal: external_exports.string().nullable(),
|
|
63024
|
+
timedOut: external_exports.boolean()
|
|
63025
|
+
})
|
|
63026
|
+
)
|
|
63027
|
+
},
|
|
63028
|
+
annotations: {
|
|
63029
|
+
readOnlyHint: true,
|
|
63030
|
+
destructiveHint: false,
|
|
63031
|
+
openWorldHint: false
|
|
63032
|
+
}
|
|
63033
|
+
},
|
|
63034
|
+
async () => textResult({ processes: processManager.list() })
|
|
63035
|
+
);
|
|
62827
63036
|
return server;
|
|
62828
63037
|
}
|
|
62829
63038
|
|
|
@@ -62884,6 +63093,7 @@ var listener = app.listen(port, host, () => {
|
|
|
62884
63093
|
});
|
|
62885
63094
|
function shutdown(signal) {
|
|
62886
63095
|
console.log(`Received ${signal}; shutting down.`);
|
|
63096
|
+
processManager.close();
|
|
62887
63097
|
listener.close((error62) => {
|
|
62888
63098
|
if (error62) {
|
|
62889
63099
|
console.error(error62);
|
package/dist/stdio.js
CHANGED
|
@@ -36479,9 +36479,30 @@ var EMPTY_COMPLETION_RESULT = {
|
|
|
36479
36479
|
}
|
|
36480
36480
|
};
|
|
36481
36481
|
|
|
36482
|
-
// src/
|
|
36483
|
-
import {
|
|
36484
|
-
|
|
36482
|
+
// src/file-reader.ts
|
|
36483
|
+
import { open as open2 } from "node:fs/promises";
|
|
36484
|
+
async function readBoundedTextFile(filename, maxCharacters) {
|
|
36485
|
+
const handle = await open2(filename, "r");
|
|
36486
|
+
try {
|
|
36487
|
+
const stats = await handle.stat();
|
|
36488
|
+
const buffer = Buffer.allocUnsafe(maxCharacters * 4 + 4);
|
|
36489
|
+
const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
|
|
36490
|
+
const hasUnreadBytes = bytesRead < stats.size;
|
|
36491
|
+
const decoded = new TextDecoder().decode(buffer.subarray(0, bytesRead), {
|
|
36492
|
+
stream: hasUnreadBytes
|
|
36493
|
+
});
|
|
36494
|
+
return {
|
|
36495
|
+
contents: decoded.slice(0, maxCharacters),
|
|
36496
|
+
truncated: hasUnreadBytes || decoded.length > maxCharacters
|
|
36497
|
+
};
|
|
36498
|
+
} finally {
|
|
36499
|
+
await handle.close();
|
|
36500
|
+
}
|
|
36501
|
+
}
|
|
36502
|
+
|
|
36503
|
+
// src/process-manager.ts
|
|
36504
|
+
import { randomUUID } from "node:crypto";
|
|
36505
|
+
import { spawn } from "node:child_process";
|
|
36485
36506
|
|
|
36486
36507
|
// src/security.ts
|
|
36487
36508
|
import { constants } from "node:fs";
|
|
@@ -36571,59 +36592,144 @@ function assertProgramAllowed(program) {
|
|
|
36571
36592
|
}
|
|
36572
36593
|
}
|
|
36573
36594
|
|
|
36574
|
-
// src/
|
|
36575
|
-
|
|
36576
|
-
|
|
36577
|
-
|
|
36578
|
-
|
|
36579
|
-
|
|
36580
|
-
|
|
36581
|
-
|
|
36582
|
-
|
|
36583
|
-
|
|
36584
|
-
|
|
36585
|
-
|
|
36586
|
-
|
|
36587
|
-
|
|
36588
|
-
|
|
36589
|
-
|
|
36590
|
-
|
|
36591
|
-
|
|
36592
|
-
}
|
|
36593
|
-
|
|
36595
|
+
// src/process-manager.ts
|
|
36596
|
+
function appendBounded(current, value, limit) {
|
|
36597
|
+
if (current.length >= limit) return current;
|
|
36598
|
+
return `${current}${value.toString("utf8")}`.slice(0, limit);
|
|
36599
|
+
}
|
|
36600
|
+
function snapshot(session) {
|
|
36601
|
+
return {
|
|
36602
|
+
sessionId: session.sessionId,
|
|
36603
|
+
program: session.program,
|
|
36604
|
+
args: session.args,
|
|
36605
|
+
cwd: session.cwd,
|
|
36606
|
+
pid: session.child.pid ?? null,
|
|
36607
|
+
running: session.running,
|
|
36608
|
+
exitCode: session.exitCode,
|
|
36609
|
+
signal: session.signal,
|
|
36610
|
+
stdout: session.stdout,
|
|
36611
|
+
stderr: session.stderr,
|
|
36612
|
+
timedOut: session.timedOut
|
|
36613
|
+
};
|
|
36614
|
+
}
|
|
36615
|
+
var ProcessManager = class {
|
|
36616
|
+
#sessions = /* @__PURE__ */ new Map();
|
|
36617
|
+
start(options) {
|
|
36618
|
+
assertProgramAllowed(options.program);
|
|
36619
|
+
const child = spawn(options.program, options.args, {
|
|
36620
|
+
cwd: options.cwd,
|
|
36621
|
+
env: safeEnvironment(),
|
|
36622
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
36623
|
+
windowsHide: true
|
|
36624
|
+
});
|
|
36625
|
+
const session = {
|
|
36626
|
+
sessionId: randomUUID(),
|
|
36627
|
+
program: options.program,
|
|
36628
|
+
args: [...options.args],
|
|
36629
|
+
cwd: options.cwd,
|
|
36630
|
+
child,
|
|
36631
|
+
maxOutputCharacters: options.maxOutputCharacters,
|
|
36632
|
+
stdout: "",
|
|
36633
|
+
stderr: "",
|
|
36634
|
+
running: true,
|
|
36635
|
+
exitCode: null,
|
|
36636
|
+
signal: null,
|
|
36637
|
+
timedOut: false
|
|
36638
|
+
};
|
|
36639
|
+
this.#sessions.set(session.sessionId, session);
|
|
36640
|
+
child.stdout.on("data", (chunk) => {
|
|
36641
|
+
session.stdout = appendBounded(
|
|
36642
|
+
session.stdout,
|
|
36643
|
+
chunk,
|
|
36644
|
+
session.maxOutputCharacters
|
|
36645
|
+
);
|
|
36646
|
+
});
|
|
36647
|
+
child.stderr.on("data", (chunk) => {
|
|
36648
|
+
session.stderr = appendBounded(
|
|
36649
|
+
session.stderr,
|
|
36650
|
+
chunk,
|
|
36651
|
+
session.maxOutputCharacters
|
|
36652
|
+
);
|
|
36653
|
+
});
|
|
36654
|
+
child.once("error", (error62) => {
|
|
36655
|
+
session.stderr = appendBounded(
|
|
36656
|
+
session.stderr,
|
|
36657
|
+
error62.message,
|
|
36658
|
+
session.maxOutputCharacters
|
|
36659
|
+
);
|
|
36660
|
+
session.running = false;
|
|
36661
|
+
session.exitCode = null;
|
|
36662
|
+
session.signal = null;
|
|
36663
|
+
if (session.timeout) clearTimeout(session.timeout);
|
|
36664
|
+
});
|
|
36665
|
+
child.once("close", (exitCode, signal) => {
|
|
36666
|
+
session.running = false;
|
|
36667
|
+
session.exitCode = exitCode;
|
|
36668
|
+
session.signal = signal;
|
|
36669
|
+
if (session.timeout) clearTimeout(session.timeout);
|
|
36670
|
+
});
|
|
36671
|
+
session.timeout = setTimeout(() => {
|
|
36672
|
+
if (!session.running) return;
|
|
36673
|
+
session.timedOut = true;
|
|
36674
|
+
child.kill("SIGTERM");
|
|
36675
|
+
}, options.timeoutMs);
|
|
36676
|
+
const result = snapshot(session);
|
|
36594
36677
|
return {
|
|
36595
|
-
|
|
36596
|
-
|
|
36597
|
-
|
|
36598
|
-
stderr: failure2.stderr ?? failure2.message,
|
|
36599
|
-
timedOut: failure2.killed === true && failure2.signal === "SIGTERM"
|
|
36678
|
+
...result,
|
|
36679
|
+
nextStdoutOffset: result.stdout.length,
|
|
36680
|
+
nextStderrOffset: result.stderr.length
|
|
36600
36681
|
};
|
|
36601
36682
|
}
|
|
36602
|
-
|
|
36603
|
-
|
|
36604
|
-
|
|
36605
|
-
|
|
36606
|
-
|
|
36607
|
-
|
|
36608
|
-
|
|
36609
|
-
|
|
36610
|
-
const
|
|
36611
|
-
|
|
36612
|
-
|
|
36613
|
-
|
|
36614
|
-
|
|
36615
|
-
|
|
36683
|
+
read(sessionId, stdoutOffset, stderrOffset, maxOutputCharacters) {
|
|
36684
|
+
const session = this.#sessions.get(sessionId);
|
|
36685
|
+
if (!session)
|
|
36686
|
+
throw new Error("The requested process session was not found.");
|
|
36687
|
+
const safeStdoutOffset = Math.max(
|
|
36688
|
+
0,
|
|
36689
|
+
Math.min(stdoutOffset, session.stdout.length)
|
|
36690
|
+
);
|
|
36691
|
+
const safeStderrOffset = Math.max(
|
|
36692
|
+
0,
|
|
36693
|
+
Math.min(stderrOffset, session.stderr.length)
|
|
36694
|
+
);
|
|
36695
|
+
const stdout = session.stdout.slice(
|
|
36696
|
+
safeStdoutOffset,
|
|
36697
|
+
safeStdoutOffset + maxOutputCharacters
|
|
36698
|
+
);
|
|
36699
|
+
const stderr = session.stderr.slice(
|
|
36700
|
+
safeStderrOffset,
|
|
36701
|
+
safeStderrOffset + maxOutputCharacters
|
|
36702
|
+
);
|
|
36616
36703
|
return {
|
|
36617
|
-
|
|
36618
|
-
|
|
36704
|
+
...snapshot(session),
|
|
36705
|
+
stdout,
|
|
36706
|
+
stderr,
|
|
36707
|
+
nextStdoutOffset: safeStdoutOffset + stdout.length,
|
|
36708
|
+
nextStderrOffset: safeStderrOffset + stderr.length
|
|
36619
36709
|
};
|
|
36620
|
-
} finally {
|
|
36621
|
-
await handle.close();
|
|
36622
36710
|
}
|
|
36623
|
-
|
|
36711
|
+
terminate(sessionId) {
|
|
36712
|
+
const session = this.#sessions.get(sessionId);
|
|
36713
|
+
if (!session)
|
|
36714
|
+
throw new Error("The requested process session was not found.");
|
|
36715
|
+
if (session.running) session.child.kill("SIGTERM");
|
|
36716
|
+
return snapshot(session);
|
|
36717
|
+
}
|
|
36718
|
+
list() {
|
|
36719
|
+
return [...this.#sessions.values()].map(snapshot);
|
|
36720
|
+
}
|
|
36721
|
+
close() {
|
|
36722
|
+
for (const session of this.#sessions.values()) {
|
|
36723
|
+
if (session.timeout) clearTimeout(session.timeout);
|
|
36724
|
+
if (session.running) session.child.kill("SIGTERM");
|
|
36725
|
+
}
|
|
36726
|
+
this.#sessions.clear();
|
|
36727
|
+
}
|
|
36728
|
+
};
|
|
36624
36729
|
|
|
36625
36730
|
// src/server.ts
|
|
36626
36731
|
var roots = configuredRoots();
|
|
36732
|
+
var processManager = new ProcessManager();
|
|
36627
36733
|
function textResult(value) {
|
|
36628
36734
|
return {
|
|
36629
36735
|
content: [{ type: "text", text: JSON.stringify(value, null, 2) }],
|
|
@@ -36763,10 +36869,10 @@ function createServer() {
|
|
|
36763
36869
|
}
|
|
36764
36870
|
);
|
|
36765
36871
|
server2.registerTool(
|
|
36766
|
-
"
|
|
36872
|
+
"start_process",
|
|
36767
36873
|
{
|
|
36768
|
-
title: "
|
|
36769
|
-
description: "
|
|
36874
|
+
title: "Start a process",
|
|
36875
|
+
description: "Start one approved executable without a shell on this machine and return a session ID for reading its output.",
|
|
36770
36876
|
inputSchema: {
|
|
36771
36877
|
program: external_exports.string().min(1),
|
|
36772
36878
|
args: external_exports.array(external_exports.string()).max(100).default([]),
|
|
@@ -36775,14 +36881,19 @@ function createServer() {
|
|
|
36775
36881
|
maxOutputCharacters: external_exports.number().int().min(1e3).max(2e5).default(5e4)
|
|
36776
36882
|
},
|
|
36777
36883
|
outputSchema: {
|
|
36884
|
+
sessionId: external_exports.string(),
|
|
36778
36885
|
program: external_exports.string(),
|
|
36779
36886
|
args: external_exports.array(external_exports.string()),
|
|
36780
36887
|
cwd: external_exports.string(),
|
|
36888
|
+
pid: external_exports.number().int().nullable(),
|
|
36889
|
+
running: external_exports.boolean(),
|
|
36781
36890
|
exitCode: external_exports.number().int().nullable(),
|
|
36782
36891
|
signal: external_exports.string().nullable(),
|
|
36783
36892
|
stdout: external_exports.string(),
|
|
36784
36893
|
stderr: external_exports.string(),
|
|
36785
|
-
timedOut: external_exports.boolean()
|
|
36894
|
+
timedOut: external_exports.boolean(),
|
|
36895
|
+
nextStdoutOffset: external_exports.number().int().nonnegative(),
|
|
36896
|
+
nextStderrOffset: external_exports.number().int().nonnegative()
|
|
36786
36897
|
},
|
|
36787
36898
|
annotations: {
|
|
36788
36899
|
readOnlyHint: false,
|
|
@@ -36795,16 +36906,114 @@ function createServer() {
|
|
|
36795
36906
|
cwd ?? roots[0],
|
|
36796
36907
|
roots
|
|
36797
36908
|
);
|
|
36798
|
-
|
|
36799
|
-
|
|
36800
|
-
|
|
36801
|
-
|
|
36802
|
-
|
|
36803
|
-
|
|
36804
|
-
|
|
36805
|
-
|
|
36909
|
+
return textResult(
|
|
36910
|
+
processManager.start({
|
|
36911
|
+
program,
|
|
36912
|
+
args,
|
|
36913
|
+
cwd: workingDirectory,
|
|
36914
|
+
timeoutMs,
|
|
36915
|
+
maxOutputCharacters
|
|
36916
|
+
})
|
|
36917
|
+
);
|
|
36806
36918
|
}
|
|
36807
36919
|
);
|
|
36920
|
+
server2.registerTool(
|
|
36921
|
+
"read_process_output",
|
|
36922
|
+
{
|
|
36923
|
+
title: "Read process output",
|
|
36924
|
+
description: "Read new stdout and stderr from a previously started process session.",
|
|
36925
|
+
inputSchema: {
|
|
36926
|
+
sessionId: external_exports.string().uuid(),
|
|
36927
|
+
stdoutOffset: external_exports.number().int().min(0).default(0),
|
|
36928
|
+
stderrOffset: external_exports.number().int().min(0).default(0),
|
|
36929
|
+
maxOutputCharacters: external_exports.number().int().min(1e3).max(2e5).default(5e4)
|
|
36930
|
+
},
|
|
36931
|
+
outputSchema: {
|
|
36932
|
+
sessionId: external_exports.string(),
|
|
36933
|
+
program: external_exports.string(),
|
|
36934
|
+
args: external_exports.array(external_exports.string()),
|
|
36935
|
+
cwd: external_exports.string(),
|
|
36936
|
+
pid: external_exports.number().int().nullable(),
|
|
36937
|
+
running: external_exports.boolean(),
|
|
36938
|
+
exitCode: external_exports.number().int().nullable(),
|
|
36939
|
+
signal: external_exports.string().nullable(),
|
|
36940
|
+
stdout: external_exports.string(),
|
|
36941
|
+
stderr: external_exports.string(),
|
|
36942
|
+
timedOut: external_exports.boolean(),
|
|
36943
|
+
nextStdoutOffset: external_exports.number().int().nonnegative(),
|
|
36944
|
+
nextStderrOffset: external_exports.number().int().nonnegative()
|
|
36945
|
+
},
|
|
36946
|
+
annotations: {
|
|
36947
|
+
readOnlyHint: true,
|
|
36948
|
+
destructiveHint: false,
|
|
36949
|
+
openWorldHint: false
|
|
36950
|
+
}
|
|
36951
|
+
},
|
|
36952
|
+
async ({ sessionId, stdoutOffset, stderrOffset, maxOutputCharacters }) => textResult(
|
|
36953
|
+
processManager.read(
|
|
36954
|
+
sessionId,
|
|
36955
|
+
stdoutOffset,
|
|
36956
|
+
stderrOffset,
|
|
36957
|
+
maxOutputCharacters
|
|
36958
|
+
)
|
|
36959
|
+
)
|
|
36960
|
+
);
|
|
36961
|
+
server2.registerTool(
|
|
36962
|
+
"terminate_process",
|
|
36963
|
+
{
|
|
36964
|
+
title: "Terminate a process",
|
|
36965
|
+
description: "Stop a previously started process session on this machine.",
|
|
36966
|
+
inputSchema: { sessionId: external_exports.string().uuid() },
|
|
36967
|
+
outputSchema: {
|
|
36968
|
+
sessionId: external_exports.string(),
|
|
36969
|
+
program: external_exports.string(),
|
|
36970
|
+
args: external_exports.array(external_exports.string()),
|
|
36971
|
+
cwd: external_exports.string(),
|
|
36972
|
+
pid: external_exports.number().int().nullable(),
|
|
36973
|
+
running: external_exports.boolean(),
|
|
36974
|
+
exitCode: external_exports.number().int().nullable(),
|
|
36975
|
+
signal: external_exports.string().nullable(),
|
|
36976
|
+
stdout: external_exports.string(),
|
|
36977
|
+
stderr: external_exports.string(),
|
|
36978
|
+
timedOut: external_exports.boolean()
|
|
36979
|
+
},
|
|
36980
|
+
annotations: {
|
|
36981
|
+
readOnlyHint: false,
|
|
36982
|
+
destructiveHint: true,
|
|
36983
|
+
openWorldHint: false
|
|
36984
|
+
}
|
|
36985
|
+
},
|
|
36986
|
+
async ({ sessionId }) => textResult(processManager.terminate(sessionId))
|
|
36987
|
+
);
|
|
36988
|
+
server2.registerTool(
|
|
36989
|
+
"list_processes",
|
|
36990
|
+
{
|
|
36991
|
+
title: "List process sessions",
|
|
36992
|
+
description: "List process sessions started through Terminal Commands.",
|
|
36993
|
+
inputSchema: {},
|
|
36994
|
+
outputSchema: {
|
|
36995
|
+
processes: external_exports.array(
|
|
36996
|
+
external_exports.object({
|
|
36997
|
+
sessionId: external_exports.string(),
|
|
36998
|
+
program: external_exports.string(),
|
|
36999
|
+
args: external_exports.array(external_exports.string()),
|
|
37000
|
+
cwd: external_exports.string(),
|
|
37001
|
+
pid: external_exports.number().int().nullable(),
|
|
37002
|
+
running: external_exports.boolean(),
|
|
37003
|
+
exitCode: external_exports.number().int().nullable(),
|
|
37004
|
+
signal: external_exports.string().nullable(),
|
|
37005
|
+
timedOut: external_exports.boolean()
|
|
37006
|
+
})
|
|
37007
|
+
)
|
|
37008
|
+
},
|
|
37009
|
+
annotations: {
|
|
37010
|
+
readOnlyHint: true,
|
|
37011
|
+
destructiveHint: false,
|
|
37012
|
+
openWorldHint: false
|
|
37013
|
+
}
|
|
37014
|
+
},
|
|
37015
|
+
async () => textResult({ processes: processManager.list() })
|
|
37016
|
+
);
|
|
36808
37017
|
return server2;
|
|
36809
37018
|
}
|
|
36810
37019
|
|
|
@@ -36813,6 +37022,7 @@ var server = createServer();
|
|
|
36813
37022
|
var transport = new StdioServerTransport();
|
|
36814
37023
|
await server.connect(transport);
|
|
36815
37024
|
async function shutdown() {
|
|
37025
|
+
processManager.close();
|
|
36816
37026
|
await server.close();
|
|
36817
37027
|
process.exit(0);
|
|
36818
37028
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "terminal-commands",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "A cross-platform, approval-gated terminal companion for ChatGPT, Claude, Codex, and any MCP-compatible AI client.",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"start:agent": "node dist/agent.js",
|
|
23
23
|
"cli": "node dist/cli.js",
|
|
24
24
|
"cli:dev": "tsx src/cli.ts",
|
|
25
|
-
"test": "tsx --test test
|
|
25
|
+
"test": "tsx --test test/*.test.ts",
|
|
26
26
|
"test:smoke": "node test/smoke.mjs",
|
|
27
27
|
"prepack": "npm run build"
|
|
28
28
|
},
|