til

Language: Always respond in the user's language. All instructions below are in English; adapt at runtime.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "til" with this command: npx skills add opentil/skills/opentil-skills-til

til

Language: Always respond in the user's language. All instructions below are in English; adapt at runtime.

Capture and manage "Today I Learned" entries on OpenTIL -- from drafting to publishing, all within the CLI.

Setup

export OPENTIL_TOKEN="til_xxx"

Token Resolution

Token resolution order:

  • ~/.til/credentials file — active profile's token (created by /til auth ) ← recommended

  • $OPENTIL_TOKEN environment variable — for CI/automation; overrides credentials when set

If neither is set, entries are saved locally to ~/.til/drafts/ .

Tip: Prefer /til auth for interactive use. When $OPENTIL_TOKEN is set, the agent cannot auto-reconnect on token expiry — it cannot unset or modify environment variables at runtime. Use the env var only for CI/automation where tokens are managed externally.

Credential File Format

~/.til/credentials stores named profiles in YAML:

active: personal profiles: personal: token: til_abc... nickname: hong site_url: https://opentil.ai/@hong host: https://opentil.ai work: token: til_xyz... nickname: hong-corp site_url: https://opentil.ai/@hong-corp host: https://opentil.ai

  • active : name of the currently active profile

  • profiles : map of profile name → credentials

  • Each profile stores: token , nickname (from API), site_url , host

Backward compatibility: If ~/.til/credentials contains a plain text token (old format), silently migrate it to a default profile in YAML format and write back.

Subcommand Routing

The first word after /til determines the action. Reserved words route to management subcommands; anything else is treated as content to capture.

Invocation Action

/til list [drafts|published|all]

List entries (default: drafts)

/til publish [<id> | last]

Publish an entry

/til unpublish <id>

Unpublish (revert to draft)

/til edit <id> [instructions]

AI-assisted edit

/til search <keyword>

Search entries by title

/til delete <id>

Delete entry (with confirmation)

/til status

Show site status and connection info

/til sync

Sync local drafts to OpenTIL

/til tags

List site tags with usage counts

/til categories

List site categories

/til batch <topics>

Batch-capture multiple TIL entries

/til update

Self-update skill files to latest version

/til auth

Connect OpenTIL account (browser auth)

/til auth switch [name]

Switch active profile (by profile name or @nickname)

/til auth list

List all profiles

/til auth remove <name>

Remove a profile

/til auth rename <old> <new>

Rename a profile

/til config

View publishing preferences

/til config set <key> <value>

Set a preference

/til config reset

Reset all preferences to defaults

/til <anything else>

Capture content as a new TIL

/til

Extract insights from conversation (multi-candidate)

Reserved words: list , publish , unpublish , edit , search , delete , status , sync , tags , categories , batch , update , auth , config .

Reference Loading

⚠️ DO NOT read reference files unless specified below. SKILL.md contains enough inline context for most operations.

On subcommand dispatch (load before execution):

Subcommand References to load

/til <content>

none

/til (extract from conversation) none

/til list|status

references/management.md

/til tags|categories

references/categories.md

/til publish|unpublish|edit|search|delete|batch

references/management.md

/til sync

references/management.md, references/local-drafts.md

/til update

none (self-contained instructions below)

/til auth

references/management.md, references/api.md

/til auth switch|list|remove|rename

references/management.md

/til config|config set|config reset

references/config.md

On-demand (load only when the situation arises):

Trigger Reference to load

API returns non-2xx after inline error handling is insufficient references/api.md

Auto-detection context (proactive TIL suggestion) references/auto-detection.md

No token found (first-run local fallback) references/local-drafts.md

Category/tag lookup/matching during entry creation references/categories.md

API Quick Reference

Create and publish an entry:

curl -X POST "https://opentil.ai/api/v1/entries"
-H "Authorization: Bearer $OPENTIL_TOKEN"
-H "Content-Type: application/json"
-H "X-OpenTIL-Source: agent"
-H "X-OpenTIL-Agent: <your agent display name>"
-H "X-OpenTIL-Model: <human-readable model name>"
-d '{ "entry": { "title": "Go interfaces are satisfied implicitly", "content": "In Go, a type implements an interface...", "summary": "Go types implement interfaces implicitly by implementing their methods, with no explicit declaration needed.", "tag_names": ["go", "interfaces"], "published": true, "lang": "en" } }'

Safe API call pattern — For POST/PATCH requests with content bodies, ALWAYS use a temp file to avoid JSON encoding issues from shell line-wrapping:

1. Write payload to temp file (JSON.stringify handles all escaping)

node -e " const fs = require('fs'); const payload = {entry: { title: 'Go interfaces are satisfied implicitly', content: `In Go, a type implements an interface...

Multi-line content with \`code blocks\` is safe here.`, tag_names: ['go', 'interfaces'], published: true, lang: 'en' }}; fs.writeFileSync('/tmp/til-payload.json', JSON.stringify(payload)); "

2. Send with curl

curl -s -X POST "https://opentil.ai/api/v1/entries"
-H "Authorization: Bearer $OPENTIL_TOKEN"
-H "Content-Type: application/json"
-H "X-OpenTIL-Source: agent"
-H "X-OpenTIL-Agent: <your agent display name>"
-H "X-OpenTIL-Model: <human-readable model name>"
-d @/tmp/til-payload.json

3. Cleanup

rm -f /tmp/til-payload.json

Never use inline -d '{...}' for content longer than a few words. Never use Python urllib — use curl.

Key create parameters:

Field Type Required Description

content

string yes Markdown body (max 100,000 chars)

title

string no Entry title (max 200 chars). Auto-generates slug.

tag_names

array no 1-3 lowercase tags, e.g. ["go", "concurrency"]

published

boolean no false for draft (default), true to publish immediately

lang

string no Language code: en , zh-CN , zh-TW , ja , ko , etc.

slug

string no Custom URL slug. Auto-generated from title if omitted.

visibility

string no public (default), unlisted , or private

category_name

string no Category name (matched or created automatically)

summary

string no AI-generated summary for listing pages (max 500 chars)

Management endpoints:

Endpoint Method Description

/entries?status=draft&q=keyword

GET List/search entries

/entries/:id

GET Get a single entry

/entries/:id

PATCH Update entry fields

/entries/:id

DELETE Permanently delete entry

/entries/:id/publish

POST Publish a draft

/entries/:id/unpublish

POST Revert to draft

/site

GET Site info (username, entry counts, preferences, etc.)

/site

PATCH Update site settings (including preferences )

/tags?sort=popular

GET List tags with usage counts

/categories

GET List categories with entry counts

Full parameter list, response format, and error handling: see references/api.md

Execution Flow

Every /til invocation follows this flow:

  • Generate -- craft the TIL entry (title, body, summary, tags, lang) 1.5. Load preferences -- resolve publishing preferences (cache → API GET /site → hardcoded defaults). See references/config.md for cache protocol.

  • Apply default_visibility to API request

  • If custom_instructions is present, treat them as additional Content Guidelines (user rules take precedence over defaults)

  • Check token -- resolve token (env var → active profile in ~/.til/credentials )

  • If ~/.til/credentials exists in old plain-text format, migrate to YAML default profile first

  • Found -> POST to API with published: true -> show published URL

  • Not found -> save to ~/.til/drafts/ -> show first-run guide with connect prompt

  • 401 response -> save locally -> inline re-authentication (see Error Handling):

  • Token from ~/.til/credentials (active profile) or no prior token: prompt to reconnect via device flow → on success, update the active profile's token and auto-retry the original operation

  • Token from $OPENTIL_TOKEN env var: cannot auto-fix — guide user to update/unset the variable

  • Show identity -- when ≥2 profiles are configured, include Account: @nickname (profile_name) in ALL result messages, confirmations, and list headers so the user always knows which account is being used. See Multi-Account Display Rules in management.md for full specification.

  • Never lose content -- the entry is always persisted somewhere

  • On API failure -> save locally as draft (fallback unchanged)

/til <content> -- Explicit Capture

The user's input is raw material -- a seed, not the final entry. Generate a complete TIL from it:

  • Short but unclear (a few words, ambiguous without context) -> expand into a full entry with context and examples

  • Short but complete (a clear, self-contained statement) -> preserve the substance as-is; still generate title, tags, summary, and lang as normal. Add a code example only if it strengthens the entry

  • Long input (a paragraph or more) -> refine and structure, but preserve the user's intent

Steps:

  • Treat the user's input as a seed -- craft a complete title + body from it

  • Generate a concise title (3-15 words) in the same language as the content

  • Generate an English slug (see Slug Guidelines below)

  • Write a self-contained Markdown body (see Content Guidelines below)

  • Generate a summary (see Summary Guidelines below)

  • Infer 1-3 lowercase tags (e.g. rails , postgresql , go , css )

  • Detect language -> set lang (en , zh-CN , zh-TW , ja , ko , es , fr , de , pt-BR , pt , ru , ar , bs , da , nb , pl , th , tr , it )

  • Follow Execution Flow above (check token -> POST or save locally)

No confirmation needed -- the user explicitly asked to capture. Execute directly.

/til -- Extract from Conversation

When /til is used without arguments, analyze the current conversation for learnable insights.

Steps:

  • Scan the conversation for knowledge worth preserving -- surprising facts, useful techniques, debugging breakthroughs, "aha" moments

  • Identify all TIL-worthy insights (not just one), up to 5

  • Branch based on count:

0 insights:

No clear TIL insights found in this conversation.

1 insight: Generate the full draft (title, body, tags), show it, ask for confirmation. On confirmation -> follow Execution Flow.

2+ insights: Show a numbered list (max 5), let the user choose:

Found 3 TIL-worthy insights:

  1. Go interfaces are satisfied implicitly
  2. PostgreSQL JSONB arrays don't support GIN @>
  3. CSS :has() enables parent selection

Which to capture? (1/2/3/all/none)

  • Single number -> generate draft for that insight, show confirmation, proceed

  • Comma-separated list (e.g. 1,3 ) -> generate drafts for selected, show all for confirmation, POST sequentially

  • all -> generate drafts for each, show all for confirmation, POST sequentially

  • none -> cancel

  • For each selected insight, generate a standalone TIL entry following Content Guidelines

  • Show the generated entry to the user and ask for confirmation before proceeding

  • On confirmation -> follow Execution Flow above (check token -> POST or save locally)

Auto-Detection

When working alongside a user, proactively detect moments worth capturing as TIL entries.

When to Suggest

Suggest when the conversation produces a genuine "aha" moment — something surprising, non-obvious, or worth remembering. Examples:

  • Debugging uncovered a non-obvious root cause

  • A language/framework behavior contradicted common assumptions

  • Refactoring revealed a clearly superior pattern

  • Performance optimization yielded measurable improvement

  • An obscure but useful tool flag or API parameter was discovered

  • Two technologies interacting produced unexpected behavior

Do NOT suggest for: standard tool usage, documented behavior, typo-caused bugs, or widely known best practices.

Rate Limiting

  • Cooldown-based — after each suggestion, enter a cooldown period before the next one can occur:

  • Accepted: Cooldown of 15 turns. After cooldown, the agent may suggest again at the next qualifying moment.

  • Declined or ignored: No more suggestions for the rest of this session.

  • Natural pauses only — suggest at resolution points or task boundaries, never mid-problem-solving

  • Respect rejection — if declined, move on without persuasion

Suggestion Format

Append at the end of your normal response. Never interrupt workflow.

Template:

💡 TIL: [concise title of the insight] Tags: [tag1, tag2] · Capture? (yes/no)

Example (at the end of a debugging response):

...so the fix is to close the channel before the goroutine exits.

💡 TIL: Unclosed Go channels in goroutines cause silent memory leaks Tags: go, concurrency · Capture? (yes/no)

Capture Flow

Auto-detected TILs bypass the extract flow. The suggestion itself is the candidate.

  • User replies yes / y / ok / sure → agent generates full entry (title, body, tags, lang) from the suggested insight → follows Execution Flow (POST or save locally)

  • User replies no / ignores / continues other topic → move on, do not ask again

Non-affirmative responses (continuing the conversation about something else) are treated as implicit decline.

Detailed trigger examples, state machine, and anti-patterns: see references/auto-detection.md

Management Subcommands

Management subcommands require a token. There is no local fallback -- management operations need the API.

/til list [drafts|published|all]

List entries. Default filter: drafts .

  • API: GET /entries?status=<filter>&per_page=10

  • Display as a compact table with short IDs (last 8 chars, prefixed with ... )

  • Show pagination info at the bottom

/til publish [<id> | last]

Publish a draft entry.

  • last resolves to the most recently created entry in this session (tracked via last_created_entry_id set on every successful POST)

  • Fetch the entry first, show title/tags, ask for confirmation

  • On success, display the published URL

  • If already published, show informational message (not an error)

/til unpublish <id>

Revert a published entry to draft.

  • Fetch the entry first, confirm before unpublishing

  • If already a draft, show informational message

/til edit <id> [instructions]

AI-assisted editing of an existing entry.

  • Fetch the full entry via GET /entries/:id

  • Apply changes based on instructions (or ask what to change if none given)

  • Show a diff preview of proposed changes

  • On confirmation, PATCH /entries/:id with only the changed fields

/til search <keyword>

Search entries by title.

  • API: GET /entries?q=<keyword>&per_page=10

  • Same compact table format as list

/til delete <id>

Permanently delete an entry.

  • Fetch the entry, show title and status

  • Double-confirm: "This cannot be undone. Type 'delete' to confirm."

  • On confirmation, DELETE /entries/:id

