diff --git a/skills/canvas/SKILL.md b/skills/canvas/SKILL.md index dc4cef3c809..d5a9bab0bc5 100644 --- a/skills/canvas/SKILL.md +++ b/skills/canvas/SKILL.md @@ -57,7 +57,7 @@ This is why localhost URLs don't work - the node receives the Tailscale hostname ## Configuration -In `~/.openclaw/openclaw.json`: +In the active OpenClaw config file (`$OPENCLAW_CONFIG_PATH`, default `~/.openclaw/openclaw.json`): ```json { @@ -106,7 +106,8 @@ HTML Check how your gateway is bound: ```bash -cat ~/.openclaw/openclaw.json | jq '.gateway.bind' +CONFIG_PATH="${OPENCLAW_CONFIG_PATH:-${OPENCLAW_STATE_DIR:-$HOME/.openclaw}/openclaw.json}" +cat "$CONFIG_PATH" | jq '.gateway.bind' ``` Then construct the URL: @@ -156,7 +157,7 @@ canvas action:hide node: **Debug steps:** -1. Check server bind: `cat ~/.openclaw/openclaw.json | jq '.gateway.bind'` +1. Check server bind: `CONFIG_PATH="${OPENCLAW_CONFIG_PATH:-${OPENCLAW_STATE_DIR:-$HOME/.openclaw}/openclaw.json}"; cat "$CONFIG_PATH" | jq '.gateway.bind'` 2. Check what port canvas is on: `lsof -i :18793` 3. Test URL directly: `curl http://:18793/__openclaw__/canvas/.html` diff --git a/skills/coding-agent/SKILL.md b/skills/coding-agent/SKILL.md index 6e4d9cc0f2d..30189f3ebe8 100644 --- a/skills/coding-agent/SKILL.md +++ b/skills/coding-agent/SKILL.md @@ -263,7 +263,7 @@ git worktree remove /tmp/issue-99 5. **--full-auto for building** - auto-approves changes 6. **vanilla for reviewing** - no special flags needed 7. **Parallel is OK** - run many Codex processes at once for batch work -8. **NEVER start Codex in ~/.openclaw/** - it'll read your soul docs and get weird ideas about the org chart! +8. **NEVER start Codex inside your OpenClaw state directory** (`$OPENCLAW_STATE_DIR`, default `~/.openclaw`) - it'll read your soul docs and get weird ideas about the org chart! 9. **NEVER checkout branches in ~/Projects/openclaw/** - that's the LIVE OpenClaw instance! --- diff --git a/skills/gh-issues/SKILL.md b/skills/gh-issues/SKILL.md index 6a2b2fb28bc..c23f7e52076 100644 --- a/skills/gh-issues/SKILL.md +++ b/skills/gh-issues/SKILL.md @@ -95,7 +95,8 @@ echo $GH_TOKEN If empty, read from config: ``` -cat ~/.openclaw/openclaw.json | jq -r '.skills.entries["gh-issues"].apiKey // empty' +CONFIG_PATH="${OPENCLAW_CONFIG_PATH:-${OPENCLAW_STATE_DIR:-$HOME/.openclaw}/openclaw.json}" +cat "$CONFIG_PATH" | jq -r '.skills.entries["gh-issues"].apiKey // empty' ``` If still empty, check `/data/.clawdbot/openclaw.json`: @@ -130,7 +131,7 @@ If in watch mode: Also filter out any issue numbers already in the PROCESSED_ISS Error handling: - If curl returns an HTTP 401 or 403 → stop and tell the user: - > "GitHub authentication failed. Please check your apiKey in the OpenClaw dashboard or in ~/.openclaw/openclaw.json under skills.entries.gh-issues." + > "GitHub authentication failed. Please check your apiKey in the OpenClaw dashboard or in the active OpenClaw config path (`$OPENCLAW_CONFIG_PATH`, default `~/.openclaw/openclaw.json`) under `skills.entries.gh-issues`." - If the response is an empty array (after filtering) → report "No issues found matching filters" and stop (or loop back if in watch mode). - If curl fails or returns any other error → report the error verbatim and stop. @@ -228,7 +229,7 @@ Run these checks sequentially via exec: If HTTP status is not 200, stop with: - > "GitHub authentication failed. Please check your apiKey in the OpenClaw dashboard or in ~/.openclaw/openclaw.json under skills.entries.gh-issues." + > "GitHub authentication failed. Please check your apiKey in the OpenClaw dashboard or in the active OpenClaw config path (`$OPENCLAW_CONFIG_PATH`, default `~/.openclaw/openclaw.json`) under `skills.entries.gh-issues`." 5. **Check for existing PRs:** For each confirmed issue number N, run: @@ -362,7 +363,8 @@ You are a focused code-fix agent. Your task is to fix a single GitHub issue and IMPORTANT: Do NOT use the gh CLI — it is not installed. Use curl with the GitHub REST API for all GitHub operations. First, ensure GH_TOKEN is set. Check: `echo $GH_TOKEN`. If empty, read from config: -GH_TOKEN=$(cat ~/.openclaw/openclaw.json 2>/dev/null | jq -r '.skills.entries["gh-issues"].apiKey // empty') || GH_TOKEN=$(cat /data/.clawdbot/openclaw.json 2>/dev/null | jq -r '.skills.entries["gh-issues"].apiKey // empty') +CONFIG_PATH="${OPENCLAW_CONFIG_PATH:-${OPENCLAW_STATE_DIR:-$HOME/.openclaw}/openclaw.json}" +GH_TOKEN=$(cat "$CONFIG_PATH" 2>/dev/null | jq -r '.skills.entries["gh-issues"].apiKey // empty') || GH_TOKEN=$(cat /data/.clawdbot/openclaw.json 2>/dev/null | jq -r '.skills.entries["gh-issues"].apiKey // empty') Use the token in all GitHub API calls: curl -s -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github+json" ... @@ -397,7 +399,8 @@ export GH_TOKEN=$(node -e "const fs=require('fs'); const c=JSON.parse(fs.readFil If that fails, also try: ``` -export GH_TOKEN=$(cat ~/.openclaw/openclaw.json 2>/dev/null | node -e "const fs=require('fs');const d=JSON.parse(fs.readFileSync(0,'utf8'));console.log(d.skills?.entries?.['gh-issues']?.apiKey||'')") +export CONFIG_PATH="${OPENCLAW_CONFIG_PATH:-${OPENCLAW_STATE_DIR:-$HOME/.openclaw}/openclaw.json}" +export GH_TOKEN=$(cat "$CONFIG_PATH" 2>/dev/null | node -e "const fs=require('fs');const d=JSON.parse(fs.readFileSync(0,'utf8'));console.log(d.skills?.entries?.['gh-issues']?.apiKey||'')") ``` Verify: echo "Token: ${GH_TOKEN:0:10}..." @@ -730,7 +733,8 @@ You are a PR review handler agent. Your task is to address review comments on a IMPORTANT: Do NOT use the gh CLI — it is not installed. Use curl with the GitHub REST API for all GitHub operations. First, ensure GH_TOKEN is set. Check: echo $GH_TOKEN. If empty, read from config: -GH_TOKEN=$(cat ~/.openclaw/openclaw.json 2>/dev/null | jq -r '.skills.entries["gh-issues"].apiKey // empty') || GH_TOKEN=$(cat /data/.clawdbot/openclaw.json 2>/dev/null | jq -r '.skills.entries["gh-issues"].apiKey // empty') +CONFIG_PATH="${OPENCLAW_CONFIG_PATH:-${OPENCLAW_STATE_DIR:-$HOME/.openclaw}/openclaw.json}" +GH_TOKEN=$(cat "$CONFIG_PATH" 2>/dev/null | jq -r '.skills.entries["gh-issues"].apiKey // empty') || GH_TOKEN=$(cat /data/.clawdbot/openclaw.json 2>/dev/null | jq -r '.skills.entries["gh-issues"].apiKey // empty') Repository: {SOURCE_REPO} diff --git a/skills/openai-whisper-api/SKILL.md b/skills/openai-whisper-api/SKILL.md index 2b18c6ec6dc..4efb7d549e0 100644 --- a/skills/openai-whisper-api/SKILL.md +++ b/skills/openai-whisper-api/SKILL.md @@ -49,7 +49,7 @@ Defaults: ## API key -Set `OPENAI_API_KEY`, or configure it in `~/.openclaw/openclaw.json`. Optionally set `OPENAI_BASE_URL` (for example `http://127.0.0.1:51805/v1`) to use an OpenAI-compatible proxy or local gateway: +Set `OPENAI_API_KEY`, or configure it in the active OpenClaw config file (`$OPENCLAW_CONFIG_PATH`, default `~/.openclaw/openclaw.json`). Optionally set `OPENAI_BASE_URL` (for example `http://127.0.0.1:51805/v1`) to use an OpenAI-compatible proxy or local gateway: ```json5 { diff --git a/skills/session-logs/SKILL.md b/skills/session-logs/SKILL.md index b581bd629ac..51d62a4a812 100644 --- a/skills/session-logs/SKILL.md +++ b/skills/session-logs/SKILL.md @@ -38,7 +38,9 @@ Use this skill when the user asks about prior chats, parent conversations, or hi ## Location -Session logs live at: `~/.openclaw/agents//sessions/` (use the `agent=` value from the system prompt Runtime line). +Session logs live under the active state directory: +`$OPENCLAW_STATE_DIR/agents//sessions/` (default: `~/.openclaw/agents//sessions/`). +Use the `agent=` value from the system prompt Runtime line. - **`sessions.json`** - Index mapping session keys to session IDs - **`.jsonl`** - Full conversation transcript per session @@ -58,7 +60,9 @@ Each `.jsonl` file contains messages with: ### List all sessions by date and size ```bash -for f in ~/.openclaw/agents//sessions/*.jsonl; do +AGENT_ID="" +SESSION_DIR="${OPENCLAW_STATE_DIR:-$HOME/.openclaw}/agents/$AGENT_ID/sessions" +for f in "$SESSION_DIR"/*.jsonl; do date=$(head -1 "$f" | jq -r '.timestamp' | cut -dT -f1) size=$(ls -lh "$f" | awk '{print $5}') echo "$date $size $(basename $f)" @@ -68,7 +72,9 @@ done | sort -r ### Find sessions from a specific day ```bash -for f in ~/.openclaw/agents//sessions/*.jsonl; do +AGENT_ID="" +SESSION_DIR="${OPENCLAW_STATE_DIR:-$HOME/.openclaw}/agents/$AGENT_ID/sessions" +for f in "$SESSION_DIR"/*.jsonl; do head -1 "$f" | jq -r '.timestamp' | grep -q "2026-01-06" && echo "$f" done ``` @@ -94,7 +100,9 @@ jq -s '[.[] | .message.usage.cost.total // 0] | add' .jsonl ### Daily cost summary ```bash -for f in ~/.openclaw/agents//sessions/*.jsonl; do +AGENT_ID="" +SESSION_DIR="${OPENCLAW_STATE_DIR:-$HOME/.openclaw}/agents/$AGENT_ID/sessions" +for f in "$SESSION_DIR"/*.jsonl; do date=$(head -1 "$f" | jq -r '.timestamp' | cut -dT -f1) cost=$(jq -s '[.[] | .message.usage.cost.total // 0] | add' "$f") echo "$date $cost" @@ -122,7 +130,9 @@ jq -r '.message.content[]? | select(.type == "toolCall") | .name' .json ### Search across ALL sessions for a phrase ```bash -rg -l "phrase" ~/.openclaw/agents//sessions/*.jsonl +AGENT_ID="" +SESSION_DIR="${OPENCLAW_STATE_DIR:-$HOME/.openclaw}/agents/$AGENT_ID/sessions" +rg -l "phrase" "$SESSION_DIR"/*.jsonl ``` ## Tips @@ -135,5 +145,7 @@ rg -l "phrase" ~/.openclaw/agents//sessions/*.jsonl ## Fast text-only hint (low noise) ```bash -jq -r 'select(.type=="message") | .message.content[]? | select(.type=="text") | .text' ~/.openclaw/agents//sessions/.jsonl | rg 'keyword' +AGENT_ID="" +SESSION_DIR="${OPENCLAW_STATE_DIR:-$HOME/.openclaw}/agents/$AGENT_ID/sessions" +jq -r 'select(.type=="message") | .message.content[]? | select(.type=="text") | .text' "$SESSION_DIR"/.jsonl | rg 'keyword' ``` diff --git a/skills/sherpa-onnx-tts/SKILL.md b/skills/sherpa-onnx-tts/SKILL.md index 46f7ead58da..7b117b656b4 100644 --- a/skills/sherpa-onnx-tts/SKILL.md +++ b/skills/sherpa-onnx-tts/SKILL.md @@ -63,10 +63,10 @@ Local TTS using the sherpa-onnx offline CLI. ## Install -1. Download the runtime for your OS (extracts into `~/.openclaw/tools/sherpa-onnx-tts/runtime`) -2. Download a voice model (extracts into `~/.openclaw/tools/sherpa-onnx-tts/models`) +1. Download the runtime for your OS (extracts into `$OPENCLAW_STATE_DIR/tools/sherpa-onnx-tts/runtime`, default `~/.openclaw/tools/sherpa-onnx-tts/runtime`) +2. Download a voice model (extracts into `$OPENCLAW_STATE_DIR/tools/sherpa-onnx-tts/models`, default `~/.openclaw/tools/sherpa-onnx-tts/models`) -Update `~/.openclaw/openclaw.json`: +Update the active OpenClaw config file (`$OPENCLAW_CONFIG_PATH`, default `~/.openclaw/openclaw.json`). Use a path inside your active state directory (`$OPENCLAW_STATE_DIR`, default `~/.openclaw`) for the runtime and model directories: ```json5 { @@ -74,8 +74,8 @@ Update `~/.openclaw/openclaw.json`: entries: { "sherpa-onnx-tts": { env: { - SHERPA_ONNX_RUNTIME_DIR: "~/.openclaw/tools/sherpa-onnx-tts/runtime", - SHERPA_ONNX_MODEL_DIR: "~/.openclaw/tools/sherpa-onnx-tts/models/vits-piper-en_US-lessac-high", + SHERPA_ONNX_RUNTIME_DIR: "/tools/sherpa-onnx-tts/runtime", + SHERPA_ONNX_MODEL_DIR: "/tools/sherpa-onnx-tts/models/vits-piper-en_US-lessac-high", }, }, }, @@ -83,6 +83,8 @@ Update `~/.openclaw/openclaw.json`: } ``` +`` means your active `OPENCLAW_STATE_DIR` (default `~/.openclaw`). + The wrapper lives in this skill folder. Run it directly, or add the wrapper to PATH: ```bash diff --git a/src/agents/skills.env-path-guidance.test.ts b/src/agents/skills.env-path-guidance.test.ts new file mode 100644 index 00000000000..a430a42d9c5 --- /dev/null +++ b/src/agents/skills.env-path-guidance.test.ts @@ -0,0 +1,64 @@ +import fs from "node:fs"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; + +const REPO_ROOT = path.resolve(import.meta.dirname, "..", ".."); + +type GuidanceCase = { + file: string; + required?: string[]; + forbidden?: string[]; +}; + +const CASES: GuidanceCase[] = [ + { + file: "skills/session-logs/SKILL.md", + required: ["OPENCLAW_STATE_DIR"], + forbidden: [ + "for f in ~/.openclaw/agents//sessions/*.jsonl", + 'rg -l "phrase" ~/.openclaw/agents//sessions/*.jsonl', + ], + }, + { + file: "skills/gh-issues/SKILL.md", + required: ["OPENCLAW_CONFIG_PATH"], + forbidden: ["cat ~/.openclaw/openclaw.json"], + }, + { + file: "skills/canvas/SKILL.md", + required: ["OPENCLAW_CONFIG_PATH"], + forbidden: ["cat ~/.openclaw/openclaw.json"], + }, + { + file: "skills/openai-whisper-api/SKILL.md", + required: ["OPENCLAW_CONFIG_PATH"], + }, + { + file: "skills/sherpa-onnx-tts/SKILL.md", + required: ["OPENCLAW_STATE_DIR", "OPENCLAW_CONFIG_PATH"], + forbidden: [ + 'SHERPA_ONNX_RUNTIME_DIR: "~/.openclaw/tools/sherpa-onnx-tts/runtime"', + 'SHERPA_ONNX_MODEL_DIR: "~/.openclaw/tools/sherpa-onnx-tts/models/vits-piper-en_US-lessac-high"', + ], + }, + { + file: "skills/coding-agent/SKILL.md", + required: ["OPENCLAW_STATE_DIR"], + forbidden: ["NEVER start Codex in ~/.openclaw/"], + }, +]; + +describe("bundled skill env-path guidance", () => { + it.each(CASES)( + "keeps $file aligned with OPENCLAW env overrides", + ({ file, required, forbidden }) => { + const content = fs.readFileSync(path.join(REPO_ROOT, file), "utf8"); + for (const needle of required ?? []) { + expect(content).toContain(needle); + } + for (const needle of forbidden ?? []) { + expect(content).not.toContain(needle); + } + }, + ); +}); diff --git a/src/cli/dns-cli.ts b/src/cli/dns-cli.ts index f97a6ff8178..4ae38c5f040 100644 --- a/src/cli/dns-cli.ts +++ b/src/cli/dns-cli.ts @@ -153,7 +153,11 @@ export function registerDnsCli(program: Command) { }).trimEnd(), ); defaultRuntime.log(""); - defaultRuntime.log(theme.heading("Recommended ~/.openclaw/openclaw.json:")); + defaultRuntime.log( + theme.heading( + "Recommended config ($OPENCLAW_CONFIG_PATH, default ~/.openclaw/openclaw.json):", + ), + ); defaultRuntime.writeJson({ gateway: { bind: "auto" }, discovery: { wideArea: { enabled: true, domain: wideAreaDomain } }, @@ -248,7 +252,7 @@ export function registerDnsCli(program: Command) { defaultRuntime.log(""); defaultRuntime.log( theme.muted( - "Note: enable discovery.wideArea.enabled in ~/.openclaw/openclaw.json on the gateway and restart the gateway so it writes the DNS-SD zone.", + "Note: enable discovery.wideArea.enabled in the active OpenClaw config ($OPENCLAW_CONFIG_PATH, default ~/.openclaw/openclaw.json) on the gateway and restart the gateway so it writes the DNS-SD zone.", ), ); } diff --git a/src/cli/program/register.setup.ts b/src/cli/program/register.setup.ts index 3546a2adbdf..84b698c863c 100644 --- a/src/cli/program/register.setup.ts +++ b/src/cli/program/register.setup.ts @@ -10,7 +10,7 @@ import { hasExplicitOptions } from "../command-options.js"; export function registerSetupCommand(program: Command) { program .command("setup") - .description("Initialize ~/.openclaw/openclaw.json and the agent workspace") + .description("Initialize the active OpenClaw config and agent workspace") .addHelpText( "after", () => diff --git a/src/cli/skills-cli.format.ts b/src/cli/skills-cli.format.ts index 7b129ff72e2..cce9b148f02 100644 --- a/src/cli/skills-cli.format.ts +++ b/src/cli/skills-cli.format.ts @@ -282,7 +282,7 @@ export function formatSkillInfo( ` Save via CLI: ${formatCliCommand(`openclaw config set skills.entries.${safeSkillKey}.apiKey YOUR_KEY`)}`, ); lines.push( - ` Stored in: ${theme.muted("~/.openclaw/openclaw.json")} ${theme.muted(`(skills.entries.${safeSkillKey}.apiKey)`)}`, + ` Stored in: ${theme.muted("$OPENCLAW_CONFIG_PATH")} ${theme.muted("(default: ~/.openclaw/openclaw.json)")}`, ); } diff --git a/src/cli/skills-cli.test.ts b/src/cli/skills-cli.test.ts index 9f344ed662d..4e2263f536f 100644 --- a/src/cli/skills-cli.test.ts +++ b/src/cli/skills-cli.test.ts @@ -149,6 +149,36 @@ describe("skills-cli", () => { expect(output).toContain("API_KEY"); }); + it("shows API key storage guidance for the active config path", () => { + const report = createMockReport([ + createMockSkill({ + name: "env-aware-skill", + skillKey: "env-aware-skill", + primaryEnv: "API_KEY", + eligible: false, + requirements: { + bins: [], + anyBins: [], + env: ["API_KEY"], + config: [], + os: [], + }, + missing: { + bins: [], + anyBins: [], + env: ["API_KEY"], + config: [], + os: [], + }, + }), + ]); + + const output = formatSkillInfo(report, "env-aware-skill", {}); + expect(output).toContain("OPENCLAW_CONFIG_PATH"); + expect(output).toContain("default: ~/.openclaw/openclaw.json"); + expect(output).toContain("skills.entries.env-aware-skill.apiKey"); + }); + it("normalizes text-presentation emoji selectors in info output", () => { const report = createMockReport([ createMockSkill({ diff --git a/src/wizard/setup.finalize.ts b/src/wizard/setup.finalize.ts index 9e72ec1590d..48fa4ffb8e6 100644 --- a/src/wizard/setup.finalize.ts +++ b/src/wizard/setup.finalize.ts @@ -390,7 +390,7 @@ export async function finalizeSetupWizard( await prompter.note( [ "Gateway token: shared auth for the Gateway + Control UI.", - "Stored in: ~/.openclaw/openclaw.json (gateway.auth.token) or OPENCLAW_GATEWAY_TOKEN.", + "Stored in: $OPENCLAW_CONFIG_PATH (default: ~/.openclaw/openclaw.json) under gateway.auth.token, or in OPENCLAW_GATEWAY_TOKEN.", `View token: ${formatCliCommand("openclaw config get gateway.auth.token")}`, `Generate token: ${formatCliCommand("openclaw doctor --generate-gateway-token")}`, "Web UI keeps dashboard URL tokens in memory for the current tab and strips them from the URL after load.",