mirror of https://github.com/openclaw/openclaw.git
Skills: prefer active OpenClaw paths
This commit is contained in:
parent
08d365f481
commit
a3de1f5f55
|
|
@ -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`
|
||||
|
||||
|
|
|
|||
|
|
@ -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!
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
|
@ -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.",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
() =>
|
||||
|
|
|
|||
|
|
@ -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)")}`,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
Loading…
Reference in New Issue