desktop-mcp 0.0.0-stage → 0.4.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.
Files changed (107) hide show
  1. package/README.md +212 -2
  2. package/dist/agent.d.ts +81 -0
  3. package/dist/agent.js +160 -0
  4. package/dist/agent.js.map +1 -0
  5. package/dist/bin/omcps.d.ts +2 -0
  6. package/dist/bin/omcps.js +4 -0
  7. package/dist/bin/omcps.js.map +1 -0
  8. package/dist/bin/osagent.d.ts +2 -0
  9. package/dist/bin/osagent.js +4 -0
  10. package/dist/bin/osagent.js.map +1 -0
  11. package/dist/cli.d.ts +39 -0
  12. package/dist/cli.js +169 -0
  13. package/dist/cli.js.map +1 -0
  14. package/dist/exec.d.ts +36 -0
  15. package/dist/exec.js +118 -0
  16. package/dist/exec.js.map +1 -0
  17. package/dist/index.d.ts +5 -0
  18. package/dist/index.js +6 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/mcpServer.d.ts +82 -0
  21. package/dist/mcpServer.js +291 -0
  22. package/dist/mcpServer.js.map +1 -0
  23. package/dist/nut/keys.d.ts +46 -0
  24. package/dist/nut/keys.js +145 -0
  25. package/dist/nut/keys.js.map +1 -0
  26. package/dist/nut/runtime.d.ts +86 -0
  27. package/dist/nut/runtime.js +125 -0
  28. package/dist/nut/runtime.js.map +1 -0
  29. package/dist/platform.d.ts +34 -0
  30. package/dist/platform.js +92 -0
  31. package/dist/platform.js.map +1 -0
  32. package/dist/plugins/color.d.ts +29 -0
  33. package/dist/plugins/color.js +65 -0
  34. package/dist/plugins/color.js.map +1 -0
  35. package/dist/plugins/matcher.d.ts +48 -0
  36. package/dist/plugins/matcher.js +87 -0
  37. package/dist/plugins/matcher.js.map +1 -0
  38. package/dist/plugins/ocr.d.ts +113 -0
  39. package/dist/plugins/ocr.js +148 -0
  40. package/dist/plugins/ocr.js.map +1 -0
  41. package/dist/plugins/providers.d.ts +13 -0
  42. package/dist/plugins/providers.js +66 -0
  43. package/dist/plugins/providers.js.map +1 -0
  44. package/dist/plugins/recorder.d.ts +19 -0
  45. package/dist/plugins/recorder.js +48 -0
  46. package/dist/plugins/recorder.js.map +1 -0
  47. package/dist/plugins/status.d.ts +22 -0
  48. package/dist/plugins/status.js +59 -0
  49. package/dist/plugins/status.js.map +1 -0
  50. package/dist/policy/capabilities.d.ts +31 -0
  51. package/dist/policy/capabilities.js +82 -0
  52. package/dist/policy/capabilities.js.map +1 -0
  53. package/dist/policy/engine.d.ts +110 -0
  54. package/dist/policy/engine.js +424 -0
  55. package/dist/policy/engine.js.map +1 -0
  56. package/dist/policy/flags.d.ts +45 -0
  57. package/dist/policy/flags.js +176 -0
  58. package/dist/policy/flags.js.map +1 -0
  59. package/dist/policy/load.d.ts +36 -0
  60. package/dist/policy/load.js +70 -0
  61. package/dist/policy/load.js.map +1 -0
  62. package/dist/policy/presets.d.ts +11 -0
  63. package/dist/policy/presets.js +53 -0
  64. package/dist/policy/presets.js.map +1 -0
  65. package/dist/policy/schema.d.ts +95 -0
  66. package/dist/policy/schema.js +69 -0
  67. package/dist/policy/schema.js.map +1 -0
  68. package/dist/safety.d.ts +2 -0
  69. package/dist/safety.js +3 -0
  70. package/dist/safety.js.map +1 -0
  71. package/dist/tools/base.d.ts +77 -0
  72. package/dist/tools/base.js +62 -0
  73. package/dist/tools/base.js.map +1 -0
  74. package/dist/tools/clipboard.d.ts +26 -0
  75. package/dist/tools/clipboard.js +77 -0
  76. package/dist/tools/clipboard.js.map +1 -0
  77. package/dist/tools/files.d.ts +30 -0
  78. package/dist/tools/files.js +91 -0
  79. package/dist/tools/files.js.map +1 -0
  80. package/dist/tools/gui.d.ts +159 -0
  81. package/dist/tools/gui.js +373 -0
  82. package/dist/tools/gui.js.map +1 -0
  83. package/dist/tools/index.d.ts +9 -0
  84. package/dist/tools/index.js +18 -0
  85. package/dist/tools/index.js.map +1 -0
  86. package/dist/tools/registry.d.ts +10 -0
  87. package/dist/tools/registry.js +31 -0
  88. package/dist/tools/registry.js.map +1 -0
  89. package/dist/tools/search.d.ts +42 -0
  90. package/dist/tools/search.js +139 -0
  91. package/dist/tools/search.js.map +1 -0
  92. package/dist/tools/shell.d.ts +9 -0
  93. package/dist/tools/shell.js +49 -0
  94. package/dist/tools/shell.js.map +1 -0
  95. package/dist/tools/system.d.ts +115 -0
  96. package/dist/tools/system.js +480 -0
  97. package/dist/tools/system.js.map +1 -0
  98. package/dist/tools/vision.d.ts +98 -0
  99. package/dist/tools/vision.js +265 -0
  100. package/dist/tools/vision.js.map +1 -0
  101. package/dist/tools/window.d.ts +126 -0
  102. package/dist/tools/window.js +310 -0
  103. package/dist/tools/window.js.map +1 -0
  104. package/dist/version.d.ts +2 -0
  105. package/dist/version.js +4 -0
  106. package/dist/version.js.map +1 -0
  107. package/package.json +82 -4
package/README.md CHANGED
@@ -1,3 +1,213 @@
1
- # Temporary Holding Version
1
+ # omcps — an MCP server that lets an AI use your computer
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ `omcps` gives Claude Desktop, Claude Code, VS Code, Cursor or any MCP client controlled access to your machine:
4
+ **shell, files, clipboard, mouse, keyboard, screen (OCR, image and colour search), windows.** Built on
5
+ [nut.js](https://nutjs.dev). Windows, Linux and macOS.
6
+
7
+ * **Everything runs on your device.** No cloud service, no API key, no model download. Only `web_search` and
8
+ `fetch_url` use the network, and they are off unless you pass `--allow-web`.
9
+ * **Nothing is allowed unless you say so.** You grant features with flags: `--allow-all`, `--allow-mouse`,
10
+ `--allow-clipboard`, ... and scope them (`--allow-path`, `--region`, `--allow-window`, ...).
11
+
12
+ ```bash
13
+ npx omcps@latest --allow-all # everything (with safety nets, see below)
14
+ npx omcps@latest --allow-mouse --allow-keyboard --allow-screen # drive the desktop, nothing else
15
+ npx omcps@latest --allow-files --allow-path ~/projects --ask-shell
16
+ npx omcps@latest --help
17
+ ```
18
+
19
+ ## Add it to your MCP client
20
+
21
+ The same pattern as other npx-based MCP servers: the command is `npx`, the flags are the permissions.
22
+
23
+ **Claude Desktop / Cursor** (`mcpServers` in the config file):
24
+
25
+ ```json
26
+ {
27
+ "mcpServers": {
28
+ "omcps": { "command": "npx", "args": ["omcps@latest", "--allow-mouse", "--allow-keyboard", "--allow-screen", "--allow-windows"] }
29
+ }
30
+ }
31
+ ```
32
+
33
+ **Claude Code:** `claude mcp add omcps -- npx omcps@latest --allow-all`
34
+
35
+ **VS Code:** `code --add-mcp '{"name":"omcps","command":"npx","args":["omcps@latest","--allow-all"]}'`
36
+
37
+ Run it as an HTTP server instead of stdio: `npx omcps@latest --allow-all --transport streamable-http --port 8000`
38
+ (`http://127.0.0.1:8000/mcp`; binds to localhost and rejects foreign `Host` headers).
39
+
40
+ > The package is named `omcps`; until it is published to the npm registry, install from the tarball:
41
+ > `npx --package=./omcps-0.4.0.tgz omcps --allow-all`.
42
+
43
+ ## Permissions
44
+
45
+ With no flags nothing is allowed: the AI sees one tool, `permissions_describe`, and stderr tells you what to add.
46
+ Every tool call passes through one engine that decides **allow / ask / deny** before anything runs; tools the AI is
47
+ not allowed to use are **not even listed**.
48
+
49
+ ### Feature flags
50
+
51
+ `--allow-<group>` grants a group; `--ask-<group>` makes every use need your approval (through MCP elicitation, when
52
+ the client supports it). `allow` beats `ask` for the same feature.
53
+
54
+ | flag | grants |
55
+ |---|---|
56
+ | `--allow-all` | every feature below |
57
+ | `--allow-gui` | mouse + keyboard + screen + windows + clipboard |
58
+ | `--allow-mouse` | move, click, drag, scroll |
59
+ | `--allow-keyboard` | type text, press keys, hold keys |
60
+ | `--allow-clipboard` / `--allow-clipboard-read` | clipboard read + write / read only |
61
+ | `--allow-screen` | screenshots, OCR, image and colour search |
62
+ | `--allow-record` | screen recording |
63
+ | `--allow-windows` / `--allow-windows-list` | list, focus, move, resize, minimise, close / list only |
64
+ | `--allow-files` / `--allow-files-read` | read + write files / read only |
65
+ | `--allow-delete` | delete and move files |
66
+ | `--allow-shell` | run shell commands |
67
+ | `--allow-processes` / `--allow-processes-list` | list + kill / list only |
68
+ | `--allow-system` | system information, environment variables |
69
+ | `--allow-web` | `web_search`, `fetch_url` (the only network use) |
70
+ | `--allow-open` | open files, folders and URLs with the default app |
71
+
72
+ A few tools combine features, and need all of them: pasting needs clipboard **and** keyboard; `click_text` needs
73
+ screen **and** mouse; `screenshot_save` and `screen_record` also need file-write permission.
74
+
75
+ ### Scoping flags (all repeatable)
76
+
77
+ `--allow-path DIR` `--deny-path P` `--readonly-path P` · `--allow-command REGEX` `--deny-command REGEX` ·
78
+ `--allow-host REGEX` `--deny-host REGEX` · `--allow-window REGEX` · `--region x,y,w,h` · `--block-key ctrl+alt+delete`
79
+ `--block-text REGEX` `--max-type-chars N` · `--rate-limit N` (per minute) `--max-actions N` ·
80
+ `--audit-file PATH` `--audit-args` · `--ask-fallback allow|deny` · `--allow-tool NAME` `--deny-tool NAME` ·
81
+ `--policy-show` (print what the AI may do and exit).
82
+
83
+ ### Safety nets for flag-only setups
84
+
85
+ When you build permissions from flags alone, `omcps` also **denies credential locations** (`~/.ssh`, `~/.aws`,
86
+ `~/.gnupg`, `~/.kube`, `~/.netrc`, `/etc/shadow`, ...) and **refuses dangerous shell patterns** (`rm -rf`, `sudo`,
87
+ `mkfs`, `curl | sh`, ...). Opt out explicitly with `--allow-sensitive-paths` / `--allow-dangerous-commands`.
88
+
89
+ ### Bases: presets and policy files
90
+
91
+ Flags can be layered on a base: `--preset locked|readonly|standard|desktop|safe|full`, or `--config policy.json`
92
+ (alias `--policy`; legacy `--safe`, `--read-only`). A preset or file is trusted as written (no extra safety nets).
93
+
94
+ ```jsonc
95
+ // policy.json
96
+ {
97
+ "name": "notes-helper", "default": "deny",
98
+ "capabilities": { "fs.read": "allow", "fs.write": "ask", "shell.exec": "ask", "screen.capture": "allow" },
99
+ "tools": { "delete_path": "deny" },
100
+ "paths": { "allow": ["~/notes/**"], "deny": ["~/notes/private"], "readOnly": ["~/notes/archive"] },
101
+ "commands": { "allow": ["^git (status|log|diff)\\b"], "blockDangerous": true },
102
+ "gui": { "windows": ["^Notes"], "regions": [{ "x": 0, "y": 0, "width": 1280, "height": 720 }] },
103
+ "limits": { "maxActionsPerMinute": 60 }, "audit": { "file": "~/omcps-audit.jsonl" }
104
+ }
105
+ ```
106
+
107
+ ### What the engine guarantees
108
+
109
+ * **Paths** are checked on the real, symlink-resolved path: a link inside an allowed folder cannot escape it.
110
+ * **Command allow-lists refuse chaining** (`; | & ` $( > <` newline) so `git status; rm -rf /` cannot pass `^git`.
111
+ * **Denial comes before asking**; approval prompts never show file contents or typed text (only their size).
112
+ * **Fail closed**: a tool without permission metadata is refused; an unwritable audit log refuses the call.
113
+ * Keys and buttons the AI left held are released when the client disconnects.
114
+
115
+ ### What it does NOT do
116
+
117
+ It controls the **tool interface**, not the operating system. It is **not a sandbox**.
118
+ `run_command` can do anything your user can, and path rules do not apply to what a shell command touches
119
+ (`cat ~/.ssh/id_rsa` is a shell command): keep `--allow-shell` off, use `--ask-shell` or a command allow-list.
120
+ `--allow-window` checks the focused window at call time. `--region` limits pointer coordinates and
121
+ captures, not what a keystroke does. Applications started with `--allow-open` run with your full rights.
122
+
123
+ ## Tools (62 with the desktop features)
124
+
125
+ | area | tools |
126
+ |---|---|
127
+ | **Mouse** | `mouse_position` `move_mouse` `mouse_move_relative` `click` (any button, `clicks=2` = double) `mouse_press` `mouse_release` `mouse_drag` `scroll` (vertical + horizontal) |
128
+ | **Keyboard** | `type_text` `press_key` (hotkeys, media keys, keypad) `key_down` `key_up` `list_keys` |
129
+ | **Screen** | `screenshot` (full or region) `screenshot_save` `screen_size` `screen_color_at` `screen_highlight` |
130
+ | **Windows** | `window_list` `window_active` `window_focus` `window_move` `window_resize` `window_minimize` `window_restore` `window_close` `window_wait_for` |
131
+ | **Clipboard** | `clipboard_get` `clipboard_set` `clipboard_clear` `clipboard_copy` `clipboard_paste` |
132
+ | **Image search** | `screen_find_image` `screen_find_images` `screen_wait_for_image` `click_image` (multi-scale, alpha-mask aware) |
133
+ | **OCR** | `screen_read_text` `screen_find_text` (exact / contains / **fuzzy**) `screen_wait_for_text` `click_text` |
134
+ | **Colour** | `screen_find_color` (regions of a colour, largest first) |
135
+ | **Recording** | `screen_record` (PNG frames; MP4 too if `ffmpeg` is installed) |
136
+ | **System** | `system_info` `get_env` `list_processes` `kill_process` `run_command` `open_path` `plugin_status` `permissions_describe` |
137
+ | **Files / web** | `read_file` `write_file` `edit_file` `list_dir` `find_files` `search_in_files` `make_dir` `copy_path` `move_path` `delete_path` `web_search` `fetch_url` |
138
+
139
+ Coordinates are in **logical** screen pixels (what clicks expect), also on HiDPI displays.
140
+
141
+ ## Runs locally — and how that is checked
142
+
143
+ * OCR is `tesseract.js` (WebAssembly) with English data shipped in the package. **It never downloads models**: a
144
+ language that is not installed is an error telling you which `@tesseract.js-data/<lang>` to install (or set
145
+ `OSAGENT_OCR_LANG_PATH`). Image matching, colour search, recording and windows are pure local code.
146
+ * There is **no cloud provider and no API key** anywhere in the MCP server. The standalone `osagent` agent (a
147
+ separate command in the same package) is the only part that talks to an LLM API; its SDK is an *optional peer
148
+ dependency* that `omcps` never installs or loads.
149
+ * Tests prove it: a static test fails if any file other than the web tools and the standalone agent contains
150
+ network-client code or a cloud-SDK reference, and an end-to-end test runs the server with **every feature allowed
151
+ inside a network-less namespace** (real desktop, OCR, image/colour search, clipboard, files, shell: all work;
152
+ only `web_search` / `fetch_url` fail, cleanly).
153
+
154
+ ### nut.js plugins
155
+
156
+ All ten plugins on nutjs.dev are sold behind a private registry and none is on public npm. Local equivalents are
157
+ built in and registered with nut.js, so its own `screen.find(...)` works with them. `plugin_status` prints this table.
158
+
159
+ | official plugin | here |
160
+ |---|---|
161
+ | nl-matcher, template-matcher | **built in**: pure-JS image matcher |
162
+ | plugin-ocr | **built in**: tesseract.js, offline |
163
+ | plugin-screenrecording | **built in**: frames, MP4 with ffmpeg |
164
+ | window search | **built in** |
165
+ | plugin-ai-sdk | **deliberately not included**: it needs a cloud LLM and an API key. Use `screen_find_text` (fuzzy), `screen_find_image`, `screen_find_color` |
166
+ | plugin-azure | not available: a cloud OCR service |
167
+ | element-inspector, playwright-bridge, selenium-bridge, plugin-rdp, bolt | not available: proprietary |
168
+
169
+ ## What was verified on a real X server, and what it changed
170
+
171
+ Tested against Xvfb with a real window manager (openbox) and real X clients (`xev`, `xterm`, `xclip`). nut.js needed
172
+ compensation:
173
+
174
+ | finding (Linux / X11) | what omcps does |
175
+ |---|---|
176
+ | `keyboard.type` types shifted punctuation as the base key (`!`→`1`), drops other characters and all non-ASCII | never used on Linux: each character is an explicit key (+Shift); non-ASCII is pasted. All 95 printable ASCII characters verified in a real terminal |
177
+ | `scrollRight` sends X button 6 (= wheel-**left**) | swapped on Linux |
178
+ | `getWindows()` returns the WM's frames and 1×1 helpers with empty titles | lists real clients from `_NET_CLIENT_LIST` |
179
+ | window geometry is frame-relative | reads absolute geometry with `xwininfo` |
180
+ | `minimize`/`restore` not implemented in libnut | `xdotool`; `wmctrl` for a polite close |
181
+ | `screen.grab(region)` ignores the region | crops itself |
182
+ | `Key.Enter` is the keypad Enter | `enter` → `Key.Return` |
183
+ | libnut **aborts the process** if the display can't be opened; needs `DISPLAY` in the *initial* environment | checks by connecting to the X socket on every call |
184
+
185
+ Linux needs an X11 display (real or Xvfb) and, for window tools, an EWMH window manager plus `xprop`/`xwininfo`
186
+ (x11-utils), `xdotool`, `wmctrl`. Wayland-only sessions are not supported by nut.js. macOS needs Screen Recording and
187
+ Accessibility permission for the terminal. The mouse in a **screen corner** is a human fail-safe that blocks all
188
+ input (an agent that parks the pointer there locks itself out until a person moves it).
189
+
190
+ ## Standalone agent
191
+
192
+ `osagent "task"` is a small agent loop (plan → act → verify) that uses the same tools and permission flags. It is the
193
+ only component that needs an LLM API: `npm install @anthropic-ai/sdk` and set `ANTHROPIC_API_KEY`.
194
+
195
+ ## Testing
196
+
197
+ ```bash
198
+ npm test # builds, then vitest with coverage; thresholds are 100% for statements, branches, functions, lines
199
+ ```
200
+
201
+ Tests run against a private Xvfb + openbox started once in `tests/globalSetup.ts` (never your real desktop). Real,
202
+ not mocked, where it matters: subprocesses and process-tree kills, files and symlink escapes, a local HTTP server, MCP
203
+ clients over in-memory/stdio/HTTP, MCP elicitation approvals, the real `main()` with real flags, real windows, real
204
+ key/mouse events observed by `xev`, typed text checked in a real `xterm`, real OCR, pixel and colour matching, the
205
+ real clipboard, a network-less namespace, and `npx` installing the packed tarball.
206
+
207
+ Coverage notes: `src/bin/*` (two-line launchers) and `src/index.ts` (re-export barrel) are excluded, and one line in
208
+ `policy/engine.ts` carries a `v8 ignore` (the filesystem root always resolves).
209
+
210
+ **Not verified here:** Windows and macOS themselves (their paths are tested against a fake nut.js and fake
211
+ `powershell`/`cmd`: that checks omcps's logic, not those platforms); the proprietary nut.js plugins (not obtainable);
212
+ `screen_highlight`'s overlay (the call is
213
+ tested; the overlay can't be observed headlessly); publishing to the npm registry.
@@ -0,0 +1,81 @@
1
+ import { DANGEROUS } from "./safety.js";
2
+ import { type PermissionEngine } from "./policy/engine.js";
3
+ import { type Tool, type ToolResult } from "./tools/base.js";
4
+ export { DANGEROUS };
5
+ /**
6
+ *
7
+ * @param fn - Loader to install, or `undefined` to restore the default.
8
+ */
9
+ export declare function __setSdkLoader(fn: (() => Promise<typeof import("@anthropic-ai/sdk")>) | undefined): void;
10
+ export declare const DEFAULT_MODEL = "claude-sonnet-5-5";
11
+ export declare const SYSTEM_PROMPT = "You are an autonomous operator of the user's computer. You act through tools: a shell, files, web search, and (when enabled) screenshots, mouse and keyboard.\n\nHost environment:\n{platform}\n\nWork step by step:\n1. PLAN - call `think` first with a short numbered plan.\n2. ACT - take one small action at a time with the most appropriate tool. Prefer the shell and files over GUI clicking when both can do the job; use the GUI for things only a GUI can do.\n3. VERIFY - after every action check the result (exit codes, file contents, a fresh screenshot). Test your work in this real environment: run the code, run the tests, open the file. Never claim success without evidence.\n4. ADAPT - if something fails, read the error, update the plan with `think`, and try a different approach.\n5. SEARCH - when you lack current or specific knowledge, use web_search and fetch_url instead of guessing.\n6. FINISH - reply with a short summary of what you did and the evidence it worked.\n\nUse commands that match the host OS. Do not run destructive or irreversible commands unless the user asked for them.";
12
+ export type Args = Record<string, unknown>;
13
+ export type ConfirmFn = (tool: string, args: Args) => boolean | Promise<boolean>;
14
+ export type EventFn = (kind: string, data: unknown) => void;
15
+ /** Minimal slice of the Anthropic client the loop needs (lets tests script the model). */
16
+ export interface MessagesClient {
17
+ messages: {
18
+ create(params: Record<string, unknown>): Promise<{
19
+ content: ContentBlock[];
20
+ }>;
21
+ };
22
+ }
23
+ export interface ContentBlock {
24
+ type: string;
25
+ text?: string;
26
+ id?: string;
27
+ name?: string;
28
+ input?: unknown;
29
+ }
30
+ export interface AgentOptions {
31
+ model?: string;
32
+ gui?: boolean;
33
+ search?: boolean;
34
+ confirm?: ConfirmFn;
35
+ confirmAll?: boolean;
36
+ maxSteps?: number;
37
+ onEvent?: EventFn;
38
+ client?: MessagesClient;
39
+ /** Permission policy; approvals (`ask`) are routed to `confirm`. */
40
+ policy?: PermissionEngine;
41
+ }
42
+ /**
43
+ *
44
+ * @param tool - Tool definition to inspect.
45
+ * @param args - Arguments supplied to the tool.
46
+ * @returns Whether confirmation is required.
47
+ */
48
+ export declare function needsConfirmation(tool: Tool, args: Args): boolean;
49
+ export declare class Agent {
50
+ private client?;
51
+ readonly model: string;
52
+ readonly confirm?: ConfirmFn;
53
+ readonly confirmAll: boolean;
54
+ readonly maxSteps: number;
55
+ readonly onEvent: EventFn;
56
+ readonly system: string;
57
+ readonly messages: {
58
+ role: "user" | "assistant";
59
+ content: unknown;
60
+ }[];
61
+ private tools?;
62
+ private readonly gui;
63
+ private readonly search;
64
+ private readonly engine?;
65
+ constructor(opts?: AgentOptions);
66
+ private toolset;
67
+ /**
68
+ * The model client. The standalone agent is the ONLY part of this package that talks to an LLM API, so the
69
+ * Anthropic SDK is an optional peer dependency, loaded here on first use (never by the omcps MCP server).
70
+ * @returns The resulting value.
71
+ */
72
+ private clientFor;
73
+ /**
74
+ * Run one tool call. Returns [result, isError].
75
+ * @param name - Tool name to execute.
76
+ * @param args - Arguments supplied to the tool.
77
+ * @returns The tool result and whether it represents an error.
78
+ */
79
+ execute(name: string, args: Args): Promise<[ToolResult, boolean]>;
80
+ run(task: string): Promise<string>;
81
+ }
package/dist/agent.js ADDED
@@ -0,0 +1,160 @@
1
+ /** The agent loop: plan step by step, act with OS tools, verify, repeat. */
2
+ import { describePlatform } from "./platform.js";
3
+ import { DANGEROUS } from "./safety.js";
4
+ import { PermissionDenied } from "./policy/engine.js";
5
+ import { buildToolset } from "./tools/index.js";
6
+ import { defineTool } from "./tools/base.js";
7
+ import { callTool, toApi } from "./tools/base.js";
8
+ export { DANGEROUS };
9
+ /**
10
+ * Test hook: where the optional Anthropic SDK comes from.
11
+ * @returns The optional Anthropic SDK module.
12
+ */
13
+ let loadSdk = () => import("@anthropic-ai/sdk");
14
+ /**
15
+ *
16
+ * @param fn - Loader to install, or `undefined` to restore the default.
17
+ */
18
+ export function __setSdkLoader(fn) {
19
+ loadSdk = fn ?? (() => import("@anthropic-ai/sdk"));
20
+ }
21
+ export const DEFAULT_MODEL = "claude-sonnet-5-5";
22
+ export const SYSTEM_PROMPT = `You are an autonomous operator of the user's computer. You act through tools: a shell, \
23
+ files, web search, and (when enabled) screenshots, mouse and keyboard.
24
+
25
+ Host environment:
26
+ {platform}
27
+
28
+ Work step by step:
29
+ 1. PLAN - call \`think\` first with a short numbered plan.
30
+ 2. ACT - take one small action at a time with the most appropriate tool. Prefer the shell and files over \
31
+ GUI clicking when both can do the job; use the GUI for things only a GUI can do.
32
+ 3. VERIFY - after every action check the result (exit codes, file contents, a fresh screenshot). Test your \
33
+ work in this real environment: run the code, run the tests, open the file. Never claim success without evidence.
34
+ 4. ADAPT - if something fails, read the error, update the plan with \`think\`, and try a different approach.
35
+ 5. SEARCH - when you lack current or specific knowledge, use web_search and fetch_url instead of guessing.
36
+ 6. FINISH - reply with a short summary of what you did and the evidence it worked.
37
+
38
+ Use commands that match the host OS. Do not run destructive or irreversible commands unless the user asked for them.`;
39
+ /**
40
+ *
41
+ * @param tool - Tool definition to inspect.
42
+ * @param args - Arguments supplied to the tool.
43
+ * @returns Whether confirmation is required.
44
+ */
45
+ export function needsConfirmation(tool, args) {
46
+ if (tool.name === "run_command")
47
+ return DANGEROUS.test(String(args.command ?? ""));
48
+ return false;
49
+ }
50
+ export class Agent {
51
+ client;
52
+ model;
53
+ confirm;
54
+ confirmAll;
55
+ maxSteps;
56
+ onEvent;
57
+ system;
58
+ messages = [];
59
+ tools;
60
+ gui;
61
+ search;
62
+ engine;
63
+ constructor(opts = {}) {
64
+ this.client = opts.client;
65
+ this.model = opts.model ?? DEFAULT_MODEL;
66
+ this.gui = opts.gui ?? false;
67
+ this.search = opts.search ?? true;
68
+ this.confirm = opts.confirm;
69
+ this.engine = opts.policy;
70
+ this.engine?.setAsk(async (req) => (this.confirm ? this.confirm(req.tool, req.args) : undefined));
71
+ this.confirmAll = opts.confirmAll ?? false;
72
+ this.maxSteps = opts.maxSteps ?? 40;
73
+ this.onEvent = opts.onEvent ?? (() => { });
74
+ this.system = SYSTEM_PROMPT.replace("{platform}", describePlatform());
75
+ }
76
+ async toolset() {
77
+ if (!this.tools) {
78
+ const all = await buildToolset(this.gui, this.search);
79
+ const e = this.engine;
80
+ if (e) {
81
+ const visible = [...all.values()].filter((t) => e.isVisible(t.name));
82
+ visible.push(defineTool({ name: "permissions_describe", description: "Describe what you are and are not allowed to do on this computer.", shape: {}, run: () => e.describe(visible), readOnly: true }));
83
+ this.tools = new Map(visible.map((t) => [t.name, e.wrap(t)]));
84
+ }
85
+ else
86
+ this.tools = all;
87
+ }
88
+ return this.tools;
89
+ }
90
+ /**
91
+ * The model client. The standalone agent is the ONLY part of this package that talks to an LLM API, so the
92
+ * Anthropic SDK is an optional peer dependency, loaded here on first use (never by the omcps MCP server).
93
+ * @returns The resulting value.
94
+ */
95
+ async clientFor() {
96
+ if (!this.client) {
97
+ const sdk = await loadSdk().catch(() => {
98
+ throw new Error("the standalone agent needs the Anthropic SDK: npm install @anthropic-ai/sdk (the omcps MCP server does not)");
99
+ });
100
+ this.client = new sdk.default();
101
+ }
102
+ return this.client;
103
+ }
104
+ /**
105
+ * Run one tool call. Returns [result, isError].
106
+ * @param name - Tool name to execute.
107
+ * @param args - Arguments supplied to the tool.
108
+ * @returns The tool result and whether it represents an error.
109
+ */
110
+ async execute(name, args) {
111
+ const tool = (await this.toolset()).get(name);
112
+ if (!tool)
113
+ return [`[error] unknown tool ${name}`, true];
114
+ const gated = (this.confirmAll && tool.risky) || needsConfirmation(tool, args);
115
+ if (gated && (!this.confirm || !(await this.confirm(name, args)))) {
116
+ return ["[denied] the user did not approve this action; choose a safer approach or explain.", true];
117
+ }
118
+ try {
119
+ return [await callTool(tool, args), false];
120
+ }
121
+ catch (e) {
122
+ if (e instanceof PermissionDenied)
123
+ return [e.message, true];
124
+ // report to the model so it can adapt
125
+ const err = e;
126
+ return [`[error] ${err.name}: ${err.message}`, true];
127
+ }
128
+ }
129
+ async run(task) {
130
+ const tools = [...(await this.toolset()).values()].map(toApi);
131
+ this.messages.push({ role: "user", content: task });
132
+ for (let step = 0; step < this.maxSteps; step++) {
133
+ const resp = await (await this.clientFor()).messages.create({
134
+ model: this.model,
135
+ max_tokens: 4096,
136
+ system: this.system,
137
+ tools,
138
+ messages: this.messages,
139
+ });
140
+ this.messages.push({ role: "assistant", content: resp.content });
141
+ for (const b of resp.content)
142
+ if (b.type === "text" && b.text?.trim())
143
+ this.onEvent("text", b.text);
144
+ const calls = resp.content.filter((b) => b.type === "tool_use");
145
+ if (calls.length === 0)
146
+ return resp.content.filter((b) => b.type === "text").map((b) => b.text ?? "").join("");
147
+ const results = [];
148
+ for (const call of calls) {
149
+ const args = (call.input ?? {});
150
+ this.onEvent("tool_call", [call.name, args]);
151
+ const [out, isError] = await this.execute(call.name, { ...args });
152
+ this.onEvent("tool_result", [call.name, out]);
153
+ results.push({ type: "tool_result", tool_use_id: call.id, content: out, is_error: isError });
154
+ }
155
+ this.messages.push({ role: "user", content: results });
156
+ }
157
+ return `[stopped after ${this.maxSteps} steps without finishing]`;
158
+ }
159
+ }
160
+ //# sourceMappingURL=agent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent.js","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { main } from "../mcpServer.js";
3
+ process.exitCode = await main();
4
+ //# sourceMappingURL=omcps.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"omcps.js","sourceRoot":"","sources":["../../src/bin/omcps.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAEvC,OAAO,CAAC,QAAQ,GAAG,MAAM,IAAI,EAAE,CAAC"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { main } from "../cli.js";
3
+ process.exitCode = await main();
4
+ //# sourceMappingURL=osagent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"osagent.js","sourceRoot":"","sources":["../../src/bin/osagent.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,CAAC,QAAQ,GAAG,MAAM,IAAI,EAAE,CAAC"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,39 @@
1
+ import type { Readable, Writable } from "node:stream";
2
+ import { type AgentOptions } from "./agent.js";
3
+ export interface Runnable {
4
+ run(task: string): Promise<string>;
5
+ }
6
+ export interface CliIo {
7
+ /** Print a line to stdout. */
8
+ out: (s: string) => void;
9
+ /** Ask a question; resolves null on EOF / Ctrl-D. */
10
+ prompt: (q: string) => Promise<string | null>;
11
+ createAgent: (opts: AgentOptions) => Runnable;
12
+ /** Streams for the default line reader (tests). */
13
+ input?: Readable;
14
+ output?: Writable;
15
+ }
16
+ /**
17
+ * Queue-based line reader: works for a TTY and for piped stdin, and survives EOF.
18
+ * @param input - Readable input stream.
19
+ * @param output - Writable output stream.
20
+ * @returns A line reader with an `ask` method.
21
+ */
22
+ export declare function makeLineReader(input?: Readable, output?: Writable): {
23
+ ask: (q: string) => Promise<string | null>;
24
+ close: () => void;
25
+ };
26
+ /**
27
+ *
28
+ * @param kind - Event kind.
29
+ * @param data - Event payload to display.
30
+ * @param out - Output writer.
31
+ */
32
+ export declare function show(kind: string, data: unknown, out?: (s: string) => void): void;
33
+ /**
34
+ *
35
+ * @param argv - Command-line arguments.
36
+ * @param io - I/O overrides.
37
+ * @returns The process exit code.
38
+ */
39
+ export declare function main(argv?: string[], io?: Partial<CliIo>): Promise<number>;