@moxxy/plugin-computer-control 0.26.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/LICENSE +21 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +43 -0
- package/dist/index.js.map +1 -0
- package/dist/shell.d.ts +56 -0
- package/dist/shell.d.ts.map +1 -0
- package/dist/shell.js +189 -0
- package/dist/shell.js.map +1 -0
- package/dist/tools/applescript.d.ts +2 -0
- package/dist/tools/applescript.d.ts.map +1 -0
- package/dist/tools/applescript.js +38 -0
- package/dist/tools/applescript.js.map +1 -0
- package/dist/tools/click.d.ts +2 -0
- package/dist/tools/click.d.ts.map +1 -0
- package/dist/tools/click.js +50 -0
- package/dist/tools/click.js.map +1 -0
- package/dist/tools/clipboard.d.ts +2 -0
- package/dist/tools/clipboard.d.ts.map +1 -0
- package/dist/tools/clipboard.js +62 -0
- package/dist/tools/clipboard.js.map +1 -0
- package/dist/tools/key.d.ts +11 -0
- package/dist/tools/key.d.ts.map +1 -0
- package/dist/tools/key.js +135 -0
- package/dist/tools/key.js.map +1 -0
- package/dist/tools/open.d.ts +2 -0
- package/dist/tools/open.d.ts.map +1 -0
- package/dist/tools/open.js +76 -0
- package/dist/tools/open.js.map +1 -0
- package/dist/tools/screenshot.d.ts +2 -0
- package/dist/tools/screenshot.d.ts.map +1 -0
- package/dist/tools/screenshot.js +162 -0
- package/dist/tools/screenshot.js.map +1 -0
- package/dist/tools/type.d.ts +8 -0
- package/dist/tools/type.d.ts.map +1 -0
- package/dist/tools/type.js +57 -0
- package/dist/tools/type.js.map +1 -0
- package/package.json +65 -0
- package/skills/computer-control.md +159 -0
- package/src/index.ts +55 -0
- package/src/shell.test.ts +186 -0
- package/src/shell.ts +213 -0
- package/src/tools/applescript-serialize.test.ts +74 -0
- package/src/tools/applescript.ts +41 -0
- package/src/tools/click.ts +52 -0
- package/src/tools/clipboard.ts +63 -0
- package/src/tools/key.ts +147 -0
- package/src/tools/open.ts +81 -0
- package/src/tools/screenshot.ts +181 -0
- package/src/tools/type.ts +60 -0
- package/src/tools.test.ts +128 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { MoxxyError } from '@moxxy/sdk';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import {
|
|
4
|
+
applescriptTool,
|
|
5
|
+
clickTool,
|
|
6
|
+
clipboardTool,
|
|
7
|
+
keyTool,
|
|
8
|
+
openTool,
|
|
9
|
+
screenshotTool,
|
|
10
|
+
typeTool,
|
|
11
|
+
} from './index.js';
|
|
12
|
+
|
|
13
|
+
/** Minimal ToolContext stub — only `signal` is read by these handlers. */
|
|
14
|
+
const fakeCtx = { signal: new AbortController().signal } as never;
|
|
15
|
+
|
|
16
|
+
describe('@moxxy/plugin-computer-control schemas', () => {
|
|
17
|
+
it('screenshot accepts an optional region', () => {
|
|
18
|
+
expect(screenshotTool.inputSchema.safeParse({}).success).toBe(true);
|
|
19
|
+
expect(
|
|
20
|
+
screenshotTool.inputSchema.safeParse({ region: { x: 0, y: 0, width: 100, height: 100 } })
|
|
21
|
+
.success,
|
|
22
|
+
).toBe(true);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('click requires x and y, count 1-3 only', () => {
|
|
26
|
+
expect(clickTool.inputSchema.safeParse({ x: 100, y: 200 }).success).toBe(true);
|
|
27
|
+
expect(clickTool.inputSchema.safeParse({ x: 100, y: 200, count: 2 }).success).toBe(true);
|
|
28
|
+
expect(clickTool.inputSchema.safeParse({ x: 100, y: 200, count: 5 }).success).toBe(false);
|
|
29
|
+
expect(clickTool.inputSchema.safeParse({ x: -1, y: 0 }).success).toBe(false);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('type rejects oversize payloads', () => {
|
|
33
|
+
expect(typeTool.inputSchema.safeParse({ text: 'hello' }).success).toBe(true);
|
|
34
|
+
expect(typeTool.inputSchema.safeParse({ text: 'a'.repeat(5000) }).success).toBe(false);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('key allows known modifier names and rejects unknown ones', () => {
|
|
38
|
+
expect(keyTool.inputSchema.safeParse({ key: 'tab' }).success).toBe(true);
|
|
39
|
+
expect(
|
|
40
|
+
keyTool.inputSchema.safeParse({ key: 'a', modifiers: ['cmd', 'shift'] }).success,
|
|
41
|
+
).toBe(true);
|
|
42
|
+
expect(
|
|
43
|
+
keyTool.inputSchema.safeParse({ key: 'a', modifiers: ['windows'] }).success,
|
|
44
|
+
).toBe(false);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('open requires target or app at runtime (not enforced by zod)', () => {
|
|
48
|
+
// Both optional at schema level — runtime guard does the validation.
|
|
49
|
+
expect(openTool.inputSchema.safeParse({ app: 'Safari' }).success).toBe(true);
|
|
50
|
+
expect(openTool.inputSchema.safeParse({ target: '/tmp' }).success).toBe(true);
|
|
51
|
+
expect(openTool.inputSchema.safeParse({}).success).toBe(true);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('clipboard requires action enum', () => {
|
|
55
|
+
expect(clipboardTool.inputSchema.safeParse({ action: 'read' }).success).toBe(true);
|
|
56
|
+
expect(clipboardTool.inputSchema.safeParse({ action: 'write', text: 'x' }).success).toBe(true);
|
|
57
|
+
expect(clipboardTool.inputSchema.safeParse({ action: 'paste' }).success).toBe(false);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('applescript needs a non-empty script', () => {
|
|
61
|
+
expect(applescriptTool.inputSchema.safeParse({ script: 'return 1' }).success).toBe(true);
|
|
62
|
+
expect(applescriptTool.inputSchema.safeParse({ script: '' }).success).toBe(false);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('open rejects a target/app beginning with "-" (argument-injection guard, darwin only)', async () => {
|
|
66
|
+
// The guard runs after ensureDarwin, so on non-darwin hosts ensureDarwin
|
|
67
|
+
// throws first — assert that path too so the test is meaningful everywhere.
|
|
68
|
+
if (process.platform === 'darwin') {
|
|
69
|
+
await expect(openTool.handler({ target: '-g' }, fakeCtx)).rejects.toThrow(
|
|
70
|
+
/must not begin with '-'/,
|
|
71
|
+
);
|
|
72
|
+
await expect(openTool.handler({ app: '-n' }, fakeCtx)).rejects.toThrow(
|
|
73
|
+
/must not begin with '-'/,
|
|
74
|
+
);
|
|
75
|
+
} else {
|
|
76
|
+
await expect(openTool.handler({ target: '-g' }, fakeCtx)).rejects.toBeInstanceOf(MoxxyError);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('screenshot returns an image-shaped result ({ mediaType, base64 }), never a stringified blob', async () => {
|
|
81
|
+
// The SDK's tool_result projection emits a provider `image` ContentBlock
|
|
82
|
+
// ONLY when the result object carries a string `mediaType` + `base64`; any
|
|
83
|
+
// other shape falls through to JSON.stringify and reaches the model as
|
|
84
|
+
// undecodable base64 TEXT. Assert the handler honors that contract.
|
|
85
|
+
if (process.platform === 'darwin') {
|
|
86
|
+
// On a host without Screen Recording permission (typical CI) the handler
|
|
87
|
+
// throws a MoxxyError instead of capturing — also acceptable; the ONLY
|
|
88
|
+
// forbidden outcome is returning a (stringified) blob the model can't see.
|
|
89
|
+
let result: unknown;
|
|
90
|
+
try {
|
|
91
|
+
// Tiny region keeps the capture cheap and well under the byte cap.
|
|
92
|
+
result = await screenshotTool.handler(
|
|
93
|
+
{ region: { x: 0, y: 0, width: 1, height: 1 } },
|
|
94
|
+
fakeCtx,
|
|
95
|
+
);
|
|
96
|
+
} catch (err) {
|
|
97
|
+
expect(err).toBeInstanceOf(MoxxyError);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
// Must be a structured object, NOT a string (a stringified blob).
|
|
101
|
+
expect(typeof result).toBe('object');
|
|
102
|
+
expect(result).not.toBeNull();
|
|
103
|
+
const r = result as { mediaType?: unknown; base64?: unknown };
|
|
104
|
+
expect(typeof r.mediaType).toBe('string');
|
|
105
|
+
expect(r.mediaType as string).toMatch(/^image\//);
|
|
106
|
+
expect(typeof r.base64).toBe('string');
|
|
107
|
+
expect((r.base64 as string).length).toBeGreaterThan(0);
|
|
108
|
+
} else {
|
|
109
|
+
// Non-darwin: ensureDarwin rejects before any capture — still never a blob.
|
|
110
|
+
await expect(screenshotTool.handler({}, fakeCtx)).rejects.toBeInstanceOf(MoxxyError);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('every tool is permission:prompt — never auto-allowed', () => {
|
|
115
|
+
const all = [
|
|
116
|
+
screenshotTool,
|
|
117
|
+
clickTool,
|
|
118
|
+
typeTool,
|
|
119
|
+
keyTool,
|
|
120
|
+
openTool,
|
|
121
|
+
clipboardTool,
|
|
122
|
+
applescriptTool,
|
|
123
|
+
];
|
|
124
|
+
for (const tool of all) {
|
|
125
|
+
expect(tool.permission?.action).toBe('prompt');
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
});
|