/til status

Show site status and connection info. Works without a token (degraded display).

  • With token: GET /site -> show username, entry breakdown (total/published/drafts), token status, local draft count, dashboard link

  • Without token: show "not connected", local draft count, setup link

/til sync

Explicitly sync local drafts from ~/.til/drafts/ to OpenTIL. Requires token.

  • List pending drafts, POST each one, delete local file on success

  • Show summary with success/failure per draft

/til tags

List site tags sorted by usage count (top 20). Requires token.

  • API: GET /tags?sort=popular&per_page=20&with_entries=true

  • Show as compact table with tag name and entry count

/til categories

List site categories. Requires token. Supports --force to bypass cache.

  • Uses taxonomy cache (~/.til/cache/taxonomy.json , 10 min TTL) — see references/categories.md

  • Show as compact table with name, entry count, and description

/til batch <topics>

Batch-capture multiple TIL entries in one invocation. Requires explicit topic list.

  • User lists topics separated by newlines, semicolons, or markdown list items (- / 1. )

  • Generate a draft for each -> show all drafts for confirmation -> POST sequentially

  • On partial failure, show per-entry success/failure (same format as /til sync )

ID Resolution

  • In listings, show IDs in short form: ...
  • last 8 characters
  • Accept both short and full IDs as input

  • Resolve short IDs by suffix match against the current listing

  • If ambiguous (multiple matches), ask for clarification

Session State

Track the following session state (not persisted across sessions):

  • last_created_entry_id -- set on every successful POST /entries (201). Used by /til publish last .

  • active_profile -- the profile name resolved at first token access. Reflects the active field from ~/.til/credentials (or $OPENTIL_TOKEN override). Used for identity display and draft attribution.

Detailed subcommand flows, display formats, and error handling: see references/management.md

/til update

Self-update the TIL skill files to the latest version.

Steps:

  • Run: npx @opentil/cli@latest update --agent <current-agent> --json

  • Replace <current-agent> with your agent identifier (e.g. claude-code , cursor , codex ). If unsure, use all .

  • Parse the JSON output

  • If updated: true → report the version change (from → to), suggest reloading the skill

  • If updated: false → "Already up to date (vX.Y.Z)"

  • If the command fails or JSON cannot be parsed → show the error and suggest manual update

Example output:

Skill updated: v1.26.0 → v1.28.0

Reload the /til skill to use the new version.

Fallback (no Node.js): Re-read https://opentil.ai/skill.md and follow the manual install section.

Agent Identity

Three layers of attribution signal distinguish human-initiated from agent-initiated TILs.

Layer 1: HTTP Headers

Include these headers on every API call:

X-OpenTIL-Source: agent X-OpenTIL-Agent: <your agent display name> X-OpenTIL-Model: <human-readable model name> X-OpenTIL-Client: til-skill/<version from metadata>

  • Source: always agent — the agent authors the content regardless of trigger method

  • Agent: use your tool's display name (e.g. Claude Code , Cursor , GitHub Copilot ). Do not use a slug.

  • Model: use a human-readable model name (e.g. Claude Opus 4.6 , GPT-4o , Gemini 2.5 Pro ). Do not use a model ID.

  • Client: use the format til-skill/{version} where version comes from this file's frontmatter metadata.

  • Agent and Model are optional -- omit them if you are unsure. Client is required.

Layer 2: Tag Convention

  • Do not add an agent-assisted tag — the source field already provides attribution via the backend display layer

Layer 3: Attribution Rendering (Backend)

TILs with source: agent are visually marked on OpenTIL automatically. No content modification needed -- the backend renders attribution in the display layer.

  • Public page: shows ✨ via {agent_name} , or ✨ AI when agent_name is absent

  • Tooltip (hover): shows {agent_name} · {model} when both are present

  • Dashboard: shows ✨ badge + agent_name, or "Agent" when agent_name is absent

Do NOT append any footer or attribution text to the content body.

Summary

Dimension /til <content>

/til

Auto-detected

Trigger User explicit User command Agent proactive

Confirmations 0 (direct publish) 1 (review before publish) 1 (suggest → capture)

Source header agent

agent

agent

Agent header Yes Yes Yes

Model header Yes Yes Yes

Client header Yes Yes Yes

Attribution Automatic (backend) Automatic (backend) Automatic (backend)

Content Guidelines

Before generating content, check for custom_instructions in preferences (see Execution Flow step 1.5). If present, append them as additional rules after the guidelines below. Custom instructions take precedence when they conflict with defaults.

