commit 206c2c3f46ed4c56445d22c4f9202be680006c7f Author: Zhongwei Li Date: Sat Nov 29 18:18:39 2025 +0800 Initial commit diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..e885045 --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,12 @@ +{ + "name": "local-env-file-management", + "description": "Tools to populate local .env files from 1password secrets.", + "version": "0.0.0-2025.11.28", + "author": { + "name": "Dennis Somerville", + "email": "densom@users.noreply.github.com" + }, + "commands": [ + "./commands" + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..d297c30 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# local-env-file-management + +Tools to populate local .env files from 1password secrets. diff --git a/commands/env-add.md b/commands/env-add.md new file mode 100644 index 0000000..332a1c0 --- /dev/null +++ b/commands/env-add.md @@ -0,0 +1,102 @@ +--- +description: Add a new environment variable to .env.1password template and optionally to 1Password +argument-hint: [op-reference-or-value] +--- + +You are tasked with adding a new environment variable to the `.env.1password` template file and optionally creating or updating the corresponding secret in 1Password. + +## Instructions: + +1. **Parse arguments**: + - First argument (required): The variable name (e.g., `API_KEY`) + - Second argument (optional): Either: + - A 1Password reference like `op://Private/myapp/api_key` + - A value to be stored in 1Password + - If not provided, ask the user for input + +2. **Check for .env.1password**: Look for the template file in the current working directory. + - If it doesn't exist, ask if the user wants to create it (and offer to run `/claude-1password:env-init` if a `.env` exists) + +3. **Check if variable already exists**: + - If the variable name already exists in `.env.1password`, ask if they want to update it + - Show the current value/reference before updating + +4. **Determine the 1Password reference**: + + **Option A**: If the second argument looks like a 1Password reference (`op://...`): + - Use it as-is + - Verify it's valid by trying `op read "reference"` (optional, warn if it fails) + + **Option B**: If the second argument is a value (not starting with `op://`): + - Ask the user for the 1Password item details: + - Vault name (default: "Private") + - Item name (suggest based on project or existing pattern) + - Field name (default: variable name in lowercase) + - Offer to create the item/field in 1Password using the CLI: + ```bash + op item create --category=password --title="item-name" \ + --vault="vault-name" field-name="value" + ``` + Or if the item exists, update it: + ```bash + op item edit "item-name" --vault="vault-name" field-name="value" + ``` + - Construct the reference: `op://vault/item/field` + + **Option C**: If no second argument provided: + - Ask the user whether they want to: + 1. Provide an existing 1Password reference + 2. Provide a value to store in 1Password + 3. Leave it as a placeholder for manual editing later + +5. **Update .env.1password**: + - Add the new variable in the format: `VAR_NAME=op://vault/item/field` + - Optionally add a comment above it if the user wants to document it + - Maintain alphabetical order or append to the end (ask user preference) + +6. **Optionally update .env**: + - Ask if they want to immediately sync to `.env` by running `/claude-1password:env-sync` + - Or just update `.env.1password` for now + +7. **Output summary**: Show the user: + - The variable that was added: `VAR_NAME=op://...` + - Whether it was also created/updated in 1Password + - Next steps (e.g., "Run `/claude-1password:env-sync` to update your `.env` file") + +## Examples: + +### Example 1: Add with existing 1Password reference +``` +/claude-1password:env-add NEW_API_KEY op://Private/myapp/new_api_key +``` + +Result in `.env.1password`: +``` +NEW_API_KEY=op://Private/myapp/new_api_key +``` + +### Example 2: Add with a new value +``` +/claude-1password:env-add STRIPE_KEY sk_test_abc123 +``` + +You would then: +1. Ask for vault/item/field details (or use smart defaults) +2. Create/update in 1Password +3. Add to `.env.1password` with the generated reference + +### Example 3: Add placeholder for manual editing +``` +/claude-1password:env-add DATABASE_PASSWORD +``` + +You would prompt for what to do, and potentially add: +``` +DATABASE_PASSWORD=op://Private/myapp/database_password +``` + +## Error Handling: + +- If 1Password CLI is not installed or not authenticated, warn the user but still allow adding to `.env.1password` as a placeholder +- If variable name is invalid (contains spaces, special chars except `_`), suggest a corrected name +- If 1Password operation fails, show the error but still update the template file diff --git a/commands/env-init.md b/commands/env-init.md new file mode 100644 index 0000000..465cef0 --- /dev/null +++ b/commands/env-init.md @@ -0,0 +1,65 @@ +--- +description: Initialize a .env.1password template from an existing .env file +argument-hint: [path-to-env-file] +--- + +You are tasked with creating a `.env.1password` template file from an existing `.env` file. + +## Instructions: + +1. **Read the .env file**: If the user provided a path argument, use that path. Otherwise, look for `.env` in the current working directory. + +2. **Parse the file**: Extract all environment variable key-value pairs. Ignore: + - Empty lines + - Lines starting with `#` (comments) + - Malformed lines without `=` + +3. **Generate template**: For each variable, create a line in the format: + ``` + VAR_NAME=op://vault-name/item-name/field-name + ``` + + You should: + - Keep the variable name exactly as it appears + - For the 1Password reference, suggest a sensible structure: + - `vault-name`: Default to "Private" or ask the user + - `item-name`: Use a descriptive name based on the project or variable context + - `field-name`: Use the variable name in lowercase or the actual field name in 1Password + + Example transformation: + ``` + DATABASE_URL=postgresql://localhost:5432/mydb + API_KEY=abc123secret + ``` + + Becomes: + ``` + DATABASE_URL=op://Private/project-secrets/database_url + API_KEY=op://Private/project-secrets/api_key + ``` + +4. **Add security header**: Prepend or update the security comment to the top of the generated `.env.1password` file: + ``` + # This file was auto-generated from .env.1password + # DO NOT commit this file to version control + # Generated on: [current date/time] + + ``` + (Note the blank line after the header) + + If the `.env.1password` template file already exists, check for the security header and add it if it does not exist. + +5. **Preserve comments**: Keep any comment lines from the original file to maintain documentation. + +6. **Write the template**: Save the result as `.env.1password` in the same directory as the source `.env` file. + +7. **Output summary**: Show the user: + - How many variables were converted + - The path to the new `.env.1password` file + - A reminder to update the 1Password references to match their actual vault structure + - Instructions: "Edit `.env.1password` to update the `op://` references to match your 1Password vault, item, and field names. Then run `/claude-1password:env-sync` to generate your `.env` file." + +## Error Handling: + +- If the `.env` file doesn't exist, inform the user and ask if they want to create a new empty template +- If `.env.1password` already exists, ask if they want to overwrite it diff --git a/commands/env-sync.md b/commands/env-sync.md new file mode 100644 index 0000000..c7d861f --- /dev/null +++ b/commands/env-sync.md @@ -0,0 +1,91 @@ +--- +description: Populate .env file from .env.1password template using 1Password CLI +argument-hint: [path-to-template] +--- + +You are tasked with generating a `.env` file by resolving 1Password secret references from a `.env.1password` template file. + +## Prerequisites Check: + +1. **Verify 1Password CLI is installed**: Run `op --version` to check if the 1Password CLI is available. + - If not installed, inform the user to install it from https://developer.1password.com/docs/cli/get-started/ + +2. **Verify 1Password CLI is authenticated**: Run `op account list` to check if the user is signed in. + - If not authenticated, inform the user to run `op signin` first + +## Instructions: + +1. **Locate the template file**: If the user provided a path argument, use that path. Otherwise, look for `.env.1password` in the current working directory. + - If `.env.1password` doesn't exist, inform the user and suggest running `/claude-1password:env-init` first + +2. **Check if .env already exists**: + - If `.env` already exists, ask the user if they want to overwrite it + - Consider backing up the existing `.env` as `.env.backup` before overwriting + +3. **Use op inject command**: Use the 1Password CLI `op inject` command to resolve all secret references in the template file and generate the `.env` file. + + The basic command syntax is: + ```bash + op inject -i .env.1password -o .env + ``` + + The `op inject` command will: + - Automatically find all `op://vault/item/field` references in the template + - Resolve them by fetching the secrets from 1Password + - Replace the references with the actual secret values + - Preserve all comments, empty lines, and non-secret values exactly as they appear + - Handle multiple references on a single line + - Provide clear error messages if any references are invalid or inaccessible + +If the user opted to overwrite the existing file, make sure to use the `--force` flag in the op command. + +4. **Output summary**: Show the user: + - Success message: "Successfully generated .env from .env.1password" + - The path to the generated `.env` file + - Number of secret references that were resolved (count `op://` occurrences in the template) + - A reminder to add `.env` to `.gitignore` if not already present + - Security reminder: "Remember to delete this file when no longer needed" + +## Example: + +Template (`.env.1password`): +``` +# Database configuration +DATABASE_URL=op://Private/myapp/database_url +API_KEY=op://Private/myapp/api_key + +# Non-secret values can be stored directly +NODE_ENV=development +``` + +Command executed: +```bash +op inject -i .env.1password -o .env +``` + +Generated (`.env`): +``` +# Database configuration +DATABASE_URL=postgresql://user:pass@localhost:5432/db +API_KEY=sk_live_abc123xyz789 + +# Non-secret values can be stored directly +NODE_ENV=development +``` + +## Additional Options: + +- **Force overwrite**: If you want to skip the overwrite confirmation, you can add the `--force` flag: `op inject -i .env.1password -o .env --force` +- **In-memory only**: To output to stdout without writing a file (useful for verification): `op inject -i .env.1password` +- **Different paths**: You can specify any template and output paths: `op inject -i path/to/template -o path/to/.env` + +## Error Handling: + +- If `.env.1password` doesn't exist, inform the user and suggest running `/claude-1password:env-init` first +- If `.env` already exists, ask if they want to overwrite it +- If `op inject` fails, show the specific error message from the CLI and suggest solutions: + - **"not signed in"**: User needs to run `op signin` + - **"item not found"** or **"vault not found"**: Check that the `op://` references in `.env.1password` match actual items in 1Password + - **"field not found"**: Verify the field name exists in the 1Password item + - For any errors, show which reference(s) caused the problem to help the user debug +- If the command succeeds but the output seems incorrect, suggest the user manually verify their 1Password references diff --git a/plugin.lock.json b/plugin.lock.json new file mode 100644 index 0000000..681bc3e --- /dev/null +++ b/plugin.lock.json @@ -0,0 +1,53 @@ +{ + "$schema": "internal://schemas/plugin.lock.v1.json", + "pluginId": "gh:densom/claude-1password:plugins/1password", + "normalized": { + "repo": null, + "ref": "refs/tags/v20251128.0", + "commit": "f30e98bedfec7f21e3aefd73b36cae33f1efa34d", + "treeHash": "b3f64f25abd79765d39a63b4f7b3bcfd8c13e8d1f8196cf4cac5042a477e4d02", + "generatedAt": "2025-11-28T10:16:18.123744Z", + "toolVersion": "publish_plugins.py@0.2.0" + }, + "origin": { + "remote": "git@github.com:zhongweili/42plugin-data.git", + "branch": "master", + "commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390", + "repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data" + }, + "manifest": { + "name": "local-env-file-management", + "description": "Tools to populate local .env files from 1password secrets.", + "version": null + }, + "content": { + "files": [ + { + "path": "README.md", + "sha256": "de26a26814ddfa74a1f75d812e19efd6ddfac6b26c2f1eafd6c81c6c4c2749a3" + }, + { + "path": ".claude-plugin/plugin.json", + "sha256": "5c355ae73cb06b029b17b19b7fc3fafb80261104aeef62ad6003bf973c03352a" + }, + { + "path": "commands/env-init.md", + "sha256": "b0492607d02ecbf36feae389e28e488ba02be3e0fecd9d35fc202d11f0d37e3b" + }, + { + "path": "commands/env-add.md", + "sha256": "25a1495bc79786b462f1934f5ed35f9e76c085c41797f5e7f768b5f73348214e" + }, + { + "path": "commands/env-sync.md", + "sha256": "19bd685da4369ec90cf9fbc5ffab09228f91c1a5e654fa5261db0fd34c1a953e" + } + ], + "dirSha256": "b3f64f25abd79765d39a63b4f7b3bcfd8c13e8d1f8196cf4cac5042a477e4d02" + }, + "security": { + "scannedAt": null, + "scannerVersion": null, + "flags": [] + } +} \ No newline at end of file