โ† All skills
Tencent SkillHub ยท Productivity

Permissions Broker

Interact with the Permissions Broker service to fetch data from Google APIs behind a Telegram approval gate. Use when an agent needs to read Google Drive/Doc...

skill openclawclawhub Free
0 Downloads
0 Stars
0 Installs
0 Score
High Signal

Interact with the Permissions Broker service to fetch data from Google APIs behind a Telegram approval gate. Use when an agent needs to read Google Drive/Doc...

โฌ‡ 0 downloads โ˜… 0 stars Unverified but indexed

Install for OpenClaw

Quick setup
  1. Download the package from Yavira.
  2. Extract the archive and review SKILL.md first.
  3. Import or place the package into your OpenClaw setup.

Requirements

Target platform
OpenClaw
Install method
Manual import
Extraction
Extract archive
Prerequisites
OpenClaw
Primary doc
SKILL.md

Package facts

Download mode
Yavira redirect
Package format
ZIP package
Source platform
Tencent SkillHub
What's included
SKILL.md, references/caldav.md, references/api_reference.md

Validation

  • Use the Yavira download entry.
  • Review SKILL.md after the package is downloaded.
  • Confirm the extracted package contains the expected setup assets.

Install with your agent

Agent handoff

Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.

  1. Download the package from Yavira.
  2. Extract it into a folder your agent can access.
  3. Paste one of the prompts below and point your agent at the extracted folder.
New install

I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Tell me what you changed and call out any manual steps you could not complete.

Upgrade existing

I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Summarize what changed and any follow-up checks I should run.

Trust & source

Release facts

Source
Tencent SkillHub
Verification
Indexed source record
Version
1.0.9

Documentation

ClawHub primary doc Primary doc: SKILL.md 20 sections Open source page

Setup (Do This First)

Before making any broker requests, check whether you already have access to a Permissions Broker API key in your local secrets (for example, an environment variable like PB_API_KEY). If you do NOT have an API key available: Ask the user to create one in Telegram by messaging the bot: /key <name> Tell the user to send the resulting key back to you (the key is shown "sent once"). Ask whether they want you to store/reuse it across sessions. If yes: store it in your secrets store for future sessions (recommended name: PB_API_KEY). If no/unclear: treat it as session-only and ask for it again next time. Remember only the storage location (e.g. "Permissions Broker key is stored in PB_API_KEY"), never the secret value. Important: Never commit the key to the repo. Never include the key in code, logs, or error output. Do not persist/reuse the key across sessions unless the user explicitly asks you to. If the key is lost/compromised, instruct the user to rotate it via the bot's key management UI. Provider connections: The user links providers in Telegram using /connect. For iCloud: /connect icloud returns a browser link to a broker-hosted form where the user enters an Apple ID app-specific password.

Overview

Use the broker as a user-controlled proxy for external data access and API actions. The mental model: You propose a specific upstream request (URL + method + body) The user approves/denies it in Telegram You poll for approval You execute it exactly once and immediately parse/persist what you need This skill is intentionally provider-agnostic. Provider support grows over time.

Agent Response Style (Important)

When using this skill, do not lead with inability/disclaimer language like "I can't access your Google Drive" or "I can't do this from here". Instead: Treat the broker as the standard mechanism for access. Ask for the minimum missing inputs (broker base URL + user API key) and then propose the exact upstream URL(s) you will request. Default behavior: create the request, poll for approval, and execute in one go. Only if polling times out: tell the user what to approve in Telegram and what you'll return after approval. Avoid: Long meta explanations about the repo setup. Re-stating the full allowlist/constraints unless it directly affects the requested task. Preferred framing: "I can do that via your Permissions Broker. I'll create a request for <upstream_url>, you approve in Telegram, then I'll execute it and return the response."

Polling Behavior (Important)

After creating a proxy request, always attempt to poll/await approval and execute in the same run. Only ask the user to approve in Telegram if polling times out. Guidelines: Default to 30 seconds of polling (or longer if the user explicitly asks you to wait). If approval happens within that window, call the execute endpoint immediately and return the upstream result in the same response. If approval has not happened within that window: Return the request_id. Tell the user to approve/deny the request in Telegram. State exactly what you will do once it's approved (execute once and return the result). Continue polling on the next user message.

Core Workflow

Collect inputs User API key (never paste into logs; never store in repo) Decide how to access the provider If the agent already has explicit, local credentials for the provider and the user explicitly wants you to use them, you may. Otherwise (default), use the broker. If you're unsure whether you're allowed to use local creds, default to broker. Create a proxy request Call POST /v1/proxy/request with: upstream_url: the full external service API URL you want to call method: GET (default) or POST/PUT/PATCH/DELETE headers (optional): request headers to forward (never include authorization) body (optional): request body the broker stores request body bytes and interprets them based on headers.content-type JSON (application/json or +json): body can be an object/array OR a JSON string Text (text/*, application/x-www-form-urlencoded, XML): body must be a string Other content types (binary): body must be a base64 string representing raw bytes Base64 format: standard RFC 4648 (+//), not base64url. Include padding (=) when in doubt. Do not include data:...;base64, prefixes. optional consent_hint: requester note shown to the user in Telegram. Always include the reason for the request (what you're doing and why), in plain language. optional idempotency_key: reuse request id on retries Notes on forwarded headers: The broker injects upstream Authorization using the linked account; any caller-provided authorization header is ignored. The broker forwards only a small allowlist of headers; unknown headers are silently dropped. Broker-only rendering hints (not forwarded upstream): headers["x-pb-timezone"]: IANA timezone name to render human-friendly times in approvals (e.g. America/Los_Angeles). The user is prompted to approve in Telegram. The approval prompt includes: API key label (trusted identity) interpreted summary when recognized (best-effort) raw URL details Poll for status / retrieve result Poll GET /v1/proxy/requests/:id until the request is APPROVED. Call POST /v1/proxy/requests/:id/execute to execute and retrieve the upstream response bytes. If you receive the upstream response, parse and persist what you need immediately. Do not assume you can execute the same request again. Important: Both status polling and execute require the exact API key that created the request. Using a different API key (even for the same user) returns 403.

Sample Code (Create + Await)

Use these snippets to create a broker request, poll status, then execute to retrieve upstream bytes. JavaScript/TypeScript (Bun/Node) type CreateRequestResponse = { request_id: string; status: string; approval_expires_at: string; }; type StatusResponse = { request_id: string; status: string; approval_expires_at?: string; error?: string; error_code?: string | null; error_message?: string | null; upstream_http_status?: number | null; upstream_content_type?: string | null; upstream_bytes?: number | null; }; async function createBrokerRequest(params: { baseUrl: string; apiKey: string; upstreamUrl: string; method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE"; headers?: Record<string, string>; body?: unknown; consentHint?: string; idempotencyKey?: string; }): Promise<CreateRequestResponse> { const res = await fetch(`${params.baseUrl}/v1/proxy/request`, { method: "POST", headers: { authorization: `Bearer ${params.apiKey}`, "content-type": "application/json", }, body: JSON.stringify({ upstream_url: params.upstreamUrl, method: params.method ?? "GET", headers: params.headers, body: params.body, consent_hint: params.consentHint, idempotency_key: params.idempotencyKey, }), }); if (!res.ok) { throw new Error(`broker create failed: ${res.status} ${await res.text()}`); } return (await res.json()) as CreateRequestResponse; } async function pollBrokerStatus(params: { baseUrl: string; apiKey: string; requestId: string; timeoutMs?: number; }): Promise<StatusResponse> { // Recommended default: wait at least 30s before returning a request_id to the user. const deadline = Date.now() + (params.timeoutMs ?? 30_000); while (Date.now() < deadline) { const res = await fetch( `${params.baseUrl}/v1/proxy/requests/${params.requestId}`, { headers: { authorization: `Bearer ${params.apiKey}` }, }, ); // Status endpoint always returns JSON for both 202 and 200. const data = (await res.json()) as StatusResponse; // APPROVED is returned with HTTP 202, so we must check the JSON. if (data.status === "APPROVED") return data; if (res.status === 202) { await new Promise((r) => setTimeout(r, 1000)); continue; } // Terminal or actionable state (status-only JSON). if (!res.ok && res.status !== 403 && res.status !== 408) { throw new Error(`broker status failed: ${res.status} ${JSON.stringify(data)}`); } return data; } throw new Error("timed out waiting for approval"); } async function awaitApprovalThenExecute(params: { baseUrl: string; apiKey: string; requestId: string; timeoutMs?: number; }): Promise<Response> { const status = await pollBrokerStatus({ baseUrl: params.baseUrl, apiKey: params.apiKey, requestId: params.requestId, timeoutMs: params.timeoutMs, }); if (status.status !== "APPROVED") { throw new Error(`request not approved yet (status=${status.status})`); } return executeBrokerRequest({ baseUrl: params.baseUrl, apiKey: params.apiKey, requestId: params.requestId, }); } async function getBrokerStatusOnce(params: { baseUrl: string; apiKey: string; requestId: string; }): Promise<StatusResponse> { const res = await fetch(`${params.baseUrl}/v1/proxy/requests/${params.requestId}`, { headers: { authorization: `Bearer ${params.apiKey}` }, }); // Always JSON (even for 202). return (await res.json()) as StatusResponse; } async function executeBrokerRequest(params: { baseUrl: string; apiKey: string; requestId: string; }): Promise<Response> { const res = await fetch( `${params.baseUrl}/v1/proxy/requests/${params.requestId}/execute`, { method: "POST", headers: { authorization: `Bearer ${params.apiKey}` }, }, ); // Terminal: upstream bytes (2xx/4xx/5xx) or broker error JSON (403/408/409/410/etc). // IMPORTANT: // - execution is one-time; subsequent calls return 410. // - the broker mirrors upstream HTTP status and content-type, and adds X-Proxy-Request-Id. // - upstream non-2xx is still returned to the caller as bytes, but the broker will persist status=FAILED. return res; } // Suggested control flow: // - Start polling for ~30 seconds. // - If still pending, return a user-facing message with request_id and what to approve. // - On the next user message, poll again (or recreate if expired/consumed). // Example usage // const baseUrl = "https://permissions-broker.steer.fun" // const apiKey = process.env.PB_API_KEY! // const upstreamUrl = "https://www.googleapis.com/drive/v3/files?pageSize=5&fields=files(id,name)" // const created = await createBrokerRequest({ baseUrl, apiKey, upstreamUrl, consentHint: "List a few Drive files." }) // Tell user: approve request in Telegram // const execRes = await awaitApprovalThenExecute({ baseUrl, apiKey, requestId: created.request_id, timeoutMs: 30_000 }) // const bodyText = await execRes.text() // GitHub example (create PR) // const created = await createBrokerRequest({ // baseUrl, // apiKey, // upstreamUrl: "https://api.github.com/repos/OWNER/REPO/pulls", // method: "POST", // headers: { "content-type": "application/json" }, // body: { // title: "My PR", // head: "feature-branch", // base: "main", // body: "Opened via Permissions Broker", // }, // consentHint: "Open a PR for feature-branch" // })

Supported Providers (Today)

The broker enforces an allowlist and chooses which linked account (OAuth token) to use based on the upstream hostname. Currently supported: Google Hosts: docs.googleapis.com, www.googleapis.com, sheets.googleapis.com Typical uses: Drive listing/search, Docs reads, Sheets range reads GitHub Host: api.github.com Typical uses: PRs/issues/comments/labels and other GitHub actions iCloud (CalDAV) Hosts: discovered on connect (starts at caldav.icloud.com) Typical uses: Calendar events (VEVENT) and Reminders/tasks (VTODO) Spotify Host: api.spotify.com Typical uses: read profile, list playlists/tracks, control playback If you need a provider that isn't supported yet: Still use the broker pattern in your plan (propose the upstream call + consent text). Then tell the user which host(s) need to be enabled/implemented. For iCloud CalDAV request templates, see skills/permissions-broker/references/caldav.md.

Git Operations (Smart HTTP Proxy)

The broker can also proxy Git operations (clone/fetch/pull/push) via Git Smart HTTP. This is separate from /v1/proxy. High-level flow: Create a git session (POST /v1/git/sessions). The user approves/denies the session in Telegram. Poll session status (GET /v1/git/sessions/:id) until approved. Fetch a session-scoped remote URL (GET /v1/git/sessions/:id/remote). Run git clone / git push against that remote URL. Important behavior: Clone/fetch sessions may require multiple git-upload-pack POSTs during a single clone. Push sessions are single-use and may become unusable after the first git-receive-pack. Push protections are enforced by the broker: tag pushes are rejected ref deletes are rejected default-branch pushes may be blocked unless explicitly allowed in the approval

Endpoints

Auth for all git session endpoints: Authorization: Bearer <USER_API_KEY> Create session POST /v1/git/sessions JSON body: operation: "clone", "fetch", "pull", or "push" repo: "owner/repo" (GitHub) optional consent_hint: requester note shown to the user in Telegram. Always include the reason for the session (what you're doing and why). Response: { "session_id": "...", "status": "PENDING_APPROVAL", "approval_expires_at": "..." } Poll status GET /v1/git/sessions/:id (status JSON) Get remote URL GET /v1/git/sessions/:id/remote Response: { "remote_url": "https://..." }

Example: Clone

Create session: { "operation": "clone", "repo": "OWNER/REPO", "consent_hint": "Clone repo to inspect code" }

Example: Fetch

Use fetch when you already have a repo locally and just need to update refs. Create session: { "operation": "fetch", "repo": "OWNER/REPO", "consent_hint": "Fetch latest refs to update local checkout" } Poll until approved. Get remote_url, then: git fetch "<remote_url>" --prune

Example: Pull

git pull is a fetch plus a local merge/rebase. The broker only proxies the network portion. git pull "<remote_url>" main Poll until status == "APPROVED". Get remote_url, then: git clone "<remote_url>" ./repo

Example: Push New Branch (Recommended)

Create session: { "operation": "push", "repo": "OWNER/REPO", "consent_hint": "Push branch feature-x for a PR" } Poll until approved. Get remote_url, add as a remote, then push to a non-default branch: git remote add broker "<remote_url>" git push broker "HEAD:refs/heads/feature-x" Notes: Prefer creating a new branch name (e.g. pb/<task>/<timestamp>) rather than pushing to main. If the broker session becomes USED, create a new push session. Python (requests) import time import requests def create_request(base_url, api_key, upstream_url, consent_hint=None, idempotency_key=None): # Optional: method/headers/body for non-GET requests. r = requests.post( f"{base_url}/v1/proxy/request", headers={"Authorization": f"Bearer {api_key}"}, json={ "upstream_url": upstream_url, # "method": "POST", # "headers": {"accept": "application/vnd.github+json"}, # "headers": {"content-type": "application/json"}, # "body": {"title": "...", "head": "...", "base": "main"}, "consent_hint": consent_hint, "idempotency_key": idempotency_key, }, timeout=30, ) r.raise_for_status() return r.json() def await_result(base_url, api_key, request_id, timeout_s=120): deadline = time.time() + timeout_s while time.time() < deadline: r = requests.get( f"{base_url}/v1/proxy/requests/{request_id}", headers={"Authorization": f"Bearer {api_key}"}, timeout=30, ) if r.status_code == 202: time.sleep(1) continue # Terminal response (status-only JSON). return r.json() raise TimeoutError("timed out waiting for approval") def execute_request(base_url, api_key, request_id): # IMPORTANT: execution is one-time; read and store now. return requests.post( f"{base_url}/v1/proxy/requests/{request_id}/execute", headers={"Authorization": f"Bearer {api_key}"}, timeout=60, ) def await_approval_then_execute(base_url, api_key, request_id, timeout_s=30): status = await_result(base_url, api_key, request_id, timeout_s=timeout_s) if status.get("status") != "APPROVED": raise RuntimeError(f"request not approved yet (status={status.get('status')})") return execute_request(base_url, api_key, request_id)

Constraints You Must Respect

Upstream scheme: HTTPS only. Upstream host allowlist: provider-defined (the request must target a supported host). Upstream methods: GET/POST/PUT/PATCH/DELETE. Upstream response size cap: 1 MiB. Upstream request body cap: 256 KiB. One-time execution: after executing a request, you cannot execute it again.

Sheets Note (Without Drama)

The broker supports the Google Sheets API host (sheets.googleapis.com). Preferred approach for reading spreadsheet data: Use Drive search/list to find the spreadsheet file. Use Sheets values read to fetch only the range you need. Fallback: Use Drive export to fetch contents as CSV when that is sufficient. Note: large exports can exceed the broker's 1 MiB upstream response cap. If an export fails due to size, narrow the scope (smaller range, fewer tabs, or fewer rows/columns).

Handling Common Terminal States

202: request is still actionable; JSON includes status (often PENDING_APPROVAL, APPROVED, or EXECUTING). If status == APPROVED, execute immediately. Otherwise keep polling. 403: denied by user. 403: forbidden (wrong API key or request not accessible) is also possible; inspect {error: ...}. 408: approval expired (user did not decide in time). 409: already executing; retry shortly. 410: already executed; recreate the request if you still need it.

How To Build Upstream URLs (Google example)

Prefer narrow reads so approvals are understandable and responses are small. Drive search/list files: https://www.googleapis.com/drive/v3/files?... Use q, pageSize, and fields to minimize payload. Drive export file contents: https://www.googleapis.com/drive/v3/files/{fileId}/export?mimeType=... Useful for Google Docs/Sheets export to text/plain or text/csv. Docs structured doc read: https://docs.googleapis.com/v1/documents/{documentId}?fields=... See references/api_reference.md for endpoint details and a Google URL cheat sheet.

How To Build Upstream URLs (GitHub examples)

Create PR: POST https://api.github.com/repos/<owner>/<repo>/pulls JSON body: { "title": "...", "head": "branch", "base": "main", "body": "..." } Create issue: POST https://api.github.com/repos/<owner>/<repo>/issues JSON body: { "title": "...", "body": "..." }

Data Handling Rules

Treat the user's API key as secret.

Resources

Reference: references/api_reference.md

Category context

Workflow acceleration for inboxes, docs, calendars, planning, and execution loops.

Source: Tencent SkillHub

Largest current source with strong distribution and engagement signals.

Package contents

Included in package
3 Docs
  • SKILL.md Primary doc
  • references/api_reference.md Docs
  • references/caldav.md Docs