koishi-plugin-terminal 1.0.1 → 1.0.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/lib/index.d.ts CHANGED
@@ -15,6 +15,7 @@ export interface ShellSession {
15
15
  terminal: pty.IPty;
16
16
  buffer: string;
17
17
  timer?: NodeJS.Timeout;
18
+ timeoutTimer?: NodeJS.Timeout;
18
19
  disposables: Array<{
19
20
  dispose(): void;
20
21
  }>;
package/lib/index.js CHANGED
@@ -37,6 +37,8 @@ __export(src_exports, {
37
37
  module.exports = __toCommonJS(src_exports);
38
38
  var import_koishi = require("koishi");
39
39
  var pty = __toESM(require("node-pty"));
40
+ var import_node_fs = require("node:fs");
41
+ var import_node_path = require("node:path");
40
42
  var import_node_timers = require("node:timers");
41
43
  var name = "terminal";
42
44
  var Config = import_koishi.Schema.object({
@@ -75,10 +77,54 @@ function getKey(session) {
75
77
  return `${session.platform}:${session.userId}`;
76
78
  }
77
79
  __name(getKey, "getKey");
80
+ function fixNodePtyHelper() {
81
+ if (process.platform !== "darwin") return;
82
+ try {
83
+ const root = (0, import_node_path.dirname)(require.resolve("node-pty/package.json"));
84
+ const helper = (0, import_node_path.join)(root, "prebuilds", `darwin-${process.arch}`, "spawn-helper");
85
+ const mode = (0, import_node_fs.statSync)(helper).mode;
86
+ if (!(mode & 73)) (0, import_node_fs.chmodSync)(helper, mode | 493);
87
+ } catch {
88
+ }
89
+ }
90
+ __name(fixNodePtyHelper, "fixNodePtyHelper");
91
+ function isInteractiveCommand(command) {
92
+ const trimmed = command.trim();
93
+ if (!trimmed) return false;
94
+ const [name2, ...args] = trimmed.split(/\s+/);
95
+ if (/^(vi|vim|nvim|nano|emacs)$/.test(name2)) return true;
96
+ if (/^(less|more|man)$/.test(name2)) return true;
97
+ if (/^(top|htop|btop|watch)$/.test(name2)) return true;
98
+ if (/^(tmux|screen)$/.test(name2)) return true;
99
+ if (/^(sftp|ftp|telnet)$/.test(name2)) return true;
100
+ if (/^(mysql|psql|sqlite3|redis-cli|mongosh)$/.test(name2)) return true;
101
+ if (/^(node|python|python3|ipython|ruby|irb|php|lua|R)$/.test(name2) && !args.length) return true;
102
+ if (name2 === "tail" && args.includes("-f")) return true;
103
+ if (name2 === "docker" && args.includes("exec") && args.some((arg) => arg.includes("it"))) return true;
104
+ if (name2 === "kubectl" && args.includes("exec") && args.some((arg) => arg.includes("it"))) return true;
105
+ return false;
106
+ }
107
+ __name(isInteractiveCommand, "isInteractiveCommand");
78
108
  var map = /* @__PURE__ */ new Map();
79
109
  function apply(ctx, config) {
110
+ fixNodePtyHelper();
80
111
  const allowedUsers = config.admin;
81
- function sendCommand(shellSession, command) {
112
+ function refreshTimeout(shellSession, key, session) {
113
+ if (!config.timeout) return;
114
+ if (shellSession.timeoutTimer) (0, import_node_timers.clearTimeout)(shellSession.timeoutTimer);
115
+ shellSession.timeoutTimer = setTimeout(async () => {
116
+ if (map.get(key) !== shellSession) return;
117
+ cleanupSession(shellSession, key, true);
118
+ await session.send("Shell session timed out.");
119
+ }, config.timeout);
120
+ }
121
+ __name(refreshTimeout, "refreshTimeout");
122
+ function sendCommand(shellSession, key, session, command) {
123
+ refreshTimeout(shellSession, key, session);
124
+ if (isInteractiveCommand(command)) {
125
+ shellSession.terminal.write(`echo "Interactive command is not supported in chat terminal. Use a non-interactive form, or run shell -t to restart."\r`);
126
+ return;
127
+ }
82
128
  shellSession.terminal.write(command + "\r");
83
129
  }
84
130
  __name(sendCommand, "sendCommand");
@@ -115,12 +161,14 @@ function apply(ctx, config) {
115
161
  });
116
162
  shellSession.disposables.push(dataDisposable, exitDisposable);
117
163
  map.set(key, shellSession);
164
+ refreshTimeout(shellSession, key, session);
118
165
  return shellSession;
119
166
  }
120
167
  __name(initSession, "initSession");
121
168
  function cleanupSession(shellSession, key, kill = true) {
122
169
  map.delete(key);
123
170
  if (shellSession.timer) (0, import_node_timers.clearTimeout)(shellSession.timer);
171
+ if (shellSession.timeoutTimer) (0, import_node_timers.clearTimeout)(shellSession.timeoutTimer);
124
172
  shellSession.disposables.forEach((d) => d.dispose());
125
173
  shellSession.disposables.length = 0;
126
174
  if (kill) {
@@ -131,7 +179,7 @@ function apply(ctx, config) {
131
179
  }
132
180
  }
133
181
  __name(cleanupSession, "cleanupSession");
134
- ctx.command("shell [command:text]", "Start a persistent shell session", { authority: 0 }).option("terminate", "-t Terminate current shell session").usage("After start up, regular user messages will be sent to shell process.").example("shell echo Operating System: Three Easy Pieces > qljj.txt").action(async ({ session, options }, command) => {
182
+ ctx.command("shell [command:text]", "Start a persistent shell session", { authority: config.auth }).option("terminate", "-t Terminate current shell session").usage("After start up, regular user messages will be sent to shell process.").example("shell echo Operating System: Three Easy Pieces > qljj.txt").action(async ({ session, options }, command) => {
135
183
  if (!allowedUsers.includes(session.userId)) {
136
184
  return "Unauthorized user.";
137
185
  }
@@ -148,7 +196,7 @@ function apply(ctx, config) {
148
196
  if (!command) return "Shell session started. Send regular messages as commands. Send shell -t to terminate.";
149
197
  }
150
198
  if (!command) return "Shell session is running.";
151
- sendCommand(current, command);
199
+ sendCommand(current, key, session, command);
152
200
  });
153
201
  ctx.middleware(async (session, next) => {
154
202
  const key = getKey(session);
@@ -159,12 +207,13 @@ function apply(ctx, config) {
159
207
  return next();
160
208
  }
161
209
  if (!content) return;
162
- sendCommand(current, content);
210
+ sendCommand(current, key, session, content);
163
211
  }, true);
164
212
  ctx.on("dispose", () => {
165
213
  for (const current of map.values()) {
166
214
  current.disposables.forEach((d) => d.dispose());
167
215
  if (current.timer) (0, import_node_timers.clearTimeout)(current.timer);
216
+ if (current.timeoutTimer) (0, import_node_timers.clearTimeout)(current.timeoutTimer);
168
217
  current.terminal.kill();
169
218
  }
170
219
  map.clear();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-terminal",
3
3
  "description": "通过 QQ 运行持久的 Shell 终端",
4
- "version": "1.0.1",
4
+ "version": "1.0.3",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [