terminal-commands 0.1.1 → 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/dist/index.js CHANGED
@@ -18390,11 +18390,11 @@ var require_parse = __commonJS({
18390
18390
  segments[segments.length] = parent;
18391
18391
  }
18392
18392
  var n = key.length;
18393
- var open2 = first;
18393
+ var open3 = first;
18394
18394
  var collected = 0;
18395
- while (open2 >= 0 && collected < options.depth) {
18395
+ while (open3 >= 0 && collected < options.depth) {
18396
18396
  var level = 1;
18397
- var i = open2 + 1;
18397
+ var i = open3 + 1;
18398
18398
  var close = -1;
18399
18399
  while (i < n && close < 0) {
18400
18400
  var cu = key.charCodeAt(i);
@@ -18409,23 +18409,23 @@ var require_parse = __commonJS({
18409
18409
  i += 1;
18410
18410
  }
18411
18411
  if (close < 0) {
18412
- segments[segments.length] = "[" + key.slice(open2) + "]";
18412
+ segments[segments.length] = "[" + key.slice(open3) + "]";
18413
18413
  return segments;
18414
18414
  }
18415
- var seg = key.slice(open2, close + 1);
18415
+ var seg = key.slice(open3, close + 1);
18416
18416
  var content = seg.slice(1, -1);
18417
18417
  if (!options.plainObjects && has.call(Object.prototype, content) && !options.allowPrototypes) {
18418
18418
  return;
18419
18419
  }
18420
18420
  segments[segments.length] = seg;
18421
18421
  collected += 1;
18422
- open2 = key.indexOf("[", close + 1);
18422
+ open3 = key.indexOf("[", close + 1);
18423
18423
  }
18424
- if (open2 >= 0) {
18424
+ if (open3 >= 0) {
18425
18425
  if (options.strictDepth === true) {
18426
18426
  throw new RangeError("Input depth exceeded depth option of " + options.depth + " and strictDepth is true");
18427
18427
  }
18428
- segments[segments.length] = "[" + key.slice(open2) + "]";
18428
+ segments[segments.length] = "[" + key.slice(open3) + "]";
18429
18429
  }
18430
18430
  return segments;
18431
18431
  };
@@ -54524,7 +54524,7 @@ var StreamableHTTPServerTransport = class {
54524
54524
  };
54525
54525
 
54526
54526
  // src/server.ts
54527
- import { readFile, readdir, writeFile } from "node:fs/promises";
54527
+ import { readdir, writeFile } from "node:fs/promises";
54528
54528
 
54529
54529
  // node_modules/zod/v3/helpers/util.js
54530
54530
  var util;
@@ -62498,9 +62498,30 @@ var EMPTY_COMPLETION_RESULT = {
62498
62498
  }
62499
62499
  };
62500
62500
 
62501
- // src/runner.ts
62502
- import { execFile } from "node:child_process";
62503
- import { promisify } from "node:util";
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,38 +62611,144 @@ function assertProgramAllowed(program) {
62590
62611
  }
62591
62612
  }
62592
62613
 
62593
- // src/runner.ts
62594
- var execFileAsync = promisify(execFile);
62595
- async function runProgram(options) {
62596
- assertProgramAllowed(options.program);
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
- }
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);
62618
+ }
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
+ );
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);
62696
+ return {
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
62609
62721
  );
62610
- return { exitCode: 0, signal: null, stdout, stderr, timedOut: false };
62611
- } catch (error62) {
62612
- const failure2 = error62;
62613
62722
  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"
62723
+ ...snapshot(session),
62724
+ stdout,
62725
+ stderr,
62726
+ nextStdoutOffset: safeStdoutOffset + stdout.length,
62727
+ nextStderrOffset: safeStderrOffset + stderr.length
62619
62728
  };
62620
62729
  }
62621
- }
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
+ };
62622
62748
 
62623
62749
  // src/server.ts
62624
62750
  var roots = configuredRoots();
62751
+ var processManager = new ProcessManager();
62625
62752
  function textResult(value) {
62626
62753
  return {
62627
62754
  content: [{ type: "text", text: JSON.stringify(value, null, 2) }],
@@ -62717,11 +62844,14 @@ function createServer() {
62717
62844
  },
62718
62845
  async ({ path: requestedPath, maxCharacters }) => {
62719
62846
  const filename = await resolveReadablePath(requestedPath, roots);
62720
- const contents = await readFile(filename, "utf8");
62847
+ const { contents, truncated } = await readBoundedTextFile(
62848
+ filename,
62849
+ maxCharacters
62850
+ );
62721
62851
  return textResult({
62722
62852
  path: filename,
62723
- contents: contents.slice(0, maxCharacters),
62724
- truncated: contents.length > maxCharacters
62853
+ contents,
62854
+ truncated
62725
62855
  });
62726
62856
  }
62727
62857
  );
@@ -62758,10 +62888,10 @@ function createServer() {
62758
62888
  }
62759
62889
  );
62760
62890
  server.registerTool(
62761
- "run_program",
62891
+ "start_process",
62762
62892
  {
62763
- title: "Run a local program",
62764
- description: "Run one executable without a shell. Shells and privilege-elevation programs are blocked unless explicitly enabled by local configuration.",
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.",
62765
62895
  inputSchema: {
62766
62896
  program: external_exports.string().min(1),
62767
62897
  args: external_exports.array(external_exports.string()).max(100).default([]),
@@ -62770,14 +62900,19 @@ function createServer() {
62770
62900
  maxOutputCharacters: external_exports.number().int().min(1e3).max(2e5).default(5e4)
62771
62901
  },
62772
62902
  outputSchema: {
62903
+ sessionId: external_exports.string(),
62773
62904
  program: external_exports.string(),
62774
62905
  args: external_exports.array(external_exports.string()),
62775
62906
  cwd: external_exports.string(),
62907
+ pid: external_exports.number().int().nullable(),
62908
+ running: external_exports.boolean(),
62776
62909
  exitCode: external_exports.number().int().nullable(),
62777
62910
  signal: external_exports.string().nullable(),
62778
62911
  stdout: external_exports.string(),
62779
62912
  stderr: external_exports.string(),
62780
- timedOut: external_exports.boolean()
62913
+ timedOut: external_exports.boolean(),
62914
+ nextStdoutOffset: external_exports.number().int().nonnegative(),
62915
+ nextStderrOffset: external_exports.number().int().nonnegative()
62781
62916
  },
62782
62917
  annotations: {
62783
62918
  readOnlyHint: false,
@@ -62790,16 +62925,114 @@ function createServer() {
62790
62925
  cwd ?? roots[0],
62791
62926
  roots
62792
62927
  );
62793
- const result = await runProgram({
62794
- program,
62795
- args,
62796
- cwd: workingDirectory,
62797
- timeoutMs,
62798
- maxOutputChars: maxOutputCharacters
62799
- });
62800
- return textResult({ program, args, cwd: workingDirectory, ...result });
62928
+ return textResult(
62929
+ processManager.start({
62930
+ program,
62931
+ args,
62932
+ cwd: workingDirectory,
62933
+ timeoutMs,
62934
+ maxOutputCharacters
62935
+ })
62936
+ );
62801
62937
  }
62802
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
+ );
62803
63036
  return server;
62804
63037
  }
62805
63038
 
@@ -62860,6 +63093,7 @@ var listener = app.listen(port, host, () => {
62860
63093
  });
62861
63094
  function shutdown(signal) {
62862
63095
  console.log(`Received ${signal}; shutting down.`);
63096
+ processManager.close();
62863
63097
  listener.close((error62) => {
62864
63098
  if (error62) {
62865
63099
  console.error(error62);