Skill: lobster-de-luo/know-my-larkmate-openclaw (version 0.1.0)

--- name: know-my-larkmate-openclaw description: Sync recent Lark context into OpenClaw daily notes. Use during heartbeat runs, after relevant Lark reads, when the user asks what they have been doing or focused on recently, or when setting up this OpenClaw-native recent-context workflow. metadata: {"openclaw":{"requires":{"bins":["lark-cli"]},"install":[{"id":"node","kind":"node","package":"@larksuite/cli","bins":["lark-cli"],"label":"Install lark-cli (npm)"}]}} --- # know-my-larkmate-openclaw Use this skill to keep OpenClaw closely synced to the user's recent context, not just to write one-off summaries. This skill is designed for an OpenClaw setup that uses: - `memory/YYYY-MM-DD.md` for fast context sync and evidence logs - `HEARTBEAT.md` for scheduled consolidation tasks This skill should write reliable, high-signal recent context into daily notes and stop there. Do not invent a parallel memory system, and do not manage long-term promotion yourself unless the user explicitly asks for that. For install/setup requirements, read [references/install-spec.md](references/install-spec.md). The recommended heartbeat template lives at [assets/HEARTBEAT.md](assets/HEARTBEAT.md). Heartbeat should update today's daily note on every useful run, and any relevant Lark-reading turn should do the same before replying. ## When To Use Keep this section aligned with the frontmatter description. Use this skill in any of these cases: - A heartbeat run should refresh the agent's view of the user's current work context. - Any normal turn that read relevant Lark artifacts should sync a short context snapshot before replying. - The user asks what they have been doing recently, what they are focused on, or what has changed lately. - The user asks to update, refresh, or accumulate recent context from Lark activity. - The user wants this OpenClaw-native recent-context workflow in place. ## Source Priority Prefer sources in this order: 1. Explicit user statements in the current chat 2. Repeated evidence across independent Lark sources 3. Single-source weak inferences ## What To Capture Capture these in today's daily note when they are newly relevant: - Active workstreams and current priorities - Recent meetings, documents, threads, and decisions that will likely matter in the next few turns - Current blockers, unresolved questions, and likely next asks - Newly important collaborators or stakeholders - Explicitly stated near-term preferences that may change how the next few turns should go Do not persist: - One-off task trivia - Third-party private information - Sensitive personal data - Speculative personality judgments - Anything blocked by missing permissions or unreadable sources ## Write Rules - Every useful heartbeat should append a short current-context update to today's daily note. - Use the `Output Format` block below instead of free-form notes. - Keep records factual, recent, and source-linked. - If something looks stable enough for long-term memory, still record it in the daily note and let OpenClaw's own promotion mechanisms handle the rest. - Conflicts should be logged as observations instead of silently normalizing them. - If a Lark read fails because tools or scopes are missing, record the failure as a blocked observation instead of inferring. - If the failure is a permission error, request only the missing read-only scope(s), then retry the same read. Do not request write scopes from this skill. ## OpenClaw File Roles - `memory/YYYY-MM-DD.md` - Append short records for fresh context, evidence, and blocked scans. - This is the main output of the skill and should be updated on each useful heartbeat run. - Use it for evidence and current context, not polished prose. - `HEARTBEAT.md` - Define recurring scan and light consolidation tasks. - Heartbeat should stay quiet by default and only surface conflicts or high-value changes. ## Operating Workflow 1. Read today's plus yesterday's daily notes if they exist. 2. Gather new user signals from direct chat or Lark artifacts. 3. Identify what changed in the user's near-term context: active workstreams, recent collaborators, new decisions, blockers, likely next requests. 4. Append a short daily-note record for each useful new signal. 5. If there is nothing new, do not manufacture context. 6. Leave long-term memory promotion to OpenClaw unless the user explicitly asks otherwise. ## Lark Read Guidance Preferred reads: - `lark-cli im +messages-search` - `lark-cli calendar +agenda` - `lark-cli vc +search` - `lark-cli minutes +search` - `lark-cli docs +search` - `lark-cli docs +fetch` - `lark-cli wiki spaces list` - `lark-cli wiki spaces get_node` When the job is specifically about reading Lark activity, also open [references/recent-context-guide.md](references/recent-context-guide.md). ## Output Format When updating `memory/YYYY-MM-DD.md`, use this structure: ```markdown # [Analysis Title: concrete and time-scoped, e.g. Heartbeat Context Sync - 2026-04-14 10:00] ## Executive summary [One short paragraph on what changed and why it matters next] ## Key findings - Finding 1 with enough source detail to trace the claim - Finding 2 with enough source detail to trace the claim ## Related Activities 1. Supporting chat, meeting, doc, minute, or task ``` If the run found nothing useful, do not append filler. If the user only asks for the skill or design, do not create the memory files unprompted.

