koishi-plugin-terminal 1.0.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/lib/index.d.ts ADDED
@@ -0,0 +1,23 @@
1
+ import { Context, Schema } from 'koishi';
2
+ import * as pty from "node-pty";
3
+ export declare const name = "terminal";
4
+ export interface Config {
5
+ admin?: Array<string>;
6
+ root?: string;
7
+ shell?: string;
8
+ encoding?: string;
9
+ timeout?: number;
10
+ cols?: number;
11
+ rows?: number;
12
+ maxOutputLength: number;
13
+ }
14
+ export interface ShellSession {
15
+ terminal: pty.IPty;
16
+ buffer: string;
17
+ timer?: NodeJS.Timeout;
18
+ disposables: Array<{
19
+ dispose(): void;
20
+ }>;
21
+ }
22
+ export declare const Config: Schema<Config>;
23
+ export declare function apply(ctx: Context, config: Config): void;
package/lib/index.js ADDED
@@ -0,0 +1,172 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
8
+ var __export = (target, all) => {
9
+ for (var name2 in all)
10
+ __defProp(target, name2, { get: all[name2], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var src_exports = {};
32
+ __export(src_exports, {
33
+ Config: () => Config,
34
+ apply: () => apply,
35
+ name: () => name
36
+ });
37
+ module.exports = __toCommonJS(src_exports);
38
+ var import_koishi = require("koishi");
39
+ var pty = __toESM(require("node-pty"));
40
+ var import_node_timers = require("node:timers");
41
+ var name = "terminal";
42
+ var Config = import_koishi.Schema.object({
43
+ admin: import_koishi.Schema.array(String).description("超级管理员用户,具有绝对权限。").default([]),
44
+ root: import_koishi.Schema.string().description("初始工作路径。").default(process.env.HOME),
45
+ shell: import_koishi.Schema.string().description("Shell路径。留空则自动检测系统默认Shell。"),
46
+ encoding: import_koishi.Schema.string().description("输出内容编码。").default("utf8"),
47
+ timeout: import_koishi.Schema.number().description("超时时长。").default(import_koishi.Time.minute),
48
+ cols: import_koishi.Schema.number().description("终端列数").default(80),
49
+ rows: import_koishi.Schema.number().description("终端行数").default(24),
50
+ maxOutputLength: import_koishi.Schema.number().description("单次发送最大输出长度。").default(1800)
51
+ });
52
+ function resolveShell(shell) {
53
+ if (shell) return shell;
54
+ switch (process.platform) {
55
+ case "win32":
56
+ return process.env.COMSPEC || "powershell.exe";
57
+ case "darwin":
58
+ return process.env.SHELL || "zsh";
59
+ case "linux":
60
+ return process.env.SHELL || "bash";
61
+ }
62
+ return "bash";
63
+ }
64
+ __name(resolveShell, "resolveShell");
65
+ function stripAnsi(input) {
66
+ return input.replace(
67
+ // eslint-disable-next-line no-control-regex
68
+ /\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g,
69
+ ""
70
+ );
71
+ }
72
+ __name(stripAnsi, "stripAnsi");
73
+ function getKey(session) {
74
+ return `${session.platform}:${session.userId}`;
75
+ }
76
+ __name(getKey, "getKey");
77
+ var map = /* @__PURE__ */ new Map();
78
+ function apply(ctx, config) {
79
+ const allowedUsers = config.admin;
80
+ function sendCommand(shellSession, command) {
81
+ shellSession.terminal.write(command + "\r");
82
+ shellSession.terminal.write("pwd\r");
83
+ }
84
+ __name(sendCommand, "sendCommand");
85
+ function initSession(session, key) {
86
+ const terminal = pty.spawn(resolveShell(config.shell), [], {
87
+ name: "terminal",
88
+ cols: config.cols,
89
+ rows: config.rows,
90
+ cwd: config.root,
91
+ env: process.env
92
+ });
93
+ const shellSession = {
94
+ terminal,
95
+ buffer: "",
96
+ disposables: []
97
+ };
98
+ const flush = /* @__PURE__ */ __name(async () => {
99
+ shellSession.timer = void 0;
100
+ const output = stripAnsi(shellSession.buffer).trim();
101
+ shellSession.buffer = "";
102
+ if (!output) return;
103
+ const text = output.length > config.maxOutputLength ? output.slice(0, config.maxOutputLength - 1) + "\n ...Truncated output" : output;
104
+ await session.send(text + "$");
105
+ }, "flush");
106
+ const dataDisposable = terminal.onData((data) => {
107
+ shellSession.buffer += data;
108
+ if (shellSession.timer) (0, import_node_timers.clearTimeout)(shellSession.timer);
109
+ shellSession.timer = setTimeout(flush, 300);
110
+ });
111
+ const exitDisposable = terminal.onExit(async () => {
112
+ cleanupSession(shellSession, key);
113
+ await session.send("Shell exited.");
114
+ });
115
+ shellSession.disposables.push(dataDisposable, exitDisposable);
116
+ map.set(key, shellSession);
117
+ return shellSession;
118
+ }
119
+ __name(initSession, "initSession");
120
+ function cleanupSession(shellSession, key) {
121
+ map.delete(key);
122
+ shellSession.disposables.forEach((d) => d.dispose());
123
+ shellSession.terminal.kill();
124
+ if (shellSession.timer) (0, import_node_timers.clearTimeout)(shellSession.timer);
125
+ }
126
+ __name(cleanupSession, "cleanupSession");
127
+ ctx.command("shell [command:text]", "Start a persistent shell session", { authority: 4 }).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) => {
128
+ if (!allowedUsers.includes(session.userId)) {
129
+ return "Unauthorized user.";
130
+ }
131
+ const key = getKey(session);
132
+ if (options.terminate) {
133
+ const current2 = map.get(key);
134
+ if (!current2) return "There doesn't exist running shell session.";
135
+ cleanupSession(current2, key);
136
+ return "Shell session terminated.";
137
+ }
138
+ let current = map.get(key);
139
+ if (!current) {
140
+ current = initSession(session, key);
141
+ if (!command) return "Shell session started. Send regular messages as commands. Send shell -t to terminate.";
142
+ }
143
+ if (!command) return "Shell session is running.";
144
+ sendCommand(current, command);
145
+ });
146
+ ctx.middleware(async (session, next) => {
147
+ const key = getKey(session);
148
+ const current = map.get(key);
149
+ if (!current) return next();
150
+ const content = session.content.trim();
151
+ if (content === "shell" || content === "shell -t") {
152
+ return next();
153
+ }
154
+ if (!content) return;
155
+ sendCommand(current, content);
156
+ }, true);
157
+ ctx.on("dispose", () => {
158
+ for (const current of map.values()) {
159
+ current.disposables.forEach((d) => d.dispose());
160
+ if (current.timer) (0, import_node_timers.clearTimeout)(current.timer);
161
+ current.terminal.kill();
162
+ }
163
+ map.clear();
164
+ });
165
+ }
166
+ __name(apply, "apply");
167
+ // Annotate the CommonJS export names for ESM import in node:
168
+ 0 && (module.exports = {
169
+ Config,
170
+ apply,
171
+ name
172
+ });
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "koishi-plugin-terminal",
3
+ "description": "Persistent terminal interface to a shell session over QQ.",
4
+ "version": "1.0.0",
5
+ "main": "lib/index.js",
6
+ "typings": "lib/index.d.ts",
7
+ "files": [
8
+ "lib",
9
+ "dist"
10
+ ],
11
+ "license": "MIT",
12
+ "scripts": {},
13
+ "keywords": [
14
+ "chatbot",
15
+ "koishi",
16
+ "plugin",
17
+ "shell",
18
+ "terminal",
19
+ "cmd",
20
+ "bash",
21
+ "zsh"
22
+ ],
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "https://github.com/Jingmozhiyu/koishi-plugin-terminal.git"
26
+ },
27
+ "peerDependencies": {
28
+ "koishi": "^4.18.7"
29
+ },
30
+ "dependencies": {
31
+ "node-pty": "^1.1.0"
32
+ }
33
+ }
package/readme.md ADDED
@@ -0,0 +1,5 @@
1
+ # koishi-plugin-terminal
2
+
3
+ [![npm](https://img.shields.io/npm/v/koishi-plugin-terminal?style=flat-square)](https://www.npmjs.com/package/koishi-plugin-terminal)
4
+
5
+ Persistent terminal interface to a shell session over QQ.