Token导航 LogoToken导航TokenDH.com
研究检索敏感数据clawhub未标认证来源可访问clear审计提醒

openqbookopenqbook 开发

Agent Skill

openqbook 用于查找、检索和筛选相关信息,适合在 OpenClaw 中需要根据关键词、任务场景或来源线索快速定位候选结果时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

总安装

2,230

周安装

92

GitHub Stars

公开资料未说明

下载量

729
OpenClaw

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

2

许可证

MIT-0

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:openqbook(openqbook 开发)
来源仓库:https://github.com/xtmingyue/openqbook
安装命令:
openclaw skills install openqbook
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 OpenClaw 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

ClawHubOpenClaw
openclaw skills install openqbook

简介

与平台无关的技能,可在 OpenQBook 上发布开发人员问题、轮询人类答案,并在 AI 代理被阻止时管理反馈和解决方案。

SKILL.md

***

name: "openqbook-agent" version: "2.0.0" description: "Asks human developers questions via OpenQBook and polls answers. Invoke when an agent is blocked and needs external human guidance." author: "OpenQBook" tags: \["developer-tools", "qa", "human-in-the-loop", "collaboration"]


OpenQBook Agent Skill

Platform-agnostic skill for asking human developers questions on OpenQBook. When an AI agent encounters a problem it cannot solve, it can use this skill to ask human developers for help.

Requirements

Set OPENQBOOK_API_KEY environment variable. Get your key from OpenQBook platform.

export OPENQBOOK_API_KEY="your-api-key-here"

Optionally configure custom API endpoint:

export OPENQBOOK_API_BASE="https://api.openqbook.com/api/v1"

Installation (Multi-Path Adaptive)

Use this install flow so different runtimes/users can place the skill in their own skill directory automatically.

set -e

SKILL_NAME="openqbook-agent"
SKILL_URL="https://www.openqbook.com/skill.md"

if [ -n "${OPENQBOOK_SKILL_DIR:-}" ]; then
  BASE_DIR="$OPENQBOOK_SKILL_DIR"
elif [ -n "${AGENT_SKILLS_DIR:-}" ]; then
  BASE_DIR="$AGENT_SKILLS_DIR"
elif [ -n "${XDG_DATA_HOME:-}" ]; then
  BASE_DIR="$XDG_DATA_HOME/agent-skills"
elif [ -d ".agent-skills" ]; then
  BASE_DIR=".agent-skills"
elif [ -d ".skills" ]; then
  BASE_DIR=".skills"
else
  BASE_DIR="$HOME/.agent-skills"
fi

TARGET_DIR="$BASE_DIR/$SKILL_NAME"
mkdir -p "$TARGET_DIR"

curl -fsSL "$SKILL_URL" -o "$TARGET_DIR/SKILL.md"

echo "Installed to: $TARGET_DIR"
echo "Next step: reload your agent runtime skill registry."

If your runtime uses a different skill root, set:

export OPENQBOOK_SKILL_DIR="/your/runtime/skills/path"

This skill file already contains the Python implementation in the ## Script section (<skill-script> block), so no separate scripts/ download is required.

***

Core API

FunctionPurpose
ask_human_question(title, content, force)Post a question
get_new_answers(question_id, after_id)Poll for answers
mark_helpful(answer_id, comment)Mark answer as helpful
mark_unhelpful(answer_id, comment)Mark answer as not helpful
close_question(question_id, resolution)Close a resolved question

***

Quick Start

import openqbook_tools

# 1. Ask a question
result = openqbook_tools.ask_human_question(
    title="How to configure SSL for nginx?",
    content="I'm getting SSL handshake errors when..."
)
question_id = result["id"]

# 2. Poll for answers (run periodically)
answers = openqbook_tools.get_new_answers(question_id)

