photofresco 0.0.0-stage → 0.1.2
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/CLI.md +192 -0
- package/CONNECTOR.md +111 -0
- package/DISCLOSURE.md +162 -0
- package/LICENSE +21 -0
- package/MCP.md +155 -0
- package/README.md +425 -2
- package/package.json +61 -4
- package/protocol/README.md +164 -0
- package/protocol/conformance.json +392 -0
- package/protocol/conformance.mjs +49 -0
- package/protocol/connector.openrpc.json +1374 -0
- package/protocol/fixtures/LICENSE.txt +4 -0
- package/protocol/fixtures/gradient.png +0 -0
- package/protocol/op-catalog.json +229 -0
- package/protocol/op-catalog.schema.json +25 -0
- package/protocol/vectors.json +75 -0
- package/protocol/verification.json +91 -0
- package/server.json +56 -0
- package/skills/photofresco/SKILL.md +262 -0
- package/skills/photofresco/references/troubleshooting.md +132 -0
- package/skills/photofresco/references/workflows.md +98 -0
- package/src/bin/photofresco.js +116 -0
- package/src/browser.js +299 -0
- package/src/cdp.js +165 -0
- package/src/cli/attach.js +486 -0
- package/src/cli/daemon.js +212 -0
- package/src/cli/exit.js +202 -0
- package/src/cli/main.js +1007 -0
- package/src/cli/result.schema.json +152 -0
- package/src/cli/workflow.js +107 -0
- package/src/connector.js +607 -0
- package/src/errors.js +81 -0
- package/src/files.js +417 -0
- package/src/index.js +12 -0
- package/src/mcp/catalog/account.json +46 -0
- package/src/mcp/catalog/action.json +148 -0
- package/src/mcp/catalog/adjust.json +3743 -0
- package/src/mcp/catalog/ai.json +129 -0
- package/src/mcp/catalog/artifact.json +92 -0
- package/src/mcp/catalog/assistant.json +28 -0
- package/src/mcp/catalog/autosave.json +77 -0
- package/src/mcp/catalog/brush.json +597 -0
- package/src/mcp/catalog/bucket.json +104 -0
- package/src/mcp/catalog/canvas.json +61 -0
- package/src/mcp/catalog/channel.json +215 -0
- package/src/mcp/catalog/clone.json +96 -0
- package/src/mcp/catalog/color.json +98 -0
- package/src/mcp/catalog/color_sampler.json +62 -0
- package/src/mcp/catalog/count.json +88 -0
- package/src/mcp/catalog/crop.json +245 -0
- package/src/mcp/catalog/debug.json +23 -0
- package/src/mcp/catalog/doc.json +269 -0
- package/src/mcp/catalog/dock.json +394 -0
- package/src/mcp/catalog/drive.json +151 -0
- package/src/mcp/catalog/edit.json +331 -0
- package/src/mcp/catalog/erase.json +114 -0
- package/src/mcp/catalog/export.json +65 -0
- package/src/mcp/catalog/feedback.json +70 -0
- package/src/mcp/catalog/file.json +23 -0
- package/src/mcp/catalog/filter.json +170 -0
- package/src/mcp/catalog/frame.json +90 -0
- package/src/mcp/catalog/gradient.json +117 -0
- package/src/mcp/catalog/heal.json +290 -0
- package/src/mcp/catalog/history.json +59 -0
- package/src/mcp/catalog/image.json +426 -0
- package/src/mcp/catalog/jev.json +28 -0
- package/src/mcp/catalog/layer.json +774 -0
- package/src/mcp/catalog/liquify.json +166 -0
- package/src/mcp/catalog/mask.json +112 -0
- package/src/mcp/catalog/note.json +112 -0
- package/src/mcp/catalog/paint.json +112 -0
- package/src/mcp/catalog/path.json +736 -0
- package/src/mcp/catalog/pattern.json +65 -0
- package/src/mcp/catalog/pattern_stamp.json +99 -0
- package/src/mcp/catalog/preset.json +141 -0
- package/src/mcp/catalog/privacy.json +23 -0
- package/src/mcp/catalog/quickmask.json +28 -0
- package/src/mcp/catalog/render.json +160 -0
- package/src/mcp/catalog/ruler.json +49 -0
- package/src/mcp/catalog/selection.json +977 -0
- package/src/mcp/catalog/shape.json +115 -0
- package/src/mcp/catalog/slice.json +97 -0
- package/src/mcp/catalog/smart.json +413 -0
- package/src/mcp/catalog/style.json +169 -0
- package/src/mcp/catalog/text.json +881 -0
- package/src/mcp/catalog/transform.json +111 -0
- package/src/mcp/catalog/ui.json +138 -0
- package/src/mcp/catalog/view.json +127 -0
- package/src/mcp/catalog/workflow.json +195 -0
- package/src/mcp/results.js +218 -0
- package/src/mcp/server.js +467 -0
- package/src/mcp/tools.js +390 -0
- package/src/page.js +323 -0
- package/src/rpc/catalog.js +19 -0
- package/src/rpc/cli.js +34 -0
- package/src/rpc/client.js +83 -0
- package/src/rpc/schema.js +46 -0
- package/src/rpc/server.js +172 -0
- package/vendor/README.md +33 -0
- package/vendor/licenses/modelcontextprotocol-core.LICENSE.txt +216 -0
- package/vendor/licenses/modelcontextprotocol-server.LICENSE.txt +216 -0
- package/vendor/licenses/zod.LICENSE.txt +21 -0
- package/vendor/mcp-core.js +1134 -0
- package/vendor/mcp-server-protocol.js +4596 -0
- package/vendor/mcp-server.d.ts +3 -0
- package/vendor/mcp-server.js +1762 -0
- package/vendor/zod.js +5800 -0
package/CLI.md
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# PhotoFresco command line (pf-agent-platform-nkwn.16)
|
|
2
|
+
|
|
3
|
+
`photofresco <command>` drives a PhotoFresco window you approve from scripts and agents: open an
|
|
4
|
+
image from a folder you granted, run a saved workflow on it, export the result, inspect status and
|
|
5
|
+
stop. It is a thin layer over the local connector ([CONNECTOR.md](CONNECTOR.md)), the same one the
|
|
6
|
+
MCP server (`photofresco mcp`, [MCP.md](MCP.md)) uses: the editor in the window does all the
|
|
7
|
+
editing, and the connector enforces pairing, permissions and folder grants. What it runs, sends and
|
|
8
|
+
fetches: [DISCLOSURE.md](DISCLOSURE.md).
|
|
9
|
+
|
|
10
|
+
Status: implemented and verified (unit and process tests, the real editor session layer, and a
|
|
11
|
+
headless real-browser run). **Not published**: the npm package is a later release step (`.39`,
|
|
12
|
+
owner approval pending), so run it from a checkout until then (nothing to install):
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
alias photofresco="node $PWD/agent/src/bin/photofresco.js"
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
A Chromium-based browser (Chrome, Edge or Chromium 120+) must be installed, or pass `--browser`
|
|
19
|
+
(or set `PHOTOFRESCO_BROWSER`) to a Chrome for Testing binary.
|
|
20
|
+
|
|
21
|
+
## Example
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
photofresco connect --read ~/Pictures --write ~/Pictures/PhotoFresco
|
|
25
|
+
photofresco run warm.json --open ~/Pictures/beach.png --input amount=0.6 \
|
|
26
|
+
--export ~/Pictures/PhotoFresco/beach-warm.png
|
|
27
|
+
photofresco stop
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
1. `connect` opens PhotoFresco in its own browser window and prints a **connection code**. Check
|
|
31
|
+
that the window shows the same code and click **Allow**, then choose the permissions in the
|
|
32
|
+
Permissions dialog (the CLI asks for Read, Edit and Export by default). `connect` returns once
|
|
33
|
+
you decided; it exits 75 if you have not decided within `--wait` seconds (run it again to keep
|
|
34
|
+
waiting).
|
|
35
|
+
2. Later commands reuse that window and session. `run` opens the image (a new tab), runs the
|
|
36
|
+
workflow through the editor's workflow engine (`workflow.run`, the Actions panel's engine) and
|
|
37
|
+
writes the export only after its SHA-256 matched.
|
|
38
|
+
3. `stop` ends the session, closes the window and stops the connector. `disconnect` ends only the
|
|
39
|
+
session; the window stays open for you.
|
|
40
|
+
|
|
41
|
+
Verified output of the same flow (`docs/e2e-runs/pf-agent-platform-nkwn.16-20261009/human-run.txt`):
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
$ photofresco run invert.json --input opacity=0.5 --export out/sunset-half.png
|
|
45
|
+
Workflow "Invert" succeeded: 2/2 steps, 2 edits (run run_…, document doc_…)
|
|
46
|
+
Exported …/out/sunset-half.png (png, 2008 bytes, sha256 2129b3c3741e784c…)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
A workflow file is the portable document `workflow.export` or the Actions panel produces
|
|
50
|
+
(`photofresco.workflow` version 1, see docs/agent-interface.md "Workflows"). `--action <id>` runs
|
|
51
|
+
an action saved in the editor instead.
|
|
52
|
+
|
|
53
|
+
## Commands
|
|
54
|
+
|
|
55
|
+
| Command | What it does |
|
|
56
|
+
|---|---|
|
|
57
|
+
| `connect` | Start the connector if needed, open PhotoFresco and pair (you click Allow) |
|
|
58
|
+
| `status` | Connector, window, session, permissions and granted folders (`running: false` when stopped) |
|
|
59
|
+
| `request-access <list>` | Ask for more permissions (`read,edit,export,external-write,spend`) |
|
|
60
|
+
| `documents` | Open documents with IDs and revisions |
|
|
61
|
+
| `attach <document-id>` | Target another open document |
|
|
62
|
+
| `open <file>` | Open an image (PNG, JPEG, WebP, GIF, BMP, AVIF, PSD/PSB, `.pfd`, ≤ 32 MiB) from a granted folder in a new tab |
|
|
63
|
+
| `run <workflow.json>` | Run a workflow (or `--action <id>`) on the session document; `--open`, `--export` around it |
|
|
64
|
+
| `export <file>` | Export the document (PNG, PSD or `.pfd`, from the extension or `--format`) into a granted folder |
|
|
65
|
+
| `exec <op> [params-json]` | Run one editor operation (`layer.add` or its tool name `layer_add`) |
|
|
66
|
+
| `outcome <request-id>` | The retained result of an earlier request, after a lost connection |
|
|
67
|
+
| `ops [prefix]` | Operations this session may run; the public catalog before connecting (`--catalog` forces it) |
|
|
68
|
+
| `describe <op>` | An operation's description, permissions, effects and parameter schema |
|
|
69
|
+
| `disconnect` | End the session; window and connector stay |
|
|
70
|
+
| `stop` | End the session, close the window and stop the connector |
|
|
71
|
+
| `mcp` | The MCP server ([MCP.md](MCP.md)) |
|
|
72
|
+
|
|
73
|
+
`photofresco <command> --help` lists each command's options. The main ones:
|
|
74
|
+
|
|
75
|
+
| Option | Command | Meaning |
|
|
76
|
+
|---|---|---|
|
|
77
|
+
| `--read <folder>`, `--write <folder>` | connect | Folders this session may open from / export into (repeatable; grants add up while the connector runs) |
|
|
78
|
+
| `--overwrite` | connect, run, export | On connect: exports may replace files in `--write` folders. On run/export: replace this file |
|
|
79
|
+
| `--grant <list>` | connect | Permissions to ask for (default `read,edit,export`; `none` asks for nothing) |
|
|
80
|
+
| `--client <name>` | connect | Shown in the window as "<name> via CLI" (default `Terminal`) |
|
|
81
|
+
| `--wait <seconds>` | connect, request-access | How long to wait for your decision (default 170, at most 180) |
|
|
82
|
+
| `--profile <folder>`, `--browser <path>` | connect | Browser profile (`ephemeral` for a throwaway one) and browser; only when the connector starts |
|
|
83
|
+
| `--input name=value`, `--inputs <file>` | run | Workflow inputs; values parse as JSON when they can (`0.6`, `true`, `"text"`), else as text |
|
|
84
|
+
| `--consent <step-id>` | run | Allow an irreversible barrier step (export, upload, paid AI, tab lifecycle) |
|
|
85
|
+
| `--on-failure stop\|rollback` | run | Keep the partial result (default) or roll the run's edits back |
|
|
86
|
+
| `--request-id <id>` | run, exec | Your ID for `outcome` |
|
|
87
|
+
| `--json` | all | One JSON object on stdout, also on failure ([result.schema.json](src/cli/result.schema.json)) |
|
|
88
|
+
| `--include-data` | all | Keep image data (base64, data URLs) in results; omitted by default |
|
|
89
|
+
| `--data-dir <folder>` | all | Connector data folder (default `PHOTOFRESCO_DATA_DIR`, else the per-OS folder in DISCLOSURE.md) |
|
|
90
|
+
|
|
91
|
+
Development only: `connect --editor-url http://127.0.0.1:<port>/app/ --allow-loopback-editor`.
|
|
92
|
+
|
|
93
|
+
## Exit codes and JSON
|
|
94
|
+
|
|
95
|
+
| Code | Meaning |
|
|
96
|
+
|---|---|
|
|
97
|
+
| 0 | Success |
|
|
98
|
+
| 1 | Unexpected internal error |
|
|
99
|
+
| 2 | Usage: bad arguments, unreadable or unsupported workflow file, unknown operation. Nothing ran |
|
|
100
|
+
| 3 | No connector or session, pairing denied/expired, browser or editor unavailable |
|
|
101
|
+
| 4 | A path was refused (not granted, escapes its folder, exists, too large, unreadable). Nothing ran |
|
|
102
|
+
| 5 | The editor refused or failed the operation or workflow (permission, revision, failed step). Nothing was written |
|
|
103
|
+
| 6 | Reading the export back failed or did not match its SHA-256. Nothing was written |
|
|
104
|
+
| 7 | Writing the file failed. The target was not replaced |
|
|
105
|
+
| 8 | Canceled (Ctrl-C, or Stop in the window). The document keeps the partial result |
|
|
106
|
+
| 75 | Still waiting for you in the window; run `connect` again |
|
|
107
|
+
|
|
108
|
+
With `--json` every command prints `{ "ok", "command", "exit_code", … }`; failures add
|
|
109
|
+
`error { code, message, hint, stage? }` plus whatever already happened (for `run`: the opened
|
|
110
|
+
file, the run record with each step's status, the run ID). Human output goes to stdout, errors and
|
|
111
|
+
the connection code to stderr. Results never contain keys, and image payloads are replaced by
|
|
112
|
+
`[omitted: N bytes …]` unless `--include-data`, so the JSON is a reproducible log of what ran
|
|
113
|
+
(workflow SHA-256, inputs, steps, document revision, exported file SHA-256).
|
|
114
|
+
|
|
115
|
+
## Cancellation and lost connections
|
|
116
|
+
|
|
117
|
+
- **Ctrl-C** during `run`, `exec`, `open` or `export` ends the session (the connector's `close`,
|
|
118
|
+
same as Stop): the editor stops before the next workflow step, keeps what was done as an
|
|
119
|
+
inspectable checkpoint and the CLI exits 8. Reconnect (`connect`, a new Allow), then inspect
|
|
120
|
+
(`exec workflow.runs`) or roll back (`exec workflow.rollback '{"run_id":"…"}'`). The session
|
|
121
|
+
protocol has no per-request cancel yet, which is why the whole session ends. A second Ctrl-C
|
|
122
|
+
exits immediately. Ctrl-C while `connect` waits only stops waiting (exit 130); the request stays
|
|
123
|
+
in the window until you answer it or it expires.
|
|
124
|
+
- **A lost connection** (the CLI killed, the terminal closed) cancels nothing: the editor finishes
|
|
125
|
+
the request. `outcome <request-id>` returns its result; the document stays open in the window.
|
|
126
|
+
- **Stop** in the window, closing it or reloading ends the session; running requests fail as
|
|
127
|
+
canceled or `session_ended` (exit 8 or 3).
|
|
128
|
+
|
|
129
|
+
## How invocations share one session (attach protocol)
|
|
130
|
+
|
|
131
|
+
`connect` starts a background **connector process** (`photofresco __daemon`, detached, no
|
|
132
|
+
console) for the data folder when none runs. It owns the connector (surface `cli`), its browser
|
|
133
|
+
window and the paired session. Every command connects to it, makes its calls and disconnects. It
|
|
134
|
+
exits on `stop`, when you close its browser window, on SIGINT/SIGTERM/SIGHUP, or after one minute
|
|
135
|
+
with no browser and no client (for example after a failed browser start).
|
|
136
|
+
|
|
137
|
+
Security model (same as `.14`; adapters add no authorization of their own):
|
|
138
|
+
|
|
139
|
+
- **No network listener.** A Unix domain socket `<data dir>/cli/connector.sock` (a named pipe
|
|
140
|
+
`\\.\pipe\photofresco-cli-<hash>` on Windows, not verified). No TCP, WebSocket or HTTP server.
|
|
141
|
+
- **Local user only.** `<data dir>/cli` must be a real directory owned by you with mode 0700; both
|
|
142
|
+
sides check it and refuse otherwise (`ipc_insecure`). The socket is 0600.
|
|
143
|
+
- **Attach key.** Each start writes 32 random bytes (hex) to `<data dir>/cli/attach.key` (0600);
|
|
144
|
+
a connection must present it in its first message, compared in constant time. Defence in depth
|
|
145
|
+
against a mis-permissioned folder, not a boundary against code running as you (out of scope,
|
|
146
|
+
as in CONNECTOR.md).
|
|
147
|
+
- **Explicit pairing.** Attaching to the process grants nothing in the editor: the session exists
|
|
148
|
+
only after your Allow, and the editor enforces the permissions you granted on every operation.
|
|
149
|
+
Folder grants come from `connect --read/--write` and accumulate while the process runs.
|
|
150
|
+
An agent that can run any shell command as you is not contained by folder grants.
|
|
151
|
+
- One connector process per data folder (a lock file; `already_running`); a stale socket left by a
|
|
152
|
+
crash is replaced. Use `--data-dir` (or `PHOTOFRESCO_DATA_DIR`) for parallel instances.
|
|
153
|
+
|
|
154
|
+
Wire format (attach protocol 1, for `.47` SDKs and other local clients): newline-delimited
|
|
155
|
+
JSON-RPC 2.0 over that socket, one message per line, requests ≤ 16 MiB.
|
|
156
|
+
|
|
157
|
+
1. First request: `hello { protocol: 1, key, client: { name, version } }` → `{ protocol,
|
|
158
|
+
connector: { name, version }, channel_protocol, pid }`. A wrong or missing key is error
|
|
159
|
+
`-32001` and the connection closes; another protocol version is `-32002` with
|
|
160
|
+
`data.supported`.
|
|
161
|
+
2. Methods mirror the connector API (CONNECTOR.md), params and results unchanged: `status`,
|
|
162
|
+
`connect`, `close`, `sessionStatus`, `documents`, `attach`, `release`, `operations`,
|
|
163
|
+
`requestAccess`, `execute`, `outcome`, `importFile`, `exportFile`, `roots`, `addRoot`,
|
|
164
|
+
`fetchArtifact` (returns `data_base64`, at most 32 MiB). `subscribe` starts `event`
|
|
165
|
+
notifications carrying connector events (`state`, `pairing`, `session`, `session_ended`).
|
|
166
|
+
`shutdown` ends the session, closes the browser and stops the process.
|
|
167
|
+
3. Errors: connector and editor failures are `-32000` with `data` = `{ code, message, stage?,
|
|
168
|
+
details? }` (the connector's error codes); `-32601` unknown method, `-32700` invalid JSON.
|
|
169
|
+
|
|
170
|
+
Implementation: `src/cli/attach.js` (socket, key, framing, client), `src/cli/daemon.js`
|
|
171
|
+
(connector process), `src/cli/main.js` (commands), `src/cli/exit.js` (exit codes, hints),
|
|
172
|
+
`src/cli/workflow.js` (workflow files).
|
|
173
|
+
|
|
174
|
+
## Tests
|
|
175
|
+
|
|
176
|
+
- `npm test` in `agent/` includes `test/cli.test.js`: argument parsing, exit codes, the JSON
|
|
177
|
+
schema, workflow file checks, socket permissions, attach key/protocol refusals, one process per
|
|
178
|
+
folder, idle exit, and the real bin spawning, reporting and stopping its connector process.
|
|
179
|
+
- Editor repository: `test/agent-cli.test.mjs` (every command over the real session layer: pairing,
|
|
180
|
+
workflow run with inputs, export, path and permission refusals, failed step checkpoint, Ctrl-C,
|
|
181
|
+
lost connection, user Stop, pending pairing, stop) and `scripts/agent-cli-e2e.mjs` (the real bin
|
|
182
|
+
against a real editor in a headless browser; see the evidence folder above).
|
|
183
|
+
|
|
184
|
+
## Not included
|
|
185
|
+
|
|
186
|
+
- Publication (npm, Homebrew tap, GitHub Release binaries): `.39`/`.43`, owner approval.
|
|
187
|
+
- A terminal recording (GIF) for the README and Terminal Trove: after publication.
|
|
188
|
+
- Per-request cancel without ending the session; Windows/Linux runs (implemented, not verified).
|
|
189
|
+
|
|
190
|
+
## Uninstall
|
|
191
|
+
|
|
192
|
+
`photofresco stop`, remove the package, then follow DISCLOSURE.md "Uninstall".
|
package/CONNECTOR.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# PhotoFresco local connector (pf-agent-platform-nkwn.14)
|
|
2
|
+
|
|
3
|
+
One in-process API (`createConnector`) shared by the MCP server (`.15`, `photofresco mcp`, see
|
|
4
|
+
[MCP.md](MCP.md)), the CLI (`.16`, `photofresco <command>`, see [CLI.md](CLI.md)) and the JSON-RPC
|
|
5
|
+
stdio endpoint (`.47`). Pairing, origin/protocol pinning, revocation and file grants are enforced
|
|
6
|
+
here; adapters add no authorization logic. What it runs, sends and fetches:
|
|
7
|
+
[DISCLOSURE.md](DISCLOSURE.md).
|
|
8
|
+
|
|
9
|
+
Separate processes reach one running connector through the CLI's **attach path**: a background
|
|
10
|
+
connector process serves this API over a local-user-only Unix socket (0700 folder, 0600 socket,
|
|
11
|
+
per-start attach key; no TCP listener) as newline-delimited JSON-RPC 2.0 whose methods mirror the
|
|
12
|
+
API below. Protocol and security model: CLI.md "How invocations share one session"; `.47` SDKs can
|
|
13
|
+
attach to it or spawn their own process.
|
|
14
|
+
|
|
15
|
+
## API
|
|
16
|
+
|
|
17
|
+
```js
|
|
18
|
+
import { createConnector } from 'photofresco' // package name pending .33
|
|
19
|
+
const connector = createConnector({
|
|
20
|
+
surface: 'mcp', // mcp | cli | sdk | vscode (shown to the user, acquisition token)
|
|
21
|
+
client: 'Claude Code', // agent name shown as "Claude Code via MCP"
|
|
22
|
+
roots: [{ path: '/Users/me/Pictures', read: true }, { path: '/Users/me/Exports', write: true, overwrite: false }],
|
|
23
|
+
// editorUrl, allowLoopbackEditor, browserPath, dataDir, profileDir ('ephemeral'), cwd, env, log, launch
|
|
24
|
+
})
|
|
25
|
+
connector.status() // sync, no browser: { protocol, connector, surface, state, editor, browser, pairing, session, last_end, roots }
|
|
26
|
+
connector.subscribe((event) => {}) // state | pairing { code, expires_at } | session { status } | session_ended { code, message }
|
|
27
|
+
await connector.connect({ access: { capabilities: ['read', 'edit', 'export'], reason: '…' }, document: 'active', timeout_ms })
|
|
28
|
+
await connector.operations() // granted ops only
|
|
29
|
+
await connector.execute({ op: 'image.desaturate', params: {}, request_id, expected_revision }) // SessionResponse; refusals are responses
|
|
30
|
+
await connector.importFile({ path: 'photo.png' }) // doc.open (Edit grant)
|
|
31
|
+
await connector.exportFile({ path: 'out/photo.psd', format: 'psd', overwrite: false }) // Read + Export grants
|
|
32
|
+
await connector.fetchArtifact({ artifact_id, max_bytes }) // verified bytes for MCP resources
|
|
33
|
+
// also: sessionStatus, documents, attach({ document }), release, requestAccess, outcome({ request_id }), roots, addRoot (trusted config only), close, shutdown
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
- `state`: `idle` (no browser) → `starting` → `ready` (editor open, no session) → `pairing` →
|
|
37
|
+
`connected`. `connect()` resolves after the user's Allow; concurrent calls share one attempt;
|
|
38
|
+
when connected it returns the current status. The `pairing` event carries the code to show the
|
|
39
|
+
user. The editor withdraws an unanswered request after 3 minutes (`timeout_ms` ≤ that).
|
|
40
|
+
- `close()` ends the session from the agent side (same as Stop) and leaves the window open;
|
|
41
|
+
`shutdown()` also closes the browser.
|
|
42
|
+
- Never expose `addRoot`, `editorUrl`, `browserPath` or `launch` to the model.
|
|
43
|
+
|
|
44
|
+
## Errors
|
|
45
|
+
|
|
46
|
+
`ConnectorError { code, message, stage?, details? }` (`toJSON()` for adapters). Editor-side
|
|
47
|
+
refusals keep the editor's codes (`capability_denied`, `stale_revision`, `artifact_expired`, …).
|
|
48
|
+
Connector codes: `invalid_options`, `invalid_request`, `not_connected`, `busy`,
|
|
49
|
+
`browser_not_found`, `browser_unsupported`, `browser_launch_failed`, `profile_in_use`,
|
|
50
|
+
`browser_closed`, `page_closed`, `page_reloaded`, `origin_changed`, `editor_unavailable`,
|
|
51
|
+
`editor_unsupported`, `channel_rejected`, `pairing_denied`, `pairing_expired`,
|
|
52
|
+
`pairing_canceled`, `pairing_in_progress`, `session_active`, `session_ended`,
|
|
53
|
+
`path_not_granted`, `path_escape`, `path_invalid`, `file_exists`, `not_a_file`, `file_too_large`,
|
|
54
|
+
`read_failed`, `write_failed`, `artifact_incomplete`, `artifact_hash_mismatch`,
|
|
55
|
+
`artifact_too_large`, `operation_failed`. File workflows set `stage`: `path` (refused before
|
|
56
|
+
anything ran), `operation`, `retrieval`, `write` — the CLI maps these to separate exit codes
|
|
57
|
+
(4–7, CLI.md). The attach path adds `connector_not_running`, `connector_disconnected`,
|
|
58
|
+
`connector_unsupported`, `ipc_unauthorized`, `ipc_insecure`, `ipc_failed` and `already_running`.
|
|
59
|
+
|
|
60
|
+
## Security model
|
|
61
|
+
|
|
62
|
+
- **Transport**: a connector-owned browser process with a dedicated profile and the DevTools
|
|
63
|
+
protocol on `--remote-debugging-pipe`: no TCP/WebSocket listener, so no Host/Origin, DNS
|
|
64
|
+
rebinding or local-network surface, and other local processes or web pages cannot reach it.
|
|
65
|
+
Works under the editor's production HTTPS/COOP/COEP; TLS and isolation are untouched.
|
|
66
|
+
- **Page channel** (`src/ops/connectorChannel.ts` in the editor): installed only when the
|
|
67
|
+
connector's binding exists; the editor takes the binding off the global scope at boot and
|
|
68
|
+
exposes a frozen `photofrescoConnector.receive`. Every message needs the page's random
|
|
69
|
+
channel key (sent only through the binding) and a strictly increasing ID: forged and replayed
|
|
70
|
+
envelopes are refused synchronously and never answered. Session calls also need the
|
|
71
|
+
per-pairing session key, which ends with Stop, close, consent revocation or reload.
|
|
72
|
+
- **Pinning**: binding calls count only from the tab's main frame, default world, at the pinned
|
|
73
|
+
editor origin; navigation away ends the session and unsubscribes the binding; protocol
|
|
74
|
+
version mismatches are refused (`editor_unsupported`). Other tabs have no binding.
|
|
75
|
+
- **Authorization**: the user allows each connection (pairing dialog with a matching code, which
|
|
76
|
+
also records the page's External agents consent) and grants capabilities in the Permissions
|
|
77
|
+
dialog; the editor enforces them on every operation (`.11`). The connector cannot answer
|
|
78
|
+
either dialog and no adapter may expose `session.*` operations.
|
|
79
|
+
- **Files**: canonical (`realpath`) folder grants; traversal and symbolic-link escapes refused;
|
|
80
|
+
no writes through links; overwrite needs the root grant and the request flag; reads open the
|
|
81
|
+
checked file without following links; writes publish atomically only after the full artifact
|
|
82
|
+
was read and its SHA-256 matched.
|
|
83
|
+
- **Threats out of scope**: code already running as the same OS user (it can read the profile or
|
|
84
|
+
control any browser), and script running inside the editor origin (it can already drive the UI).
|
|
85
|
+
|
|
86
|
+
## Packaging decision
|
|
87
|
+
|
|
88
|
+
- Runtime: Node.js ≥ 22. The connector modules import no dependencies; the MCP server
|
|
89
|
+
(`src/mcp`) uses the official MCP TypeScript SDK (`@modelcontextprotocol/server` 2.2.0, which
|
|
90
|
+
brings `@modelcontextprotocol/core` and `zod`), bundled readable in `vendor/` so the package and
|
|
91
|
+
the plugin installs need no `npm install` (pf-agent-platform-nkwn.37; `vendor/README.md`). Browser: an installed Chromium-based browser
|
|
92
|
+
≥ 120 (Google Chrome, Microsoft Edge, Chromium), or any Chromium/Chrome for Testing binary in
|
|
93
|
+
`PHOTOFRESCO_BROWSER`. The connector downloads nothing; machines without one need a browser
|
|
94
|
+
install first (for example `npx @puppeteer/browsers install chrome@stable`, then point
|
|
95
|
+
`PHOTOFRESCO_BROWSER` at it).
|
|
96
|
+
- OS: macOS arm64 verified (Chrome for Testing 145). Windows and Linux discovery paths are
|
|
97
|
+
implemented but not verified. The pipe transport is the same one Playwright uses on all three.
|
|
98
|
+
- Clean uninstall: remove the npm package and the data dir listed in DISCLOSURE.md.
|
|
99
|
+
- One connector per profile at a time (`profile_in_use`); use `profileDir: 'ephemeral'` for parallel
|
|
100
|
+
instances.
|
|
101
|
+
|
|
102
|
+
## Tests
|
|
103
|
+
|
|
104
|
+
`npm test` (node:test: path grants, staged writes, CDP framing, tab pinning, launch with a fake
|
|
105
|
+
browser, API guards, `test/mcp.test.js`: the MCP server as a clean stdio process driven by the
|
|
106
|
+
official MCP client, and `test/cli.test.js`: the CLI and its attach socket); `npm run typecheck`.
|
|
107
|
+
The editor repository adds `test/agent-connector.test.mjs` (real page channel + connector),
|
|
108
|
+
`test/agent-mcp.test.mjs` (MCP over the real session layer), `test/agent-mcp-catalog.test.mjs`
|
|
109
|
+
(the bundled op catalog), `test/agent-cli.test.mjs` (CLI over the real session layer),
|
|
110
|
+
`scripts/agent-connector-browser-tests.mjs`, `scripts/agent-mcp-e2e.mjs` and
|
|
111
|
+
`scripts/agent-cli-e2e.mjs` (real browser).
|
package/DISCLOSURE.md
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# What the PhotoFresco connector runs, sends and fetches
|
|
2
|
+
|
|
3
|
+
Record for pf-agent-platform-nkwn.14, the MCP server (pf-agent-platform-nkwn.15) and the CLI
|
|
4
|
+
(pf-agent-platform-nkwn.16) (source for the `.34` public README section). Observed on macOS arm64
|
|
5
|
+
with Chrome for Testing 145 against a local editor build on 2026-10-09
|
|
6
|
+
(`docs/e2e-runs/pf-agent-platform-nkwn.14-20261009/log.json`,
|
|
7
|
+
`docs/e2e-runs/pf-agent-platform-nkwn.15-20261009/log.json`,
|
|
8
|
+
`docs/e2e-runs/pf-agent-platform-nkwn.16-20261009/log.json` and the `.34` request log
|
|
9
|
+
`docs/e2e-runs/pf-agent-platform-nkwn.34-20261009/demo/user-side.json` in the editor repository).
|
|
10
|
+
Anything not observed in those runs is marked "not verified".
|
|
11
|
+
|
|
12
|
+
## Runs
|
|
13
|
+
|
|
14
|
+
- **A Node.js process**: whatever adapter loaded the connector (MCP server, CLI, SDK endpoint).
|
|
15
|
+
Constructing the connector starts nothing.
|
|
16
|
+
- **One browser process tree, only on `connect()`**: an installed Chromium-based browser
|
|
17
|
+
(`browserPath` option, else `PHOTOFRESCO_BROWSER`, else Google Chrome, Microsoft Edge or
|
|
18
|
+
Chromium from their standard install locations). Nothing is downloaded. Exact arguments:
|
|
19
|
+
`--user-data-dir=<profile> --remote-debugging-pipe --no-first-run --no-default-browser-check --disable-extensions about:blank`.
|
|
20
|
+
Observed: 7 processes (browser plus its renderer/GPU/utility children), all ending when the
|
|
21
|
+
connector shuts down or its process exits (the browser exits when its pipe closes).
|
|
22
|
+
- **No other programs**, no shell commands, no installers.
|
|
23
|
+
|
|
24
|
+
## Browser profile and local files
|
|
25
|
+
|
|
26
|
+
- **Dedicated profile**: `<data dir>/browser-profile`, where the data dir is
|
|
27
|
+
`~/Library/Application Support/PhotoFresco Connector` (macOS), `%LOCALAPPDATA%\PhotoFresco Connector`
|
|
28
|
+
(Windows) or `$XDG_DATA_HOME/photofresco-connector` / `~/.local/share/photofresco-connector`
|
|
29
|
+
(Linux). It holds what any browser profile holds for photofresco.com (cookies and sign-in if
|
|
30
|
+
you sign in there, cache, local storage including the editor's acquisition first-touch,
|
|
31
|
+
editor autosave). Your normal browser profiles are never opened, read or attached to.
|
|
32
|
+
`profileDir: 'ephemeral'` uses a temporary profile deleted when the browser exits.
|
|
33
|
+
- **Lock file**: `<profile>/PhotoFrescoConnector.lock` (process ID and a random nonce) while the
|
|
34
|
+
connector owns the profile.
|
|
35
|
+
- **Reads**: only files inside folders granted for reading, only on `importFile`, at most 32 MiB.
|
|
36
|
+
- **Writes**: only inside folders granted for writing, only on `exportFile`: missing subfolders,
|
|
37
|
+
a hidden temporary `.<name>.<random>.pf-partial` next to the target during the transfer, then
|
|
38
|
+
the target (atomic rename, or hard link when not overwriting). Existing files are replaced
|
|
39
|
+
only with the folder's overwrite grant and `overwrite: true`. A transfer whose SHA-256 does not
|
|
40
|
+
match leaves no file.
|
|
41
|
+
- The connector itself writes no log files (the CLI's connector process does; see below). Its
|
|
42
|
+
optional `log` callback receives events such as `file_written { path, bytes, sha256 }`; it never
|
|
43
|
+
receives keys, file contents or pixels.
|
|
44
|
+
|
|
45
|
+
## Local ports and channels
|
|
46
|
+
|
|
47
|
+
- **No listening port.** The DevTools protocol runs over the browser's private pipe (file
|
|
48
|
+
descriptors 3/4 of the browser process). Observed: no TCP listener in any browser process,
|
|
49
|
+
and no `DevToolsActivePort` file. No HTTP/WebSocket server is started.
|
|
50
|
+
- The connector attaches only to the tab it opened, registers one binding there
|
|
51
|
+
(`__photofrescoConnector`) and calls one fixed function (`photofrescoConnector.receive`) with
|
|
52
|
+
data arguments. It evaluates no other page code and never attaches to other tabs.
|
|
53
|
+
- Messages carry a random channel key and, per approved connection, a session key. Neither
|
|
54
|
+
is put in a URL, a log or an event.
|
|
55
|
+
|
|
56
|
+
## Fetches and sends
|
|
57
|
+
|
|
58
|
+
- **The connector process itself makes no network requests.**
|
|
59
|
+
- The browser opens `https://photofresco.com/app/?pf_ref=<surface>` (the editor URL is pinned
|
|
60
|
+
per connector; a loopback `http://127.0.0.1` editor is allowed only for development). The
|
|
61
|
+
`pf_ref` value is the non-secret surface name (`mcp`, `cli`, `sdk`, `vscode`) used for
|
|
62
|
+
acquisition attribution. From there the page loads its assets like a normal visit to the editor.
|
|
63
|
+
- **The connector's window starts with session recording & diagnostics off** (owner decision,
|
|
64
|
+
2026-10-09). The editor knows the connector opened it (the DevTools binding the connector
|
|
65
|
+
registers on its own tab; no link or website can set it) and starts that page with nothing
|
|
66
|
+
allowed in Data & privacy; the menu button reads "Data: local-only" until you click Allow,
|
|
67
|
+
which adds only External agents. So the page sends PhotoFresco **no session recording, no
|
|
68
|
+
diagnostics, no copies of the files the agent imports and no acquisition events**, and keeps
|
|
69
|
+
`pf_ref` unprocessed in the address bar. Observed in the `.39` runs (local build with
|
|
70
|
+
`?replay=1`, which records the way production does for a normal visit): no `/replay/` or
|
|
71
|
+
`/telemetry` request from pairing to the end of the session, through import, edits and exports.
|
|
72
|
+
- **Unless you turn Session recording & diagnostics on in Data & privacy** (`/app/privacy/`) in that window: from then
|
|
73
|
+
on the page sends the session recording (input events, editor commands, document names, typed
|
|
74
|
+
text except password and email fields) **with a copy of each file opened afterwards in the
|
|
75
|
+
window, including files the agent imports through the connector** (at most 16 MiB each, 6 per
|
|
76
|
+
page load), diagnostics (errors, feature events, performance vitals, a device profile) and
|
|
77
|
+
privacy-safe acquisition events (`referral`, `open`, `edit`, `export`, each `{ channel: 'mcp',
|
|
78
|
+
source: 'mcp', surface: 'mcp', actor: 'automated' }`, no file names or pixels); the editor then
|
|
79
|
+
removes `pf_ref` from the address bar. Observed after turning it on: `POST /telemetry`,
|
|
80
|
+
`/telemetry/acquisition` and `/replay/sessions/<id>/chunks`, all to the editor's own origin.
|
|
81
|
+
A normal visit to the editor (not opened by the connector) keeps recording on by default.
|
|
82
|
+
- In the observed runs (local build) the tab requested only the editor's own origin. Production
|
|
83
|
+
third-party requests were not verified.
|
|
84
|
+
- The connector's browser reports `navigator.webdriver = true` (a consequence of the DevTools
|
|
85
|
+
pipe), so the editor shows no third-party ads there and counts any acquisition events as
|
|
86
|
+
`automated`.
|
|
87
|
+
- The browser's own background traffic (component updates, safe browsing and similar) follows
|
|
88
|
+
the installed browser's defaults; not measured.
|
|
89
|
+
- **To the agent** (through the adapter): only what the user's grants allow — session status,
|
|
90
|
+
document state, previews (pixels), operation results and exported artifacts. Previews and
|
|
91
|
+
other results returned to an agent go to that agent's model provider under its policy.
|
|
92
|
+
- Paid AI runs only with the Spend grant and the budget the user sets (pf-agent-platform-nkwn.11).
|
|
93
|
+
|
|
94
|
+
## The MCP server (`photofresco mcp`)
|
|
95
|
+
|
|
96
|
+
- **Runs** as a Node.js process your MCP client starts and talks to over stdin/stdout. With
|
|
97
|
+
`npx photofresco …`, npm downloads the package from the npm registry first (npm's fetch, before
|
|
98
|
+
the server runs); as a Claude Code, Codex, Cursor or Gemini CLI plugin, the client copies this
|
|
99
|
+
repository and runs `node <plugin folder>/src/bin/photofresco.js mcp`, downloading nothing at start.
|
|
100
|
+
Runtime dependencies: none to install. The official MCP SDK (`@modelcontextprotocol/server` and
|
|
101
|
+
`@modelcontextprotocol/core` 2.2.0, `zod` 4.6.5) is bundled readable in `vendor/` (versions,
|
|
102
|
+
tarball SHA-256 and licenses in `vendor/README.md`); the server uses only its stdio transport. It
|
|
103
|
+
starts no browser until the agent calls `photofresco_connect`.
|
|
104
|
+
- **No network, no listening port** of its own. Observed: no socket in the server process during a
|
|
105
|
+
session (`lsof -i`).
|
|
106
|
+
- **Files**: reads its own package files; reads and writes your files only through the folders you
|
|
107
|
+
pass with `--read`/`--write` (the connector rules above). No log files; it writes to stderr only
|
|
108
|
+
for startup errors.
|
|
109
|
+
- **To the editor**: the MCP client's own name from its `initialize` message, shown to you as
|
|
110
|
+
"<name> via MCP", and the surface token `mcp`.
|
|
111
|
+
- **To the agent**: the tool list (before a session, the public operation catalog bundled in the
|
|
112
|
+
package), connection state and code, session status (target document, revision, grants, the
|
|
113
|
+
pending request, budget), the folders you granted (paths), the browser's product name, operation
|
|
114
|
+
results, previews as images, exports as resources (at most 6 MiB each) and the paths and SHA-256
|
|
115
|
+
of files it wrote. All of it goes to the agent's model provider under that provider's policy.
|
|
116
|
+
- **Ends** when the client closes stdin: the server ends the session, closes its browser and exits.
|
|
117
|
+
Observed: the browser process was gone after the client closed.
|
|
118
|
+
|
|
119
|
+
## The CLI (`photofresco <command>`)
|
|
120
|
+
|
|
121
|
+
- **Runs** two kinds of Node.js process: each command you type (it exits when done), and one
|
|
122
|
+
background **connector process** per data folder, started by `photofresco connect` (`photofresco
|
|
123
|
+
__daemon`, detached from the terminal). The connector process owns the browser described above.
|
|
124
|
+
It ends on `photofresco stop`, when you close its browser window, on SIGINT/SIGTERM/SIGHUP, or
|
|
125
|
+
after a minute with no browser and no command attached. Runtime dependencies: none beyond Node.js
|
|
126
|
+
(it reads the bundled op catalog for `ops`/`describe` before a session).
|
|
127
|
+
- **Local channel, no network listener**: `<data dir>/cli/connector.sock`, a Unix domain socket
|
|
128
|
+
(mode 0600) in a folder that must be owned by you with mode 0700; a named pipe on Windows (not
|
|
129
|
+
verified). Every connection must present `<data dir>/cli/attach.key` (32 random bytes, 0600,
|
|
130
|
+
new each start, deleted on stop). Observed: no network socket in the connector process during a
|
|
131
|
+
session (`lsof -i`), socket 0600 in a 0700 folder.
|
|
132
|
+
- **Files**: `<data dir>/cli/` holds the socket, the key, a lock file (`PhotoFrescoConnector.lock`,
|
|
133
|
+
process ID and nonce) and `connector.log`: the connector's secret-free events as JSON lines
|
|
134
|
+
(paths of granted folders and of files read or written, byte counts, SHA-256 of exports, session
|
|
135
|
+
and browser start/stop), overwritten at each start and capped at 4 MiB. Observed: no key, file
|
|
136
|
+
content or pixel data in it. It reads workflow and inputs files you name on the command line (at
|
|
137
|
+
most 1 MiB, JSON only; anything else is refused before it is sent) and your images only through
|
|
138
|
+
the folders you pass to `connect --read/--write` (the connector rules above).
|
|
139
|
+
- **To the editor**: the name from `--client` (default `Terminal`), shown to you as "<name> via
|
|
140
|
+
CLI", the surface token `cli` (`pf_ref=cli`; the window starts with recording off, so no
|
|
141
|
+
acquisition events are sent unless you turn recording on there, then `referral`, `open`, `edit`
|
|
142
|
+
and `export`, each `{ channel: 'cli', source: 'cli', surface: 'cli', actor: 'automated' }`, no
|
|
143
|
+
file names or pixels) and the workflow documents and inputs you run.
|
|
144
|
+
- **To the terminal**: results on stdout (with `--json`, one object per command), the connection
|
|
145
|
+
code and errors on stderr. Image data in results is omitted unless `--include-data`. Whatever
|
|
146
|
+
runs the CLI (for example an agent) receives that output.
|
|
147
|
+
- **Ends**: `photofresco stop` closes the browser and the connector process. Observed: browser and
|
|
148
|
+
connector processes gone and the socket removed after `stop`.
|
|
149
|
+
|
|
150
|
+
## User control
|
|
151
|
+
|
|
152
|
+
- Each connection needs the user's **Allow** in the editor window (the window shows the same
|
|
153
|
+
connection code the agent reports); the session starts with no permissions; the Permissions
|
|
154
|
+
dialog grants them. Stop, closing the window, reloading or leaving the editor origin ends the
|
|
155
|
+
session; a new connection needs a new Allow.
|
|
156
|
+
- Closing the connector (or its process exiting) closes its browser window.
|
|
157
|
+
|
|
158
|
+
## Uninstall
|
|
159
|
+
|
|
160
|
+
Run `photofresco stop` if the CLI is running, remove the package (for example `npm uninstall -g
|
|
161
|
+
<package>`) and delete the data dir above. Nothing else is installed: no services, no login items,
|
|
162
|
+
no browser extensions.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 PhotoFresco / Josh Wardini
|
|
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.
|
package/MCP.md
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# PhotoFresco MCP server (pf-agent-platform-nkwn.15)
|
|
2
|
+
|
|
3
|
+
`photofresco mcp` is a stdio [Model Context Protocol](https://modelcontextprotocol.io) server. It
|
|
4
|
+
lets an AI agent edit images in a PhotoFresco window on your computer: you see every change, you
|
|
5
|
+
allow the connection, you choose what the agent may do, and you can stop it at any time. It is a
|
|
6
|
+
thin adapter over the local connector ([CONNECTOR.md](CONNECTOR.md)); what it runs, sends and
|
|
7
|
+
fetches is in [DISCLOSURE.md](DISCLOSURE.md).
|
|
8
|
+
|
|
9
|
+
Status: implemented and verified with the official MCP TypeScript client over stdio (both protocol
|
|
10
|
+
eras) and against a real editor in a real browser. **Not published**: the npm package and the MCP
|
|
11
|
+
Registry entry are a later release step, so the `npx photofresco` lines below work only after that
|
|
12
|
+
release. Until then, run it from a checkout (see "From a checkout").
|
|
13
|
+
|
|
14
|
+
## Configure your agent
|
|
15
|
+
|
|
16
|
+
Choose the folders the agent may open files from (`--read`) and save exports into (`--write`).
|
|
17
|
+
Without them the agent can still edit and preview, but cannot touch your files.
|
|
18
|
+
|
|
19
|
+
> To run from a clone instead, use `node /path/to/photofresco-agent/src/bin/photofresco.js` in place of
|
|
20
|
+
> the `npx -y photofresco@0.1.2` part. Agents such as Cline can follow [llms-install.md](llms-install.md).
|
|
21
|
+
|
|
22
|
+
**Claude Desktop** — `claude_desktop_config.json` (Settings → Developer → Edit Config):
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"mcpServers": {
|
|
27
|
+
"photofresco": {
|
|
28
|
+
"command": "npx",
|
|
29
|
+
"args": ["-y", "photofresco@0.1.2", "mcp", "--read", "/Users/you/Pictures", "--write", "/Users/you/Pictures/PhotoFresco"]
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Claude Code**:
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
claude mcp add photofresco -- npx -y photofresco@0.1.2 mcp --read ~/Pictures --write ~/Pictures/PhotoFresco
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Cursor** — `~/.cursor/mcp.json` (all projects) or `.cursor/mcp.json` (one project): the same
|
|
42
|
+
`mcpServers` JSON as Claude Desktop.
|
|
43
|
+
|
|
44
|
+
**VS Code** — `.vscode/mcp.json` (one workspace) or **MCP: Open User Configuration** (all): the
|
|
45
|
+
same arguments under `"servers"` with `"type": "stdio"`:
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"servers": {
|
|
50
|
+
"photofresco": {
|
|
51
|
+
"type": "stdio",
|
|
52
|
+
"command": "npx",
|
|
53
|
+
"args": ["-y", "photofresco@0.1.2", "mcp", "--read", "/Users/you/Pictures", "--write", "/Users/you/Pictures/PhotoFresco"]
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**From a checkout** (before publication; nothing to install, the MCP SDK is bundled in `vendor/`):
|
|
60
|
+
use `"command": "node"` with `"args": ["/path/to/agent/src/bin/photofresco.js", "mcp", "--read", …]`.
|
|
61
|
+
|
|
62
|
+
These configurations follow each client's documented `command`/`args` stdio format. The server
|
|
63
|
+
itself was exercised with the official MCP client; it has not yet been run inside Claude Desktop,
|
|
64
|
+
Claude Code, Cursor or VS Code (recorded as a release check).
|
|
65
|
+
|
|
66
|
+
### Options
|
|
67
|
+
|
|
68
|
+
| Option | Meaning |
|
|
69
|
+
|---|---|
|
|
70
|
+
| `--read <folder>` | The agent may open image files from this folder (repeatable) |
|
|
71
|
+
| `--write <folder>` | The agent may save exports into this folder and new subfolders (repeatable) |
|
|
72
|
+
| `--overwrite` | Exports may replace existing files in `--write` folders (the agent must also ask for it) |
|
|
73
|
+
| `--tools <groups>` | Which operations are listed as tools: `essentials` (default, at most 40 tools with both folder options), `all`, or a comma list (below) |
|
|
74
|
+
| `--profile <folder>` | Browser profile folder, or `ephemeral` for a throwaway one |
|
|
75
|
+
| `--browser <path>` | Chromium-based browser to use (same as `PHOTOFRESCO_BROWSER`) |
|
|
76
|
+
|
|
77
|
+
A Chromium-based browser (Chrome, Edge or Chromium 120+) must be installed; see CONNECTOR.md
|
|
78
|
+
"Packaging decision".
|
|
79
|
+
|
|
80
|
+
## How a session goes
|
|
81
|
+
|
|
82
|
+
1. The agent calls `photofresco_connect`. PhotoFresco opens in its own window with a **connection
|
|
83
|
+
code**; the agent tells you the same code. Check it and click **Allow**.
|
|
84
|
+
2. The session starts with **no permissions**. The agent asks (Read, Edit, Export; Send and Spend
|
|
85
|
+
only for cloud and paid AI) and you choose in the **Permissions** dialog. Editing tools appear
|
|
86
|
+
for the agent as you grant them.
|
|
87
|
+
3. The agent edits with one tool per editor operation; every document edit is in History.
|
|
88
|
+
Editing yourself pauses the agent until you press **Resume**. **Stop** ends the session.
|
|
89
|
+
|
|
90
|
+
Initialization and the tool list never start a browser: nothing opens until the agent calls
|
|
91
|
+
`photofresco_connect`.
|
|
92
|
+
|
|
93
|
+
## Tools
|
|
94
|
+
|
|
95
|
+
- **Session**: `photofresco_connect`, `photofresco_status`, `photofresco_request_access`,
|
|
96
|
+
`photofresco_documents`, `photofresco_attach`, `photofresco_disconnect`.
|
|
97
|
+
- **Files** (listed only for configured folders): `photofresco_import_file` (PNG, JPEG, WebP, GIF,
|
|
98
|
+
BMP, AVIF, PSD/PSB, `.pfd`, at most 32 MiB) and `photofresco_export_file` (PNG, PSD or `.pfd`,
|
|
99
|
+
written only after the bytes' SHA-256 matched).
|
|
100
|
+
- **Tool groups**: `photofresco_list_tool_groups`, `photofresco_select_tool_groups`.
|
|
101
|
+
- **Operations**: one tool per editor operation, named like `layer_add`, `adjust_add`,
|
|
102
|
+
`render_region`, `history_undo`, with the editor's exact input schema. Each has a title, a
|
|
103
|
+
description of its inputs, effect, undo behaviour and required permission, and MCP annotations
|
|
104
|
+
derived from the operation's metadata (read-only, destructive, open-world). There is no generic
|
|
105
|
+
"execute" tool, and the user-only controls (pairing, permissions, pause/resume/stop, privacy
|
|
106
|
+
policy) are never tools.
|
|
107
|
+
|
|
108
|
+
Before a session the list shows the public catalog of operations; once connected it shows only
|
|
109
|
+
what your grants allow, and changes (`notifications/tools/list_changed`) when you grant, revoke or
|
|
110
|
+
stop. Listing a tool is documentation, not permission: the editor checks every call.
|
|
111
|
+
|
|
112
|
+
| Group | Operations |
|
|
113
|
+
|---|---|
|
|
114
|
+
| `essentials` (default) | Orient, preview, layers, adjustments, masks, selections, resize/crop, fill, undo, export (30 operations) |
|
|
115
|
+
| `document` | Documents, history, previews, image/canvas/crop, exports and artifacts |
|
|
116
|
+
| `layers` | Layers, masks, layer styles, smart objects, frames, channels |
|
|
117
|
+
| `selection` | Selections, quick mask, paths |
|
|
118
|
+
| `adjustments` | Adjustment layers, filters, liquify, warps |
|
|
119
|
+
| `painting` | Brushes, retouching, fills, gradients, colors, presets, edit commands |
|
|
120
|
+
| `text` | Type, shapes, notes, counts, ruler, slices |
|
|
121
|
+
| `automation` | Actions, workflows, autosave |
|
|
122
|
+
| `workspace` | Panels, view, dialogs, account usage, diagnostics |
|
|
123
|
+
| `cloud` | My Documents, feedback, paid AI (needs Send and Spend) |
|
|
124
|
+
|
|
125
|
+
### Results
|
|
126
|
+
|
|
127
|
+
- Previews (`render_preview`, `render_region`, `render_mask`) return the PNG as MCP **image
|
|
128
|
+
content** plus the coordinate metadata as JSON.
|
|
129
|
+
- `export_artifact` returns a **resource link** `photofresco://artifact/<id>`; `resources/read`
|
|
130
|
+
returns the bytes (verified against their SHA-256, at most 6 MiB; larger exports go through
|
|
131
|
+
`photofresco_export_file`). Exports live 10 minutes in the editor.
|
|
132
|
+
- Refusals and failures are tool errors with the editor's code, its message and the next step, for
|
|
133
|
+
example `capability_denied` → ask with `photofresco_request_access`, `lease_paused` → ask the user
|
|
134
|
+
to press Resume.
|
|
135
|
+
|
|
136
|
+
## Agent skill
|
|
137
|
+
|
|
138
|
+
The package also carries an [Agent Skill](https://agentskills.io/specification),
|
|
139
|
+
`skills/photofresco/` (pf-agent-platform-nkwn.36): instructions that teach skills-capable agents
|
|
140
|
+
(Claude Code, Codex, Cursor and others) when to use PhotoFresco, how to connect and ask for
|
|
141
|
+
permissions, the orient → edit → preview → undo loop, workflows, exports, every error code and the
|
|
142
|
+
`photofresco` command line. It
|
|
143
|
+
does not install or configure the MCP server; set that up as above. Install it with the
|
|
144
|
+
[`skills` CLI](https://github.com/vercel-labs/skills) into the current project (`-g` for your user):
|
|
145
|
+
|
|
146
|
+
```sh
|
|
147
|
+
npx skills add /path/to/photofresco-checkout/agent -a claude-code -a codex -a cursor
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Once the public repository is published (planned as `photofresco/photofresco-agent`), the source
|
|
151
|
+
becomes `npx skills add photofresco/photofresco-agent`. Remove it with `npx skills remove photofresco`.
|
|
152
|
+
|
|
153
|
+
## Uninstall
|
|
154
|
+
|
|
155
|
+
Remove the MCP entry from your client configuration, then follow DISCLOSURE.md "Uninstall".
|