Skills: prefer active OpenClaw paths

This commit is contained in:
Shakker 2026-03-30 15:50:44 +01:00 committed by Shakker
parent 08d365f481
commit a3de1f5f55
12 changed files with 144 additions and 27 deletions

View File

@ -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:<node-id>
**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://<hostname>:18793/__openclaw__/canvas/<file>.html`

View File

@ -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!
---

View File

@ -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')
<config>
Repository: {SOURCE_REPO}

View File

@ -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
{

View File

@ -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/<agentId>/sessions/` (use the `agent=<id>` value from the system prompt Runtime line).
Session logs live under the active state directory:
`$OPENCLAW_STATE_DIR/agents/<agentId>/sessions/` (default: `~/.openclaw/agents/<agentId>/sessions/`).
Use the `agent=<id>` value from the system prompt Runtime line.
- **`sessions.json`** - Index mapping session keys to session IDs
- **`<session-id>.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/<agentId>/sessions/*.jsonl; do
AGENT_ID="<agentId>"
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/<agentId>/sessions/*.jsonl; do
AGENT_ID="<agentId>"
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' <session>.jsonl
### Daily cost summary
```bash
for f in ~/.openclaw/agents/<agentId>/sessions/*.jsonl; do
AGENT_ID="<agentId>"
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' <session>.json
### Search across ALL sessions for a phrase
```bash
rg -l "phrase" ~/.openclaw/agents/<agentId>/sessions/*.jsonl
AGENT_ID="<agentId>"
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/<agentId>/sessions/*.jsonl
## Fast text-only hint (low noise)
```bash
jq -r 'select(.type=="message") | .message.content[]? | select(.type=="text") | .text' ~/.openclaw/agents/<agentId>/sessions/<id>.jsonl | rg 'keyword'
AGENT_ID="<agentId>"
SESSION_DIR="${OPENCLAW_STATE_DIR:-$HOME/.openclaw}/agents/$AGENT_ID/sessions"
jq -r 'select(.type=="message") | .message.content[]? | select(.type=="text") | .text' "$SESSION_DIR"/<id>.jsonl | rg 'keyword'
```

View File

@ -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: "<state-dir>/tools/sherpa-onnx-tts/runtime",
SHERPA_ONNX_MODEL_DIR: "<state-dir>/tools/sherpa-onnx-tts/models/vits-piper-en_US-lessac-high",
},
},
},
@ -83,6 +83,8 @@ Update `~/.openclaw/openclaw.json`:
}
```
`<state-dir>` 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

View File

@ -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/<agentId>/sessions/*.jsonl",
'rg -l "phrase" ~/.openclaw/agents/<agentId>/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);
}
},
);
});

View File

@ -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.",
),
);
}

View File

@ -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",
() =>

View File

@ -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)")}`,
);
}

View File

@ -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({

View File

@ -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.",