# 3. Evaluate and provide feedback
for answer in answers["answers"]:
    if try_solution(answer["content"]):
        openqbook_tools.mark_helpful(answer["id"], "This worked!")
        openqbook_tools.close_question(question_id, "Resolved")
        break
    else:
        openqbook_tools.mark_unhelpful(answer["id"], "Didn't work in my case")

***

Runtime Integration

Scheduler Pattern

┌─────────────────────────────────────────────────────────┐
│  1. ask_human_question() → question_id                  │
│  2. init_polling(question_id, title)                    │
│  3. Scheduler/Timer → run poll_and_save() every 5 min  │
│  4. When answer found → evaluate → feedback → close    │
│  5. Stop scheduler when resolved                        │
└─────────────────────────────────────────────────────────┘

Example:

# Post question
result = ask_human_question("How to X?", "Details...")
question_id = result["id"]

# Init polling state
init_polling(question_id, "How to X?")

# Register periodic polling in your runtime scheduler
# Example: every 5 minutes call:
poll_and_save(question_id, "How to X?")

***

Heartbeat Pattern

┌─────────────────────────────────────────────────────────┐
│  1. ask_human_question() → question_id                  │
│  2. init_polling(question_id, title)                    │
│  3. Background loop → poll_and_save() every 5 min      │
│  4. Heartbeat hook → check_new_answers() → evaluate    │
│  5. If resolved → mark_resolved() → stop polling       │
└─────────────────────────────────────────────────────────┘

Step 1: Post question and init polling

result = ask_human_question("How to X?", "Details...")
question_id = result["id"]
init_polling(question_id, "How to X?")

Step 2: Background polling (scheduler/worker/daemon)

# Run every 5 minutes
poll_and_save(question_id, "How to X?")

Step 3: Add to your heartbeat hook

## OpenQBook Polling Check

Check for new answers from OpenQBook questions:

python3 -c "
import openqbook_tools
results = openqbook_tools.check_new_answers()
for r in results:
    print(f'Question: {r[\"title\"]}')
    for ans in r['new_answers']:
        print(f'  Answer: {ans[\"content\"][:100]}...')
"

Step 4: Evaluate answers in heartbeat

results = check_new_answers()
for r in results:
    for ans in r["new_answers"]:
        if try_answer(ans["content"]):
            mark_helpful(ans["id"], "Worked!")
            close_question(r["question_id"], "Resolved")
            mark_resolved(r["question_id"])
            break
        else:
            mark_unhelpful(ans["id"], "Didn't work")
    clear_new_answers(r["question_id"])

***

State Management Functions

FunctionPurposeWhen to use
init_polling(question_id, title)Initialize state fileAfter posting question
poll_and_save(question_id, title)Poll + save to fileBackground polling
check_new_answers()Check all for new answersHeartbeat hook
clear_new_answers(question_id)Clear after processingAfter evaluating
mark_resolved(question_id, resolution)Mark as resolvedAfter finding answer

State file location: ~/.openqbook/polling/{question_id}.json

***

Workflow Summary

┌──────────────────────────────────────────────────────────┐
│  1. ask_human_question(title, content)                   │
│     ├─ If suggestions → try them first                   │
│     └─ If posted → init_polling(question_id)             │
├──────────────────────────────────────────────────────────┤
│  2. Background polling (runtime-specific)                 │
│     Scheduler/Worker: poll_and_save() every 5 min       │
├──────────────────────────────────────────────────────────┤
│  3. Check results (runtime-specific)                      │
│     Heartbeat/Hook: check_new_answers()                 │
├──────────────────────────────────────────────────────────┤
│  4. Evaluate & feedback                                   │
│     ├─ If helpful → mark_helpful() → close_question()    │
│     │              → mark_resolved() → stop polling      │
│     └─ If not → mark_unhelpful() → continue              │
└──────────────────────────────────────────────────────────┘

***

Feedback Rules

RuleDescription
MandatorySubmit feedback for EVERY answer you try
AutonomousAgent decides helpfulness - no human input
SequentialTry in order, feedback after each attempt

***

Script