path: agents/openai.yaml
content: interface:
  display_name: "Know My Larkmate"
  short_description: "Sync recent Lark context into OpenClaw daily notes"
  default_prompt: "Use $know-my-larkmate-openclaw to sync recent Lark activity into today's OpenClaw daily note and carry forward the most relevant recent context."

policy:
  allow_implicit_invocation: true


path: assets/HEARTBEAT.md
content: tasks:

- name: recent-lark-scan
  interval: 4h
  prompt: "Use $know-my-larkmate-openclaw to scan the last 72h of messages and 7d of meetings, docs, and minutes. Use lightweight reads and bounded pagination. Process at most 20 artifacts and deep-read at most 3. Keep updates factual and source-linked. If blocked by missing permissions or missing `lark-cli`, note it briefly. Append useful new context to today's memory/YYYY-MM-DD.md; otherwise reply HEARTBEAT_OK."
- name: daily-context-consolidation
  interval: 24h
  prompt: "Review today's and yesterday's snapshots. Merge duplicates, keep still-relevant context, and cut noise. Keep updates factual and source-linked. If blocked by missing permissions or missing `lark-cli`, note it briefly. If nothing changes, reply HEARTBEAT_OK."
- name: weekly-context-prune
  interval: 7d
  prompt: "Prune stale or repetitive recent-context patterns so future heartbeats stay focused. Keep updates factual and source-linked where applicable. If blocked by missing permissions or missing `lark-cli`, note it briefly. If nothing changes, reply HEARTBEAT_OK."


path: references/install-spec.md
content: # Install Checklist

Use this reference when the user is installing, packaging, or validating the skill.

## Definition Of Done

Treat the skill as installed only when all of these are true:
- `lark-cli` is installed and runnable on `PATH`
- the required read-only user scopes are authorized
- at least one live Lark read loop succeeds for the kind of source this skill will use
- the workspace has a usable `HEARTBEAT.md` task block for this skill

Do not treat "skill folder copied successfully" as done.

## Checklist 1: CLI Ready

- [ ] `lark-cli` is available on `PATH`
- [ ] `lark-cli --help` runs successfully
- [ ] if missing, install it through OpenClaw onboarding metadata or manually with `npm i -g @larksuite/cli`

## Checklist 2: Read-Only Auth Ready

- [ ] run `./scripts/request-readonly-auth.sh`
- [ ] keep this skill read-only
- [ ] if a runtime permission error happens later, request only the missing read-only scope(s)

Do not use this skill to request write scopes.

## Checklist 3: Live Validation

- [ ] do not use `lark-cli auth status` as the pass signal
- [ ] run `./scripts/validate-readiness.sh`
- [ ] also verify one or two lightweight live read loops that match real usage

Good enough examples:
- docs: `docs +search` then `docs +fetch`
- wiki: `spaces list` then `nodes list` or `spaces get_node`
- minutes: `minutes +search` then `minutes get`

Use judgment. The goal is to prove the skill can actually read what it needs, not to exhaustively test every path.

## Checklist 4: Workspace Ready

- [ ] if the workspace already has `HEARTBEAT.md`, merge in the task block from [../assets/HEARTBEAT.md](../assets/HEARTBEAT.md)
- [ ] if `HEARTBEAT.md` is missing, copy the template into the workspace root
- [ ] treat `HEARTBEAT.md` as the source of truth for this skill's cadence and task prompts
- [ ] keep the shipped heartbeat style unless the workspace already has a stronger local convention


path: references/recent-context-guide.md
content: # Recent Context Guide

Use this reference when the task requires reading Lark activity and turning it into a recent-context snapshot for `memory/YYYY-MM-DD.md`.

This skill writes heartbeat-sized context updates into daily notes and leaves long-term promotion to OpenClaw.

## What To Look For

Capture changes that should affect the next few turns:
- active workstreams
- recent decisions
- blockers
- likely next asks
- newly important collaborators
- near-term preferences that change how the agent should work

Skip items that are unlikely to matter soon:
- pure system noise
- repeated auth prompts
- bot cards with no immediate meaning
- one-off trivia
- speculative judgments about the user

## Output Format

Append a compact block in this shape:

```markdown
# [Analysis Title: concrete and time-scoped, e.g. Heartbeat Context Sync - 2026-04-14 10:00]

## Executive summary
[One short paragraph on what changed and why it matters next]

## Key findings
- Finding 1 with enough source detail to trace the claim
- Finding 2 with enough source detail to trace the claim

## Related Activities
1. Supporting chat, meeting, doc, minute, or task
```

Example:

