Skip to content

feat(plugins): Foundations Pack — OS basics (apps, clipboard, files, state) - #25

Merged
rrader26 merged 1 commit into
mainfrom
feat/foundations-pack
May 12, 2026
Merged

feat(plugins): Foundations Pack — OS basics (apps, clipboard, files, state)#25
rrader26 merged 1 commit into
mainfrom
feat/foundations-pack

Conversation

@rrader26

Copy link
Copy Markdown
Contributor

Summary

  • New `createFoundationsPlugin()` adds 14 cross-platform tools across four surfaces: app launching, clipboard, allowlisted filesystem, durable state.
  • Pure Node (only shells out to OS-shipped binaries — no external npm deps).
  • Opt-in; not part of the default plugin set.

Why

Foundation for everything in the "AI agent does anything on a desktop" mandate. Today the desktop driver can manipulate a running app's accessibility tree, but the agent can't:

  • launch an app or open a file
  • copy/paste between apps
  • read/write working files outside what the running app exposes
  • remember anything across MCP sessions

These four surfaces unblock all of that. Together they're the OS-basics layer that complements (not replaces) the desktop driver.

Tools shipped

Surface Tools
Apps `agentmark_app_run`
Clipboard `agentmark_clipboard_read`, `agentmark_clipboard_write`
Filesystem `agentmark_files_list`, `..._read`, `..._write`, `..._stat`, `..._delete`, `..._move`, `..._mkdir`
State `agentmark_state_get`, `..._set`, `..._delete`, `..._list`

Safety

Filesystem ops are gated by an allowlist (default: `cwd` + `os.tmpdir`). Every path is canonicalised via `realpath` before the prefix check, so symlinks like `~/Documents/safe → /etc` can't escape. Roots are themselves canonicalised on first use to handle macOS's `/var → /private/var` alias.

App launch detaches by default so the spawned app survives the MCP server's death; safe for any UI app.

State store writes atomically (temp file + rename), mode 0600 on POSIX. Single global namespace per user — not a database, don't put megabytes in it.

Usage

```ts
import {
createMcpServer,
createWebPlugin, createPdfPlugin, createDesktopPlugin, createMetaPlugin,
createFoundationsPlugin,
} from '@thinkfleet/agentmark'

const foundations = createFoundationsPlugin({
fileRoots: ['/Users/me/Documents', '/Users/me/Downloads'],
})

createMcpServer({
plugins: [web, pdf, desktop, foundations, meta],
})
```

Test plan

  • `pnpm build` clean
  • `pnpm test` — 355 pass / 10 skip (16 new tests: allowlist enforcement + symlink-escape resistance + state round-trip + plugin registration + handler error paths)
  • Manual smoke on macOS: `agentmark_app_run` opens TextEdit; `agentmark_clipboard_write` followed by ⌘V in TextEdit pastes the right text
  • Manual smoke on Windows VM: same drill with Notepad

What's next in the roadmap

  • PR-γ: Window management (focus / move / resize / minimize / close / screenshot) — needs bridge changes
  • Then: Snapshot diff → Recipes → Self-healing IDs → Vision fallback → Recording → Notifications → Network tools → Voice → Process introspection → Hotkeys

🤖 Generated with Claude Code

Cross-platform pure-Node plugin layering OS basics on top of the
desktop driver. Lets agents touch the rest of the machine, not just
the running app they're driving. 14 new tools across four surfaces:

  Apps      agentmark_app_run
  Clipboard agentmark_clipboard_read, …_write
  Files     agentmark_files_list, …_read, …_write, …_stat,
            …_delete, …_move, …_mkdir
  State     agentmark_state_get, …_set, …_delete, …_list

App launcher uses platform-native invocation (macOS `open`, Windows
`start`, Linux `xdg-open` for files) so file associations + Start
Menu / Launch Services resolution work for free.

Filesystem operations are gated by an allowlist of root directories
(defaults: cwd + os.tmpdir; configurable via fileRoots config option
or AGENTMARK_FILES_ROOTS env var). Every path is realpath'd before
the prefix check so symlinks cannot escape the boundary. Roots are
canonicalised at first call (macOS /var → /private/var alias).

Durable state is a single JSON file at
~/.thinkfleet/agentmark/state.json (0600), updated via atomic
write-temp-and-rename. Suitable for small slow-changing data:
last-customer worked with, retry counters, cached enumerations.

Clipboard shells out to OS utilities (pbcopy/pbpaste, Get-Clipboard /
Set-Clipboard, xclip/wl-paste). No external Node dependency.

Opt-in plugin — not part of the default set. Compose:

  import { createMcpServer, createWebPlugin, createPdfPlugin,
           createDesktopPlugin, createMetaPlugin,
           createFoundationsPlugin } from '@thinkfleet/agentmark'

  const foundations = createFoundationsPlugin({
      fileRoots: ['/Users/me/Documents'],
  })
  createMcpServer({ plugins: [web, pdf, desktop, foundations, meta] })

Tests: 16 new (filesystem allowlist + escape resistance + symlink
follow, state round-trip + persistence + prefix filter, plugin
registration). 355 pass / 10 skip total. Build clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@rrader26
rrader26 merged commit 823632f into main May 12, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants