diff --git a/.cursor-plugin/plugin.json b/.cursor-plugin/plugin.json
index 0e3e1b2..881857a 100644
--- a/.cursor-plugin/plugin.json
+++ b/.cursor-plugin/plugin.json
@@ -1,11 +1,12 @@
{
"name": "1password",
- "version": "1.1.0",
- "description": "1Password plugin for Cursor — securely manage development secrets.",
+ "displayName": "1Password",
+ "version": "1.2.0",
+ "description": "1Password Developer Environments for Cursor: MCP tools to create, import, and manage project secrets; an agent skill with the full import-and-mount workflow; and a hook that validates local .env mounts before shell commands run. Requires 1Password desktop with Labs MCP Server enabled. Local .env mounts: macOS/Linux only.",
"author": {
"name": "1Password"
},
- "homepage": "https://1password.com",
+ "homepage": "https://www.1password.dev/",
"repository": "https://github.com/1Password/cursor-plugin",
"license": "MIT",
"keywords": [
@@ -16,8 +17,12 @@
"env",
"dotenv",
"hooks",
- "validation"
+ "validation",
+ "mcp",
+ "developer-environments"
],
- "logo": "assets/logo.svg",
- "hooks": "hooks/hooks.json"
+ "logo": "assets/icon.svg",
+ "hooks": "./hooks/hooks.json",
+ "skills": "./skills/",
+ "mcpServers": "./mcp.json"
}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..95ed20e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,16 @@
+.DS_Store
+
+# Environment files (keep templates committable)
+.env
+.env.*
+!.env.example
+!.env.sample
+!.env.template
+!.env.dist
+
+# Python
+__pycache__/
+*.py[cod]
+
+# Local dev hook wiring (use when team policy blocks local plugin imports)
+.cursor/hooks.json
diff --git a/README.md b/README.md
index ffb5555..152c611 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,21 @@
# 1Password Plugin for Cursor
-The official [1Password](https://1password.com) plugin for [Cursor](https://cursor.com). It brings 1Password's secret management capabilities directly into your editor, helping you develop securely without leaving your workflow.
+The official [1Password](https://1password.com) plugin for [Cursor](https://cursor.com). It ships three pieces that work together: **hooks** that validate locally mounted `.env` files, an **agent skill** with the complete Developer Environment workflow, and **MCP configuration** for the 1Password desktop app server. Secret values stay in 1Password — the agent sees variable names and mount paths, not secret contents.
+
+Install the **plugin** (not a hand-configured MCP entry alone). The bundled `1password-environments` skill is the authoritative agent workflow; the MCP server's built-in documentation resources cover tool basics only and omit import-and-mount steps.
For more on 1Password's developer tools, see the [1Password Developer Documentation](https://developer.1password.com).
## Requirements
- [1Password](https://1password.com) subscription
-- [1Password for Mac or Linux](https://1password.com/downloads)
+- [1Password for Mac](https://1password.com/downloads) (this plugin targets **macOS**)
- [Cursor](https://cursor.com)
-- [sqlite3](https://www.sqlite.org/) installed and available in your `PATH` (pre-installed on macOS; install via your package manager on Linux)
+
+Additional requirements by feature:
+
+- **Hooks** — [sqlite3](https://www.sqlite.org/) installed and available in your `PATH` (pre-installed on macOS; install via your package manager on Linux)
+- **MCP** — 1Password Labs **MCP Server** experiment enabled in the desktop app (`onepassword://settings/labs`). If the setting is missing, your account may not have the `ai-local-mcp-server` feature flag.
> **Note:** 1Password Environments local `.env` mounts only apply on **macOS and Linux**. **`hooks.json`** invokes **`./scripts/validate-mounted-env-files`** with no extension. On **macOS / Linux**, that runs the **Bash** script. On **Windows** the shell looks for a real file by trying suffixes from **`PATHEXT`** until one matches on disk. That yields **`validate-mounted-env-files.cmd`**, which returns **`allow`** and skips validation so agent shells are not blocked.
@@ -24,7 +30,7 @@ Before using this plugin, you'll need to configure your secrets in 1Password:
### Step 2: Install the plugin
-Install from the [Cursor Marketplace](https://cursor.com/marketplace):
+Install from the [Cursor Marketplace](https://cursor.com/marketplace). This registers hooks, the `1password-environments` agent skill, and `mcp.json` together. Do not add the 1Password MCP server manually in user settings instead of installing the plugin — agents will get MCP tools without the skill workflow.
1. Open **Cursor Settings** > **Plugins**.
2. Search for **1password**.
@@ -32,6 +38,24 @@ Install from the [Cursor Marketplace](https://cursor.com/marketplace):
Or use the command palette: `Ctrl+Shift+P` (or `Cmd+Shift+P` on macOS) > **Plugins: Install Plugin** > search for `1password`.
+Or install directly:
+
+```
+/add-plugin 1password
+```
+
+### Step 3: Enable MCP in 1Password (required for Environment management)
+
+Enable the **MCP Server** experiment in the 1Password desktop app: open **Settings → Labs** (or use `onepassword://settings/labs`) and turn on **MCP Server**. The plugin's `mcp.json` connects Cursor to that server after this step.
+
+The MCP server binary on macOS:
+
+```text
+/Applications/1Password.app/Contents/MacOS/onepassword-mcp
+```
+
+On Linux, see the [1Password MCP server documentation](https://www.1password.dev/environments/mcp-server) for the binary path on your platform.
+
## Features
### Hooks
@@ -40,7 +64,7 @@ Or use the command palette: `Ctrl+Shift+P` (or `Cmd+Shift+P` on macOS) > **Plugi
Validates locally mounted `.env` files from [1Password Environments](https://developer.1password.com/docs/environments) before any shell command executes. When required environment files are missing, disabled, or misconfigured, the hook blocks execution and surfaces actionable error messages so the Cursor Agent can guide you to a fix.
-This hook was originally developed in the [1Password Cursor Hooks](https://github.com/1Password/cursor-hooks) repository. For the full setup guide, see [Validate local `.env` files with Cursor Agent](https://developer.1password.com/docs/environments/cursor-hook-validate/).
+This hook was originally developed in the [1Password Agent Hooks](https://github.com/1Password/agent-hooks) repository. For the full setup guide, see [Validate local `.env` files with Cursor Agent](https://developer.1password.com/docs/environments/cursor-hook-validate/).
**How it works:**
@@ -122,26 +146,66 @@ DEBUG=1 echo '{"command": "echo test", "workspace_roots": ["/path/to/your/projec
When not running in debug mode, the hook writes logs to `/tmp/1password-cursor-hooks.log`. Log entries include timestamps and details about 1Password queries, validation results, and permission decisions.
+### MCP and agent skill
+
+The plugin connects Cursor to the local 1Password MCP server and bundles the **`1password-environments`** skill (`skills/1password-environments/SKILL.md`). Agents should read that skill before calling MCP tools — it defines the complete workflow for importing a plain `.env` file, appending variables, and mounting at the source path. The MCP server's built-in docs cover tool basics but omit those import-and-mount steps.
+
+See `skills/1password-environments/reference.md` for setup, mount conflicts, and shell validation details.
+
+#### Example prompts
+
+- "List my 1Password Environments"
+- "Mount my staging Environment as `.env` in this repo"
+- "What variables are in my production Environment?"
+- "Create a new Environment called `my-app-dev`"
+- "Create an Environment from my project `.env` file"
+- "Import `.env` into 1Password and mount it here"
+- "Add a placeholder for my OpenAI API key"
+
+#### MCP tools
+
+| Tool | Description |
+|------|-------------|
+| `authenticate` | Authenticate with the 1Password desktop app; returns `accountId` |
+| `list_environments` | List Developer Environments for an account |
+| `create_environment` | Create a new Developer Environment |
+| `rename_environment` | Rename an existing Developer Environment |
+| `list_variables` | List variable names in an Environment (no values) |
+| `append_variables` | Add or update Environment variables |
+| `create_local_env_file` | Mount an Environment as a local `.env` file |
+| `list_local_env_files` | List existing local `.env` mounts for an Environment |
+
+Confirm the MCP server is connected in **Cursor Settings → MCP** after installing the plugin and enabling the Labs experiment in 1Password.
+
## Plugin Structure
```
-1password/
+cursor-plugin/
├── .cursor-plugin/
│ └── plugin.json # Plugin manifest
├── hooks/
-│ └── hooks.json # Hook event configuration
+│ └── hooks.json # beforeShellExecution mount validation
+├── skills/
+│ └── 1password-environments/
+│ ├── SKILL.md # Agent skill for MCP workflows
+│ └── reference.md # Setup, mount conflict, and troubleshooting
+├── mcp.json # MCP server configuration
├── assets/
-│ └── logo.svg # Plugin logo
+│ ├── logo.svg # Plugin logo
+│ └── icon.svg
├── scripts/
+│ ├── lib/
+│ │ └── telemetry.sh # Opt-in telemetry helpers for the validation hook
│ ├── validate-mounted-env-files # Bash hook (macOS / Linux)
│ └── validate-mounted-env-files.cmd # Windows cmd wrapper returns allow (validation skipped)
├── LICENSE
└── README.md
```
+
## Telemetry
-The plugin emits **opt-in** telemetry so 1Password can understand plugin adoption and the prevalence of common failure modes (missing files, disabled mounts). Two event types are emitted:
+The validation hook emits **opt-in** telemetry so 1Password can understand plugin adoption and the prevalence of common failure modes (missing files, disabled mounts). Two event types are emitted:
- `agent_hook_execution` — fired once per hook invocation; carries the hook name, plugin version, client (`cursor`), bucketed duration, decision (`allow`/`deny`), reason for deny, validation mode (`default`/`configured`), and a count of mounts checked.
- `agent_hook_install` — fired once per `(hook_name, plugin_version)` on the first hook run after installation or upgrade; `install_method` is `plugin_marketplace`.
@@ -159,6 +223,7 @@ The plugin emits **opt-in** telemetry so 1Password can understand plugin adoptio
## Resources
- [Validate local `.env` files with Cursor Agent](https://developer.1password.com/docs/environments/cursor-hook-validate/) — full setup guide on the 1Password Developer site
+- [1Password MCP server documentation](https://www.1password.dev/environments/mcp-server)
- [1Password Agent Hooks](https://github.com/1Password/agent-hooks) — the original hooks repository this plugin is based on
- [1Password Environments](https://developer.1password.com/docs/environments) — documentation for 1Password's environment and secrets management
- [1Password Local `.env` Files](https://developer.1password.com/docs/environments/local-env-file) — how local `.env` file mounting works
diff --git a/assets/icon.svg b/assets/icon.svg
new file mode 100644
index 0000000..eda328f
--- /dev/null
+++ b/assets/icon.svg
@@ -0,0 +1,23 @@
+
+
+
diff --git a/assets/logo-dark.png b/assets/logo-dark.png
new file mode 100644
index 0000000..9f83808
Binary files /dev/null and b/assets/logo-dark.png differ
diff --git a/assets/logo-light.png b/assets/logo-light.png
new file mode 100644
index 0000000..d167b26
Binary files /dev/null and b/assets/logo-light.png differ
diff --git a/mcp.json b/mcp.json
new file mode 100644
index 0000000..c79ee39
--- /dev/null
+++ b/mcp.json
@@ -0,0 +1,8 @@
+{
+ "mcpServers": {
+ "1password": {
+ "command": "/Applications/1Password.app/Contents/MacOS/onepassword-mcp",
+ "args": []
+ }
+ }
+}
diff --git a/scripts/lib/telemetry.sh b/scripts/lib/telemetry.sh
index 8d64d21..af68d9a 100644
--- a/scripts/lib/telemetry.sh
+++ b/scripts/lib/telemetry.sh
@@ -59,16 +59,6 @@ get_telemetry_dir() {
echo "${HOME}/.config/1Password/data/hook-events"
}
-# Emit the `,"ts":` JSON fragment stamping when the event was
-# produced, or an empty string if the clock is unreadable.
-ts_json_fragment() {
- local ts
- ts=$(current_time_ms)
- if [[ "$ts" =~ ^[0-9]+$ ]]; then
- printf ',"ts":%s' "$ts"
- fi
-}
-
# Check whether the 1Password app has signaled that telemetry is enabled.
# Returns 0 (true) if the signal file exists, 1 (false) otherwise.
telemetry_consent_enabled() {
@@ -76,7 +66,7 @@ telemetry_consent_enabled() {
}
# Append a single JSON line to the events.jsonl file.
-# Checks consent and enforces a 1MB file size cap (drop-newest).
+# Checks consent and enforces a 1MB file size cap.
write_telemetry_event() {
local json_line="$1"
local event_dir
@@ -152,11 +142,8 @@ write_execution_event() {
local duration_bucket
duration_bucket=$(bucket_duration_ms "$duration_ms")
- local ts_field
- ts_field=$(ts_json_fragment)
-
local json_line
- json_line="{\"schema\":\"agent_hook_execution\",\"hook_name\":\"${escaped_hook_name}\",\"hook_version\":\"${escaped_hook_version}\",\"client\":\"${escaped_client}\",\"event_type\":\"${escaped_event_type}\",\"decision\":\"${decision}\",\"deny_reason\":${deny_reason_json},\"duration_bucket\":\"${duration_bucket}\",\"mode\":${mode_json},\"mount_count\":${mount_count_json}${ts_field}}"
+ json_line="{\"schema\":\"agent_hook_execution\",\"hook_name\":\"${escaped_hook_name}\",\"hook_version\":\"${escaped_hook_version}\",\"client\":\"${escaped_client}\",\"event_type\":\"${escaped_event_type}\",\"decision\":\"${decision}\",\"deny_reason\":${deny_reason_json},\"duration_bucket\":\"${duration_bucket}\",\"mode\":${mode_json},\"mount_count\":${mount_count_json}}"
write_telemetry_event "$json_line"
}
@@ -173,11 +160,8 @@ write_install_event() {
escaped_hook_name=$(escape_json_string "$hook_name")
escaped_hook_version=$(escape_json_string "$hook_version")
- local ts_field
- ts_field=$(ts_json_fragment)
-
local json_line
- json_line="{\"schema\":\"agent_hook_install\",\"client\":\"${escaped_client}\",\"hook_name\":\"${escaped_hook_name}\",\"hook_version\":\"${escaped_hook_version}\",\"install_method\":\"${install_method}\"${ts_field}}"
+ json_line="{\"schema\":\"agent_hook_install\",\"client\":\"${escaped_client}\",\"hook_name\":\"${escaped_hook_name}\",\"hook_version\":\"${escaped_hook_version}\",\"install_method\":\"${install_method}\"}"
write_telemetry_event "$json_line"
}
diff --git a/skills/1password-environments/SKILL.md b/skills/1password-environments/SKILL.md
new file mode 100644
index 0000000..30ff7ff
--- /dev/null
+++ b/skills/1password-environments/SKILL.md
@@ -0,0 +1,134 @@
+---
+name: 1password-environments
+description: >-
+ Manage 1Password Developer Environments via the bundled MCP server. Use when
+ creating, importing, or mounting .env files; listing Environment variable names;
+ adding or updating Environment variables; renaming environments; or calling any
+ 1Password MCP tool. Import-from-.env always includes create_local_env_file at
+ the source .env path unless the user explicitly opts out of mounting.
+---
+
+# 1Password Environments
+
+Workflow for the 1Password MCP tools bundled with this plugin. Read this skill
+before calling MCP tools — the MCP server's built-in docs cover tool basics but
+omit import-and-mount steps.
+
+## Prerequisites
+
+- 1Password desktop app with the **MCP Server** Labs experiment enabled (`onepassword://settings/labs`)
+- macOS or Linux for local `.env` mounts (`create_local_env_file`)
+- Plugin installed (registers MCP config, this skill, and mount validation hooks together)
+
+Setup details: [reference.md](reference.md)
+
+## Not done until
+
+**Import / create from `.env`** (including "using values from the project `.env`"):
+
+- [ ] Environment created or resolved
+- [ ] Variables appended via `append_variables`
+- [ ] `create_local_env_file` at the **source** `.env` absolute path — **always**
+- [ ] Mount verified with `list_local_env_files`
+
+Mounting at the source `.env` path is mandatory, not optional follow-up. The
+**only** exception is the user explicitly opting out ("without mounting", "do not
+mount", "skip the mount"). Never ask "want me to mount?" — just mount.
+
+Stopping after `create_environment` + `append_variables` is **incomplete**.
+`list_variables` is not mount verification. Do not report success until the mount
+checklist is done.
+
+**Mount only:** mount exists at the requested path (`list_local_env_files`).
+
+## Do not
+
+- Skip `create_local_env_file` on import unless the user explicitly opted out
+- Report success before the import checklist (including mount) is complete
+- Offer mounting as optional follow-up — it is mandatory on import
+- Call `create_environment` when `list_environments` already shows that name — ask the user first (see **Duplicate environment name**)
+- Reveal secret values in chat
+- Read a mounted `.env` path — once mounted, the path is a live FIFO (named pipe); use `list_variables` instead
+
+## MCP tools
+
+Discover tool schemas with `GetMcpTools` before calling. Request params are
+**camelCase**; responses may use snake_case. Get `accountId` from
+`authenticate`; get `environmentId` from `create_environment` /
+`list_environments`. Pass **every** required param — omitting one fails with
+`missing field ''`.
+
+| Tool | Required params | Returns / notes |
+| ----------------------- | ----------------------------------------------------------------------- | -------------------------------------------------------- |
+| `authenticate` | _(none)_ | `accountId`. First call each session. |
+| `list_environments` | `accountId` | Environments to resolve by name. |
+| `create_environment` | `accountId`, `environmentName` | `environmentId`. Import continues below. |
+| `rename_environment` | `accountId`, `environmentId`, `environmentName` | `environmentName` is the **new** name. |
+| `list_variables` | `accountId`, `environmentId` | Names only — never values. |
+| `append_variables` | `accountId`, `environmentId`, `variables: [{ name, value, concealed }]` | `variables` is an **array**; secrets `concealed: true`. |
+| `list_local_env_files` | `accountId`, `environmentId` | Existing mounts. |
+| `create_local_env_file` | `accountId`, `environmentId`, `environmentName`, `mountPath` | `mountPath` = absolute source `.env` path (macOS/Linux). |
+
+If MCP calls fail with authentication errors, call `mcp_auth` for the
+`plugin-1password-1password` server, then retry.
+
+## Duplicate environment name
+
+Before **`create_environment`**, call **`list_environments`** and check whether the
+target `environmentName` already exists.
+
+If it does, **stop and ask the user** how they want to proceed. Offer options such as:
+
+- **Use the existing environment** — skip `create_environment`; use its `environmentId` for later steps (`append_variables`, mount, etc.)
+- **Use a different name** — wait for a new name from the user, then `create_environment`
+- **Cancel** — do not create or modify anything
+
+Do not silently choose one of these paths. Do not call `create_environment` with a
+name that already exists unless the user has explicitly chosen a different name.
+
+## Import from a `.env` file
+
+Default path: `{workspace_root}/.env` unless the user names another path.
+
+1. **Read** the `.env` file with the Read tool to get its keys and values. Strip optional surrounding quotes from values. Pass values to MCP only — never paste secret values into chat.
+2. **`authenticate`** → `accountId`
+3. **`list_environments`** — if the target name already exists, follow **Duplicate environment name** and wait for the user's choice. Otherwise **`create_environment`** (new name) or resolve the existing environment per the user's choice.
+4. **`append_variables`** with all variables (secrets `concealed: true`)
+5. **Mount** — **always** (skip only if the user explicitly said not to mount):
+ - `list_local_env_files` — skip `create_local_env_file` only if a mount already exists at the source path
+ - `create_local_env_file` with `accountId`, `environmentId`, `environmentName`, `mountPath` (absolute path of the original `.env`)
+ - `list_local_env_files` again to verify
+
+If shell commands are blocked because 1Password expects a mount at the path, see
+[reference.md](reference.md) (mount conflict).
+
+## Other flows
+
+**Create new Environment:** authenticate → `list_environments` → if the name exists, follow **Duplicate environment name** → `create_environment` only when the name is available or the user chose a different name.
+
+**Mount existing Environment:** authenticate → resolve Environment → step 5 above.
+
+**Inspect names:** authenticate → resolve → `list_variables` → summarize names only.
+
+**Rename:** authenticate → resolve → confirm name → `rename_environment`.
+
+**Add/update variables:** authenticate → resolve → `list_variables` → `append_variables`.
+
+## Safety
+
+- Never reveal secret values in chat
+- Store secrets with `concealed: true`
+- Ask before changing variables unless the request is explicit
+
+## Plugin hook
+
+The plugin runs `validate-mounted-env-files` on `beforeShellExecution`. It blocks
+shell commands when 1Password expects a mount that is missing, disabled, or not a
+FIFO (for example a plain `.env` still on disk at the mount path). Reading the
+`.env` with the Read tool is unaffected.
+
+Validation modes and recovery steps: [reference.md](reference.md)
+
+## Troubleshooting
+
+Mount conflict, validation modes, setup: [reference.md](reference.md)
diff --git a/skills/1password-environments/reference.md b/skills/1password-environments/reference.md
new file mode 100644
index 0000000..1481c73
--- /dev/null
+++ b/skills/1password-environments/reference.md
@@ -0,0 +1,91 @@
+# 1Password Environments — reference
+
+Supplement to [SKILL.md](SKILL.md). Use for setup, mount conflicts, and shell
+validation behavior.
+
+## Plugin components
+
+This plugin ships:
+
+| Component | Path | Purpose |
+|-----------|------|---------|
+| MCP config | `mcp.json` | Connects Cursor to the 1Password desktop MCP server |
+| Agent skill | `skills/1password-environments/SKILL.md` | Import, mount, and Environment management workflow |
+| Mount validation hook | `hooks/hooks.json` → `scripts/validate-mounted-env-files` | Blocks shell when mounts are missing or misconfigured |
+
+Install the plugin from the Cursor marketplace — do not add the MCP server
+manually in user settings without the bundled skill and hooks.
+
+## Setup
+
+**Requirements**
+
+- macOS or Linux with the 1Password desktop app installed (local `.env` mounts are macOS/Linux only; on Windows the validation hook is a no-op)
+- **1Password Cursor plugin installed** (marketplace or local symlink) so this skill, hooks, and MCP config load together
+- 1Password Labs **MCP Server** experiment enabled in the desktop app (`onepassword://settings/labs`)
+- Access to a 1Password account with Developer Environments enabled
+- `sqlite3` in `PATH` for mount validation (pre-installed on macOS)
+
+The MCP server binary on macOS:
+
+```text
+/Applications/1Password.app/Contents/MacOS/onepassword-mcp
+```
+
+On Linux, see the [1Password MCP server documentation](https://www.1password.dev/environments/mcp-server) for the binary path on your platform.
+
+**When things fail**
+
+- Authentication or environment access fails — the 1Password desktop app may need approval, unlocking, or account access
+- MCP server unavailable — enable the **1Password Labs MCP Server** experiment via `onepassword://settings/labs`. If the Labs setting is missing, the account may not have the required `ai-local-mcp-server` feature flag
+- `create_local_env_file` fails — confirm the user is on macOS or Linux
+- Shell commands denied while 1Password expects a mount that is missing or disabled — see **Mount conflict** below
+
+## Mount validation hook
+
+`scripts/validate-mounted-env-files` runs on `beforeShellExecution`. It blocks
+**all** shell commands when 1Password expects a mount at a path that is missing,
+disabled, or not a FIFO (for example a plain `.env` still on disk at the mount
+path). Reading the `.env` with the Read tool is unaffected — only shell commands
+are gated.
+
+The hook **fails open**: if 1Password is not installed, the database is
+unavailable, or `sqlite3` is missing, shell commands proceed normally.
+
+### Validation modes
+
+Scope depends on `.1password/environments.toml` at the project root:
+
+| Configuration | Behavior |
+|---------------|----------|
+| No TOML file (or no `mount_paths` field) | **Default mode** — all 1Password mount destinations for this workspace are validated |
+| `mount_paths = [".env", ...]` | **Configured mode** — only listed paths are validated |
+| `mount_paths = []` | Validation disabled for this repo; all shell commands allowed |
+
+Example configured mode:
+
+```toml
+mount_paths = [".env", "billing.env"]
+```
+
+For each path, the hook checks that the file exists, is a FIFO (named pipe), and
+is enabled in 1Password.
+
+### Mount conflict
+
+If shell commands are blocked with a message about missing, invalid, or disabled
+environment files:
+
+1. Check whether 1Password already has a destination for the same path (`list_local_env_files`, or the 1Password app Destinations tab)
+2. Resolve by one of:
+ - Temporarily set `mount_paths = []` in `.1password/environments.toml` to disable mount validation for this repo
+ - Fix the mount in 1Password (enable the destination, or remove it until migration finishes)
+
+The import itself does not need shell — Read the `.env` directly to get its keys
+and values, then use MCP tools per [SKILL.md](SKILL.md).
+
+### Debugging the hook
+
+- **Cursor Settings → Hooks → Execution Log** — look for `beforeShellExecution` entries tied to `validate-mounted-env-files`
+- **Debug run:** `DEBUG=1 echo '{"command": "echo test", "workspace_roots": ["/path/to/project"]}' | ./scripts/validate-mounted-env-files`
+- **Log file:** `/tmp/1password-cursor-hooks.log` (when not in debug mode)