```markdown
# Heartbeat Context Sync - 2026-04-14 10:00

## Executive summary
The user is currently focused on making the OpenClaw skill self-contained and making heartbeat memory updates more reliable for recent context. The immediate implementation boundary is to keep writing high-signal recent context into daily notes and avoid introducing a competing long-term promotion flow.

## Key findings
- The user wants the skill to prioritize recent-context sync into `memory/YYYY-MM-DD.md` rather than managing its own long-term memory layer.
- The skill package should be self-contained and avoid references to legacy materials outside the skill directory.

## Related Activities
1. Revised the skill to remove legacy reference dependencies.
2. Renamed the skill to `know-my-larkmate-openclaw` for packaging safety.
```

## Lark Read Shortcuts

Common read entrypoints:

```bash
lark-cli im +messages-search --start "<ISO-8601>" --page-size 50 --page-all --format json --as user
lark-cli calendar +agenda --start "<ISO-8601>" --end "<ISO-8601>" --format pretty --as user
lark-cli vc +search --start "<ISO-8601>" --page-size 20 --format pretty --as user
lark-cli minutes +search --page-size 20 --format json --as user
lark-cli docs +search --query "<keyword>" --page-size 20 --format json --as user
lark-cli docs +fetch --doc "<doc-token-or-url>" --format json --as user
```

## Read Bounds And Filtering

Use bounded reads by default so heartbeat stays cheap and frequent:
- Messages: last 72 hours, `--page-size 50`, use `--page-all` only when volume is still manageable.
- Calendar and VC: last 7 days.
- Docs and wiki: search first, then deep-read at most 3 high-signal items.
- Minutes: read title or summary first, fetch detailed AI outputs only when clearly relevant.

Prefer these filters before writing memory:
- Group messages by chat or thread before concluding something is a recurring theme.
- Prefer recency plus repetition over volume.
- Treat a single meeting title or doc title as context only, not a stable fact.
- When blocked by permissions or missing tools, record the blockage as a finding instead of inferring around it.
- When blocked by permissions, request only the missing read-only scope(s), rerun the failed read, then continue.

When data is too large:
- Truncate oldest material first.
- Preserve the newest artifacts tied to current workstreams.
- Keep enough source detail to trace each finding back to a specific artifact.


path: scripts/request-readonly-auth.sh
content: #!/usr/bin/env bash
set -euo pipefail

SCOPES="auth:user.id:read calendar:calendar.event:read docs:document.content:read docx:document:readonly drive:drive.metadata:readonly im:chat:read im:message.group_msg:get_as_user im:message.p2p_msg:get_as_user minutes:minutes.search:read minutes:minutes:readonly search:docs:read search:message vc:meeting.search:read wiki:node:read wiki:node:retrieve wiki:space:read wiki:space:retrieve offline_access"

exec lark-cli auth login --scope "$SCOPES"


path: scripts/validate-readiness.sh
content: #!/usr/bin/env bash
set -euo pipefail

if ! command -v lark-cli >/dev/null 2>&1; then
  echo "FAIL lark-cli not found on PATH"
  exit 1
fi

tmp_dir="$(mktemp -d)"
trap 'rm -rf "$tmp_dir"' EXIT

python3 - <<'PY' >"$tmp_dir/dates.env"
from datetime import datetime, timedelta, timezone

tz = timezone(timedelta(hours=8))
now = datetime.now(tz)
today_start = now.replace(hour=0, minute=0, second=0, microsecond=0)
tomorrow_start = today_start + timedelta(days=1)
start_72h = now - timedelta(hours=72)
start_7d = now - timedelta(days=7)

print(f'START_72H="{start_72h.isoformat(timespec="seconds")}"')
print(f'START_7D="{start_7d.isoformat(timespec="seconds")}"')
print(f'TODAY_START="{today_start.isoformat(timespec="seconds")}"')
print(f'TOMORROW_START="{tomorrow_start.isoformat(timespec="seconds")}"')
PY

# shellcheck disable=SC1090
source "$tmp_dir/dates.env"

run_probe() {
  local name="$1"
  shift
  local log="$tmp_dir/$name.log"

  echo "== $name =="
  if "$@" >"$log" 2>&1; then
    echo "PASS $name"
    return 0
  fi

  echo "FAIL $name"
  cat "$log"
  return 1
}

extract_first_value() {
  local log="$1"
  shift
  python3 - "$log" "$@" <<'PY'
import json
import sys

path = sys.argv[2:]
with open(sys.argv[1], "r", encoding="utf-8") as fh:
    data = json.load(fh)

def descend(value, key):
    if isinstance(value, dict):
        return value.get(key)
    if isinstance(value, list) and key.isdigit():
        idx = int(key)
        if 0 <= idx < len(value):
            return value[idx]
    return None

cur = data
for key in path:
    cur = descend(cur, key)
    if cur is None:
        raise SystemExit(1)

if isinstance(cur, str) and cur:
    print(cur)
    raise SystemExit(0)

raise SystemExit(1)
PY
}

failures=0

run_probe docs_search \
  lark-cli docs +search --query "" --page-size 1 --format json --as user || failures=$((failures + 1))

run_probe wiki_spaces_list \
  lark-cli wiki spaces list --params '{"page_size":1}' --format json --as user || failures=$((failures + 1))

run_probe messages_search \
  lark-cli im +messages-search --query "" --start "$START_72H" --end "$TOMORROW_START" --page-size 1 --format json --as user || failures=$((failures + 1))

run_probe minutes_search \
  lark-cli minutes +search --page-size 1 --format json --as user || failures=$((failures + 1))

run_probe calendar_agenda \
  lark-cli calendar +agenda --start "$TODAY_START" --end "$TOMORROW_START" --format pretty --as user || failures=$((failures + 1))

run_probe vc_search \
  lark-cli vc +search --start "$START_7D" --page-size 1 --format pretty --as user || failures=$((failures + 1))

doc_ref=""
if [ -f "$tmp_dir/docs_search.log" ]; then
  doc_ref="$(extract_first_value "$tmp_dir/docs_search.log" items 0 url 2>/dev/null || true)"
  if [ -z "$doc_ref" ]; then
    doc_ref="$(extract_first_value "$tmp_dir/docs_search.log" data items 0 url 2>/dev/null || true)"
  fi
  if [ -z "$doc_ref" ]; then
    doc_ref="$(extract_first_value "$tmp_dir/docs_search.log" items 0 token 2>/dev/null || true)"
  fi
  if [ -z "$doc_ref" ]; then
    doc_ref="$(extract_first_value "$tmp_dir/docs_search.log" data items 0 token 2>/dev/null || true)"
  fi
  if [ -z "$doc_ref" ]; then
    doc_ref="$(extract_first_value "$tmp_dir/docs_search.log" items 0 obj_token 2>/dev/null || true)"
  fi
  if [ -z "$doc_ref" ]; then
    doc_ref="$(extract_first_value "$tmp_dir/docs_search.log" data items 0 obj_token 2>/dev/null || true)"
  fi
fi

if [ -n "$doc_ref" ]; then
  run_probe docs_fetch \
    lark-cli docs +fetch --doc "$doc_ref" --format json --as user || failures=$((failures + 1))
else
  echo "SKIP docs_fetch (no searchable doc result)"
fi

space_id=""
if [ -f "$tmp_dir/wiki_spaces_list.log" ]; then
  space_id="$(extract_first_value "$tmp_dir/wiki_spaces_list.log" items 0 space_id 2>/dev/null || true)"
  if [ -z "$space_id" ]; then
    space_id="$(extract_first_value "$tmp_dir/wiki_spaces_list.log" data items 0 space_id 2>/dev/null || true)"
  fi
fi

if [ -n "$space_id" ]; then
  run_probe wiki_nodes_list \
    lark-cli wiki nodes list --params "{\"space_id\":\"$space_id\",\"page_size\":1}" --format json --as user || failures=$((failures + 1))
else
  echo "SKIP wiki_nodes_list/wiki_get_node (no space_id found)"
fi

node_token=""
if [ -f "$tmp_dir/wiki_nodes_list.log" ]; then
  node_token="$(extract_first_value "$tmp_dir/wiki_nodes_list.log" items 0 node_token 2>/dev/null || true)"
  if [ -z "$node_token" ]; then
    node_token="$(extract_first_value "$tmp_dir/wiki_nodes_list.log" data items 0 node_token 2>/dev/null || true)"
  fi
fi

if [ -n "$node_token" ]; then
  run_probe wiki_get_node \
    lark-cli wiki spaces get_node --params "{\"token\":\"$node_token\"}" --format json --as user || failures=$((failures + 1))
elif [ -n "$space_id" ]; then
  echo "SKIP wiki_get_node (no node_token found)"
fi

minute_token=""
if [ -f "$tmp_dir/minutes_search.log" ]; then
  minute_token="$(extract_first_value "$tmp_dir/minutes_search.log" items 0 token 2>/dev/null || true)"
  if [ -z "$minute_token" ]; then
    minute_token="$(extract_first_value "$tmp_dir/minutes_search.log" data items 0 token 2>/dev/null || true)"
  fi
fi

if [ -n "$minute_token" ]; then
  run_probe minutes_get \
    lark-cli minutes minutes get --params "{\"minute_token\":\"$minute_token\"}" --format json --as user || failures=$((failures + 1))
else
  echo "SKIP minutes_get (no minute_token found)"
fi

if [ "$failures" -ne 0 ]; then
  echo
  echo "Readiness validation failed: $failures probe(s) failed."
  exit 1
fi

echo
echo "Readiness validation passed."


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)