@desktalk/miniapp-terminal 0.1.0-alpha.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DeskTalk contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,5 @@
1
+ import type { MiniAppManifest, MiniAppContext, MiniAppBackendActivation } from '@desktalk/sdk';
2
+ export declare const manifest: MiniAppManifest;
3
+ export declare function activate(ctx: MiniAppContext): MiniAppBackendActivation;
4
+ export declare function deactivate(): void;
5
+ //# sourceMappingURL=backend.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../src/backend.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AAY/F,eAAO,MAAM,QAAQ,EAAE,eAMtB,CAAC;AAkCF,wBAAgB,QAAQ,CAAC,GAAG,EAAE,cAAc,GAAG,wBAAwB,CAyMtE;AAED,wBAAgB,UAAU,IAAI,IAAI,CAGjC"}
@@ -0,0 +1,275 @@
1
+ // src/backend.ts
2
+ import { isAbsolute, join } from "node:path";
3
+
4
+ // src/safety-analyzer.ts
5
+ var BLOCK_RULES = [
6
+ {
7
+ test: (_prog, raw) => /rm\s+.*-[^\s]*r[^\s]*f[^\s]*\s+\/(\s|$|\*)/.test(raw) || /rm\s+.*-[^\s]*f[^\s]*r[^\s]*\s+\/(\s|$|\*)/.test(raw),
8
+ level: "block",
9
+ reason: "Catastrophic: rm -rf on root filesystem"
10
+ },
11
+ {
12
+ test: (_prog, raw) => /:\(\)\s*\{[^}]*:\s*\|\s*:\s*&[^}]*\}/.test(raw),
13
+ level: "block",
14
+ reason: "Fork bomb detected"
15
+ },
16
+ {
17
+ test: (_prog, raw) => />\s*\/dev\/([sh]d[a-z]|nvme|loop)/.test(raw),
18
+ level: "block",
19
+ reason: "Direct write to block device"
20
+ }
21
+ ];
22
+ var WARN_RULES = [
23
+ {
24
+ test: (prog, raw) => prog === "rm" || /\bxargs\s+.*\brm\b/.test(raw),
25
+ level: "warn",
26
+ reason: "rm command may delete files permanently"
27
+ },
28
+ {
29
+ test: (prog, raw) => prog === "chmod" && /777/.test(raw),
30
+ level: "warn",
31
+ reason: "chmod 777 grants full permissions to all users"
32
+ },
33
+ {
34
+ test: (prog) => prog === "mkfs" || prog.startsWith("mkfs."),
35
+ level: "warn",
36
+ reason: "mkfs formats a filesystem \u2014 data loss is irreversible"
37
+ },
38
+ {
39
+ test: (prog, raw) => prog === "dd" && /of=\s*\/dev\//.test(raw),
40
+ level: "warn",
41
+ reason: "dd writing to a device \u2014 potential data loss"
42
+ }
43
+ ];
44
+ function tokenize(input) {
45
+ return input.split(/\s*(?:;|&&|\|\||(?<!\|)\|(?!\|)|\n)\s*/).map((s) => s.trim()).filter((s) => s.length > 0);
46
+ }
47
+ function extractProgram(segment) {
48
+ let s = segment.replace(/^(\s*\w+=\S*\s+)*/, "");
49
+ s = s.replace(/^(sudo\s+)*(env\s+(\w+=\S*\s+)*)*/g, "");
50
+ s = s.replace(/^(\s*\w+=\S*\s+)*/, "");
51
+ const match = s.match(/^(\S+)/);
52
+ return match ? match[1] : "";
53
+ }
54
+ function analyzeCommand(input) {
55
+ for (const rule of BLOCK_RULES) {
56
+ if (rule.test("", input)) {
57
+ return {
58
+ level: "block",
59
+ command: input,
60
+ reason: rule.reason,
61
+ segments: [{ raw: input, program: "", level: "block", reason: rule.reason }]
62
+ };
63
+ }
64
+ }
65
+ const rawSegments = tokenize(input);
66
+ if (rawSegments.length === 0) {
67
+ return { level: "safe", command: input, segments: [] };
68
+ }
69
+ const segments = rawSegments.map((raw) => {
70
+ const program = extractProgram(raw);
71
+ for (const rule of BLOCK_RULES) {
72
+ if (rule.test(program, raw)) {
73
+ return { raw, program, level: rule.level, reason: rule.reason };
74
+ }
75
+ }
76
+ for (const rule of WARN_RULES) {
77
+ if (rule.test(program, raw)) {
78
+ return { raw, program, level: rule.level, reason: rule.reason };
79
+ }
80
+ }
81
+ return { raw, program, level: "safe" };
82
+ });
83
+ let overall = "safe";
84
+ let overallReason;
85
+ for (const seg of segments) {
86
+ if (seg.level === "block") {
87
+ return { level: "block", command: input, reason: seg.reason, segments };
88
+ }
89
+ if (seg.level === "warn" && overall === "safe") {
90
+ overall = "warn";
91
+ overallReason = seg.reason;
92
+ }
93
+ }
94
+ return { level: overall, command: input, reason: overallReason, segments };
95
+ }
96
+
97
+ // src/backend.ts
98
+ var manifest = {
99
+ id: "terminal",
100
+ name: "Terminal",
101
+ icon: "\u{1F5A5}\uFE0F",
102
+ version: "0.1.0",
103
+ description: "Terminal emulator with multi-tab support and command safety analysis"
104
+ };
105
+ var MAX_SCROLLBACK_LINES = 5e3;
106
+ var DEFAULT_COLS = 80;
107
+ var DEFAULT_ROWS = 24;
108
+ function resolveCwd(root, cwd) {
109
+ if (!cwd || cwd === ".") {
110
+ return root;
111
+ }
112
+ return isAbsolute(cwd) ? cwd : join(root, cwd);
113
+ }
114
+ function generateId() {
115
+ return Date.now().toString(36) + "-" + Math.random().toString(36).slice(2, 8);
116
+ }
117
+ function detectShell() {
118
+ return process.env.SHELL || "/bin/bash";
119
+ }
120
+ function activate(ctx) {
121
+ ctx.logger.info("Terminal MiniApp activated");
122
+ const sessions = /* @__PURE__ */ new Map();
123
+ const pendingConfirms = /* @__PURE__ */ new Map();
124
+ let ptyModule = null;
125
+ async function loadPty() {
126
+ if (!ptyModule) {
127
+ ptyModule = await import("node-pty");
128
+ }
129
+ return ptyModule;
130
+ }
131
+ function appendScrollback(session, data) {
132
+ const lines = data.split("\n");
133
+ session.scrollback.push(...lines);
134
+ if (session.scrollback.length > MAX_SCROLLBACK_LINES) {
135
+ session.scrollback.splice(0, session.scrollback.length - MAX_SCROLLBACK_LINES);
136
+ }
137
+ }
138
+ function getScrollbackLines(session, lines) {
139
+ const start = Math.max(0, session.scrollback.length - lines);
140
+ return session.scrollback.slice(start).join("\n");
141
+ }
142
+ ctx.messaging.onCommand(
143
+ "terminal.create",
144
+ async (req) => {
145
+ const nodePty = await loadPty();
146
+ const tabId = generateId();
147
+ const shell = detectShell();
148
+ const homeDir = ctx.paths.home || process.env.HOME || "/";
149
+ const cwd = resolveCwd(homeDir, req.cwd);
150
+ const label = req.label || shell.split("/").pop() || "shell";
151
+ const pty = nodePty.spawn(shell, [], {
152
+ name: "xterm-256color",
153
+ cols: DEFAULT_COLS,
154
+ rows: DEFAULT_ROWS,
155
+ cwd,
156
+ env: { ...process.env }
157
+ });
158
+ const tab = {
159
+ tabId,
160
+ label,
161
+ cwd,
162
+ pid: pty.pid,
163
+ running: true,
164
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
165
+ };
166
+ const session = { tab, pty, scrollback: [] };
167
+ sessions.set(tabId, session);
168
+ pty.onData((data) => {
169
+ appendScrollback(session, data);
170
+ const event = { tabId, data };
171
+ ctx.messaging.emit("terminal.output", event);
172
+ });
173
+ pty.onExit(({ exitCode }) => {
174
+ session.tab.running = false;
175
+ const event = { tabId, exitCode };
176
+ ctx.messaging.emit("terminal.exit", event);
177
+ ctx.logger.info(`PTY exited: tabId=${tabId}, exitCode=${exitCode}`);
178
+ });
179
+ ctx.logger.info(`Created PTY: tabId=${tabId}, shell=${shell}, cwd=${cwd}, pid=${pty.pid}`);
180
+ return { tabId };
181
+ }
182
+ );
183
+ ctx.messaging.onCommand("terminal.input", async (req) => {
184
+ const session = sessions.get(req.tabId);
185
+ if (!session) throw new Error(`Terminal tab not found: ${req.tabId}`);
186
+ if (!session.tab.running) throw new Error(`Terminal tab is not running: ${req.tabId}`);
187
+ session.pty.write(req.data);
188
+ });
189
+ ctx.messaging.onCommand(
190
+ "terminal.resize",
191
+ async (req) => {
192
+ const session = sessions.get(req.tabId);
193
+ if (!session) throw new Error(`Terminal tab not found: ${req.tabId}`);
194
+ session.pty.resize(req.cols, req.rows);
195
+ }
196
+ );
197
+ ctx.messaging.onCommand("terminal.close", async (req) => {
198
+ const session = sessions.get(req.tabId);
199
+ if (!session) throw new Error(`Terminal tab not found: ${req.tabId}`);
200
+ try {
201
+ session.pty.kill();
202
+ } catch {
203
+ }
204
+ sessions.delete(req.tabId);
205
+ ctx.logger.info(`Closed PTY: tabId=${req.tabId}`);
206
+ });
207
+ ctx.messaging.onCommand("terminal.list", async () => {
208
+ return Array.from(sessions.values()).map((s) => s.tab);
209
+ });
210
+ ctx.messaging.onCommand(
211
+ "terminal.getOutput",
212
+ async (req) => {
213
+ const session = sessions.get(req.tabId);
214
+ if (!session) throw new Error(`Terminal tab not found: ${req.tabId}`);
215
+ const lines = req.lines ?? 50;
216
+ return { output: getScrollbackLines(session, lines) };
217
+ }
218
+ );
219
+ ctx.messaging.onCommand("terminal.execute", async (req) => {
220
+ const session = sessions.get(req.tabId);
221
+ if (!session) throw new Error(`Terminal tab not found: ${req.tabId}`);
222
+ if (!session.tab.running) throw new Error(`Terminal tab is not running: ${req.tabId}`);
223
+ const analysis = analyzeCommand(req.command);
224
+ ctx.logger.info(
225
+ `Safety analysis: tabId=${req.tabId} command="${req.command}" level=${analysis.level}`
226
+ );
227
+ if (analysis.level === "block") {
228
+ return { accepted: false, reason: analysis.reason };
229
+ }
230
+ if (analysis.level === "warn") {
231
+ const requestId = generateId();
232
+ pendingConfirms.set(requestId, { command: req.command, tabId: req.tabId });
233
+ const event = {
234
+ tabId: req.tabId,
235
+ command: req.command,
236
+ risk: analysis.reason || "Potentially dangerous command",
237
+ requestId
238
+ };
239
+ ctx.messaging.emit("terminal.confirm", event);
240
+ return { accepted: true, reason: "Awaiting user confirmation" };
241
+ }
242
+ session.pty.write(req.command + "\n");
243
+ return { accepted: true };
244
+ });
245
+ ctx.messaging.onCommand(
246
+ "terminal.confirmResponse",
247
+ async (req) => {
248
+ const pending = pendingConfirms.get(req.requestId);
249
+ if (!pending) throw new Error(`No pending confirmation: ${req.requestId}`);
250
+ pendingConfirms.delete(req.requestId);
251
+ if (req.confirmed) {
252
+ const session = sessions.get(pending.tabId);
253
+ if (session && session.tab.running) {
254
+ session.pty.write(pending.command + "\n");
255
+ ctx.logger.info(
256
+ `User confirmed dangerous command: tabId=${pending.tabId} command="${pending.command}"`
257
+ );
258
+ }
259
+ } else {
260
+ ctx.logger.info(
261
+ `User cancelled dangerous command: tabId=${pending.tabId} command="${pending.command}"`
262
+ );
263
+ }
264
+ }
265
+ );
266
+ return {};
267
+ }
268
+ function deactivate() {
269
+ }
270
+ export {
271
+ activate,
272
+ deactivate,
273
+ manifest
274
+ };
275
+ //# sourceMappingURL=backend.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/backend.ts", "../src/safety-analyzer.ts"],
4
+ "sourcesContent": ["import type { MiniAppManifest, MiniAppContext, MiniAppBackendActivation } from '@desktalk/sdk';\nimport { isAbsolute, join } from 'node:path';\nimport type {\n TerminalTab,\n TerminalOutputEvent,\n TerminalExitEvent,\n TerminalConfirmEvent,\n} from './types';\nimport { analyzeCommand } from './safety-analyzer';\n\n// \u2500\u2500\u2500 Manifest \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport const manifest: MiniAppManifest = {\n id: 'terminal',\n name: 'Terminal',\n icon: '\uD83D\uDDA5\uFE0F',\n version: '0.1.0',\n description: 'Terminal emulator with multi-tab support and command safety analysis',\n};\n\n// \u2500\u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ninterface PtySession {\n tab: TerminalTab;\n pty: import('node-pty').IPty;\n scrollback: string[];\n}\n\n// \u2500\u2500\u2500 Constants \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst MAX_SCROLLBACK_LINES = 5000;\nconst DEFAULT_COLS = 80;\nconst DEFAULT_ROWS = 24;\n\nfunction resolveCwd(root: string, cwd?: string): string {\n if (!cwd || cwd === '.') {\n return root;\n }\n\n return isAbsolute(cwd) ? cwd : join(root, cwd);\n}\n\nfunction generateId(): string {\n return Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 8);\n}\n\nfunction detectShell(): string {\n return process.env.SHELL || '/bin/bash';\n}\n\n// \u2500\u2500\u2500 Activate \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function activate(ctx: MiniAppContext): MiniAppBackendActivation {\n ctx.logger.info('Terminal MiniApp activated');\n\n const sessions = new Map<string, PtySession>();\n const pendingConfirms = new Map<string, { command: string; tabId: string }>();\n\n /** Load node-pty dynamically (it's a native module). */\n let ptyModule: typeof import('node-pty') | null = null;\n async function loadPty(): Promise<typeof import('node-pty')> {\n if (!ptyModule) {\n ptyModule = await import('node-pty');\n }\n return ptyModule;\n }\n\n /** Append data to a session's scrollback buffer. */\n function appendScrollback(session: PtySession, data: string): void {\n const lines = data.split('\\n');\n session.scrollback.push(...lines);\n // Trim to max\n if (session.scrollback.length > MAX_SCROLLBACK_LINES) {\n session.scrollback.splice(0, session.scrollback.length - MAX_SCROLLBACK_LINES);\n }\n }\n\n /** Get the last N lines from scrollback. */\n function getScrollbackLines(session: PtySession, lines: number): string {\n const start = Math.max(0, session.scrollback.length - lines);\n return session.scrollback.slice(start).join('\\n');\n }\n\n // \u2500\u2500\u2500 terminal.create \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n ctx.messaging.onCommand<{ label?: string; cwd?: string }, { tabId: string }>(\n 'terminal.create',\n async (req) => {\n const nodePty = await loadPty();\n const tabId = generateId();\n const shell = detectShell();\n const homeDir = ctx.paths.home || process.env.HOME || '/';\n const cwd = resolveCwd(homeDir, req.cwd);\n const label = req.label || shell.split('/').pop() || 'shell';\n\n const pty = nodePty.spawn(shell, [], {\n name: 'xterm-256color',\n cols: DEFAULT_COLS,\n rows: DEFAULT_ROWS,\n cwd,\n env: { ...process.env } as Record<string, string>,\n });\n\n const tab: TerminalTab = {\n tabId,\n label,\n cwd,\n pid: pty.pid,\n running: true,\n createdAt: new Date().toISOString(),\n };\n\n const session: PtySession = { tab, pty, scrollback: [] };\n sessions.set(tabId, session);\n\n // Stream PTY output to frontend\n pty.onData((data: string) => {\n appendScrollback(session, data);\n const event: TerminalOutputEvent = { tabId, data };\n ctx.messaging.emit('terminal.output', event);\n });\n\n pty.onExit(({ exitCode }: { exitCode: number }) => {\n session.tab.running = false;\n const event: TerminalExitEvent = { tabId, exitCode };\n ctx.messaging.emit('terminal.exit', event);\n ctx.logger.info(`PTY exited: tabId=${tabId}, exitCode=${exitCode}`);\n });\n\n ctx.logger.info(`Created PTY: tabId=${tabId}, shell=${shell}, cwd=${cwd}, pid=${pty.pid}`);\n return { tabId };\n },\n );\n\n // \u2500\u2500\u2500 terminal.input \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n ctx.messaging.onCommand<{ tabId: string; data: string }, void>('terminal.input', async (req) => {\n const session = sessions.get(req.tabId);\n if (!session) throw new Error(`Terminal tab not found: ${req.tabId}`);\n if (!session.tab.running) throw new Error(`Terminal tab is not running: ${req.tabId}`);\n session.pty.write(req.data);\n });\n\n // \u2500\u2500\u2500 terminal.resize \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n ctx.messaging.onCommand<{ tabId: string; cols: number; rows: number }, void>(\n 'terminal.resize',\n async (req) => {\n const session = sessions.get(req.tabId);\n if (!session) throw new Error(`Terminal tab not found: ${req.tabId}`);\n session.pty.resize(req.cols, req.rows);\n },\n );\n\n // \u2500\u2500\u2500 terminal.close \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n ctx.messaging.onCommand<{ tabId: string }, void>('terminal.close', async (req) => {\n const session = sessions.get(req.tabId);\n if (!session) throw new Error(`Terminal tab not found: ${req.tabId}`);\n\n try {\n session.pty.kill();\n } catch {\n // Already dead \u2014 ignore\n }\n sessions.delete(req.tabId);\n ctx.logger.info(`Closed PTY: tabId=${req.tabId}`);\n });\n\n // \u2500\u2500\u2500 terminal.list \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n ctx.messaging.onCommand<void, TerminalTab[]>('terminal.list', async () => {\n return Array.from(sessions.values()).map((s) => s.tab);\n });\n\n // \u2500\u2500\u2500 terminal.getOutput \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n ctx.messaging.onCommand<{ tabId: string; lines?: number }, { output: string }>(\n 'terminal.getOutput',\n async (req) => {\n const session = sessions.get(req.tabId);\n if (!session) throw new Error(`Terminal tab not found: ${req.tabId}`);\n const lines = req.lines ?? 50;\n return { output: getScrollbackLines(session, lines) };\n },\n );\n\n // \u2500\u2500\u2500 terminal.execute \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n ctx.messaging.onCommand<\n { tabId: string; command: string },\n { accepted: boolean; reason?: string }\n >('terminal.execute', async (req) => {\n const session = sessions.get(req.tabId);\n if (!session) throw new Error(`Terminal tab not found: ${req.tabId}`);\n if (!session.tab.running) throw new Error(`Terminal tab is not running: ${req.tabId}`);\n\n const analysis = analyzeCommand(req.command);\n ctx.logger.info(\n `Safety analysis: tabId=${req.tabId} command=\"${req.command}\" level=${analysis.level}`,\n );\n\n if (analysis.level === 'block') {\n return { accepted: false, reason: analysis.reason };\n }\n\n if (analysis.level === 'warn') {\n // Emit a confirmation request to the frontend\n const requestId = generateId();\n pendingConfirms.set(requestId, { command: req.command, tabId: req.tabId });\n const event: TerminalConfirmEvent = {\n tabId: req.tabId,\n command: req.command,\n risk: analysis.reason || 'Potentially dangerous command',\n requestId,\n };\n ctx.messaging.emit('terminal.confirm', event);\n // Return accepted: true to indicate the command is pending user confirmation\n // The actual execution happens when the user confirms via terminal.confirmResponse\n return { accepted: true, reason: 'Awaiting user confirmation' };\n }\n\n // Safe \u2014 execute directly\n session.pty.write(req.command + '\\n');\n return { accepted: true };\n });\n\n // \u2500\u2500\u2500 terminal.confirmResponse \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n ctx.messaging.onCommand<{ requestId: string; confirmed: boolean }, void>(\n 'terminal.confirmResponse',\n async (req) => {\n const pending = pendingConfirms.get(req.requestId);\n if (!pending) throw new Error(`No pending confirmation: ${req.requestId}`);\n pendingConfirms.delete(req.requestId);\n\n if (req.confirmed) {\n const session = sessions.get(pending.tabId);\n if (session && session.tab.running) {\n session.pty.write(pending.command + '\\n');\n ctx.logger.info(\n `User confirmed dangerous command: tabId=${pending.tabId} command=\"${pending.command}\"`,\n );\n }\n } else {\n ctx.logger.info(\n `User cancelled dangerous command: tabId=${pending.tabId} command=\"${pending.command}\"`,\n );\n }\n },\n );\n\n return {};\n}\n\nexport function deactivate(): void {\n // Child process cleanup \u2014 node-pty processes are killed when the\n // child process exits (handled by BackendProcessManager).\n}\n", "/**\n * Command safety analyzer for the Terminal MiniApp.\n *\n * Tokenizes shell input, checks each segment against block/warn lists,\n * and returns a safety classification. Runs on the backend so it cannot\n * be bypassed by a modified frontend.\n */\n\nimport type { SafetyLevel, SafetyAnalysisResult, CommandSegment } from './types';\n\n// \u2500\u2500\u2500 Block patterns \u2014 rejected unconditionally \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ninterface CommandRule {\n test: (program: string, raw: string) => boolean;\n level: SafetyLevel;\n reason: string;\n}\n\nconst BLOCK_RULES: CommandRule[] = [\n {\n test: (_prog, raw) => /rm\\s+.*-[^\\s]*r[^\\s]*f[^\\s]*\\s+\\/(\\s|$|\\*)/.test(raw) ||\n /rm\\s+.*-[^\\s]*f[^\\s]*r[^\\s]*\\s+\\/(\\s|$|\\*)/.test(raw),\n level: 'block',\n reason: 'Catastrophic: rm -rf on root filesystem',\n },\n {\n test: (_prog, raw) => /:\\(\\)\\s*\\{[^}]*:\\s*\\|\\s*:\\s*&[^}]*\\}/.test(raw),\n level: 'block',\n reason: 'Fork bomb detected',\n },\n {\n test: (_prog, raw) => />\\s*\\/dev\\/([sh]d[a-z]|nvme|loop)/.test(raw),\n level: 'block',\n reason: 'Direct write to block device',\n },\n];\n\nconst WARN_RULES: CommandRule[] = [\n {\n test: (prog, raw) => prog === 'rm' || /\\bxargs\\s+.*\\brm\\b/.test(raw),\n level: 'warn',\n reason: 'rm command may delete files permanently',\n },\n {\n test: (prog, raw) => prog === 'chmod' && /777/.test(raw),\n level: 'warn',\n reason: 'chmod 777 grants full permissions to all users',\n },\n {\n test: (prog) => prog === 'mkfs' || prog.startsWith('mkfs.'),\n level: 'warn',\n reason: 'mkfs formats a filesystem \u2014 data loss is irreversible',\n },\n {\n test: (prog, raw) => prog === 'dd' && /of=\\s*\\/dev\\//.test(raw),\n level: 'warn',\n reason: 'dd writing to a device \u2014 potential data loss',\n },\n];\n\n// \u2500\u2500\u2500 Tokenizer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Split a shell command line into individual command segments by splitting\n * on shell operators: ; && || | \\n\n *\n * This is a simplified tokenizer \u2014 it does not handle quoted strings or\n * escaped characters perfectly, but it covers the common cases for safety\n * gating purposes.\n */\nexport function tokenize(input: string): string[] {\n // Split on ; && || | (but not ||) and newlines\n // We use a regex that matches the operators as delimiters\n return input\n .split(/\\s*(?:;|&&|\\|\\||(?<!\\|)\\|(?!\\|)|\\n)\\s*/)\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n}\n\n/**\n * Extract the program name (first word) from a command segment.\n * Handles env var assignments (e.g., `FOO=bar cmd`) and sudo/env prefixes.\n */\nexport function extractProgram(segment: string): string {\n // Strip leading env assignments like VAR=value\n let s = segment.replace(/^(\\s*\\w+=\\S*\\s+)*/, '');\n // Strip sudo / env prefixes (env may be followed by VAR=val pairs)\n s = s.replace(/^(sudo\\s+)*(env\\s+(\\w+=\\S*\\s+)*)*/g, '');\n // Strip any remaining leading env assignments after env/sudo\n s = s.replace(/^(\\s*\\w+=\\S*\\s+)*/, '');\n // The first word is the program\n const match = s.match(/^(\\S+)/);\n return match ? match[1] : '';\n}\n\n// \u2500\u2500\u2500 Analyzer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Analyze a command string for safety.\n *\n * Returns a SafetyAnalysisResult with the overall level (worst of all segments)\n * and per-segment details.\n */\nexport function analyzeCommand(input: string): SafetyAnalysisResult {\n // First check whole-input block rules (e.g., fork bombs span operators)\n for (const rule of BLOCK_RULES) {\n if (rule.test('', input)) {\n return {\n level: 'block',\n command: input,\n reason: rule.reason,\n segments: [{ raw: input, program: '', level: 'block', reason: rule.reason }],\n };\n }\n }\n\n const rawSegments = tokenize(input);\n if (rawSegments.length === 0) {\n return { level: 'safe', command: input, segments: [] };\n }\n\n const segments: CommandSegment[] = rawSegments.map((raw) => {\n const program = extractProgram(raw);\n\n // Check block rules per-segment\n for (const rule of BLOCK_RULES) {\n if (rule.test(program, raw)) {\n return { raw, program, level: rule.level, reason: rule.reason };\n }\n }\n\n // Check warn rules per-segment\n for (const rule of WARN_RULES) {\n if (rule.test(program, raw)) {\n return { raw, program, level: rule.level, reason: rule.reason };\n }\n }\n\n return { raw, program, level: 'safe' as const };\n });\n\n // Overall level is the worst across all segments\n let overall: SafetyLevel = 'safe';\n let overallReason: string | undefined;\n for (const seg of segments) {\n if (seg.level === 'block') {\n return { level: 'block', command: input, reason: seg.reason, segments };\n }\n if (seg.level === 'warn' && overall === 'safe') {\n overall = 'warn';\n overallReason = seg.reason;\n }\n }\n\n return { level: overall, command: input, reason: overallReason, segments };\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,9 @@
1
+ interface SafetyConfirmDialogProps {
2
+ command: string;
3
+ risk: string;
4
+ onConfirm: () => void;
5
+ onCancel: () => void;
6
+ }
7
+ export declare function SafetyConfirmDialog({ command, risk, onConfirm, onCancel, }: SafetyConfirmDialogProps): import("react/jsx-runtime").JSX.Element;
8
+ export {};
9
+ //# sourceMappingURL=SafetyConfirmDialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SafetyConfirmDialog.d.ts","sourceRoot":"","sources":["../../src/components/SafetyConfirmDialog.tsx"],"names":[],"mappings":"AAGA,UAAU,wBAAwB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,wBAAgB,mBAAmB,CAAC,EAClC,OAAO,EACP,IAAI,EACJ,SAAS,EACT,QAAQ,GACT,EAAE,wBAAwB,2CAqB1B"}
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ interface TerminalActionsProps {
3
+ children: React.ReactNode;
4
+ activeTabId: string | null;
5
+ onTabCreated: (tabId: string) => void;
6
+ onTabClosed: (tabId: string) => void;
7
+ onTabFocused: (tabId: string) => void;
8
+ }
9
+ export declare function TerminalActions({ children, activeTabId, onTabCreated, onTabClosed, onTabFocused, }: TerminalActionsProps): import("react/jsx-runtime").JSX.Element;
10
+ export {};
11
+ //# sourceMappingURL=TerminalActions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TerminalActions.d.ts","sourceRoot":"","sources":["../../src/components/TerminalActions.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsB,MAAM,OAAO,CAAC;AAI3C,UAAU,oBAAoB;IAC5B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACvC;AAED,wBAAgB,eAAe,CAAC,EAC9B,QAAQ,EACR,WAAW,EACX,YAAY,EACZ,WAAW,EACX,YAAY,GACb,EAAE,oBAAoB,2CA0ItB"}
@@ -0,0 +1,7 @@
1
+ interface TerminalContainerProps {
2
+ tabIds: string[];
3
+ activeTabId: string | null;
4
+ }
5
+ export declare function TerminalContainer({ tabIds, activeTabId }: TerminalContainerProps): import("react/jsx-runtime").JSX.Element;
6
+ export {};
7
+ //# sourceMappingURL=TerminalContainer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TerminalContainer.d.ts","sourceRoot":"","sources":["../../src/components/TerminalContainer.tsx"],"names":[],"mappings":"AAIA,UAAU,sBAAsB;IAC9B,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,wBAAgB,iBAAiB,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE,sBAAsB,2CAahF"}
@@ -0,0 +1,15 @@
1
+ interface TabInfo {
2
+ tabId: string;
3
+ label: string;
4
+ running: boolean;
5
+ }
6
+ interface TerminalTabBarProps {
7
+ tabs: TabInfo[];
8
+ activeTabId: string | null;
9
+ onSelectTab: (tabId: string) => void;
10
+ onCloseTab: (tabId: string) => void;
11
+ onNewTab: () => void;
12
+ }
13
+ export declare function TerminalTabBar({ tabs, activeTabId, onSelectTab, onCloseTab, onNewTab, }: TerminalTabBarProps): import("react/jsx-runtime").JSX.Element;
14
+ export {};
15
+ //# sourceMappingURL=TerminalTabBar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TerminalTabBar.d.ts","sourceRoot":"","sources":["../../src/components/TerminalTabBar.tsx"],"names":[],"mappings":"AAGA,UAAU,OAAO;IACf,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,UAAU,mBAAmB;IAC3B,IAAI,EAAE,OAAO,EAAE,CAAC;IAChB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,wBAAgB,cAAc,CAAC,EAC7B,IAAI,EACJ,WAAW,EACX,WAAW,EACX,UAAU,EACV,QAAQ,GACT,EAAE,mBAAmB,2CA8BrB"}
@@ -0,0 +1,7 @@
1
+ interface TerminalViewProps {
2
+ tabId: string;
3
+ visible: boolean;
4
+ }
5
+ export declare function TerminalView({ tabId, visible }: TerminalViewProps): import("react/jsx-runtime").JSX.Element;
6
+ export {};
7
+ //# sourceMappingURL=TerminalView.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TerminalView.d.ts","sourceRoot":"","sources":["../../src/components/TerminalView.tsx"],"names":[],"mappings":"AAOA,UAAU,iBAAiB;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,wBAAgB,YAAY,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,iBAAiB,2CA+GjE"}
@@ -0,0 +1,4 @@
1
+ import type { MiniAppFrontendActivation, MiniAppFrontendContext } from '@desktalk/sdk';
2
+ import '@xterm/xterm/css/xterm.css';
3
+ export declare function activate(ctx: MiniAppFrontendContext): MiniAppFrontendActivation;
4
+ //# sourceMappingURL=frontend.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontend.d.ts","sourceRoot":"","sources":["../src/frontend.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,yBAAyB,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAUvF,OAAO,4BAA4B,CAAC;AAgLpC,wBAAgB,QAAQ,CAAC,GAAG,EAAE,sBAAsB,GAAG,yBAAyB,CAgB/E"}