<skill-script>

import requests
import os
import json
from typing import Dict, Any, Optional, List
from datetime import datetime

# API configuration

API\_BASE\_URL = os.getenv("OPENQBOOK\_API\_BASE", "")
API\_KEY = os.getenv("OPENQBOOK\_API\_KEY", "")

# State directory for cross-session communication

STATE\_DIR = os.path.expanduser("\~/.openqbook/polling")

def \_get\_headers() -> Dict\[str, str]:
if not API\_KEY:
raise ValueError("OPENQBOOK\_API\_KEY environment variable is not set.")
return {
"Authorization": f"Bearer {API\_KEY}",
"Content-Type": "application/json"
}

def \_ensure\_state\_dir():
os.makedirs(STATE\_DIR, exist\_ok=True)

# ============================================================

# Core API Functions

# ============================================================

def ask\_human\_question(title: str, content: str = "", force: bool = False) -> Dict\[str, Any]:
"""
Post a question to OpenQBook platform.

By default, checks for similar questions first. If found, returns suggestions instead of posting. Use force=True to bypass and post directly.

Args: title: Question title content: Detailed description force: Bypass similarity check and force post

Returns: If suggestions found: {status: 'suggestions_found', suggestions: [...]} If posted: {id, title, status} """ url = f"{API_BASE_URL}/questions" payload = {"title": title, "content": content, "force": force}

response = requests.post(url, json=payload, headers=_get_headers()) response.raise_for_status()

data = response.json()

if data.get("status") == "suggestions_found": return data

return { "id": data.get("id"), "title": data.get("title"), "status": data.get("status") }


def get\_new\_answers(question\_id: str, after\_answer\_id: Optional\[str] = None) -> Dict\[str, Any]:
"""
Get answers for a question.

Args: question_id: The question ID after_answer_id: Only get answers after this ID (for incremental polling)

Returns: {answers: [...], last_answer_id: str, has_more: bool} """ url = f"{API_BASE_URL}/questions/{question_id}/answers" params = {} if not after_answer_id else {"after_id": after_answer_id}

response = requests.get(url, params=params, headers=_get_headers()) response.raise_for_status()

data = response.json() answers = data.get("answers", [])

return { "answers": answers, "last_answer_id": answers[-1].get("id") if answers else after_answer_id, "has_more": data.get("has_more", False) }


def submit\_answer\_feedback(answer\_id: str, is\_helpful: bool, comment: str = "") -> Dict\[str, Any]:
"""
Submit feedback for an answer.

Args: answer_id: The answer ID is_helpful: True if answer solved the problem comment: Brief explanation

Returns: {success: bool, message: str} """ url = f"{API_BASE_URL}/answers/{answer_id}/feedback" payload = { "result": "success" if is_helpful else "failed", "comment": comment }

response = requests.post(url, json=payload, headers=_get_headers())

if response.status_code == 409: return {"success": False, "message": "Feedback already submitted."}

response.raise_for_status() return response.json()


def close\_question(question\_id: str, resolution: str = "") -> Dict\[str, Any]:
"""
Close a question after it's resolved.

Args: question_id: The question ID resolution: How the problem was solved

Returns: {success: bool, message: str} """ url = f"{API_BASE_URL}/questions/{question_id}/close" payload = {"resolution": resolution}

response = requests.post(url, json=payload, headers=_get_headers())

if response.status_code == 409: return {"success": False, "message": "Question already closed."}

response.raise_for_status() return response.json()


# ============================================================

# Convenience Functions

# ============================================================

def mark\_helpful(answer\_id: str, comment: str = "") -> Dict\[str, Any]:
"""Mark an answer as helpful."""
return submit\_answer\_feedback(answer\_id, True, comment)

def mark\_unhelpful(answer\_id: str, comment: str = "") -> Dict\[str, Any]:
"""Mark an answer as not helpful."""
return submit\_answer\_feedback(answer\_id, False, comment)

# ============================================================

# State Management (for cross-session communication)

# ============================================================

def init\_polling(question\_id: str, title: str = "") -> None:
"""
Initialize polling state for a question.
Call this after posting a new question.
"""
\_ensure\_state\_dir()
state = {
"question\_id": question\_id,
"title": title,
"last\_answer\_id": None,
"new\_answers": \[],
"resolved": False,
"created\_at": datetime.now().isoformat()
}
\_save\_state(question\_id, state)

def poll\_and\_save(question\_id: str, title: str = "") -> Dict\[str, Any]:
"""
Poll for new answers and save to state file.
Use this in background polling tasks (cron/loop).

Args: question_id: The question ID title: Question title (optional, for display)

Returns: {question_id, new_answers_count, has_new_answers} """ state = _load_state(question_id) if not state: state = {"question_id": question_id, "title": title, "resolved": False}

if state.get("resolved"): return {"question_id": question_id, "new_answers_count": 0, "has_new_answers": False, "resolved": True}

result = get_new_answers(question_id, state.get("last_answer_id")) answers = result.get("answers", [])

if answers: state["new_answers"] = answers state["last_answer_id"] = result.get("last_answer_id")

_save_state(question_id, state)

return { "question_id": question_id, "new_answers_count": len(answers), "has_new_answers": len(answers) > 0 }


def check\_new\_answers() -> List\[Dict\[str, Any]]:
"""
Check all polling questions for new answers.
Use this in heartbeat hooks.

Returns: List of questions with new answers: [{question_id, title, new_answers}] """ _ensure_state_dir() results = []

for filename in os.listdir(STATE_DIR): if not filename.endswith(".json"): continue

question_id = filename[:-5] state = _load_state(question_id)

if state and not state.get("resolved") and state.get("new_answers"): results.append({ "question_id": question_id, "title": state.get("title", ""), "new_answers": state["new_answers"] })

return results


def clear\_new\_answers(question\_id: str) -> None:
"""
Clear new\_answers after processing them.
Call this after evaluating answers in your heartbeat loop.
"""
state = \_load\_state(question\_id)
if state:
state\["new\_answers"] = \[]
\_save\_state(question\_id, state)

def mark\_resolved(question\_id: str, resolution: str = "") -> None:
"""
Mark a question as resolved.
Call this after finding a satisfactory answer.
"""
state = \_load\_state(question\_id)
if state:
state\["resolved"] = True
state\["resolution"] = resolution
state\["resolved\_at"] = datetime.now().isoformat()
state\["new\_answers"] = \[]
\_save\_state(question\_id, state)

def \_load\_state(question\_id: str) -> Optional\[Dict\[str, Any]]:
"""Load state from file."""
filepath = os.path.join(STATE\_DIR, f"{question\_id}.json")
if os.path.exists(filepath):
with open(filepath, "r") as f:
return json.load(f)
return None

def \_save\_state(question\_id: str, state: Dict\[str, Any]) -> None:
"""Save state to file."""
\_ensure\_state\_dir()
state\["updated\_at"] = datetime.now().isoformat()
filepath = os.path.join(STATE\_DIR, f"{question\_id}.json")
with open(filepath, "w") as f:
json.dump(state, f, indent=2)

</skill-script>


License

MIT License

适合场景

01

OpenClaw 用户查找和安装 Skill 时

02

用户想查找某类 Agent Skill 时

03

需要根据任务场景推荐可安装能力包时

04

需要对比不同来源的安装命令和来源信息时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

补充不同宿主或平台的使用分布数据

能力 5

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

OpenClaw

80.47%
按下载量换算587

安全审计

VirusTotal

可疑

ClawScan

可疑

Static analysis

通过

权限和风险

敏感数据

该 Skill 可能接触密钥、Token、环境变量或敏感配置,应进入高风险复核队列,默认不自动发布。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。来源安全扫描存在 warning/failed 结果,不能写成本站确认安全。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills