diff --git a/.gitignore b/.gitignore index a0a03556..ab43e056 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .DS_Store +.env .cache/ # Claude Code runtime state (ignored by default) .claude/* diff --git a/plugins/docs-tools/.claude-plugin/plugin.json b/plugins/docs-tools/.claude-plugin/plugin.json index 5464925c..3ca2c03e 100644 --- a/plugins/docs-tools/.claude-plugin/plugin.json +++ b/plugins/docs-tools/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "docs-tools", - "version": "0.0.63", + "version": "0.0.64", "description": "Documentation review, writing, and workflow tools for Red Hat AsciiDoc and Markdown documentation.", "author": { "name": "Red Hat Documentation Team", diff --git a/plugins/docs-tools/README.md b/plugins/docs-tools/README.md index f85635be..5424e599 100644 --- a/plugins/docs-tools/README.md +++ b/plugins/docs-tools/README.md @@ -10,10 +10,14 @@ - Install [software dependencies](https://redhat-documentation.github.io/redhat-docs-agent-tools/install/#software-dependencies) -- Create an `~/.env` file with your tokens: +- Create an `.env` file with your tokens. You can use either location: + + - **`~/.env`** — global defaults, shared across all projects + - **`.env`** in the project root — project-specific overrides (takes precedence over `~/.env`) ```bash JIRA_API_TOKEN=your_jira_api_token + # JIRA_AUTH_TOKEN is also accepted as a backward-compatible alias # Required for Atlassian Cloud authentication JIRA_EMAIL=you@redhat.com # Optional: defaults to https://redhat.atlassian.net if not set @@ -23,8 +27,8 @@ # Required scope: "api" GITLAB_TOKEN=your_gitlab_pat ``` - -- Add the following to the end of your `~/.bashrc` (Linux) or `~/.zshrc` (macOS): + + All scripts load both files automatically (global first, then local overrides). You can also add the following to `~/.bashrc` (Linux) or `~/.zshrc` (macOS) to export them into your shell: ```bash if [ -f ~/.env ]; then diff --git a/plugins/docs-tools/agents/docs-planner.md b/plugins/docs-tools/agents/docs-planner.md index 6c8b67e6..a232ba89 100644 --- a/plugins/docs-tools/agents/docs-planner.md +++ b/plugins/docs-tools/agents/docs-planner.md @@ -35,9 +35,9 @@ If either file cannot be read, **STOP** and report the error. Do not proceed fro **You MUST successfully read the requirements input file before proceeding.** If the input file is missing or empty, STOP and report the error. -If access to JIRA or Git is needed for supplemental research and fails, **STOP IMMEDIATELY**, report the exact error, and instruct the user to check their credentials in `~/.env`. Never guess or infer content. +If access to JIRA or Git is needed for supplemental research and fails, **STOP IMMEDIATELY**, report the exact error, and instruct the user to check their credentials in `.env` or `~/.env`. Never guess or infer content. -**Do not** prepend `source ~/.env` to bash commands — all Python scripts load `~/.env` automatically. +**Do not** prepend `source ~/.env` to bash commands — all Python scripts load `.env` files automatically. ## When invoked diff --git a/plugins/docs-tools/agents/docs-writer.md b/plugins/docs-tools/agents/docs-writer.md index c41f48f1..564d04cb 100644 --- a/plugins/docs-tools/agents/docs-writer.md +++ b/plugins/docs-tools/agents/docs-writer.md @@ -30,9 +30,9 @@ Before writing any documentation: ### JIRA/Git access failures during writing -If access to JIRA or Git fails during writing, **STOP IMMEDIATELY**, report the exact error, and instruct the user to check their credentials in `~/.env`. Never guess or infer content. +If access to JIRA or Git fails during writing, **STOP IMMEDIATELY**, report the exact error, and instruct the user to check their credentials in `.env` or `~/.env`. Never guess or infer content. -**Do not** prepend `source ~/.env` to bash commands — all Python scripts load `~/.env` automatically. +**Do not** prepend `source ~/.env` to bash commands — all Python scripts load `.env` files automatically. ## CRITICAL: Mandatory reference loading diff --git a/plugins/docs-tools/agents/requirements-analyst.md b/plugins/docs-tools/agents/requirements-analyst.md index 7103e4d4..8e987a64 100644 --- a/plugins/docs-tools/agents/requirements-analyst.md +++ b/plugins/docs-tools/agents/requirements-analyst.md @@ -23,7 +23,7 @@ export CLAUDE_PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(git rev-parse --show-toplevel Before fetching, inspect the requirement's `sources` list. Only attempt access to systems that are actually listed (JIRA for `type: "jira"`, Git for `type: "pr"`, etc.). If a listed source fails access, **STOP IMMEDIATELY** and return an error result (see output format). Do not hard-fail for systems that are not in the sources list. -**Do not** prepend `source ~/.env` to bash commands — all Python scripts load `~/.env` automatically. +**Do not** prepend `source ~/.env` to bash commands — all Python scripts load `.env` files automatically. **Note:** The jira-reader script requires `jira` and `ratelimit` Python packages. If not installed: `python3 -m pip install jira ratelimit` @@ -196,7 +196,7 @@ python3 ${CLAUDE_PLUGIN_ROOT}/skills/git-pr-reader/scripts/git_pr_reader.py file python3 ${CLAUDE_PLUGIN_ROOT}/skills/git-pr-reader/scripts/git_pr_reader.py diff ``` -Requires `GITHUB_TOKEN` (GitHub) or `GITLAB_TOKEN` (GitLab) in `~/.env`. +Requires `GITHUB_TOKEN` (GitHub) or `GITLAB_TOKEN` (GitLab) in `.env` or `~/.env`. ### Reading Red Hat documentation diff --git a/plugins/docs-tools/agents/requirements-discoverer.md b/plugins/docs-tools/agents/requirements-discoverer.md index 7eced1e0..ecaa84c3 100644 --- a/plugins/docs-tools/agents/requirements-discoverer.md +++ b/plugins/docs-tools/agents/requirements-discoverer.md @@ -25,7 +25,7 @@ If JIRA access fails, **STOP IMMEDIATELY**, report the exact error in your JSON Git access (for PR/MR details) is only required when PR/MR URLs are present — either provided manually or auto-discovered from the JIRA graph. If no PR/MR URLs exist, skip PR listing entirely. If a specific PR/MR URL fails to fetch, log it in the `errors` array but continue discovery from other sources. -**Do not** prepend `source ~/.env` to bash commands — all Python scripts load `~/.env` automatically. +**Do not** prepend `source ~/.env` to bash commands — all Python scripts load `.env` files automatically. **Note:** The jira-reader script requires `jira` and `ratelimit` Python packages. If these are not installed, you will see `ModuleNotFoundError`. Run: `python3 -m pip install jira ratelimit` diff --git a/plugins/docs-tools/agents/technical-reviewer.md b/plugins/docs-tools/agents/technical-reviewer.md index ab83820d..380f0b71 100644 --- a/plugins/docs-tools/agents/technical-reviewer.md +++ b/plugins/docs-tools/agents/technical-reviewer.md @@ -11,9 +11,9 @@ You are not a style reviewer. You do not flag grammar, formatting, or style guid ## CRITICAL: Access failure procedure -If access to JIRA or Git fails during technical review, **STOP IMMEDIATELY**, report the exact error, and instruct the user to check their credentials in `~/.env`. Do not guess or infer technical details. +If access to JIRA or Git fails during technical review, **STOP IMMEDIATELY**, report the exact error, and instruct the user to check their credentials in `.env` or `~/.env`. Do not guess or infer technical details. -**Do not** prepend `source ~/.env` to bash commands — all Python scripts load `~/.env` automatically. +**Do not** prepend `source ~/.env` to bash commands — all Python scripts load `.env` files automatically. ## Your reviewer persona diff --git a/plugins/docs-tools/skills/docs-orchestrator/SKILL.md b/plugins/docs-tools/skills/docs-orchestrator/SKILL.md index 887ca9d1..610e62f9 100644 --- a/plugins/docs-tools/skills/docs-orchestrator/SKILL.md +++ b/plugins/docs-tools/skills/docs-orchestrator/SKILL.md @@ -23,7 +23,7 @@ Install the workflow completion Stop hook (safe to re-run, skips if already inst bash ${CLAUDE_SKILL_DIR}/scripts/setup-hooks.sh ``` -**Do not** source `~/.env` or check for tokens/CLIs here — Python scripts (`jira_reader.py`, `resolve_source.py`, etc.) load `~/.env` and validate prerequisites themselves, producing clear errors on failure. +**Do not** source `.env` files or check for tokens/CLIs here — Python scripts (`jira_reader.py`, `resolve_source.py`, etc.) load `.env` files and validate prerequisites themselves, producing clear errors on failure. ## Parse arguments diff --git a/plugins/docs-tools/skills/docs-review-style/SKILL.md b/plugins/docs-tools/skills/docs-review-style/SKILL.md index dbbec7f9..c2884daf 100644 --- a/plugins/docs-tools/skills/docs-review-style/SKILL.md +++ b/plugins/docs-tools/skills/docs-review-style/SKILL.md @@ -590,7 +590,7 @@ If any changes were applied, remind the user: - Always use `git_pr_reader.py extract` for deterministic line numbers — never estimate or guess - Use Bash with heredoc/cat for writing /tmp files (not the Write tool) - Cite the specific style guide rule or review skill for each issue -- Comments are posted under YOUR username using tokens from `~/.env` +- Comments are posted under YOUR username using tokens from `.env` files - For .adoc files, modular docs compliance uses `docs-review-modular-docs` - Release notes skills only apply to .adoc files that appear to be release notes - Vale linting requires Vale to be installed and configured diff --git a/plugins/docs-tools/skills/docs-review-technical/SKILL.md b/plugins/docs-tools/skills/docs-review-technical/SKILL.md index 9ca3518c..90b7ba3d 100644 --- a/plugins/docs-tools/skills/docs-review-technical/SKILL.md +++ b/plugins/docs-tools/skills/docs-review-technical/SKILL.md @@ -774,7 +774,7 @@ Entities found in API surface but not referenced in reviewed documentation: - Always use `git_pr_reader.py extract` for deterministic line numbers — never estimate or guess - Use Bash with heredoc/cat for writing /tmp files (not the Write tool) - Include source code evidence in each issue's `reason` field -- Comments are posted under YOUR username using tokens from `~/.env` +- Comments are posted under YOUR username using tokens from `.env` files - `scripts/extract_refs.py` extracts technical references from doc files (commands, APIs, configs, file paths) - Code-finder wrappers live in `${CLAUDE_PLUGIN_ROOT}/skills/code-evidence/scripts/`: - `grounded_review.py` — validates doc claims against code (verdicts: supported/unsupported/partially_supported/no_evidence_found) diff --git a/plugins/docs-tools/skills/docs-workflow-create-jira/SKILL.md b/plugins/docs-tools/skills/docs-workflow-create-jira/SKILL.md index a97bb2bb..f9bc7522 100644 --- a/plugins/docs-tools/skills/docs-workflow-create-jira/SKILL.md +++ b/plugins/docs-tools/skills/docs-workflow-create-jira/SKILL.md @@ -28,7 +28,7 @@ Unlike other step skills, this skill does **not** dispatch an agent. It runs `sc ## Environment -Requires `JIRA_API_TOKEN` and `JIRA_EMAIL` in the environment (typically sourced from `~/.env`). +Requires `JIRA_API_TOKEN` (or the backward-compatible alias `JIRA_AUTH_TOKEN`) and `JIRA_EMAIL` in the environment. `create-jira-ticket.sh` sources `~/.env` then `/.env`, where the project root is resolved from the `PLAN_FILE` location. ## Execution diff --git a/plugins/docs-tools/skills/docs-workflow-create-jira/scripts/create-jira-ticket.sh b/plugins/docs-tools/skills/docs-workflow-create-jira/scripts/create-jira-ticket.sh index f77772d6..38c0a74f 100755 --- a/plugins/docs-tools/skills/docs-workflow-create-jira/scripts/create-jira-ticket.sh +++ b/plugins/docs-tools/skills/docs-workflow-create-jira/scripts/create-jira-ticket.sh @@ -15,8 +15,32 @@ PLAN_FILE="${3:?Missing PLAN_FILE argument}" SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -# Source ~/.env for credential mapping -source ~/.env 2>/dev/null || true +# Load local overrides first, then global defaults (resolve .env from project root) +# Safe key/value parser: only reads KEY=VALUE lines, skips shell commands +_safe_load_env() { + local file="$1" + [[ -f "$file" ]] || return 0 + while IFS= read -r line || [[ -n "$line" ]]; do + [[ "$line" =~ ^[[:space:]]*# ]] && continue + [[ "$line" =~ ^[[:space:]]*$ ]] && continue + [[ "$line" =~ ^[[:space:]]*([a-zA-Z_][a-zA-Z0-9_]*)[[:space:]]*=(.*) ]] || continue + local key="${BASH_REMATCH[1]}" + local value="${BASH_REMATCH[2]}" + value="${value#"${value%%[![:space:]]*}"}" + value="${value%"${value##*[![:space:]]}"}" + if [[ "$value" =~ ^\"(.*)\"$ ]] || [[ "$value" =~ ^\'(.*)\'$ ]]; then + value="${BASH_REMATCH[1]}" + fi + if [[ -z "${!key+x}" ]]; then + export "$key=$value" + fi + done < "$file" +} +_project_root="$(cd "$(dirname "$PLAN_FILE")" 2>/dev/null && git rev-parse --show-toplevel 2>/dev/null || true)" +if [[ -n "$_project_root" ]]; then + _safe_load_env "$_project_root/.env" +fi +_safe_load_env ~/.env # Fallback: accept JIRA_AUTH_TOKEN for backward compatibility : "${JIRA_API_TOKEN:=${JIRA_AUTH_TOKEN:-}}" JIRA_URL="${JIRA_URL:-https://redhat.atlassian.net}" diff --git a/plugins/docs-tools/skills/docs-workflow-jira-ready/SKILL.md b/plugins/docs-tools/skills/docs-workflow-jira-ready/SKILL.md index 87fe6300..ad11fa92 100644 --- a/plugins/docs-tools/skills/docs-workflow-jira-ready/SKILL.md +++ b/plugins/docs-tools/skills/docs-workflow-jira-ready/SKILL.md @@ -24,7 +24,7 @@ Gate skill for automated docs-orchestrator runs. Checks JIRA for new tickets mat ## Environment -Requires `JIRA_API_TOKEN` and `JIRA_EMAIL` in the environment (typically sourced from `~/.env`). Both are validated before any API calls. +Requires `JIRA_API_TOKEN` (or the backward-compatible alias `JIRA_AUTH_TOKEN`) and `JIRA_EMAIL` in the environment. `jira-ready-check.sh` loads `~/.env` then `/.env` using a safe key/value parser (no shell execution). Both variables are validated before any API calls. ## Execution diff --git a/plugins/docs-tools/skills/docs-workflow-jira-ready/scripts/jira-ready-check.sh b/plugins/docs-tools/skills/docs-workflow-jira-ready/scripts/jira-ready-check.sh index ec0a5894..10e42956 100755 --- a/plugins/docs-tools/skills/docs-workflow-jira-ready/scripts/jira-ready-check.sh +++ b/plugins/docs-tools/skills/docs-workflow-jira-ready/scripts/jira-ready-check.sh @@ -58,19 +58,45 @@ if [[ -z "$JQL" ]]; then exit 1 fi -# --- Validate environment --- -if [[ -z "${JIRA_API_TOKEN:-}" ]]; then - # Try sourcing ~/.env - set -a; source ~/.env 2>/dev/null || true; set +a +# --- Load environment --- +# Safe key/value parser: only reads KEY=VALUE lines, skips shell commands +_safe_load_env() { + local file="$1" + [[ -f "$file" ]] || return 0 + while IFS= read -r line || [[ -n "$line" ]]; do + [[ "$line" =~ ^[[:space:]]*# ]] && continue + [[ "$line" =~ ^[[:space:]]*$ ]] && continue + [[ "$line" =~ ^[[:space:]]*([a-zA-Z_][a-zA-Z0-9_]*)[[:space:]]*=(.*) ]] || continue + local key="${BASH_REMATCH[1]}" + local value="${BASH_REMATCH[2]}" + value="${value#"${value%%[![:space:]]*}"}" + value="${value%"${value##*[![:space:]]}"}" + if [[ "$value" =~ ^\"(.*)\"$ ]] || [[ "$value" =~ ^\'(.*)\'$ ]]; then + value="${BASH_REMATCH[1]}" + fi + if [[ -z "${!key+x}" ]]; then + export "$key=$value" + fi + done < "$file" +} +_project_root="$(git rev-parse --show-toplevel 2>/dev/null || true)" +if [[ -z "$_project_root" ]]; then + _project_root="$(cd "$SCRIPT_DIR" 2>/dev/null && git rev-parse --show-toplevel 2>/dev/null || true)" +fi +if [[ -n "$_project_root" ]]; then + _safe_load_env "$_project_root/.env" fi +_safe_load_env ~/.env +# Fallback: accept JIRA_AUTH_TOKEN for backward compatibility +: "${JIRA_API_TOKEN:=${JIRA_AUTH_TOKEN:-}}" if [[ -z "${JIRA_API_TOKEN:-}" ]]; then - echo '{"error": "JIRA_API_TOKEN is not set. Add it to ~/.env."}' + echo '{"error": "JIRA_API_TOKEN is not set. Add it to .env or ~/.env."}' exit 1 fi if [[ -z "${JIRA_EMAIL:-}" ]]; then - echo '{"error": "JIRA_EMAIL is not set. Add it to ~/.env."}' + echo '{"error": "JIRA_EMAIL is not set. Add it to .env or ~/.env."}' exit 1 fi diff --git a/plugins/docs-tools/skills/git-pr-reader/SKILL.md b/plugins/docs-tools/skills/git-pr-reader/SKILL.md index 933e332c..1ca5dacf 100644 --- a/plugins/docs-tools/skills/git-pr-reader/SKILL.md +++ b/plugins/docs-tools/skills/git-pr-reader/SKILL.md @@ -96,14 +96,14 @@ python3 ${CLAUDE_SKILL_DIR}/scripts/git_pr_reader.py detect --json ### Authentication -Set in `~/.env` (see docs-tools README for setup): +Set in `~/.env` (global) or `.env` in the project root (local override). See docs-tools README for setup: ```bash GITHUB_TOKEN=your-github-pat # required scope: "repo" for private, "public_repo" for public GITLAB_TOKEN=your-gitlab-pat # required scope: "api" ``` -The script loads `~/.env` automatically — do **not** prepend `source ~/.env` to bash commands. +The script loads `.env` files automatically — do **not** prepend `source ~/.env` to bash commands. ### Python Library Usage diff --git a/plugins/docs-tools/skills/git-pr-reader/scripts/git_pr_reader.py b/plugins/docs-tools/skills/git-pr-reader/scripts/git_pr_reader.py index 9d43a100..77136aaf 100755 --- a/plugins/docs-tools/skills/git-pr-reader/scripts/git_pr_reader.py +++ b/plugins/docs-tools/skills/git-pr-reader/scripts/git_pr_reader.py @@ -40,7 +40,7 @@ python git_pr_reader.py detect Authentication: - Requires tokens in ~/.env: + Requires tokens in .env or ~/.env: - GitHub: GITHUB_TOKEN environment variable - GitLab: GITLAB_TOKEN environment variable """ @@ -83,15 +83,25 @@ def load_env_file() -> None: - """Load environment variables from ~/.env file.""" - env_file = os.path.expanduser("~/.env") - if os.path.exists(env_file): - with open(env_file) as f: - for line in f: - line = line.strip() - if line and not line.startswith("#") and "=" in line: - key, value = line.split("=", 1) - os.environ.setdefault(key.strip(), value.strip()) + """Load environment variables from .env files. + + Loads ./.env first (local settings), then ~/.env (global defaults). + Pre-existing environment variables are never overwritten. + Surrounding quotes on values are stripped. + """ + for env_path in [".env", os.path.expanduser("~/.env")]: + if os.path.exists(env_path): + with open(env_path) as f: + for line in f: + line = line.strip() + if line and not line.startswith("#") and "=" in line: + key, value = line.split("=", 1) + value = value.strip() + if (value.startswith('"') and value.endswith('"')) or ( + value.startswith("'") and value.endswith("'") + ): + value = value[1:-1] + os.environ.setdefault(key.strip(), value) def color_print(prefix: str, message: str) -> None: @@ -1612,7 +1622,7 @@ def cmd_detect(args) -> int: # Try GitLab via urllib.request gitlab_token = os.environ.get("GITLAB_TOKEN") if not gitlab_token: - print("Error: GITLAB_TOKEN not set in ~/.env") + print("Error: GITLAB_TOKEN not set in .env or ~/.env") return 1 for remote_name, remote_url in remotes.items(): diff --git a/plugins/docs-tools/skills/jira-reader/SKILL.md b/plugins/docs-tools/skills/jira-reader/SKILL.md index b479d007..bc1b3a17 100644 --- a/plugins/docs-tools/skills/jira-reader/SKILL.md +++ b/plugins/docs-tools/skills/jira-reader/SKILL.md @@ -33,7 +33,7 @@ The skill uses a Python script that connects to JIRA using an authentication tok ### Environment Variables Required -Set in `~/.env` (see docs-tools README for setup): +Set in `~/.env` (global) or `.env` in the project root (local override). See docs-tools README for setup: ```bash JIRA_API_TOKEN=your-jira-api-token @@ -41,7 +41,7 @@ JIRA_EMAIL=you@redhat.com # required for Atlassian Cloud JIRA_URL=https://redhat.atlassian.net # optional, defaults to redhat.atlassian.net ``` -The script loads `~/.env` automatically — do **not** prepend `source ~/.env` to bash commands. +The script loads `.env` files automatically — do **not** prepend `source ~/.env` to bash commands. ### Examples diff --git a/plugins/docs-tools/skills/jira-reader/scripts/jira_reader.py b/plugins/docs-tools/skills/jira-reader/scripts/jira_reader.py index 53b66825..b24260c7 100755 --- a/plugins/docs-tools/skills/jira-reader/scripts/jira_reader.py +++ b/plugins/docs-tools/skills/jira-reader/scripts/jira_reader.py @@ -125,15 +125,25 @@ def adf_to_text(node): def load_env_file(): - """Load environment variables from ~/.env file.""" - env_file = os.path.expanduser("~/.env") - if os.path.exists(env_file): - with open(env_file) as f: - for line in f: - line = line.strip() - if line and not line.startswith("#") and "=" in line: - key, value = line.split("=", 1) - os.environ.setdefault(key.strip(), value.strip()) + """Load environment variables from .env files. + + Loads ./.env first (local settings), then ~/.env (global defaults). + Pre-existing environment variables are never overwritten. + Surrounding quotes on values are stripped. + """ + for env_path in [".env", os.path.expanduser("~/.env")]: + if os.path.exists(env_path): + with open(env_path) as f: + for line in f: + line = line.strip() + if line and not line.startswith("#") and "=" in line: + key, value = line.split("=", 1) + value = value.strip() + if (value.startswith('"') and value.endswith('"')) or ( + value.startswith("'") and value.endswith("'") + ): + value = value[1:-1] + os.environ.setdefault(key.strip(), value) class JiraReader: @@ -145,7 +155,9 @@ def __init__(self, server=None): token = os.environ.get("JIRA_API_TOKEN") or os.environ.get("JIRA_AUTH_TOKEN") if not token: - raise ValueError("JIRA_API_TOKEN environment variable not set. Add it to ~/.env") + raise ValueError( + "JIRA_API_TOKEN (or JIRA_AUTH_TOKEN) not set. Add it to .env or ~/.env" + ) server = server or os.environ.get("JIRA_URL", "https://redhat.atlassian.net") @@ -155,7 +167,7 @@ def __init__(self, server=None): if not email: raise ValueError( "JIRA_EMAIL environment variable not set. " - "Required for Atlassian Cloud. Add it to ~/.env" + "Required for Atlassian Cloud. Add it to .env or ~/.env" ) self.jira = JIRA(server=server, basic_auth=(email, token), options=options) else: diff --git a/plugins/docs-tools/skills/jira-writer/SKILL.md b/plugins/docs-tools/skills/jira-writer/SKILL.md index ef570dbb..d8043253 100644 --- a/plugins/docs-tools/skills/jira-writer/SKILL.md +++ b/plugins/docs-tools/skills/jira-writer/SKILL.md @@ -24,7 +24,7 @@ The skill uses a Python script that connects to JIRA using an authentication tok ### Environment Variables Required -Set in `~/.env` (see docs-tools README for setup): +Set in `~/.env` (global) or `.env` in the project root (local override). See docs-tools README for setup: ```bash JIRA_API_TOKEN=your-jira-api-token @@ -32,7 +32,7 @@ JIRA_EMAIL=you@redhat.com # required for Atlassian Cloud JIRA_URL=https://redhat.atlassian.net # optional, defaults to redhat.atlassian.net ``` -The script loads `~/.env` automatically — do **not** prepend `source ~/.env` to bash commands. +The script loads `.env` files automatically — do **not** prepend `source ~/.env` to bash commands. ### Examples diff --git a/plugins/docs-tools/skills/jira-writer/scripts/jira_writer.py b/plugins/docs-tools/skills/jira-writer/scripts/jira_writer.py index ae2dddef..32417f19 100755 --- a/plugins/docs-tools/skills/jira-writer/scripts/jira_writer.py +++ b/plugins/docs-tools/skills/jira-writer/scripts/jira_writer.py @@ -27,15 +27,25 @@ def load_env_file(): - """Load environment variables from ~/.env file.""" - env_file = os.path.expanduser("~/.env") - if os.path.exists(env_file): - with open(env_file) as f: - for line in f: - line = line.strip() - if line and not line.startswith("#") and "=" in line: - key, value = line.split("=", 1) - os.environ.setdefault(key.strip(), value.strip()) + """Load environment variables from .env files. + + Loads ./.env first (local settings), then ~/.env (global defaults). + Pre-existing environment variables are never overwritten. + Surrounding quotes on values are stripped. + """ + for env_path in [".env", os.path.expanduser("~/.env")]: + if os.path.exists(env_path): + with open(env_path) as f: + for line in f: + line = line.strip() + if line and not line.startswith("#") and "=" in line: + key, value = line.split("=", 1) + value = value.strip() + if (value.startswith('"') and value.endswith('"')) or ( + value.startswith("'") and value.endswith("'") + ): + value = value[1:-1] + os.environ.setdefault(key.strip(), value) # Custom field IDs used in Red Hat JIRA @@ -52,7 +62,9 @@ def __init__(self, server=None): token = os.environ.get("JIRA_API_TOKEN") or os.environ.get("JIRA_AUTH_TOKEN") if not token: - raise ValueError("JIRA_API_TOKEN environment variable not set. Add it to ~/.env") + raise ValueError( + "JIRA_API_TOKEN (or JIRA_AUTH_TOKEN) not set. Add it to .env or ~/.env" + ) server = server or os.environ.get("JIRA_URL", "https://redhat.atlassian.net") @@ -61,7 +73,7 @@ def __init__(self, server=None): if not email: raise ValueError( "JIRA_EMAIL environment variable not set. " - "Required for Atlassian Cloud. Add it to ~/.env" + "Required for Atlassian Cloud. Add it to .env or ~/.env" ) self.jira = JIRA(server=server, basic_auth=(email, token)) else: