Skip to content
Back to skills

Naver Blog Writer

BSecurity

이 스킬은 ACP/OpenClaw 환경에서 네이버 블로그 발행을 자동화합니다.

  • 16 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added June 7, 2026
toolsbashnodeapi

Works with

  • cli
  • api

Security analysis

B75/100
  • criticalSends environment variables or credentials to an external URL
  • criticalDownloads and executes remote scripts — classic supply chain attack

Pro scans all 8 files and shows the line behind each finding

Scanned June 7, 2026

npx -y skills add modbender/skill-library-mcp --skill naver-blog-writer --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Naver Blog Writer?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Naver Blog Writer
[![Security: B — Skills Directory](https://www.skillsdirectory.com/api/skills/modbender-naver-blog-writer/badge)](https://www.skillsdirectory.com/skills/modbender-naver-blog-writer)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: Naver Blog Writer Skill
description: 이 스킬은 ACP/OpenClaw 환경에서 네이버 블로그 발행을 자동화합니다.
---

# Naver Blog Writer Skill

이 스킬은 ACP/OpenClaw 환경에서 네이버 블로그 발행을 자동화합니다.

## Important

- ClawHub 배포본에는 보통 `SKILL.md`만 포함됩니다.
- 따라서 `tools/preflight`, `tools/publish` 같은 로컬 파일 경로를 전제로 실행하지 마세요.
- 항상 아래 **Inline Runner** 절차를 사용하세요.

## Billing Prerequisites

- 이 스킬은 ACP 유료 오퍼링(`naver-blog-writer`)입니다.
- 결제/사용 승인 후에만 온보딩 증빙이 생성됩니다.
- 온보딩 증빙(둘 중 하나 필요):
  - `SETUP_URL`
  - `PROOF_TOKEN + SETUP_ISSUE_URL`
- 결제 컨텍스트가 없으면 다음 형식으로 실패를 반환하세요:

```json
{
  "error": "RUNNER_NOT_READY",
  "setup_url": null,
  "next_action": "PURCHASE_AND_GET_SETUP_CREDENTIALS",
  "hint": "Purchase/authorize this ACP offering first, then provide SETUP_URL or PROOF_TOKEN + SETUP_ISSUE_URL.",
  "missing": ["ACP_PAYMENT_CONTEXT", "SETUP_URL_OR_PROOF_TOKEN_AND_SETUP_ISSUE_URL"]
}
```

## Runtime Config

- `OPENCLAW_OFFERING_ID` (default: `naver-blog-writer`)
- `X_LOCAL_TOKEN` (optional; 없으면 `~/.config/naver-thin-runner/config.json`에서 자동 로드)
- `LOCAL_DAEMON_PORT` (default: `19090`)
- `DEVICE_FINGERPRINT` (default: `hostname-platform-arch`)
- `OPENCLAW_OFFERING_EXECUTE_URL` (+ optional `OPENCLAW_CORE_API_KEY`)
- fallback: `CONTROL_PLANE_URL` + `ACP_ADMIN_API_KEY`

## Inline Runner (Always Use This)

아래 스크립트를 실행해 게시하세요.  
입력: `TITLE`, `BODY`, optional `TAGS` (comma separated).

```bash
bash -s -- --title "제목" --body "본문" --tags "tag1,tag2" <<'BASH'
set -euo pipefail

TITLE=''
BODY=''
TAGS=''
while [[ $# -gt 0 ]]; do
  case "$1" in
    --title) TITLE="$2"; shift 2 ;;
    --body) BODY="$2"; shift 2 ;;
    --tags) TAGS="$2"; shift 2 ;;
    *) echo "unknown arg: $1" >&2; exit 1 ;;
  esac
done

if [[ -z "$TITLE" || -z "$BODY" ]]; then
  echo '{"error":"INVALID_INPUT","next_action":"PROVIDE_TITLE_AND_BODY","hint":"title and body are required"}'
  exit 1
fi

SETUP_URL="${SETUP_URL:-}"
PROOF_TOKEN="${PROOF_TOKEN:-}"
SETUP_ISSUE_URL="${SETUP_ISSUE_URL:-}"
DEVICE_FINGERPRINT="${DEVICE_FINGERPRINT:-$(hostname)-$(uname -s)-$(uname -m)}"
LOCAL_DAEMON_PORT="${LOCAL_DAEMON_PORT:-19090}"
X_LOCAL_TOKEN="${X_LOCAL_TOKEN:-${THIN_RUNNER_LOCAL_TOKEN:-}}"
OFFERING_ID="${OPENCLAW_OFFERING_ID:-naver-blog-writer}"
OFFERING_EXECUTE_URL="${OPENCLAW_OFFERING_EXECUTE_URL:-}"
OFFERING_API_KEY="${OPENCLAW_CORE_API_KEY:-}"
OFFERING_API_KEY_HEADER="${OPENCLAW_CORE_API_KEY_HEADER:-x-api-key}"
CONTROL_PLANE_URL="${CONTROL_PLANE_URL:-${ACP_RUNNER_SERVER_URL:-}}"
ADMIN_KEY_RAW="${ACP_ADMIN_API_KEY:-${ACP_CONTROL_PLANE_API_KEYS:-}}"
ADMIN_KEY="${ADMIN_KEY_RAW%%,*}"

json_error() {
  local code="$1"; local action="$2"; local hint="$3"; local setup="${4:-}"
  node - "$code" "$action" "$hint" "$setup" <<'NODE'
const code = process.argv[2];
const action = process.argv[3];
const hint = process.argv[4];
const setup = process.argv[5] || '';
console.log(JSON.stringify({
  error: code,
  setup_url: setup || null,
  next_action: action,
  hint,
}, null, 2));
NODE
}

read_local_token() {
  node - <<'NODE'
const fs = require('node:fs');
const p = `${process.env.HOME}/.config/naver-thin-runner/config.json`;
try {
  const j = JSON.parse(fs.readFileSync(p, 'utf8'));
  process.stdout.write(typeof j.localToken === 'string' ? j.localToken.trim() : '');
} catch {
  process.stdout.write('');
}
NODE
}

runner_cmd() {
  if npx @y80163442/naver-thin-runner --help >/dev/null 2>&1; then
    npx @y80163442/naver-thin-runner "$@"
  else
    echo '{"error":"RUNNER_CLI_NOT_FOUND","next_action":"INSTALL_THIN_RUNNER","hint":"Install @y80163442/naver-thin-runner first"}'
    exit 1
  fi
}

issue_setup_url() {
  if [[ -n "$SETUP_URL" ]]; then
    printf '%s' "$SETUP_URL"; return 0
  fi
  if [[ -z "$PROOF_TOKEN" || -z "$SETUP_ISSUE_URL" ]]; then
    printf ''; return 0
  fi
  local payload
  payload="$(node - "$PROOF_TOKEN" "$DEVICE_FINGERPRINT" <<'NODE'
const proof = process.argv[2];
const fp = process.argv[3];
process.stdout.write(JSON.stringify({ proof_token: proof, device_fingerprint: fp }));
NODE
)"
  curl -sS -X POST -H 'content-type: application/json' -d "$payload" "$SETUP_ISSUE_URL" \
    | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{const j=JSON.parse(d);process.stdout.write(j.setup_url||'')}catch{process.stdout.write('')}});"
}

if [[ -z "$X_LOCAL_TOKEN" ]]; then
  X_LOCAL_TOKEN="$(read_local_token)"
fi

identity_tmp="$(mktemp)"
seal_tmp="$(mktemp)"
exec_tmp="$(mktemp)"
trap 'rm -f "$identity_tmp" "$seal_tmp" "$exec_tmp"' EXIT

call_identity() {
  curl -sS --max-time 5 -o "$identity_tmp" -w '%{http_code}' \
    -H "x-local-token: ${X_LOCAL_TOKEN}" \
    "http://127.0.0.1:${LOCAL_DAEMON_PORT}/v1/local/identity" || true
}

http_code="$(call_identity)"
if [[ "$http_code" != "200" ]]; then
  # try daemon boot once
  runner_cmd daemon start --port "$LOCAL_DAEMON_PORT" >/tmp/naver-thin-runner-daemon.log 2>&1 &
  sleep 2
  http_code="$(call_identity)"
fi

if [[ "$http_code" != "200" ]]; then
  setup_url="$(issue_setup_url)"
  if [[ -z "$setup_url" && ( -z "$PROOF_TOKEN" || -z "$SETUP_ISSUE_URL" ) ]]; then
    json_error \
      "RUNNER_NOT_READY" \
      "PURCHASE_AND_GET_SETUP_CREDENTIALS" \
      "Purchase/authorize ACP offering and provide SETUP_URL or PROOF_TOKEN + SETUP_ISSUE_URL." \
      ""
    exit 2
  fi

  if [[ -n "$setup_url" ]]; then
    runner_cmd setup --setup-url "$setup_url" --auto-service both
  else
    runner_cmd setup --proof-token "$PROOF_TOKEN" --setup-issue-url "$SETUP_ISSUE_URL" --device-fingerprint "$DEVICE_FINGERPRINT" --auto-service both
  fi

  # first-time login (human action in browser)
  runner_cmd login
  X_LOCAL_TOKEN="$(read_local_token)"
  runner_cmd daemon start --port "$LOCAL_DAEMON_PORT" >/tmp/naver-thin-runner-daemon.log 2>&1 &
  sleep 2
  http_code="$(call_identity)"
fi

if [[ "$http_code" != "200" ]]; then
  json_error "RUNNER_NOT_READY" "CHECK_LOCAL_DAEMON" "Local daemon identity failed after setup/login retry." ""
  exit 2
fi

seal_payload="$(node - "$TITLE" "$BODY" "$TAGS" <<'NODE'
const title = process.argv[2] || '';
const body = process.argv[3] || '';
const tags = (process.argv[4] || '').split(',').map(v => v.trim()).filter(Boolean);
process.stdout.write(JSON.stringify({ title, body_markdown: body, tags, publish_at: null }));
NODE
)"

seal_code="$({
  curl -sS -o "$seal_tmp" -w '%{http_code}' \
    -X POST \
    -H 'content-type: application/json' \
    -H "x-local-token: ${X_LOCAL_TOKEN}" \
    -d "$seal_payload" \
    "http://127.0.0.1:${LOCAL_DAEMON_PORT}/v1/local/seal-job"
} || true)"

if [[ "$seal_code" != "200" ]]; then
  cat "$seal_tmp"; exit 1
fi

requirement_payload="$(node - "$(cat "$identity_tmp")" "$(cat "$seal_tmp")" <<'NODE'
const identity = JSON.parse(process.argv[2]);
const sealed = JSON.parse(process.argv[3]);
process.stdout.write(JSON.stringify({
  runner_attestation: identity.runner_attestation,
  sealed_payload: sealed.sealed_payload,
  payload_digest: sealed.payload_digest,
  idempotency_key: sealed.idempotency_key,
}));
NODE
)"

if [[ -n "$OFFERING_EXECUTE_URL" ]]; then
  execute_body="$(node - "$OFFERING_ID" "$requirement_payload" <<'NODE'
const offeringId = process.argv[2];
const requirements = JSON.parse(process.argv[3]);
process.stdout.write(JSON.stringify({ offering_id: offeringId, requirements }));
NODE
)"
  if [[ -n "$OFFERING_API_KEY" ]]; then
    curl -sS -X POST -H 'content-type: application/json' -H "${OFFERING_API_KEY_HEADER}: ${OFFERING_API_KEY}" -d "$execute_body" "$OFFERING_EXECUTE_URL"
  else
    curl -sS -X POST -H 'content-type: application/json' -d "$execute_body" "$OFFERING_EXECUTE_URL"
  fi
  exit 0
fi

if [[ -z "$CONTROL_PLANE_URL" || -z "$ADMIN_KEY" ]]; then
  json_error "EXECUTE_ENDPOINT_NOT_CONFIGURED" "CONFIGURE_EXECUTE_ENDPOINT" "Set OPENCLAW_OFFERING_EXECUTE_URL or CONTROL_PLANE_URL + ACP_ADMIN_API_KEY." ""
  exit 1
fi

curl -sS -X POST \
  -H 'content-type: application/json' \
  -H "x-api-key: ${ADMIN_KEY}" \
  -d "$requirement_payload" \
  "${CONTROL_PLANE_URL%/}/v2/jobs/dispatch-and-wait"
BASH
```

## Agent Behavior Rules

- `RUNNER_NOT_READY`면 위 Inline Runner를 통해 auto-setup/login을 먼저 시도하세요.
- 자동 복구 후에도 실패하면 원문 JSON(`error`, `next_action`, `hint`)을 사용자에게 그대로 전달하세요.
- 사람 개입이 필요한 단계는 기본적으로 네이버 로그인 1회뿐입니다.

Files in this skill

  • SKILL.md8.7 KB
  • references/ops-checklist.md1 KB
  • references/publish-checklist.md928 B
  • references/setup.md1.3 KB
  • scripts/preflight.sh2.7 KB
  • scripts/publish.sh5.1 KB
  • scripts/setup_runner.sh1.3 KB
  • scripts/smoke_test.sh905 B

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.