Every TIL entry must follow these rules:

  • Self-contained: The reader must understand the entry without any conversation context. Never write "as we discussed", "the above error", "this project's config", etc.

  • Desensitized: Remove project names, company details, colleague names, internal URLs, and proprietary business logic. Generalize specifics: "our User model" -> "a model", "the production server" -> "a production environment", "the Acme payment service" -> "a payment gateway".

  • Universally valuable: Write to StackOverflow-answer standards. A stranger searching for this topic should find the entry immediately useful. Content only useful to the author belongs in private notes, not TIL.

  • Factual tone: State facts, show examples, explain why. Second-person instructional voice ("You can...", "If you want to...") is natural and preferred for TIL entries. Brief first-person context is fine to ground the insight. Good: "You can use git diff --word-diff=color to see inline character-level changes." Good: "When upgrading Rails from 7.2 to 8.0, the asset pipeline silently stopped compiling SCSS." Avoid: multi-sentence personal stories ("I was debugging for hours and finally my colleague suggested...").

  • One insight per entry: Each TIL teaches exactly ONE thing. If there are multiple insights, create separate entries.

  • Concrete examples: Include code snippets, commands, or specific data whenever relevant. Avoid vague descriptions. Show expected output inline using comment syntax (# => true , //=> [1, 2, 3] ) to make examples self-verifying and scannable.

  • Title: 3-15 words. Descriptive, same language as content. No "TIL:" prefix.

  • Slug: Always generate an English ASCII slug and include it in the API request. The server auto-generates slugs from titles, but it cannot translate — non-English titles produce meaningless fragments (e.g. "跟 AI 结对一整天" → ai ). The agent must provide a meaningful English slug.

  • Translate the title's core meaning into a concise English slug (3-8 words, lowercase, hyphenated)

  • Use concrete nouns and verbs, not filler words (omit "the", "a", "how-to", etc.)

  • Examples:

  • "跟 AI 结对一整天,关掉终端什么都没留下?" → ai-pair-programming-nothing-left

  • "30 秒装好,3 种方式记 TIL" → 3-ways-to-capture-til

  • "二分查找的时间复杂度推导与决策流程" → binary-search-time-complexity

  • "Docker 多阶段构建能把镜像体积缩小十几倍" → docker-multi-stage-build-image-size

  • "Rails ETag caching keys" → rails-etag-caching-keys (English title — slug matches naturally)

  • If the title is already in English, parameterize produces a good slug — you may still omit it and let the server handle it

  • Preferred structure (flexible, not mandatory):

  • 1-2 sentence context or problem statement

  • Core insight or explanation

  • Code example, command, or concrete demonstration — for multi-faceted topics, use 2-3 code blocks that build progressively (basic usage → advanced usage → full example or edge case)

  • (Optional) Caveats, edge cases, or reference links

  • Variant — "Expected vs. Actual": Show the expected behavior first, then reveal the surprising actual result. Effective for gotcha/pitfall TILs where the "aha" is in the contrast.

  • Content: Use the most efficient format for the knowledge — tables for comparisons, code blocks for examples, lists for enumerations, math ($inline$ / $$display$$ ) for formulas with fractions/subscripts/superscripts/greek letters, Mermaid diagrams (```mermaid ) for flows/states/sequences that text cannot clearly express, images/screenshots for visual bugs or UI behavior (see Image Support below). Simple expressions like O(n) stay as inline code; use math only when notation complexity warrants it. Only use prose when explaining causation or context. Never pad content; if one sentence suffices, don't write a paragraph.

  • Content length: Most entries are 10-100 lines. Shorter is fine when the insight is simple. Longer is fine for multi-example walkthroughs — let topic complexity dictate length.

  • Tags: 1-3 lowercase tags (if the topic warrants more, pick the 3 most specific). Use concrete technology/tool names, not meta-categories. Generic tags like programming , til , webdev , or backend describe what TIL is rather than what the insight is about — avoid them. Domain-specific tags like cooking or astronomy are fine because they name the subject area. Examples: [go, concurrency] , [postgresql, indexing] , [css, flexbox] , [docker, networking] , [cooking, fermentation] , [astronomy, optics] .

  • Lang: Detect from content. Chinese -> zh-CN , Traditional Chinese -> zh-TW , English -> en , Japanese -> ja , Korean -> ko .

  • Category: Before creating an entry, try loading the taxonomy cache (see references/categories.md). Match the content to an existing category if a good fit exists. If no existing category fits, suggest a new concise category name. Include category_name in the creation request. If the cache/API fails, proceed without category_name -- never let a category lookup failure block entry creation.

  • Tag matching: When tags are specified, match each tag name against the taxonomy cache (exact → case-insensitive → slug). Use matched canonical names to avoid duplicates like "Rails" vs "rails". Unmatched names pass through as-is (server creates them). If cache fetch fails, use original names.

  • Summary: 1-2 sentences, plain text (no markdown). Max 500 chars and must be shorter than the content body. Same language as content. Self-contained: the reader should understand the core takeaway from the summary alone. Be specific about what the reader will learn, not meta ("this article discusses..."). No first person, no meta-descriptions. Omit if the content is already very short (under ~200 chars) -- the excerpt fallback is sufficient.

  • Anti-patterns (avoid these):

  • Command-only: A single command or snippet with no explanation of when/why to use it. At minimum, explain the context or problem it solves.

  • Title = body: If the title fully conveys the insight and the body just restates it, either expand with a concrete example or accept that the insight may be too thin for a standalone entry.

  • "X exists" without "so what": Simply noting a feature or tool exists is not a TIL. Explain why it matters, when you'd reach for it, or what problem it solves.

Image Support

When the user's conversation includes images (screenshots, diagrams, photos) that are relevant to the TIL entry, upload them and embed markdown image links in the body.

When to Include Images

  • Include: Screenshots of visual bugs, UI before/after comparisons, architecture diagrams, terminal output that's hard to reproduce as text, visual diffs.

  • Don't include: Plain text terminal output (use code blocks), simple command output, anything that's better represented as text.

Upload Flow

  • Detect image file path in the conversation (user-pasted images are saved as temporary files)

  • Upload via CLI: npx @opentil/cli image upload <path> --json

  • Parse the JSON output to get url

  • Embed in the TIL markdown body: descriptive alt text

The CLI handles the full 3-step flow (presign → upload to storage → confirm) and returns:

{ "id": "...", "url": "https://...", "width": 800, "height": 600, "byte_size": 12345, "content_type": "image/png" }

Placement Rules

  • Place the image at the point of relevance in the content, not at the end as an afterthought

  • Write descriptive alt text: CSS grid gap collapsing between nested containers not screenshot

  • For before/after comparisons, label clearly: "Before:" / "After:" above each image

  • Keep images to a maximum of 3 per entry — more than that signals the entry should be split

Multiple Images

Upload each image separately. If the user provides multiple images and it's ambiguous where they should go, ask the user for preferred placement.

Error Handling

  • Upload fails (network/auth): Save the draft locally with the local image path in the body. The image path will be a placeholder until the user runs til sync with network access.

  • File too large (>5 MB): Inform the user and suggest compressing or resizing the image before retrying.

  • Unsupported format: Inform the user. Supported: JPEG, PNG, GIF, WebP.

Offline / Local Draft

When saving locally (no token or network), copy the image to ~/.til/drafts/images/ and reference it with a local path in the markdown body. Add image metadata to the draft frontmatter images field so til sync knows which files need uploading. During sync, images are uploaded first, local paths are replaced with URLs in memory, and only after the entry POST succeeds are the local image files deleted. If the POST fails, both the draft and its images are preserved unchanged for the next sync attempt.

Result Messages

API Success (token configured, 201)

Published to OpenTIL

Title: Go interfaces are satisfied implicitly Tags: go, interfaces URL: https://opentil.ai/@username/go-interfaces-are-satisfied-implicitly

When ≥2 profiles are configured, add an Account line:

Published to OpenTIL

Account: @hong (personal) Title: Go interfaces are satisfied implicitly Tags: go, interfaces URL: https://opentil.ai/@hong/go-interfaces-are-satisfied-implicitly

Single-profile users see no Account line — keep the output clean.

Extract the url field from the API response for the URL.

Sync Local Drafts

After the first successful API call, check ~/.til/drafts/ for pending files. If any exist, offer to sync:

Draft saved to OpenTIL

Title: Go interfaces are satisfied implicitly Tags: go, interfaces Review: https://opentil.ai/@username/go-interfaces-are-satisfied-implicitly

Found 3 local drafts from before. Sync them to OpenTIL?

Multi-profile variant (≥2 profiles):

Draft saved to OpenTIL

Account: @hong (personal) Title: Go interfaces are satisfied implicitly Tags: go, interfaces Review: https://opentil.ai/@hong/go-interfaces-are-satisfied-implicitly

Found 3 local drafts from before. Sync them to OpenTIL?

On confirmation, POST each draft to the API. Delete the local file after each successful sync. Keep files that fail. Show summary:

Synced 3 local drafts to OpenTIL

  • Go defer runs in LIFO order
  • PostgreSQL JSONB indexes support GIN operators
  • CSS :has() selector enables parent selection

Multi-profile variant (≥2 profiles):

Synced 3 local drafts to OpenTIL

Account: @hong (personal)

  • Go defer runs in LIFO order
  • PostgreSQL JSONB indexes support GIN operators
  • CSS :has() selector enables parent selection

If the user declines, keep the local files and do not ask again in this session.

First Run (no token)

Save the draft locally, then proactively offer to connect. This is NOT an error -- the user successfully captured a TIL.

TIL captured

Title: Go interfaces are satisfied implicitly Tags: go, interfaces File: ~/.til/drafts/20260210-143022-go-interfaces.md

Connect to OpenTIL to publish entries online. Connect now? (y/n)

  • y → run inline device flow (same as /til auth ) → on success, sync the just-saved draft + any other pending drafts in ~/.til/drafts/

  • n → show manual setup instructions (see Manual Setup Instructions below)

Only show the connect prompt on the first local save in this session. On subsequent saves, use the short form (no prompt):

TIL captured

Title: Go interfaces are satisfied implicitly Tags: go, interfaces File: ~/.til/drafts/20260210-143022-go-interfaces.md

Update Hint

After the first successful API call in a conversation, check the installed skill age:

  • Read ~/.til/manifest.json and parse updatedAt (ISO timestamp)

  • If updatedAt is more than 30 days ago, append after the success message: Tip: Skill installed 45 days ago. Run /til update to get the latest version.

  • If the file doesn't exist or can't be parsed, skip silently

  • Only check once per conversation — do not repeat on subsequent saves

Error Handling

On API failure (except 409 duplicate), always save the draft locally first. Never let user content be lost.

409 -- Duplicate content: The server detected identical content already exists. Do NOT save a local draft (the content is already on the server). Do NOT retry. Show the existing entry's URL from the response body and inform the user "This content has already been published." Set last_created_entry_id to the existing entry's id from the response (the 409 response body has the same shape as a 201 response).

422 -- Validation error: Analyze the error response, fix the issue (e.g. truncate title to 200 chars, correct lang code), and retry. Only save locally if the retry also fails.

401 -- Token invalid or expired (token from ~/.til/credentials active profile):

TIL captured (saved locally)

File: ~/.til/drafts/20260210-143022-go-interfaces.md

Token expired for @hong (personal). Reconnect now? (y/n)

  • y → run inline device flow (same as /til auth ) → on success, update the active profile's token in ~/.til/credentials and auto-retry the original POST (publish the just-saved draft, then delete the local file)

  • n → show manual setup instructions (see Manual Setup Instructions below)

When only one profile exists, omit the @nickname (profile) from the message.

401 -- Token invalid or expired (token from $OPENTIL_TOKEN env var):

The env var takes priority over ~/.til/credentials , so saving a new token via device flow would not help — the env var would still be used. Guide the user instead:

TIL captured (saved locally)

File: ~/.til/drafts/20260210-143022-go-interfaces.md

Your $OPENTIL_TOKEN is expired or invalid. To fix: • Update the variable with a new token, or • unset OPENTIL_TOKEN, then run /til auth

Create a new token: https://opentil.ai/dashboard/settings/tokens

Network failure or 5xx:

If the error is a DNS/connection failure (e.g. Could not resolve host , ECONNREFUSED , ETIMEDOUT before receiving any HTTP response): → Follow "Sandboxed Environments" flow below (save locally + guide + retry)

If the error is an HTTP 5xx response:

TIL captured (saved locally -- API unavailable)

File: ~/.til/drafts/20260210-143022-go-interfaces.md

Sandboxed Environments

Many agents run in sandboxed environments where network access is restricted by default. If an API call fails with a DNS or connection error (not an HTTP status code), you are likely in a sandbox — this is a network restriction, not an API outage.

Known sandbox configurations:

Agent How to enable network Reference

Codex ~/.codex/config.toml → [sandbox_workspace_write] network_access = true

Config Reference

Claude Code /sandbox command → add opentil.ai to allowed domains Sandboxing docs

Cursor sandbox.json → add opentil.ai to domain allowlist Sandbox controls

Other agents Check your agent's sandbox/network configuration —

Flow with retry:

  • Save content locally first (never lose content)

  • Inform the user: Network request failed — this appears to be a sandbox network restriction, not an API outage. Enable network access for opentil.ai in your agent's sandbox settings, then I can retry.

Include the agent-specific configuration hint from the table above if you know which agent you are.

  • Ask: "Retry? (y/n)"

  • On y → retry the API call once

  • Success → delete the local draft, show published URL

  • Fail again → keep local draft, show short network-error message (do not ask again)

  • On n → keep local draft, move on

Retry limit: At most 1 retry per API call. Do not loop.

Full error codes, 422 auto-fix logic, and rate limit details: see references/api.md

Re-authentication Safeguards

Rule Behavior

No retry loops If re-auth succeeds but the retry still returns 401 → stop and show the error. Do not re-authenticate again.

Batch-aware During batch/sync operations, re-authenticate at most once. On success, continue processing remaining items with the new token.

Respect refusal If the user declines re-authentication (n ), do not prompt again for the rest of this session. Use the short local-save format silently.

Env var awareness When the active token comes from $OPENTIL_TOKEN , never attempt device flow — it cannot override the env var. Always show the env var guidance instead.

Profile-aware re-auth On successful re-authentication, update the corresponding profile's token in ~/.til/credentials . Do not create a new profile.

Manual Setup Instructions

When the user declines inline authentication (answers n ), show:

Or set up manually:

  1. Visit https://opentil.ai/dashboard/settings/tokens
  2. Create a token (select read + write + delete scopes)
  3. Add to shell profile: export OPENTIL_TOKEN="til_..."

Local Draft Fallback

When the API is unavailable or no token is configured, drafts are saved locally to ~/.til/drafts/ .

File format: YYYYMMDD-HHMMSS-<slug>.md


title: "Go interfaces are satisfied implicitly" tags: [go, interfaces] lang: en summary: "Go types implement interfaces implicitly by implementing their methods, with no explicit declaration needed." profile: personal

In Go, a type implements an interface...

The profile field records the active profile name at save time, ensuring sync uses the correct account's token. Omitted when no profiles are configured (backward-compatible).

Full directory structure, metadata fields, and sync protocol: see references/local-drafts.md

/til config -- Publishing Preferences

View and modify publishing preferences. Requires token.

/til config

Display current preferences:

Publishing Preferences:

Visibility: Public

Custom instructions: - Use casual tone - Never start with "Note:"

Edit at: https://opentil.ai/dashboard/settings/publishing

  • Load preferences from cache (~/.til/cache/preferences.json ) -> fallback to GET /site -> extract preferences

/til config set <key> <value>

Set a single preference. Keys: visibility , instructions .

  • PATCH /api/v1/site with the updated preferences

  • Invalidate preferences cache

  • Show updated value

/til config reset

Reset all preferences to defaults.

  • Confirm before resetting

  • PATCH /api/v1/site with default preferences (defaults: { visibility: "public" } , custom_instructions: null )

  • Invalidate preferences cache

Detailed flows, cache protocol, and display formats: see references/config.md

Notes

  • UI language adaptation: All prompts, result messages, and error messages in this document are written in English as canonical examples. At runtime, adapt them to match the user's language in the current session (e.g. if the user writes in Chinese, display messages in Chinese). Entry content language (lang field) is independent -- it is always detected from the content itself.

  • Entries are published immediately by default (published: true ) -- use /til unpublish <id> to revert to draft

  • The API auto-generates a URL slug from the title

  • Tags are created automatically if they don't exist on the site

  • Content is rendered to HTML server-side (GFM Markdown with syntax highlighting, KaTeX math, and Mermaid diagrams)

  • Management subcommands (list , publish , edit , search , delete , tags , categories , sync , batch ) require a token -- no local fallback. Exception: status and auth (including auth switch , auth list , auth remove , auth rename ) work without a token.

  • Scope errors map to specific scopes: list /search /tags /categories need read:entries , publish /unpublish /edit /sync /batch need write:entries , delete needs delete:entries . status uses read:entries when available but works without a token.

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

OpenClaw Skill Growth

Make OpenClaw Skills observable, diagnosable, and safely improvable over time. Use this when the user wants to maintain many SKILL.md files, inspect repeated...

Registry SourceRecently Updated
171Profile unavailable
General

Find Skills for ClawHub

Search for and discover OpenClaw skills from ClawHub (the official skill registry). Activate when user asks about finding skills, installing skills, or wants...

Registry SourceRecently Updated
2871Profile unavailable
General

Skill Listing Polisher

Improve a skill's public listing before publish. Use when tightening title, description, tags, changelog, and scan-friendly packaging so the listing looks cl...

Registry SourceRecently Updated
1130Profile unavailable
General

Skill Priority Setup

Scans installed skills, suggests L0-L3 priority tiers, and auto-configures skill injection policy. Use when: setting up skill priorities, optimizing token bu...

Registry SourceRecently Updated
2510Profile unavailable