@alisaitteke/photoshop-mcp 1.1.3 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +162 -19
- package/dist/analytics/beta-telemetry.d.ts +11 -0
- package/dist/analytics/beta-telemetry.d.ts.map +1 -0
- package/dist/analytics/beta-telemetry.js +47 -0
- package/dist/analytics/beta-telemetry.js.map +1 -0
- package/dist/analytics/config.d.ts +6 -0
- package/dist/analytics/config.d.ts.map +1 -0
- package/dist/analytics/config.js +23 -0
- package/dist/analytics/config.js.map +1 -0
- package/dist/analytics/context.d.ts +2 -0
- package/dist/analytics/context.d.ts.map +1 -0
- package/dist/analytics/context.js +13 -0
- package/dist/analytics/context.js.map +1 -0
- package/dist/analytics/events.d.ts +3 -0
- package/dist/analytics/events.d.ts.map +1 -0
- package/dist/analytics/events.js +75 -0
- package/dist/analytics/events.js.map +1 -0
- package/dist/analytics/identity.d.ts +9 -0
- package/dist/analytics/identity.d.ts.map +1 -0
- package/dist/analytics/identity.js +166 -0
- package/dist/analytics/identity.js.map +1 -0
- package/dist/analytics/index.d.ts +10 -0
- package/dist/analytics/index.d.ts.map +1 -0
- package/dist/analytics/index.js +31 -0
- package/dist/analytics/index.js.map +1 -0
- package/dist/analytics/launch-method.d.ts +4 -0
- package/dist/analytics/launch-method.d.ts.map +1 -0
- package/dist/analytics/launch-method.js +33 -0
- package/dist/analytics/launch-method.js.map +1 -0
- package/dist/analytics/noop.d.ts +6 -0
- package/dist/analytics/noop.d.ts.map +1 -0
- package/dist/analytics/noop.js +9 -0
- package/dist/analytics/noop.js.map +1 -0
- package/dist/analytics/posthog-node.d.ts +9 -0
- package/dist/analytics/posthog-node.d.ts.map +1 -0
- package/dist/analytics/posthog-node.js +36 -0
- package/dist/analytics/posthog-node.js.map +1 -0
- package/dist/analytics/provider.d.ts +5 -0
- package/dist/analytics/provider.d.ts.map +1 -0
- package/dist/analytics/provider.js +27 -0
- package/dist/analytics/provider.js.map +1 -0
- package/dist/analytics/types.d.ts +23 -0
- package/dist/analytics/types.d.ts.map +1 -0
- package/dist/analytics/types.js +2 -0
- package/dist/analytics/types.js.map +1 -0
- package/dist/core/server.d.ts +1 -0
- package/dist/core/server.d.ts.map +1 -1
- package/dist/core/server.js +3 -0
- package/dist/core/server.js.map +1 -1
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -1
- package/dist/platform/macos-detector.js +4 -4
- package/dist/platform/macos-detector.js.map +1 -1
- package/dist/platform/macos-executor.js +1 -1
- package/dist/platform/macos-executor.js.map +1 -1
- package/dist/platform/windows-detector.js +4 -4
- package/dist/platform/windows-detector.js.map +1 -1
- package/dist/platform/windows-executor.js +2 -2
- package/dist/platform/windows-executor.js.map +1 -1
- package/dist/ui/agent/action-plan.d.ts +19 -0
- package/dist/ui/agent/action-plan.d.ts.map +1 -0
- package/dist/ui/agent/action-plan.js +552 -0
- package/dist/ui/agent/action-plan.js.map +1 -0
- package/dist/ui/agent/api-key.d.ts +19 -0
- package/dist/ui/agent/api-key.d.ts.map +1 -0
- package/dist/ui/agent/api-key.js +131 -0
- package/dist/ui/agent/api-key.js.map +1 -0
- package/dist/ui/agent/claude-account.d.ts +16 -0
- package/dist/ui/agent/claude-account.d.ts.map +1 -0
- package/dist/ui/agent/claude-account.js +164 -0
- package/dist/ui/agent/claude-account.js.map +1 -0
- package/dist/ui/agent/gemini-account.d.ts +15 -0
- package/dist/ui/agent/gemini-account.d.ts.map +1 -0
- package/dist/ui/agent/gemini-account.js +222 -0
- package/dist/ui/agent/gemini-account.js.map +1 -0
- package/dist/ui/agent/mcp-transport.d.ts +9 -0
- package/dist/ui/agent/mcp-transport.d.ts.map +1 -0
- package/dist/ui/agent/mcp-transport.js +34 -0
- package/dist/ui/agent/mcp-transport.js.map +1 -0
- package/dist/ui/agent/shared.d.ts +55 -0
- package/dist/ui/agent/shared.d.ts.map +1 -0
- package/dist/ui/agent/shared.js +121 -0
- package/dist/ui/agent/shared.js.map +1 -0
- package/dist/ui/agent.d.ts +11 -26
- package/dist/ui/agent.d.ts.map +1 -1
- package/dist/ui/agent.js +84 -169
- package/dist/ui/agent.js.map +1 -1
- package/dist/ui/cli.js +10 -1
- package/dist/ui/cli.js.map +1 -1
- package/dist/ui/config.d.ts +12 -0
- package/dist/ui/config.d.ts.map +1 -1
- package/dist/ui/config.js +11 -0
- package/dist/ui/config.js.map +1 -1
- package/dist/ui/providers/anthropic.d.ts.map +1 -1
- package/dist/ui/providers/anthropic.js +24 -0
- package/dist/ui/providers/anthropic.js.map +1 -1
- package/dist/ui/providers/cli-utils.d.ts +12 -0
- package/dist/ui/providers/cli-utils.d.ts.map +1 -0
- package/dist/ui/providers/cli-utils.js +46 -0
- package/dist/ui/providers/cli-utils.js.map +1 -0
- package/dist/ui/providers/google.d.ts.map +1 -1
- package/dist/ui/providers/google.js +18 -0
- package/dist/ui/providers/google.js.map +1 -1
- package/dist/ui/providers/openai.d.ts.map +1 -1
- package/dist/ui/providers/openai.js +1 -0
- package/dist/ui/providers/openai.js.map +1 -1
- package/dist/ui/providers/openrouter.d.ts.map +1 -1
- package/dist/ui/providers/openrouter.js +1 -0
- package/dist/ui/providers/openrouter.js.map +1 -1
- package/dist/ui/providers/registry.d.ts +1 -1
- package/dist/ui/providers/registry.d.ts.map +1 -1
- package/dist/ui/providers/types.d.ts +11 -0
- package/dist/ui/providers/types.d.ts.map +1 -1
- package/dist/ui/server.d.ts.map +1 -1
- package/dist/ui/server.js +167 -10
- package/dist/ui/server.js.map +1 -1
- package/dist/ui/store/chats.d.ts +11 -0
- package/dist/ui/store/chats.d.ts.map +1 -1
- package/dist/ui/store/chats.js.map +1 -1
- package/package.json +7 -3
- package/web/dist/assets/index-BgiXf1Jy.css +1 -0
- package/web/dist/assets/index-BuJJLllx.js +249 -0
- package/web/dist/index.html +9 -3
- package/web/dist/assets/index-4AnFityQ.css +0 -1
- package/web/dist/assets/index-CFTrtoOu.js +0 -138
package/README.md
CHANGED
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
# Photoshop MCP Server
|
|
2
2
|
|
|
3
|
-
*v1.1+ — recipe workflows, fewer round-trips, snappier sessions.*
|
|
3
|
+
*v1.1+ — recipe workflows, fewer round-trips, snappier sessions. Standalone UI ships **Action Plan (beta)** for plan-then-execute runs.*
|
|
4
4
|
|
|
5
5
|
> **Note:** This is an unofficial, community-maintained project and is not affiliated with or endorsed by Adobe Inc.
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/@alisaitteke/photoshop-mcp)
|
|
8
|
+
[](#action-plan-beta)
|
|
8
9
|
[](https://opensource.org/licenses/MIT)
|
|
9
10
|
[](https://www.typescriptlang.org/)
|
|
10
11
|
[]()
|
|
11
12
|
|
|
12
|
-
A Model Context Protocol (MCP) server that enables AI assistants like Claude and Cursor to control Adobe Photoshop programmatically. This allows you to create designs, manipulate images, and automate Photoshop workflows through natural language commands while working in your IDE.
|
|
13
|
+
A Model Context Protocol (MCP) server that enables AI assistants like Claude and Cursor to control Adobe Photoshop programmatically. This allows you to create designs, manipulate images, and automate Photoshop workflows through natural language commands while working in your IDE — or through the bundled **standalone web UI**, which supports both API keys and CLI subscription accounts (Claude Code / Gemini CLI). The UI also offers an opt-in **Action Plan (beta)** mode that plans every Photoshop step in one LLM call, then runs them in a single pass.
|
|
13
14
|
|
|
14
15
|
## 🖥️ Standalone UI (no IDE required)
|
|
15
16
|
|
|
16
17
|
Don't want to wire this into Claude Desktop or Cursor? The same package ships a
|
|
17
18
|
fully local web UI that lets you chat with an AI model and drive Photoshop
|
|
18
|
-
through this MCP server underneath.
|
|
19
|
+
through this MCP server underneath. Connect with a provider API key **or**, for
|
|
20
|
+
Anthropic and Google, reuse the OAuth session from **Claude Code** or **Gemini
|
|
21
|
+
CLI** — no separate API key required.
|
|
19
22
|
|
|
20
23
|

|
|
21
24
|
|
|
@@ -28,26 +31,77 @@ default browser opens the chat UI automatically.
|
|
|
28
31
|
|
|
29
32
|
### Supported providers
|
|
30
33
|
|
|
31
|
-
Pick any of the following on first launch —
|
|
34
|
+
Pick any of the following on first launch — use an API key **or** your existing
|
|
35
|
+
CLI subscription account (Anthropic and Google):
|
|
32
36
|
|
|
33
|
-
| Provider | Models |
|
|
34
|
-
|
|
35
|
-
| **Anthropic** | Claude Sonnet / Opus / Haiku | [console.anthropic.com](https://console.anthropic.com/settings/keys) |
|
|
36
|
-
| **OpenAI** | GPT-5, GPT-4.1, o-series | [platform.openai.com](https://platform.openai.com/api-keys) |
|
|
37
|
-
| **Google** | Gemini 2.5 Pro / Flash / Flash-Lite | [aistudio.google.com](https://aistudio.google.com/apikey) |
|
|
38
|
-
| **OpenRouter** | 100+ models from any provider | [openrouter.ai](https://openrouter.ai/keys) |
|
|
37
|
+
| Provider | Models | API key | CLI account |
|
|
38
|
+
|---|---|---|---|
|
|
39
|
+
| **Anthropic** | Claude Sonnet / Opus / Haiku | [console.anthropic.com](https://console.anthropic.com/settings/keys) | `npm i -g @anthropic-ai/claude-code` → `claude auth login` |
|
|
40
|
+
| **OpenAI** | GPT-5, GPT-4.1, o-series | [platform.openai.com](https://platform.openai.com/api-keys) | — |
|
|
41
|
+
| **Google** | Gemini 2.5 Pro / Flash / Flash-Lite | [aistudio.google.com](https://aistudio.google.com/apikey) | `npm i -g @google/gemini-cli` → `gemini auth login` |
|
|
42
|
+
| **OpenRouter** | 100+ models from any provider | [openrouter.ai](https://openrouter.ai/keys) | — |
|
|
43
|
+
|
|
44
|
+
### Authentication modes
|
|
45
|
+
|
|
46
|
+
- **`api_key` (default)** — Vercel AI SDK + your provider API key. Usage is billed
|
|
47
|
+
per token at API rates; the UI shows estimated cost per chat.
|
|
48
|
+
- **`cli_account`** — Uses your local Claude Code or Gemini CLI OAuth session.
|
|
49
|
+
No API key is stored; the UI probes `claude auth status` / `gemini` headless
|
|
50
|
+
to verify login. Usage counts against your **subscription quota**, not API
|
|
51
|
+
billing — the status bar shows "Included in subscription".
|
|
52
|
+
|
|
53
|
+
You can switch auth method per provider in Settings without losing the other
|
|
54
|
+
credential (e.g. keep an API key while trying CLI account, then switch back).
|
|
55
|
+
|
|
56
|
+
### Action Plan (beta)
|
|
57
|
+
|
|
58
|
+
An optional execution mode in the standalone web UI for **API key auth only**
|
|
59
|
+
(`cli_account` always uses the default agentic flow). Turn it on with the
|
|
60
|
+
**Action Plan** toggle next to the model selector in the composer.
|
|
61
|
+
|
|
62
|
+
Instead of a per-step ReAct loop (model → tool → model → tool …), Action Plan:
|
|
63
|
+
|
|
64
|
+
1. Makes **one** planning LLM call that outputs an ordered todo list of
|
|
65
|
+
Photoshop MCP tool calls with parameters.
|
|
66
|
+
2. Executes those tools **directly** in sequence — no extra model round-trips
|
|
67
|
+
between steps.
|
|
68
|
+
3. On a failed step or unresolved dependency, runs a bounded **repair** loop
|
|
69
|
+
(re-plans only the remaining steps, up to 3 times).
|
|
70
|
+
|
|
71
|
+
The plan appears as a live todo list above the tool-call cards, with per-step
|
|
72
|
+
status (`pending` → `running` → `done` / `error`). Plans are persisted in chat
|
|
73
|
+
history so they survive reload. The toggle is off by default; the existing
|
|
74
|
+
agentic flow is unchanged when Action Plan is disabled.
|
|
75
|
+
|
|
76
|
+
Good for multi-step prompts such as *"remove the background and export for web"*
|
|
77
|
+
where you want fewer model calls and faster end-to-end execution.
|
|
39
78
|
|
|
40
79
|
### What happens on first launch
|
|
41
80
|
|
|
42
|
-
1. Pick a provider and
|
|
43
|
-
2.
|
|
44
|
-
`~/.photoshop-mcp/data.db` (SQLite, `chmod 600`).
|
|
45
|
-
machine
|
|
81
|
+
1. Pick a provider and choose **API key** or **Uses your account**.
|
|
82
|
+
2. Validate the key or check the CLI connection. Config is stored locally at
|
|
83
|
+
`~/.photoshop-mcp/data.db` (SQLite, `chmod 600`). API keys never leave your
|
|
84
|
+
machine; CLI mode inherits OAuth from `~/.claude/` or `~/.gemini/`.
|
|
46
85
|
3. Type natural-language prompts. The UI streams the model's reply, runs
|
|
47
86
|
Photoshop tool calls in real time, and renders each tool call as an
|
|
48
87
|
inspectable card (input + result).
|
|
49
|
-
4. Switch provider or model anytime from
|
|
50
|
-
tool history are persisted across sessions.
|
|
88
|
+
4. Switch provider, auth method, or model anytime from Settings / model selector
|
|
89
|
+
— chats, costs and tool history are persisted across sessions.
|
|
90
|
+
|
|
91
|
+
### Switching auth method later
|
|
92
|
+
|
|
93
|
+
Open **Settings** from the sidebar at any time:
|
|
94
|
+
|
|
95
|
+
| Action | API key mode | CLI account mode |
|
|
96
|
+
|---|---|---|
|
|
97
|
+
| Set up | Paste key → **Save** | Install CLI → `auth login` → **Check connection** |
|
|
98
|
+
| Switch away | Choose **API key** — stored key is kept | Choose **Uses your account** — key is not deleted |
|
|
99
|
+
| Custom binary | — | Optional **CLI path** if `claude` / `gemini` is not on `PATH` |
|
|
100
|
+
| Cost display | Per-token estimate in status bar | **Included in subscription** badge |
|
|
101
|
+
|
|
102
|
+
Auth method is stored per provider in `~/.photoshop-mcp/data.db` (`authMethod`:
|
|
103
|
+
`api_key` or `cli_account`). Existing configs without `authMethod` default to
|
|
104
|
+
`api_key` and keep working unchanged.
|
|
51
105
|
|
|
52
106
|
### CLI flags
|
|
53
107
|
|
|
@@ -60,9 +114,15 @@ photoshop-mcp-ui [--port 5174] [--host 127.0.0.1] [--no-open]
|
|
|
60
114
|
- The agent is restricted to Photoshop MCP tools only — built-in shell, file
|
|
61
115
|
and web tools are disabled.
|
|
62
116
|
- Tech stack: Vue 3 + Tailwind v4 + [shadcn-vue](https://www.shadcn-vue.com/)
|
|
63
|
-
on the frontend; [Hono](https://hono.dev/)
|
|
64
|
-
|
|
65
|
-
|
|
117
|
+
on the frontend; [Hono](https://hono.dev/) on the backend. API-key mode uses
|
|
118
|
+
the [Vercel AI SDK](https://sdk.vercel.ai/); CLI account mode uses the
|
|
119
|
+
[Claude Agent SDK](https://code.claude.com/docs/en/agent-sdk/mcp) (Anthropic)
|
|
120
|
+
or Gemini CLI headless `stream-json` (Google). All paths talk to this same
|
|
121
|
+
Photoshop MCP server over STDIO.
|
|
122
|
+
- **CLI account limitations:** Gemini headless may open a new session each turn
|
|
123
|
+
(history is prepended to the prompt). Anthropic CLI account consumes
|
|
124
|
+
subscription quota. OAuth login is macOS-first (`claude auth login` /
|
|
125
|
+
`gemini auth login` in Terminal).
|
|
66
126
|
|
|
67
127
|
---
|
|
68
128
|
|
|
@@ -388,6 +448,10 @@ Never guess — read get_state after a failure and propose the next single step.
|
|
|
388
448
|
|
|
389
449
|
## Features
|
|
390
450
|
|
|
451
|
+
- **Standalone web UI** — local chat interface (`photoshop-mcp-ui`); API key or CLI
|
|
452
|
+
subscription auth per provider (Anthropic, Google)
|
|
453
|
+
- **Action Plan (beta)** — opt-in plan-then-execute mode in the web UI (API key
|
|
454
|
+
only): one planning call, direct tool execution, bounded repair on failure
|
|
391
455
|
- **Works on both Windows and macOS**
|
|
392
456
|
- **Supports Photoshop 2012-2025+**
|
|
393
457
|
- **ExtendScript API**: Universal compatibility via AppleScript/COM automation
|
|
@@ -461,6 +525,10 @@ Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_
|
|
|
461
525
|
|
|
462
526
|
- `PHOTOSHOP_PATH`: (Optional) Specify custom Photoshop installation path
|
|
463
527
|
- `LOG_LEVEL`: Logging level (0=DEBUG, 1=INFO, 2=WARN, 3=ERROR)
|
|
528
|
+
- `POSTHOG_DISABLED`: Set to `1` or `true` to disable anonymous usage analytics entirely
|
|
529
|
+
- `POSTHOG_KEY`: (Optional) Override the PostHog project key used for analytics
|
|
530
|
+
- `POSTHOG_API_HOST`: (Optional) PostHog ingest host (default: `https://a.alisait.com`)
|
|
531
|
+
- `POSTHOG_UI_HOST`: (Optional) PostHog UI host for dashboard links (default: `https://eu.posthog.com`)
|
|
464
532
|
|
|
465
533
|
## Available Tools
|
|
466
534
|
|
|
@@ -531,6 +599,9 @@ This context helps AI assistants remember what document and layer they're workin
|
|
|
531
599
|
- Uses AppleScript/OSA for Photoshop communication
|
|
532
600
|
- Spotlight-based auto-detection
|
|
533
601
|
- Supports multiple Photoshop versions installed simultaneously
|
|
602
|
+
- **CLI account auth** (standalone UI) is macOS-first: run `claude auth login` /
|
|
603
|
+
`gemini auth login` in Terminal; credentials live under `~/.claude/` and
|
|
604
|
+
`~/.gemini/`
|
|
534
605
|
|
|
535
606
|
## Supported Photoshop Versions
|
|
536
607
|
|
|
@@ -543,6 +614,16 @@ This context helps AI assistants remember what document and layer they're workin
|
|
|
543
614
|
Common connection, scripting, and logging issues:
|
|
544
615
|
[`docs/troubleshooting.md`](docs/troubleshooting.md).
|
|
545
616
|
|
|
617
|
+
### Standalone UI — CLI account auth
|
|
618
|
+
|
|
619
|
+
| Symptom | Likely cause | Fix |
|
|
620
|
+
|---|---|---|
|
|
621
|
+
| `cli_not_found` | Claude Code / Gemini CLI not installed | `npm i -g @anthropic-ai/claude-code` or `npm i -g @google/gemini-cli` |
|
|
622
|
+
| `not_authenticated` | No OAuth session | Run `claude auth login` or `gemini auth login` in Terminal |
|
|
623
|
+
| `claude` / `gemini` not on `PATH` | Custom install location | Settings → **CLI path** → **Check connection** |
|
|
624
|
+
| Chat works in IDE but not UI (CLI mode) | OAuth tokens are CLI-only | Use **CLI account** in UI; API keys and CLI sessions are separate |
|
|
625
|
+
| Gemini multi-turn feels forgetful | Headless CLI may start a fresh session each turn | Known limitation; history is prepended to the prompt (MVP) |
|
|
626
|
+
|
|
546
627
|
## Development
|
|
547
628
|
|
|
548
629
|
From-source setup, build, lint, integration tests (with latest results), and usage examples:
|
|
@@ -561,6 +642,68 @@ Contributions are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) before
|
|
|
561
642
|
|
|
562
643
|
MIT
|
|
563
644
|
|
|
645
|
+
## Anonymous Usage Analytics
|
|
646
|
+
|
|
647
|
+
This project collects **anonymous, aggregated usage events** to understand how the
|
|
648
|
+
MCP server and standalone UI are used and to improve the product. Analytics are
|
|
649
|
+
**enabled by default** and can be turned off at any time.
|
|
650
|
+
|
|
651
|
+
### What we collect
|
|
652
|
+
|
|
653
|
+
- App version, operating system, and Node.js version
|
|
654
|
+
- Startup signals (MCP server or UI server started, Photoshop detected or not)
|
|
655
|
+
- Setup funnel events (provider chosen, auth method, validation success/failure
|
|
656
|
+
codes — not credentials)
|
|
657
|
+
- Generic UI events (app loaded, onboarding completed)
|
|
658
|
+
|
|
659
|
+
Events use a random anonymous identifier stored locally at
|
|
660
|
+
`~/.photoshop-mcp/` (SQLite `kv` table and/or `analytics-store.json`). We do
|
|
661
|
+
**not** call `identify()` — no person profiles are created for anonymous usage
|
|
662
|
+
(`person_profiles: identified_only`).
|
|
663
|
+
|
|
664
|
+
### What we do **not** collect (unless you opt into beta team sharing)
|
|
665
|
+
|
|
666
|
+
- API keys or OAuth tokens
|
|
667
|
+
- Chat messages, prompts, or model responses **by default**
|
|
668
|
+
- Photoshop document or layer names, file paths, or image content
|
|
669
|
+
- CLI account labels, email addresses, or other account identifiers
|
|
670
|
+
- Tool call arguments or results
|
|
671
|
+
|
|
672
|
+
### Beta team content sharing (opt-in)
|
|
673
|
+
|
|
674
|
+
On first launch of the standalone UI, you are asked whether you want to **join the
|
|
675
|
+
beta team**. This is separate from anonymous usage analytics above.
|
|
676
|
+
|
|
677
|
+
If you accept:
|
|
678
|
+
|
|
679
|
+
- Your **prompts**, **assistant responses**, **reasoning text**, and **tool names**
|
|
680
|
+
(not arguments or results) may be sent to PostHog after each chat turn
|
|
681
|
+
- Content is truncated for very long messages
|
|
682
|
+
- Requires anonymous analytics to remain enabled
|
|
683
|
+
|
|
684
|
+
If you decline, no chat content is logged. You can change this later in
|
|
685
|
+
**Settings → General → Privacy → Beta team content sharing**.
|
|
686
|
+
|
|
687
|
+
Existing installs that have not answered yet are prompted once on the next launch.
|
|
688
|
+
|
|
689
|
+
### Processor and hosting
|
|
690
|
+
|
|
691
|
+
Analytics are processed by [PostHog](https://posthog.com/). Events are sent to a
|
|
692
|
+
managed reverse proxy at `https://a.alisait.com`; the PostHog project UI is
|
|
693
|
+
hosted in the EU (`https://eu.posthog.com`). See the
|
|
694
|
+
[PostHog privacy policy](https://posthog.com/privacy) for how PostHog handles
|
|
695
|
+
data on their side.
|
|
696
|
+
|
|
697
|
+
### How to opt out
|
|
698
|
+
|
|
699
|
+
1. **Standalone UI:** Settings → General → Privacy → set **Anonymous usage
|
|
700
|
+
analytics** to **Off** (also disables beta content sharing).
|
|
701
|
+
2. **Beta content only:** Settings → General → Privacy → set **Beta team content
|
|
702
|
+
sharing** to **Off** (anonymous analytics can stay on).
|
|
703
|
+
3. **Environment variable:** set `POSTHOG_DISABLED=1` before starting
|
|
704
|
+
`photoshop-mcp` or `photoshop-mcp-ui` (disables all analytics for that
|
|
705
|
+
process and persists opt-out in local storage).
|
|
706
|
+
|
|
564
707
|
## Acknowledgments
|
|
565
708
|
|
|
566
709
|
- Built with the [Model Context Protocol SDK](https://github.com/modelcontextprotocol/sdk)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface BetaChatTurnInput {
|
|
2
|
+
providerId: string;
|
|
3
|
+
model: string;
|
|
4
|
+
authMethod: string;
|
|
5
|
+
userPrompt: string;
|
|
6
|
+
assistantText: string;
|
|
7
|
+
assistantReasoning?: string;
|
|
8
|
+
toolNames: string[];
|
|
9
|
+
}
|
|
10
|
+
export declare function captureBetaChatTurn(input: BetaChatTurnInput): void;
|
|
11
|
+
//# sourceMappingURL=beta-telemetry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"beta-telemetry.d.ts","sourceRoot":"","sources":["../../src/analytics/beta-telemetry.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAYD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAgClE"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { isAnalyticsEnabled, isBetaTelemetryOptIn } from './identity.js';
|
|
2
|
+
import { getLaunchMethod } from './launch-method.js';
|
|
3
|
+
import { getAnalytics } from './provider.js';
|
|
4
|
+
const MAX_TEXT_LENGTH = 16_000;
|
|
5
|
+
const TRUNCATION_SUFFIX = '…[truncated]';
|
|
6
|
+
function truncateText(value) {
|
|
7
|
+
if (!value)
|
|
8
|
+
return undefined;
|
|
9
|
+
if (value.length <= MAX_TEXT_LENGTH)
|
|
10
|
+
return value;
|
|
11
|
+
return `${value.slice(0, MAX_TEXT_LENGTH)}${TRUNCATION_SUFFIX}`;
|
|
12
|
+
}
|
|
13
|
+
function uniqueToolNames(names) {
|
|
14
|
+
return [...new Set(names.map((name) => name.trim()).filter(Boolean))];
|
|
15
|
+
}
|
|
16
|
+
export function captureBetaChatTurn(input) {
|
|
17
|
+
if (!isAnalyticsEnabled() || !isBetaTelemetryOptIn())
|
|
18
|
+
return;
|
|
19
|
+
const assistantText = input.assistantText.trim();
|
|
20
|
+
const assistantReasoning = input.assistantReasoning?.trim() ?? '';
|
|
21
|
+
const hasAssistantContent = assistantText.length > 0 ||
|
|
22
|
+
assistantReasoning.length > 0 ||
|
|
23
|
+
input.toolNames.length > 0;
|
|
24
|
+
if (!hasAssistantContent)
|
|
25
|
+
return;
|
|
26
|
+
const toolNames = uniqueToolNames(input.toolNames);
|
|
27
|
+
getAnalytics().capture({
|
|
28
|
+
name: 'beta_chat_turn',
|
|
29
|
+
properties: {
|
|
30
|
+
os: process.platform,
|
|
31
|
+
arch: process.arch,
|
|
32
|
+
node_version: process.version,
|
|
33
|
+
launch_method: getLaunchMethod(),
|
|
34
|
+
event_source: 'server',
|
|
35
|
+
provider_id: input.providerId,
|
|
36
|
+
auth_method: input.authMethod,
|
|
37
|
+
model: input.model,
|
|
38
|
+
user_prompt: truncateText(input.userPrompt.trim()) ?? '',
|
|
39
|
+
assistant_text: truncateText(assistantText) ?? '',
|
|
40
|
+
...(assistantReasoning
|
|
41
|
+
? { assistant_reasoning: truncateText(assistantReasoning) }
|
|
42
|
+
: {}),
|
|
43
|
+
...(toolNames.length > 0 ? { tool_names: toolNames.join(',') } : {}),
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=beta-telemetry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"beta-telemetry.js","sourceRoot":"","sources":["../../src/analytics/beta-telemetry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACzE,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C,MAAM,eAAe,GAAG,MAAM,CAAC;AAC/B,MAAM,iBAAiB,GAAG,cAAc,CAAC;AAYzC,SAAS,YAAY,CAAC,KAAyB;IAC7C,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,IAAI,KAAK,CAAC,MAAM,IAAI,eAAe;QAAE,OAAO,KAAK,CAAC;IAClD,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,eAAe,CAAC,GAAG,iBAAiB,EAAE,CAAC;AAClE,CAAC;AAED,SAAS,eAAe,CAAC,KAAe;IACtC,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AACxE,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,KAAwB;IAC1D,IAAI,CAAC,kBAAkB,EAAE,IAAI,CAAC,oBAAoB,EAAE;QAAE,OAAO;IAE7D,MAAM,aAAa,GAAG,KAAK,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC;IACjD,MAAM,kBAAkB,GAAG,KAAK,CAAC,kBAAkB,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAClE,MAAM,mBAAmB,GACvB,aAAa,CAAC,MAAM,GAAG,CAAC;QACxB,kBAAkB,CAAC,MAAM,GAAG,CAAC;QAC7B,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC;IAC7B,IAAI,CAAC,mBAAmB;QAAE,OAAO;IAEjC,MAAM,SAAS,GAAG,eAAe,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IAEnD,YAAY,EAAE,CAAC,OAAO,CAAC;QACrB,IAAI,EAAE,gBAAgB;QACtB,UAAU,EAAE;YACV,EAAE,EAAE,OAAO,CAAC,QAAQ;YACpB,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,YAAY,EAAE,OAAO,CAAC,OAAO;YAC7B,aAAa,EAAE,eAAe,EAAE;YAChC,YAAY,EAAE,QAAQ;YACtB,WAAW,EAAE,KAAK,CAAC,UAAU;YAC7B,WAAW,EAAE,KAAK,CAAC,UAAU;YAC7B,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,WAAW,EAAE,YAAY,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE;YACxD,cAAc,EAAE,YAAY,CAAC,aAAa,CAAC,IAAI,EAAE;YACjD,GAAG,CAAC,kBAAkB;gBACpB,CAAC,CAAC,EAAE,mBAAmB,EAAE,YAAY,CAAC,kBAAkB,CAAC,EAAE;gBAC3D,CAAC,CAAC,EAAE,CAAC;YACP,GAAG,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACrE;KACF,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function isPostHogDisabledByEnv(): boolean;
|
|
2
|
+
export declare function resolvePostHogKey(): string;
|
|
3
|
+
export declare function resolvePostHogApiHost(): string;
|
|
4
|
+
export declare function resolvePostHogUiHost(): string;
|
|
5
|
+
export declare function hasPostHogKey(): boolean;
|
|
6
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/analytics/config.ts"],"names":[],"mappings":"AASA,wBAAgB,sBAAsB,IAAI,OAAO,CAEhD;AAED,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED,wBAAgB,qBAAqB,IAAI,MAAM,CAE9C;AAED,wBAAgB,oBAAoB,IAAI,MAAM,CAE7C;AAED,wBAAgB,aAAa,IAAI,OAAO,CAEvC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const DEFAULT_POSTHOG_KEY = 'phc_mejq4ZZ8jTNZPiusjh7vHyPzWYinzsDwVJW43SM5FEcg';
|
|
2
|
+
const DEFAULT_API_HOST = 'https://a.alisait.com';
|
|
3
|
+
const DEFAULT_UI_HOST = 'https://eu.posthog.com';
|
|
4
|
+
function envTruthy(name) {
|
|
5
|
+
const value = process.env[name]?.trim().toLowerCase();
|
|
6
|
+
return value === '1' || value === 'true' || value === 'yes';
|
|
7
|
+
}
|
|
8
|
+
export function isPostHogDisabledByEnv() {
|
|
9
|
+
return envTruthy('POSTHOG_DISABLED');
|
|
10
|
+
}
|
|
11
|
+
export function resolvePostHogKey() {
|
|
12
|
+
return process.env.POSTHOG_KEY?.trim() || DEFAULT_POSTHOG_KEY;
|
|
13
|
+
}
|
|
14
|
+
export function resolvePostHogApiHost() {
|
|
15
|
+
return process.env.POSTHOG_API_HOST?.trim() || DEFAULT_API_HOST;
|
|
16
|
+
}
|
|
17
|
+
export function resolvePostHogUiHost() {
|
|
18
|
+
return process.env.POSTHOG_UI_HOST?.trim() || DEFAULT_UI_HOST;
|
|
19
|
+
}
|
|
20
|
+
export function hasPostHogKey() {
|
|
21
|
+
return resolvePostHogKey().length > 0;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/analytics/config.ts"],"names":[],"mappings":"AAAA,MAAM,mBAAmB,GAAG,kDAAkD,CAAC;AAC/E,MAAM,gBAAgB,GAAG,uBAAuB,CAAC;AACjD,MAAM,eAAe,GAAG,wBAAwB,CAAC;AAEjD,SAAS,SAAS,CAAC,IAAY;IAC7B,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACtD,OAAO,KAAK,KAAK,GAAG,IAAI,KAAK,KAAK,MAAM,IAAI,KAAK,KAAK,KAAK,CAAC;AAC9D,CAAC;AAED,MAAM,UAAU,sBAAsB;IACpC,OAAO,SAAS,CAAC,kBAAkB,CAAC,CAAC;AACvC,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,OAAO,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,EAAE,IAAI,mBAAmB,CAAC;AAChE,CAAC;AAED,MAAM,UAAU,qBAAqB;IACnC,OAAO,OAAO,CAAC,GAAG,CAAC,gBAAgB,EAAE,IAAI,EAAE,IAAI,gBAAgB,CAAC;AAClE,CAAC;AAED,MAAM,UAAU,oBAAoB;IAClC,OAAO,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,IAAI,EAAE,IAAI,eAAe,CAAC;AAChE,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,OAAO,iBAAiB,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;AACxC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/analytics/context.ts"],"names":[],"mappings":"AAGA,wBAAgB,yBAAyB,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CASnE"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { loadConfig } from '../ui/config.js';
|
|
2
|
+
import { isAnalyticsEnabled, isBetaTelemetryOptIn } from './identity.js';
|
|
3
|
+
export function getServerAnalyticsContext() {
|
|
4
|
+
const config = loadConfig();
|
|
5
|
+
const analyticsEnabled = isAnalyticsEnabled();
|
|
6
|
+
return {
|
|
7
|
+
privacy_mode: !analyticsEnabled,
|
|
8
|
+
analytics_enabled: analyticsEnabled,
|
|
9
|
+
beta_telemetry_opt_in: isBetaTelemetryOptIn(),
|
|
10
|
+
action_plan_enabled: Boolean(config.actionPlanBeta),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/analytics/context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAEzE,MAAM,UAAU,yBAAyB;IACvC,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,MAAM,gBAAgB,GAAG,kBAAkB,EAAE,CAAC;IAC9C,OAAO;QACL,YAAY,EAAE,CAAC,gBAAgB;QAC/B,iBAAiB,EAAE,gBAAgB;QACnC,qBAAqB,EAAE,oBAAoB,EAAE;QAC7C,mBAAmB,EAAE,OAAO,CAAC,MAAM,CAAC,cAAc,CAAC;KACpD,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare function sanitizeAnalyticsProperties(properties: Record<string, unknown> | undefined): Record<string, string | number | boolean>;
|
|
2
|
+
export declare function buildRuntimeProperties(properties: Record<string, unknown> | undefined): Record<string, string | number | boolean>;
|
|
3
|
+
//# sourceMappingURL=events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/analytics/events.ts"],"names":[],"mappings":"AAiDA,wBAAgB,2BAA2B,CACzC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAC9C,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAc3C;AAED,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAC9C,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAS3C"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { getServerAnalyticsContext } from './context.js';
|
|
2
|
+
import { getLaunchMethod } from './launch-method.js';
|
|
3
|
+
const BLOCKED_PROPERTY_KEYS = new Set([
|
|
4
|
+
'api_key',
|
|
5
|
+
'apikey',
|
|
6
|
+
'key',
|
|
7
|
+
'token',
|
|
8
|
+
'secret',
|
|
9
|
+
'password',
|
|
10
|
+
'email',
|
|
11
|
+
'account_label',
|
|
12
|
+
'cli_path',
|
|
13
|
+
'path',
|
|
14
|
+
'file_path',
|
|
15
|
+
'filepath',
|
|
16
|
+
'message',
|
|
17
|
+
'content',
|
|
18
|
+
'prompt',
|
|
19
|
+
'chat_id',
|
|
20
|
+
'session_id',
|
|
21
|
+
]);
|
|
22
|
+
const ALLOWED_PROPERTY_KEYS = new Set([
|
|
23
|
+
'app_version',
|
|
24
|
+
'os',
|
|
25
|
+
'arch',
|
|
26
|
+
'node_version',
|
|
27
|
+
'provider_id',
|
|
28
|
+
'auth_method',
|
|
29
|
+
'ok',
|
|
30
|
+
'error_code',
|
|
31
|
+
'photoshop_detected',
|
|
32
|
+
'action_plan_enabled',
|
|
33
|
+
'event_source',
|
|
34
|
+
'port',
|
|
35
|
+
'host',
|
|
36
|
+
'no_open',
|
|
37
|
+
'has_auth',
|
|
38
|
+
'has_custom_path',
|
|
39
|
+
'opted_out',
|
|
40
|
+
'opted_in',
|
|
41
|
+
'privacy_mode',
|
|
42
|
+
'analytics_enabled',
|
|
43
|
+
'beta_telemetry_opt_in',
|
|
44
|
+
'theme',
|
|
45
|
+
'launch_method',
|
|
46
|
+
]);
|
|
47
|
+
export function sanitizeAnalyticsProperties(properties) {
|
|
48
|
+
if (!properties)
|
|
49
|
+
return {};
|
|
50
|
+
const out = {};
|
|
51
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
52
|
+
const normalized = key.toLowerCase();
|
|
53
|
+
if (BLOCKED_PROPERTY_KEYS.has(normalized))
|
|
54
|
+
continue;
|
|
55
|
+
if (!ALLOWED_PROPERTY_KEYS.has(normalized))
|
|
56
|
+
continue;
|
|
57
|
+
if (value === null || value === undefined)
|
|
58
|
+
continue;
|
|
59
|
+
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
60
|
+
out[normalized] = value;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
export function buildRuntimeProperties(properties) {
|
|
66
|
+
return {
|
|
67
|
+
os: process.platform,
|
|
68
|
+
arch: process.arch,
|
|
69
|
+
node_version: process.version,
|
|
70
|
+
launch_method: getLaunchMethod(),
|
|
71
|
+
...getServerAnalyticsContext(),
|
|
72
|
+
...sanitizeAnalyticsProperties(properties),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/analytics/events.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAErD,MAAM,qBAAqB,GAAG,IAAI,GAAG,CAAC;IACpC,SAAS;IACT,QAAQ;IACR,KAAK;IACL,OAAO;IACP,QAAQ;IACR,UAAU;IACV,OAAO;IACP,eAAe;IACf,UAAU;IACV,MAAM;IACN,WAAW;IACX,UAAU;IACV,SAAS;IACT,SAAS;IACT,QAAQ;IACR,SAAS;IACT,YAAY;CACb,CAAC,CAAC;AAEH,MAAM,qBAAqB,GAAG,IAAI,GAAG,CAAC;IACpC,aAAa;IACb,IAAI;IACJ,MAAM;IACN,cAAc;IACd,aAAa;IACb,aAAa;IACb,IAAI;IACJ,YAAY;IACZ,oBAAoB;IACpB,qBAAqB;IACrB,cAAc;IACd,MAAM;IACN,MAAM;IACN,SAAS;IACT,UAAU;IACV,iBAAiB;IACjB,WAAW;IACX,UAAU;IACV,cAAc;IACd,mBAAmB;IACnB,uBAAuB;IACvB,OAAO;IACP,eAAe;CAChB,CAAC,CAAC;AAEH,MAAM,UAAU,2BAA2B,CACzC,UAA+C;IAE/C,IAAI,CAAC,UAAU;QAAE,OAAO,EAAE,CAAC;IAE3B,MAAM,GAAG,GAA8C,EAAE,CAAC;IAC1D,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;QACtD,MAAM,UAAU,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;QACrC,IAAI,qBAAqB,CAAC,GAAG,CAAC,UAAU,CAAC;YAAE,SAAS;QACpD,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,UAAU,CAAC;YAAE,SAAS;QACrD,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;YAAE,SAAS;QACpD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,SAAS,EAAE,CAAC;YACzF,GAAG,CAAC,UAAU,CAAC,GAAG,KAAK,CAAC;QAC1B,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,UAAU,sBAAsB,CACpC,UAA+C;IAE/C,OAAO;QACL,EAAE,EAAE,OAAO,CAAC,QAAQ;QACpB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,YAAY,EAAE,OAAO,CAAC,OAAO;QAC7B,aAAa,EAAE,eAAe,EAAE;QAChC,GAAG,yBAAyB,EAAE;QAC9B,GAAG,2BAA2B,CAAC,UAAU,CAAC;KAC3C,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { BetaTelemetryState } from './types.js';
|
|
2
|
+
export declare function getOrCreateDistinctId(): string;
|
|
3
|
+
export declare function isAnalyticsOptedOut(): boolean;
|
|
4
|
+
export declare function setAnalyticsOptOut(optedOut: boolean): void;
|
|
5
|
+
export declare function isAnalyticsEnabled(): boolean;
|
|
6
|
+
export declare function getBetaTelemetryState(): BetaTelemetryState;
|
|
7
|
+
export declare function isBetaTelemetryOptIn(): boolean;
|
|
8
|
+
export declare function setBetaTelemetryChoice(optedIn: boolean): void;
|
|
9
|
+
//# sourceMappingURL=identity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../../src/analytics/identity.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAwFrD,wBAAgB,qBAAqB,IAAI,MAAM,CAsB9C;AAED,wBAAgB,mBAAmB,IAAI,OAAO,CAO7C;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAI1D;AAED,wBAAgB,kBAAkB,IAAI,OAAO,CAE5C;AAqBD,wBAAgB,qBAAqB,IAAI,kBAAkB,CAW1D;AAED,wBAAgB,oBAAoB,IAAI,OAAO,CAE9C;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAS7D"}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { getPhotoshopMcpHomeDir } from '../lib/export-paths.js';
|
|
5
|
+
import { kvGet, kvSet } from '../ui/store/kv.js';
|
|
6
|
+
import { isPostHogDisabledByEnv } from './config.js';
|
|
7
|
+
const DISTINCT_ID_KEY = 'analytics_distinct_id';
|
|
8
|
+
const OPT_OUT_KEY = 'analytics_opt_out';
|
|
9
|
+
const BETA_TELEMETRY_OPT_IN_KEY = 'beta_telemetry_opt_in';
|
|
10
|
+
const BETA_TELEMETRY_PROMPT_ANSWERED_KEY = 'beta_telemetry_prompt_answered';
|
|
11
|
+
const STORE_FILE = 'analytics-store.json';
|
|
12
|
+
let memoryDistinctId = null;
|
|
13
|
+
let dbAvailable = null;
|
|
14
|
+
function getStorePath() {
|
|
15
|
+
return join(getPhotoshopMcpHomeDir(), STORE_FILE);
|
|
16
|
+
}
|
|
17
|
+
function readFileStore() {
|
|
18
|
+
try {
|
|
19
|
+
const raw = readFileSync(getStorePath(), 'utf8');
|
|
20
|
+
return JSON.parse(raw);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return {};
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function writeFileStore(store) {
|
|
27
|
+
const dir = getPhotoshopMcpHomeDir();
|
|
28
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
29
|
+
writeFileSync(getStorePath(), JSON.stringify(store), { mode: 0o600 });
|
|
30
|
+
}
|
|
31
|
+
function canUseKv() {
|
|
32
|
+
if (dbAvailable === false)
|
|
33
|
+
return false;
|
|
34
|
+
try {
|
|
35
|
+
kvGet('__analytics_probe__');
|
|
36
|
+
dbAvailable = true;
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
dbAvailable = false;
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function readDistinctIdFromKv() {
|
|
45
|
+
if (!canUseKv())
|
|
46
|
+
return undefined;
|
|
47
|
+
try {
|
|
48
|
+
return kvGet(DISTINCT_ID_KEY);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
dbAvailable = false;
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function writeDistinctIdToKv(id) {
|
|
56
|
+
if (!canUseKv())
|
|
57
|
+
return;
|
|
58
|
+
try {
|
|
59
|
+
kvSet(DISTINCT_ID_KEY, id);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
dbAvailable = false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function readOptOutFromKv() {
|
|
66
|
+
if (!canUseKv())
|
|
67
|
+
return undefined;
|
|
68
|
+
try {
|
|
69
|
+
return kvGet(OPT_OUT_KEY);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
dbAvailable = false;
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function writeOptOutToKv(optedOut) {
|
|
77
|
+
if (!canUseKv())
|
|
78
|
+
return;
|
|
79
|
+
try {
|
|
80
|
+
kvSet(OPT_OUT_KEY, optedOut);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
dbAvailable = false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
export function getOrCreateDistinctId() {
|
|
87
|
+
if (memoryDistinctId)
|
|
88
|
+
return memoryDistinctId;
|
|
89
|
+
const fromKv = readDistinctIdFromKv();
|
|
90
|
+
if (fromKv) {
|
|
91
|
+
memoryDistinctId = fromKv;
|
|
92
|
+
return fromKv;
|
|
93
|
+
}
|
|
94
|
+
const fromFile = readFileStore().distinctId;
|
|
95
|
+
if (fromFile) {
|
|
96
|
+
memoryDistinctId = fromFile;
|
|
97
|
+
writeDistinctIdToKv(fromFile);
|
|
98
|
+
return fromFile;
|
|
99
|
+
}
|
|
100
|
+
const id = randomUUID();
|
|
101
|
+
memoryDistinctId = id;
|
|
102
|
+
writeDistinctIdToKv(id);
|
|
103
|
+
const store = readFileStore();
|
|
104
|
+
writeFileStore({ ...store, distinctId: id });
|
|
105
|
+
return id;
|
|
106
|
+
}
|
|
107
|
+
export function isAnalyticsOptedOut() {
|
|
108
|
+
if (isPostHogDisabledByEnv())
|
|
109
|
+
return true;
|
|
110
|
+
const fromKv = readOptOutFromKv();
|
|
111
|
+
if (fromKv !== undefined)
|
|
112
|
+
return fromKv;
|
|
113
|
+
return readFileStore().optedOut === true;
|
|
114
|
+
}
|
|
115
|
+
export function setAnalyticsOptOut(optedOut) {
|
|
116
|
+
writeOptOutToKv(optedOut);
|
|
117
|
+
const store = readFileStore();
|
|
118
|
+
writeFileStore({ ...store, optedOut });
|
|
119
|
+
}
|
|
120
|
+
export function isAnalyticsEnabled() {
|
|
121
|
+
return !isAnalyticsOptedOut();
|
|
122
|
+
}
|
|
123
|
+
function readBooleanFromKv(key) {
|
|
124
|
+
if (!canUseKv())
|
|
125
|
+
return undefined;
|
|
126
|
+
try {
|
|
127
|
+
return kvGet(key);
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
dbAvailable = false;
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function writeBooleanToKv(key, value) {
|
|
135
|
+
if (!canUseKv())
|
|
136
|
+
return;
|
|
137
|
+
try {
|
|
138
|
+
kvSet(key, value);
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
dbAvailable = false;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
export function getBetaTelemetryState() {
|
|
145
|
+
const fromKvOptIn = readBooleanFromKv(BETA_TELEMETRY_OPT_IN_KEY);
|
|
146
|
+
const fromKvAnswered = readBooleanFromKv(BETA_TELEMETRY_PROMPT_ANSWERED_KEY);
|
|
147
|
+
const fileStore = readFileStore();
|
|
148
|
+
return {
|
|
149
|
+
betaTelemetryOptIn: fromKvOptIn ?? fileStore.betaTelemetryOptIn ?? false,
|
|
150
|
+
betaTelemetryPromptAnswered: fromKvAnswered ?? fileStore.betaTelemetryPromptAnswered ?? false,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
export function isBetaTelemetryOptIn() {
|
|
154
|
+
return getBetaTelemetryState().betaTelemetryOptIn;
|
|
155
|
+
}
|
|
156
|
+
export function setBetaTelemetryChoice(optedIn) {
|
|
157
|
+
writeBooleanToKv(BETA_TELEMETRY_OPT_IN_KEY, optedIn);
|
|
158
|
+
writeBooleanToKv(BETA_TELEMETRY_PROMPT_ANSWERED_KEY, true);
|
|
159
|
+
const store = readFileStore();
|
|
160
|
+
writeFileStore({
|
|
161
|
+
...store,
|
|
162
|
+
betaTelemetryOptIn: optedIn,
|
|
163
|
+
betaTelemetryPromptAnswered: true,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
//# sourceMappingURL=identity.js.map
|