@priyanshumit/macos-terminal-mcp 0.3.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.
@@ -0,0 +1,70 @@
1
+ import { randomUUID } from "node:crypto";
2
+ const STALE_MS = 10 * 60 * 1000;
3
+ const pending = new Map();
4
+ export function enqueue(input) {
5
+ const id = randomUUID();
6
+ const now = Date.now();
7
+ const expiresAt = now + STALE_MS;
8
+ const promise = new Promise((resolve) => {
9
+ const timer = setTimeout(() => {
10
+ const entry = pending.get(id);
11
+ if (entry) {
12
+ pending.delete(id);
13
+ entry.resolve({ approved: false, source: "expired" });
14
+ }
15
+ }, STALE_MS);
16
+ pending.set(id, {
17
+ id,
18
+ tty: input.tty,
19
+ command: input.command,
20
+ matchedPattern: input.matchedPattern,
21
+ matchedDescription: input.matchedDescription,
22
+ createdAt: now,
23
+ expiresAt,
24
+ resolve,
25
+ timer,
26
+ });
27
+ });
28
+ return { id, promise };
29
+ }
30
+ export function resolvePending(id, approved, source, reason) {
31
+ const entry = pending.get(id);
32
+ if (!entry)
33
+ return false;
34
+ clearTimeout(entry.timer);
35
+ pending.delete(id);
36
+ entry.resolve({ approved, source, ...(reason ? { reason } : {}) });
37
+ return true;
38
+ }
39
+ export function listPending() {
40
+ const now = Date.now();
41
+ return Array.from(pending.values())
42
+ .map((e) => ({
43
+ id: e.id,
44
+ tty: e.tty,
45
+ command: e.command,
46
+ ...(e.matchedPattern ? { matchedPattern: e.matchedPattern } : {}),
47
+ ...(e.matchedDescription ? { matchedDescription: e.matchedDescription } : {}),
48
+ createdAt: e.createdAt,
49
+ expiresAt: e.expiresAt,
50
+ ageMs: now - e.createdAt,
51
+ }))
52
+ .sort((a, b) => a.createdAt - b.createdAt);
53
+ }
54
+ export function getPending(id) {
55
+ const e = pending.get(id);
56
+ if (!e)
57
+ return undefined;
58
+ const now = Date.now();
59
+ return {
60
+ id: e.id,
61
+ tty: e.tty,
62
+ command: e.command,
63
+ ...(e.matchedPattern ? { matchedPattern: e.matchedPattern } : {}),
64
+ ...(e.matchedDescription ? { matchedDescription: e.matchedDescription } : {}),
65
+ createdAt: e.createdAt,
66
+ expiresAt: e.expiresAt,
67
+ ageMs: now - e.createdAt,
68
+ };
69
+ }
70
+ //# sourceMappingURL=queue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queue.js","sourceRoot":"","sources":["../../src/safety/queue.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,100 @@
1
+ import { z } from "zod";
2
+ import { OsascriptError, runJxa } from "../applescript.js";
3
+ import { appendAudit } from "../safety/audit.js";
4
+ import { confirmWithUser, isWriteToolsEnabled, writeToolsDisabledMessage, } from "../safety/confirm.js";
5
+ export function buildClearScript(tty) {
6
+ return `
7
+ function safe(fn) { try { return fn(); } catch (e) { return null; } }
8
+ var app = Application.currentApplication();
9
+ app.includeStandardAdditions = true;
10
+ (function clearTab(targetTty) {
11
+ const terminal = Application("Terminal");
12
+ const wins = terminal.windows();
13
+ for (let wi = 0; wi < wins.length; wi++) {
14
+ const w = wins[wi];
15
+ const tabs = w.tabs();
16
+ for (let ti = 0; ti < tabs.length; ti++) {
17
+ const t = tabs[ti];
18
+ if (safe(function () { return t.tty(); }) === targetTty) {
19
+ terminal.activate();
20
+ try { w.frontmost = true; } catch (e) {}
21
+ try { t.selected = true; } catch (e) {}
22
+ delay(0.2);
23
+ Application("System Events").keystroke("k", { using: "command down" });
24
+ return "OK";
25
+ }
26
+ }
27
+ }
28
+ throw new Error("No Terminal.app tab found with tty " + targetTty);
29
+ })(${JSON.stringify(tty)});
30
+ `;
31
+ }
32
+ export async function clearHandler({ tty }) {
33
+ if (!isWriteToolsEnabled()) {
34
+ return {
35
+ content: [{ type: "text", text: writeToolsDisabledMessage("terminal_clear") }],
36
+ isError: true,
37
+ };
38
+ }
39
+ let allowed;
40
+ try {
41
+ allowed = await confirmWithUser({
42
+ title: "macos-terminal-mcp · terminal_clear",
43
+ message: `Clear scrollback of ${tty}?\n\nThis briefly switches focus to Terminal.app to deliver Cmd+K.`,
44
+ });
45
+ }
46
+ catch (err) {
47
+ const message = err instanceof Error ? err.message : String(err);
48
+ return {
49
+ content: [{ type: "text", text: `Confirmation dialog failed: ${message}` }],
50
+ isError: true,
51
+ };
52
+ }
53
+ if (!allowed) {
54
+ await appendAudit({
55
+ tool: "terminal_clear",
56
+ outcome: "denied",
57
+ tty,
58
+ source: "dialog",
59
+ });
60
+ return { content: [{ type: "text", text: "User denied the clear." }], isError: true };
61
+ }
62
+ try {
63
+ await runJxa(buildClearScript(tty));
64
+ await appendAudit({
65
+ tool: "terminal_clear",
66
+ outcome: "success",
67
+ tty,
68
+ source: "dialog",
69
+ });
70
+ return { content: [{ type: "text", text: `Cleared scrollback of ${tty}.` }] };
71
+ }
72
+ catch (err) {
73
+ const message = err instanceof Error ? err.message : String(err);
74
+ const hint = err instanceof OsascriptError && /not authorized/i.test(err.stderr)
75
+ ? "\nMissing Automation OR Accessibility permission. Both are required for keystroke simulation."
76
+ : "";
77
+ await appendAudit({
78
+ tool: "terminal_clear",
79
+ outcome: "error",
80
+ tty,
81
+ errorMessage: message,
82
+ });
83
+ return {
84
+ content: [{ type: "text", text: `terminal_clear failed: ${message}${hint}` }],
85
+ isError: true,
86
+ };
87
+ }
88
+ }
89
+ export function register(server) {
90
+ server.registerTool("terminal_clear", {
91
+ description: "Clear the buffer AND scrollback of a specific Terminal.app tab by simulating Cmd+K (Edit → Clear Scrollback). Side effect: briefly steals focus to Terminal.app to deliver the keystroke; the user may need to switch back to their previous app. Requires WRITE_TOOLS_ENABLED=1 and triggers a confirmation dialog.",
92
+ inputSchema: {
93
+ tty: z
94
+ .string()
95
+ .regex(/^\/dev\/ttys[0-9]+$/)
96
+ .describe('Target tab tty, e.g. "/dev/ttys003"'),
97
+ },
98
+ }, clearHandler);
99
+ }
100
+ //# sourceMappingURL=clear.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clear.js","sourceRoot":"","sources":["../../src/tools/clear.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,158 @@
1
+ import { z } from "zod";
2
+ import { OsascriptError, runJxa } from "../applescript.js";
3
+ import { appendAudit } from "../safety/audit.js";
4
+ import { confirmWithUser, isWriteToolsEnabled, writeToolsDisabledMessage, } from "../safety/confirm.js";
5
+ import { evaluateCommand, loadSafetyConfig } from "../safety/patterns.js";
6
+ import { enqueue, resolvePending } from "../safety/queue.js";
7
+ export function buildExecuteScript(tty, command) {
8
+ return `
9
+ function safe(fn) { try { return fn(); } catch (e) { return null; } }
10
+ (function executeInTab(targetTty, command) {
11
+ const terminal = Application("Terminal");
12
+ const wins = terminal.windows();
13
+ for (let wi = 0; wi < wins.length; wi++) {
14
+ const w = wins[wi];
15
+ const tabs = w.tabs();
16
+ for (let ti = 0; ti < tabs.length; ti++) {
17
+ const t = tabs[ti];
18
+ if (safe(function () { return t.tty(); }) === targetTty) {
19
+ terminal.doScript(command, { in: t });
20
+ return "OK";
21
+ }
22
+ }
23
+ }
24
+ throw new Error("No Terminal.app tab found with tty " + targetTty);
25
+ })(${JSON.stringify(tty)}, ${JSON.stringify(command)});
26
+ `;
27
+ }
28
+ function truncate(s, max) {
29
+ return s.length > max ? `${s.slice(0, max)}… [${s.length - max} chars elided]` : s;
30
+ }
31
+ export async function executeHandler({ tty, command }) {
32
+ if (!isWriteToolsEnabled()) {
33
+ return {
34
+ content: [{ type: "text", text: writeToolsDisabledMessage("terminal_execute") }],
35
+ isError: true,
36
+ };
37
+ }
38
+ const config = await loadSafetyConfig();
39
+ const verdict = evaluateCommand(command, config);
40
+ if (verdict.level === "forbidden") {
41
+ const descPart = verdict.matchedDescription ? ` (${verdict.matchedDescription})` : "";
42
+ await appendAudit({
43
+ tool: "terminal_execute",
44
+ outcome: "refused",
45
+ tty,
46
+ command,
47
+ level: "forbidden",
48
+ matchedPattern: verdict.matchedPattern,
49
+ });
50
+ return {
51
+ content: [
52
+ {
53
+ type: "text",
54
+ text: `Refused: command matches FORBIDDEN pattern ${verdict.matchedPattern}${descPart}. ` +
55
+ `Forbidden commands cannot be approved through this tool — run them yourself in a terminal if you need to.`,
56
+ },
57
+ ],
58
+ isError: true,
59
+ };
60
+ }
61
+ let resolutionSource = "auto";
62
+ if (verdict.level === "requires_approval") {
63
+ const descPart = verdict.matchedDescription
64
+ ? `\nMatched: ${verdict.matchedPattern} (${verdict.matchedDescription})`
65
+ : verdict.matchedPattern
66
+ ? `\nMatched pattern: ${verdict.matchedPattern}`
67
+ : "\nNo matching pattern — default policy requires approval.";
68
+ const { id, promise } = enqueue({
69
+ tty,
70
+ command,
71
+ matchedPattern: verdict.matchedPattern,
72
+ matchedDescription: verdict.matchedDescription,
73
+ });
74
+ void confirmWithUser({
75
+ title: "macos-terminal-mcp · terminal_execute",
76
+ message: `Run in ${tty}:\n\n${truncate(command, 800)}${descPart}\n\nQueue id: ${id}`,
77
+ }).then((allowed) => resolvePending(id, allowed, "dialog"), () => undefined);
78
+ const result = await promise;
79
+ if (!result.approved) {
80
+ const reasonPart = result.reason ? ` (${result.reason})` : "";
81
+ await appendAudit({
82
+ tool: "terminal_execute",
83
+ outcome: "denied",
84
+ tty,
85
+ command,
86
+ level: "requires_approval",
87
+ matchedPattern: verdict.matchedPattern,
88
+ source: result.source,
89
+ ...(result.reason ? { details: { reason: result.reason } } : {}),
90
+ });
91
+ return {
92
+ content: [
93
+ {
94
+ type: "text",
95
+ text: `Command denied via ${result.source}${reasonPart}.`,
96
+ },
97
+ ],
98
+ isError: true,
99
+ };
100
+ }
101
+ resolutionSource = result.source;
102
+ }
103
+ try {
104
+ await runJxa(buildExecuteScript(tty, command));
105
+ const path = verdict.level === "safe"
106
+ ? `auto-run (safe pattern: ${verdict.matchedPattern})`
107
+ : `approved via ${resolutionSource}`;
108
+ await appendAudit({
109
+ tool: "terminal_execute",
110
+ outcome: "success",
111
+ tty,
112
+ command,
113
+ level: verdict.level,
114
+ matchedPattern: verdict.matchedPattern,
115
+ source: verdict.level === "safe" ? "auto" : resolutionSource,
116
+ });
117
+ return {
118
+ content: [
119
+ {
120
+ type: "text",
121
+ text: `Executed in ${tty} [${path}]: ${truncate(command, 200)}`,
122
+ },
123
+ ],
124
+ };
125
+ }
126
+ catch (err) {
127
+ const message = err instanceof Error ? err.message : String(err);
128
+ const hint = err instanceof OsascriptError && /not authorized/i.test(err.stderr)
129
+ ? "\nAutomation permission missing — System Settings → Privacy & Security → Automation."
130
+ : "";
131
+ await appendAudit({
132
+ tool: "terminal_execute",
133
+ outcome: "error",
134
+ tty,
135
+ command,
136
+ level: verdict.level,
137
+ matchedPattern: verdict.matchedPattern,
138
+ errorMessage: message,
139
+ });
140
+ return {
141
+ content: [{ type: "text", text: `terminal_execute failed: ${message}${hint}` }],
142
+ isError: true,
143
+ };
144
+ }
145
+ }
146
+ export function register(server) {
147
+ server.registerTool("terminal_execute", {
148
+ description: 'Run a shell command in a specific Terminal.app tab identified by its tty (e.g. "/dev/ttys003"). Behaves as if the command was typed by the user and Enter was pressed — output appears in the tab. Three-tier safety: "safe" patterns auto-run, "requires_approval" patterns trigger a native confirmation dialog, "forbidden" patterns are refused outright. Requires WRITE_TOOLS_ENABLED=1.',
149
+ inputSchema: {
150
+ tty: z
151
+ .string()
152
+ .regex(/^\/dev\/ttys[0-9]+$/)
153
+ .describe('Target tab tty, e.g. "/dev/ttys003"'),
154
+ command: z.string().min(1).max(8192).describe("Shell command to run in the target tab"),
155
+ },
156
+ }, executeHandler);
157
+ }
158
+ //# sourceMappingURL=execute.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"execute.js","sourceRoot":"","sources":["../../src/tools/execute.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EACL,eAAe,EACf,mBAAmB,EACnB,yBAAyB,GAC1B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC1E,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAE7D,MAAM,UAAU,kBAAkB,CAAC,GAAW,EAAE,OAAe;IAC7D,OAAO;;;;;;;;;;;;;;;;;KAiBJ,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;CACnD,CAAC;AACF,CAAC;AAED,SAAS,QAAQ,CAAC,CAAS,EAAE,GAAW;IACtC,OAAO,CAAC,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,MAAM,GAAG,GAAG,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC;AACrF,CAAC;AAOD,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,EAAE,GAAG,EAAE,OAAO,EAAgB;IACjE,IAAI,CAAC,mBAAmB,EAAE,EAAE,CAAC;QAC3B,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,yBAAyB,CAAC,kBAAkB,CAAC,EAAE,CAAC;YAChF,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,gBAAgB,EAAE,CAAC;IACxC,MAAM,OAAO,GAAG,eAAe,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAEjD,IAAI,OAAO,CAAC,KAAK,KAAK,WAAW,EAAE,CAAC;QAClC,MAAM,QAAQ,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,kBAAkB,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;QACtF,MAAM,WAAW,CAAC;YAChB,IAAI,EAAE,kBAAkB;YACxB,OAAO,EAAE,SAAS;YAClB,GAAG;YACH,OAAO;YACP,KAAK,EAAE,WAAW;YAClB,cAAc,EAAE,OAAO,CAAC,cAAc;SACvC,CAAC,CAAC;QACH,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAM;oBACZ,IAAI,EACF,8CAA8C,OAAO,CAAC,cAAc,GAAG,QAAQ,IAAI;wBACnF,2GAA2G;iBAC9G;aACF;YACD,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;IAED,IAAI,gBAAgB,GAA4C,MAAM,CAAC;IACvE,IAAI,OAAO,CAAC,KAAK,KAAK,mBAAmB,EAAE,CAAC;QAC1C,MAAM,QAAQ,GAAG,OAAO,CAAC,kBAAkB;YACzC,CAAC,CAAC,cAAc,OAAO,CAAC,cAAc,KAAK,OAAO,CAAC,kBAAkB,GAAG;YACxE,CAAC,CAAC,OAAO,CAAC,cAAc;gBACtB,CAAC,CAAC,sBAAsB,OAAO,CAAC,cAAc,EAAE;gBAChD,CAAC,CAAC,2DAA2D,CAAC;QAElE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;YAC9B,GAAG;YACH,OAAO;YACP,cAAc,EAAE,OAAO,CAAC,cAAc;YACtC,kBAAkB,EAAE,OAAO,CAAC,kBAAkB;SAC/C,CAAC,CAAC;QAEH,KAAK,eAAe,CAAC;YACnB,KAAK,EAAE,uCAAuC;YAC9C,OAAO,EAAE,UAAU,GAAG,QAAQ,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC,GAAG,QAAQ,iBAAiB,EAAE,EAAE;SACrF,CAAC,CAAC,IAAI,CACL,CAAC,OAAO,EAAE,EAAE,CAAC,cAAc,CAAC,EAAE,EAAE,OAAO,EAAE,QAAQ,CAAC,EAClD,GAAG,EAAE,CAAC,SAAS,CAChB,CAAC;QAEF,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC;QAC7B,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;YACrB,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YAC9D,MAAM,WAAW,CAAC;gBAChB,IAAI,EAAE,kBAAkB;gBACxB,OAAO,EAAE,QAAQ;gBACjB,GAAG;gBACH,OAAO;gBACP,KAAK,EAAE,mBAAmB;gBAC1B,cAAc,EAAE,OAAO,CAAC,cAAc;gBACtC,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACjE,CAAC,CAAC;YACH,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAM;wBACZ,IAAI,EAAE,sBAAsB,MAAM,CAAC,MAAM,GAAG,UAAU,GAAG;qBAC1D;iBACF;gBACD,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;QACD,gBAAgB,GAAG,MAAM,CAAC,MAAM,CAAC;IACnC,CAAC;IAED,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,kBAAkB,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC;QAC/C,MAAM,IAAI,GACR,OAAO,CAAC,KAAK,KAAK,MAAM;YACtB,CAAC,CAAC,2BAA2B,OAAO,CAAC,cAAc,GAAG;YACtD,CAAC,CAAC,gBAAgB,gBAAgB,EAAE,CAAC;QACzC,MAAM,WAAW,CAAC;YAChB,IAAI,EAAE,kBAAkB;YACxB,OAAO,EAAE,SAAS;YAClB,GAAG;YACH,OAAO;YACP,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,cAAc,EAAE,OAAO,CAAC,cAAc;YACtC,MAAM,EAAE,OAAO,CAAC,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,gBAAgB;SAC7D,CAAC,CAAC;QACH,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAM;oBACZ,IAAI,EAAE,eAAe,GAAG,KAAK,IAAI,MAAM,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE;iBAChE;aACF;SACF,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,MAAM,IAAI,GACR,GAAG,YAAY,cAAc,IAAI,iBAAiB,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC;YACjE,CAAC,CAAC,sFAAsF;YACxF,CAAC,CAAC,EAAE,CAAC;QACT,MAAM,WAAW,CAAC;YAChB,IAAI,EAAE,kBAAkB;YACxB,OAAO,EAAE,OAAO;YAChB,GAAG;YACH,OAAO;YACP,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,cAAc,EAAE,OAAO,CAAC,cAAc;YACtC,YAAY,EAAE,OAAO;SACtB,CAAC,CAAC;QACH,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,4BAA4B,OAAO,GAAG,IAAI,EAAE,EAAE,CAAC;YAC/E,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,MAAiB;IACxC,MAAM,CAAC,YAAY,CACjB,kBAAkB,EAClB;QACE,WAAW,EACT,+XAA+X;QACjY,WAAW,EAAE;YACX,GAAG,EAAE,CAAC;iBACH,MAAM,EAAE;iBACR,KAAK,CAAC,qBAAqB,CAAC;iBAC5B,QAAQ,CAAC,qCAAqC,CAAC;YAClD,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,wCAAwC,CAAC;SACxF;KACF,EACD,cAAc,CACf,CAAC;AACJ,CAAC"}
@@ -0,0 +1,52 @@
1
+ import { OsascriptError, runJxa } from "../applescript.js";
2
+ const LIST_SCRIPT = `
3
+ function safe(fn) {
4
+ try { return fn(); } catch (e) { return null; }
5
+ }
6
+ (function listTerminals() {
7
+ const terminal = Application("Terminal");
8
+ const result = [];
9
+ const wins = terminal.windows();
10
+ for (let wi = 0; wi < wins.length; wi++) {
11
+ const w = wins[wi];
12
+ const tabs = w.tabs();
13
+ for (let ti = 0; ti < tabs.length; ti++) {
14
+ const t = tabs[ti];
15
+ result.push({
16
+ windowId: safe(function () { return w.id(); }),
17
+ windowName: safe(function () { return w.name(); }) || "",
18
+ tabIndex: ti + 1,
19
+ tty: safe(function () { return t.tty(); }) || "",
20
+ title: safe(function () { return t.customTitle(); }) || safe(function () { return t.title(); }) || "",
21
+ busy: safe(function () { return t.busy(); }) || false,
22
+ selected: safe(function () { return t.selected(); }) || false,
23
+ processes: safe(function () { return t.processes(); }) || [],
24
+ });
25
+ }
26
+ }
27
+ return JSON.stringify(result);
28
+ })();
29
+ `;
30
+ export async function listHandler() {
31
+ try {
32
+ const json = await runJxa(LIST_SCRIPT);
33
+ return { content: [{ type: "text", text: json }] };
34
+ }
35
+ catch (err) {
36
+ const hint = err instanceof OsascriptError && /not authorized/i.test(err.stderr)
37
+ ? "\n\nThis usually means Automation permission has not been granted. Open System Settings → Privacy & Security → Automation and allow the MCP server's host process to control Terminal.app."
38
+ : "";
39
+ const message = err instanceof Error ? err.message : String(err);
40
+ return {
41
+ content: [{ type: "text", text: `terminal_list failed: ${message}${hint}` }],
42
+ isError: true,
43
+ };
44
+ }
45
+ }
46
+ export function register(server) {
47
+ server.registerTool("terminal_list", {
48
+ description: 'List every open Terminal.app window and tab with metadata. Returns a JSON array where each entry has: windowId, windowName, tabIndex, tty, title, busy (is a command running), selected (is the active tab), processes (foreground process names). Use the `tty` value (e.g. "/dev/ttys003") as the stable identifier when calling terminal_read.',
49
+ annotations: { readOnlyHint: true, idempotentHint: true },
50
+ }, listHandler);
51
+ }
52
+ //# sourceMappingURL=list.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list.js","sourceRoot":"","sources":["../../src/tools/list.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAE3D,MAAM,WAAW,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2BnB,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,WAAW;IAC/B,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,CAAC;QACvC,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;IACrD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,GACR,GAAG,YAAY,cAAc,IAAI,iBAAiB,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC;YACjE,CAAC,CAAC,4LAA4L;YAC9L,CAAC,CAAC,EAAE,CAAC;QACT,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,yBAAyB,OAAO,GAAG,IAAI,EAAE,EAAE,CAAC;YAC5E,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,MAAiB;IACxC,MAAM,CAAC,YAAY,CACjB,eAAe,EACf;QACE,WAAW,EACT,mVAAmV;QACrV,WAAW,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE;KAC1D,EACD,WAAW,CACZ,CAAC;AACJ,CAAC"}
@@ -0,0 +1,125 @@
1
+ import { z } from "zod";
2
+ import { appendAudit } from "../safety/audit.js";
3
+ import { confirmWithUser, isWriteToolsEnabled, writeToolsDisabledMessage, } from "../safety/confirm.js";
4
+ import { getPending, listPending, resolvePending } from "../safety/queue.js";
5
+ function asTextResult(text, isError = false) {
6
+ return { content: [{ type: "text", text }], ...(isError ? { isError } : {}) };
7
+ }
8
+ function truncate(s, max) {
9
+ return s.length > max ? `${s.slice(0, max)}… [${s.length - max} chars elided]` : s;
10
+ }
11
+ function registerList(server) {
12
+ server.registerTool("pending_list", {
13
+ description: "List commands currently awaiting approval. Each entry has {id, tty, command, matchedPattern?, matchedDescription?, createdAt, expiresAt, ageMs}. Entries auto-expire 10 minutes after enqueue. Read-only — does not require WRITE_TOOLS_ENABLED.",
14
+ annotations: { readOnlyHint: true, idempotentHint: true },
15
+ }, async () => {
16
+ const snapshot = listPending();
17
+ return asTextResult(JSON.stringify(snapshot, null, 2));
18
+ });
19
+ }
20
+ function registerApprove(server) {
21
+ server.registerTool("pending_approve", {
22
+ description: "Approve a queued command by its id. Triggers a native confirmation dialog showing the queued command details before resolving. Requires WRITE_TOOLS_ENABLED=1. Calling this races with the native dialog originally raised by terminal_execute — whichever resolves first wins.",
23
+ inputSchema: {
24
+ id: z
25
+ .string()
26
+ .uuid()
27
+ .describe("Pending queue entry id (from pending_list or terminal_execute response)"),
28
+ },
29
+ }, async ({ id }) => {
30
+ if (!isWriteToolsEnabled()) {
31
+ return asTextResult(writeToolsDisabledMessage("pending_approve"), true);
32
+ }
33
+ const entry = getPending(id);
34
+ if (!entry) {
35
+ return asTextResult(`No pending entry with id ${id} (expired, already resolved, or never existed).`, true);
36
+ }
37
+ const descPart = entry.matchedDescription
38
+ ? `\nMatched: ${entry.matchedPattern} (${entry.matchedDescription})`
39
+ : entry.matchedPattern
40
+ ? `\nMatched pattern: ${entry.matchedPattern}`
41
+ : "";
42
+ const allowed = await confirmWithUser({
43
+ title: "macos-terminal-mcp · pending_approve",
44
+ message: `Approve queued command?\n\n` +
45
+ `Target: ${entry.tty}\n` +
46
+ `Command: ${truncate(entry.command, 800)}${descPart}\n` +
47
+ `Queue id: ${id}`,
48
+ });
49
+ if (!allowed) {
50
+ await appendAudit({
51
+ tool: "pending_approve",
52
+ outcome: "denied",
53
+ tty: entry.tty,
54
+ command: entry.command,
55
+ source: "dialog",
56
+ details: { queueId: id },
57
+ });
58
+ return asTextResult("User denied the approval.", true);
59
+ }
60
+ const ok = resolvePending(id, true, "queue");
61
+ if (!ok) {
62
+ return asTextResult(`Entry ${id} was already resolved by another path (likely the original dialog).`, true);
63
+ }
64
+ await appendAudit({
65
+ tool: "pending_approve",
66
+ outcome: "success",
67
+ tty: entry.tty,
68
+ command: entry.command,
69
+ source: "queue",
70
+ details: { queueId: id },
71
+ });
72
+ return asTextResult(`Approved ${id}.`);
73
+ });
74
+ }
75
+ function registerDeny(server) {
76
+ server.registerTool("pending_deny", {
77
+ description: "Deny a queued command by its id. Triggers a confirmation dialog with the queued command details. Requires WRITE_TOOLS_ENABLED=1.",
78
+ inputSchema: {
79
+ id: z.string().uuid().describe("Pending queue entry id"),
80
+ reason: z
81
+ .string()
82
+ .max(500)
83
+ .optional()
84
+ .describe("Optional human-readable reason for denial"),
85
+ },
86
+ }, async ({ id, reason }) => {
87
+ if (!isWriteToolsEnabled()) {
88
+ return asTextResult(writeToolsDisabledMessage("pending_deny"), true);
89
+ }
90
+ const entry = getPending(id);
91
+ if (!entry) {
92
+ return asTextResult(`No pending entry with id ${id} (expired, already resolved, or never existed).`, true);
93
+ }
94
+ const reasonPart = reason ? `\n\nReason: ${reason}` : "";
95
+ const allowed = await confirmWithUser({
96
+ title: "macos-terminal-mcp · pending_deny",
97
+ message: `Deny queued command?\n\n` +
98
+ `Target: ${entry.tty}\n` +
99
+ `Command: ${truncate(entry.command, 800)}\n` +
100
+ `Queue id: ${id}${reasonPart}`,
101
+ });
102
+ if (!allowed) {
103
+ return asTextResult("User cancelled the denial.", true);
104
+ }
105
+ const ok = resolvePending(id, false, "queue", reason);
106
+ if (!ok) {
107
+ return asTextResult(`Entry ${id} was already resolved by another path.`, true);
108
+ }
109
+ await appendAudit({
110
+ tool: "pending_deny",
111
+ outcome: "success",
112
+ tty: entry.tty,
113
+ command: entry.command,
114
+ source: "queue",
115
+ details: { queueId: id, ...(reason ? { reason } : {}) },
116
+ });
117
+ return asTextResult(`Denied ${id}${reason ? `: ${reason}` : ""}.`);
118
+ });
119
+ }
120
+ export function register(server) {
121
+ registerList(server);
122
+ registerApprove(server);
123
+ registerDeny(server);
124
+ }
125
+ //# sourceMappingURL=pending.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pending.js","sourceRoot":"","sources":["../../src/tools/pending.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,64 @@
1
+ import { z } from "zod";
2
+ import { OsascriptError, runJxa } from "../applescript.js";
3
+ export function buildReadScript(tty) {
4
+ return `
5
+ function safe(fn) {
6
+ try { return fn(); } catch (e) { return null; }
7
+ }
8
+ (function readTerminal(targetTty) {
9
+ const terminal = Application("Terminal");
10
+ const wins = terminal.windows();
11
+ for (let wi = 0; wi < wins.length; wi++) {
12
+ const w = wins[wi];
13
+ const tabs = w.tabs();
14
+ for (let ti = 0; ti < tabs.length; ti++) {
15
+ const t = tabs[ti];
16
+ if (safe(function () { return t.tty(); }) === targetTty) {
17
+ return safe(function () { return t.contents(); }) || "";
18
+ }
19
+ }
20
+ }
21
+ throw new Error("No Terminal.app tab found with tty " + targetTty);
22
+ })(${JSON.stringify(tty)});
23
+ `;
24
+ }
25
+ export async function readHandler({ tty, lines }) {
26
+ try {
27
+ let text = await runJxa(buildReadScript(tty));
28
+ if (lines !== undefined) {
29
+ const split = text.split("\n");
30
+ text = split.slice(-lines).join("\n");
31
+ }
32
+ return { content: [{ type: "text", text }] };
33
+ }
34
+ catch (err) {
35
+ const message = err instanceof Error ? err.message : String(err);
36
+ const hint = err instanceof OsascriptError && /not authorized/i.test(err.stderr)
37
+ ? "\n\nAutomation permission missing — System Settings → Privacy & Security → Automation."
38
+ : "";
39
+ return {
40
+ content: [{ type: "text", text: `terminal_read failed: ${message}${hint}` }],
41
+ isError: true,
42
+ };
43
+ }
44
+ }
45
+ export function register(server) {
46
+ server.registerTool("terminal_read", {
47
+ description: 'Read the full contents (visible buffer + scrollback) of a specific Terminal.app tab identified by its tty (e.g. "/dev/ttys003"). Call terminal_list first to discover tty values. Optional `lines` returns only the last N lines.',
48
+ inputSchema: {
49
+ tty: z
50
+ .string()
51
+ .regex(/^\/dev\/ttys[0-9]+$/)
52
+ .describe('The tty path identifying the target tab, e.g. "/dev/ttys003"'),
53
+ lines: z
54
+ .number()
55
+ .int()
56
+ .positive()
57
+ .max(20000)
58
+ .optional()
59
+ .describe("If provided, return only the last N lines of the buffer."),
60
+ },
61
+ annotations: { readOnlyHint: true, idempotentHint: true },
62
+ }, readHandler);
63
+ }
64
+ //# sourceMappingURL=read.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read.js","sourceRoot":"","sources":["../../src/tools/read.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAE3D,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,OAAO;;;;;;;;;;;;;;;;;;KAkBJ,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC;CACvB,CAAC;AACF,CAAC;AAOD,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,EAAE,GAAG,EAAE,KAAK,EAAa;IACzD,IAAI,CAAC;QACH,IAAI,IAAI,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC;QAC9C,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC/B,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxC,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;IAC/C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,MAAM,IAAI,GACR,GAAG,YAAY,cAAc,IAAI,iBAAiB,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC;YACjE,CAAC,CAAC,wFAAwF;YAC1F,CAAC,CAAC,EAAE,CAAC;QACT,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,yBAAyB,OAAO,GAAG,IAAI,EAAE,EAAE,CAAC;YAC5E,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,MAAiB;IACxC,MAAM,CAAC,YAAY,CACjB,eAAe,EACf;QACE,WAAW,EACT,mOAAmO;QACrO,WAAW,EAAE;YACX,GAAG,EAAE,CAAC;iBACH,MAAM,EAAE;iBACR,KAAK,CAAC,qBAAqB,CAAC;iBAC5B,QAAQ,CAAC,8DAA8D,CAAC;YAC3E,KAAK,EAAE,CAAC;iBACL,MAAM,EAAE;iBACR,GAAG,EAAE;iBACL,QAAQ,EAAE;iBACV,GAAG,CAAC,KAAK,CAAC;iBACV,QAAQ,EAAE;iBACV,QAAQ,CAAC,0DAA0D,CAAC;SACxE;QACD,WAAW,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE;KAC1D,EACD,WAAW,CACZ,CAAC;AACJ,CAAC"}
@@ -0,0 +1,15 @@
1
+ import * as clear from "./clear.js";
2
+ import * as execute from "./execute.js";
3
+ import * as list from "./list.js";
4
+ import * as pending from "./pending.js";
5
+ import * as read from "./read.js";
6
+ import * as safety from "./safety.js";
7
+ export function registerAll(server) {
8
+ list.register(server);
9
+ read.register(server);
10
+ execute.register(server);
11
+ clear.register(server);
12
+ safety.register(server);
13
+ pending.register(server);
14
+ }
15
+ //# sourceMappingURL=register.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register.js","sourceRoot":"","sources":["../../src/tools/register.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,YAAY,CAAC;AACpC,OAAO,KAAK,OAAO,MAAM,cAAc,CAAC;AACxC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,KAAK,OAAO,MAAM,cAAc,CAAC;AACxC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AAEtC,MAAM,UAAU,WAAW,CAAC,MAAiB;IAC3C,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACtB,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACzB,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACvB,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AAC3B,CAAC"}