til
Original:🇺🇸 English
Translated
Capture and manage TIL (Today I Learned) entries on OpenTIL. Use /til <content> to capture, /til to extract insights from conversation, or /til list|publish|edit|search|delete|status|sync|tags|categories|batch to manage entries -- all without leaving the CLI.
1installs
Sourceopentil/skills
Added on
NPX Install
npx skill4agent add opentil/skills tilTags
Translated version includes tags in frontmatterSKILL.md Content
View Translation Comparison →til
Capture and manage "Today I Learned" entries on OpenTIL -- from drafting to publishing, all within the CLI.
Setup
- Go to https://opentil.ai/dashboard/settings/tokens and create a Personal Access Token with ,
read:entries, andwrite:entriesscopesdelete:entries - Copy the token (starts with )
til_ - Set the environment variable:
bash
export OPENTIL_TOKEN="til_xxx"Token Resolution
Token resolution order:
- environment variable
$OPENTIL_TOKEN - file (created by
~/.til/credentials)/til auth
If neither is set, entries are saved locally to .
~/.til/drafts/Subcommand Routing
The first word after determines the action. Reserved words route to management subcommands; anything else is treated as content to capture.
/til| Invocation | Action |
|---|---|
| List entries (default: drafts) |
| Publish an entry |
| Unpublish (revert to draft) |
| AI-assisted edit |
| Search entries by title |
| Delete entry (with confirmation) |
| Show site status and connection info |
| Sync local drafts to OpenTIL |
| List site tags with usage counts |
| List site categories |
| Batch-capture multiple TIL entries |
| Connect OpenTIL account (browser auth) |
| Capture content as a new TIL |
| Extract insights from conversation (multi-candidate) |
Reserved words: , , , , , , , , , , , .
listpublishunpublisheditsearchdeletestatussynctagscategoriesbatchauthReference 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 |
|---|---|
| none |
| none |
| references/management.md |
| references/management.md |
| references/management.md, references/local-drafts.md |
| references/management.md, references/api.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 |
API Quick Reference
Create and publish an entry:
bash
curl -X POST "https://opentil.ai/api/v1/entries" \
-H "Authorization: Bearer $OPENTIL_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"entry": {
"title": "Go interfaces are satisfied implicitly",
"content": "In Go, a type implements an interface...",
"tag_names": ["go", "interfaces"],
"published": true,
"lang": "en"
}
}'Key create parameters:
| Field | Type | Required | Description |
|---|---|---|---|
| string | yes | Markdown body (max 100,000 chars) |
| string | no | Entry title (max 200 chars). Auto-generates slug. |
| array | no | 1-3 lowercase tags, e.g. |
| boolean | no | |
| string | no | Language code: |
| string | no | Custom URL slug. Auto-generated from title if omitted. |
| string | no | |
Management endpoints:
| Endpoint | Method | Description |
|---|---|---|
| GET | List/search entries |
| GET | Get a single entry |
| PATCH | Update entry fields |
| DELETE | Permanently delete entry |
| POST | Publish a draft |
| POST | Revert to draft |
| GET | Site info (username, entry counts, etc.) |
| GET | List tags with usage counts |
| GET | List categories with entry counts |
Full parameter list, response format, and error handling: see references/api.md
Execution Flow
Every invocation follows this flow:
/til- Generate -- craft the TIL entry (title, body, tags, lang)
- Check token -- resolve token (env var → )
~/.til/credentials- Found -> POST to API with -> show published URL
published: true - Not found -> save to -> show first-run setup guide
~/.til/drafts/
- Found -> POST to API with
- Never lose content -- the entry is always persisted somewhere
- On API failure -> save locally as draft (fallback unchanged)
/til <content>
-- Explicit Capture
/til <content>The user's input is raw material -- a seed, not the final entry. Generate a complete TIL from it:
- Short input (a sentence or phrase) -> expand into a full entry with context and examples
- 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 (5-15 words) in the same language as the content
- Write a self-contained Markdown body (see Content Guidelines below)
- Infer 1-3 lowercase tags from technical domain (e.g. ,
rails,postgresql)go - 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
/tilWhen is used without arguments, analyze the current conversation for learnable insights.
/tilSteps:
- 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. ) -> generate drafts for selected, show all for confirmation, POST sequentially
1,3 - -> generate drafts for each, show all for confirmation, POST sequentially
all - -> cancel
none
- 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
- Once per session — after suggesting once (accepted or declined), never suggest again
- 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→ agent generates full entry (title, body, tags, lang) from the suggested insight → follows Execution Flow (POST or save locally)sure - User replies / ignores / continues other topic → move on, do not ask again
no
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]
/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]
/til publish [<id> | last]Publish a draft entry.
- resolves to the most recently created entry in this session (tracked via
lastset on every successful POST)last_created_entry_id - 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>
/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]
/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, with only the changed fields
PATCH /entries/:id
/til search <keyword>
/til search <keyword>Search entries by title.
- API:
GET /entries?q=<keyword>&per_page=10 - Same compact table format as
list
/til delete <id>
/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
/til statusShow site status and connection info. Works without a token (degraded display).
- With token: -> show username, entry breakdown (total/published/drafts), token status, local draft count, dashboard link
GET /site - Without token: show "not connected", local draft count, setup link
/til sync
/til syncExplicitly sync local drafts from to OpenTIL. Requires token.
~/.til/drafts/- List pending drafts, POST each one, delete local file on success
- Show summary with success/failure per draft
/til tags
/til tagsList 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
/til categoriesList site categories. Requires token.
- API:
GET /categories - Show as compact table with name, entry count, and description
/til batch <topics>
/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 -- set on every successful (201). Used by . Not persisted across sessions.
last_created_entry_idPOST /entries/til publish lastDetailed subcommand flows, display formats, and error handling: see references/management.md
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: human | agent
X-OpenTIL-Agent: <your agent display name>
X-OpenTIL-Model: <human-readable model name>- Source: and
/til <content>->/til; Auto-detected ->humanagent - Agent: use your tool's display name (e.g. ,
Claude Code,Cursor). Do not use a slug.GitHub Copilot - Model: use a human-readable model name (e.g. ,
Claude Opus 4.6,GPT-4o). Do not use a model ID.Gemini 2.5 Pro - Agent and Model are optional -- omit them if you are unsure.
Layer 2: Tag Convention
- Auto-detected TILs: automatically add to the tag list
agent-assisted - and
/til <content>: do not add the tag (unless the Agent substantially rewrote the content)/til
Layer 3: Attribution Rendering (Backend)
Agent-initiated TILs are visually marked on OpenTIL automatically based on the
field. No content modification needed -- the backend renders attribution
in the display layer.
source- Public page: shows , or
✨ via {agent_name}when agent_name is absent✨ AI - Tooltip (hover): shows when both are present
{agent_name} · {model} - 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 | | | Auto-detected |
|---|---|---|---|
| Trigger | User explicit | User command | Agent proactive |
| Confirmations | 0 (direct publish) | 1 (review before publish) | 1 (suggest → capture) |
| Source header | | | |
| Agent header | Yes | Yes | Yes |
| Model header | Yes | Yes | Yes |
| No | No | Yes |
| Attribution | Automatic (backend) | Automatic (backend) | Automatic (backend) |
Content Guidelines
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. Avoid first-person narrative ("I was debugging...", "I discovered..."). Exception: brief situational context is fine ("When upgrading Rails from 7.2 to 8.0...").
- 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.
- Title: 5-15 words. Descriptive, same language as content. No "TIL:" prefix.
- Content: Use the most efficient format for the knowledge — tables for comparisons, code blocks for examples, lists for enumerations, math (/
$inline$) for formulas with fractions/subscripts/superscripts/greek letters, Mermaid diagrams ($$display$$) for flows/states/sequences that text cannot clearly express. Simple expressions like```mermaidstay 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.O(n) - Tags: 1-3 lowercase tags from the technical domain (,
go,rails,postgresql,css). No generic tags likelinuxorprogramming.til - Lang: Detect from content. Chinese -> , Traditional Chinese ->
zh-CN, English ->zh-TW, Japanese ->en, Korean ->ja.ko - Category: Do not auto-infer -- only include it if the user explicitly specifies a category/topic.
category_name
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-implicitlyExtract the field from the API response for the URL.
urlSync Local Drafts
After the first successful API call, check for pending files. If any exist, offer to sync:
~/.til/drafts/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?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 selectionIf the user declines, keep the local files and do not ask again in this session.
First Run (no token)
Save the draft locally, then show a concise setup hint. 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
Sync to OpenTIL? Run: /til authOnly show the setup hint on the first local save in this session. On subsequent saves, use the short form:
TIL captured
Title: Go interfaces are satisfied implicitly
Tags: go, interfaces
File: ~/.til/drafts/20260210-143022-go-interfaces.mdError Handling
On ANY API failure, always save the draft locally first. Never let user content be lost.
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:
TIL captured (saved locally -- token expired)
File: ~/.til/drafts/20260210-143022-go-interfaces.md
Token expired. Run /til auth to reconnect.Network failure or 5xx:
TIL captured (saved locally -- API unavailable)
File: ~/.til/drafts/20260210-143022-go-interfaces.mdFull error codes, 422 auto-fix logic, and rate limit details: see references/api.md
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>.mdmarkdown
---
title: "Go interfaces are satisfied implicitly"
tags: [go, interfaces]
lang: en
---
In Go, a type implements an interface...Full directory structure, metadata fields, and sync protocol: see references/local-drafts.md
Notes
- Entries are published immediately by default () -- use
published: trueto revert to draft/til unpublish <id> - 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) require a token -- no local fallback. Exception:batchandstatuswork without a token.auth - Scope errors map to specific scopes: /
list/search/tagsneedcategories,read:entries/publish/unpublish/edit/syncneedbatch,write:entriesneedsdelete.delete:entriesusesstatuswhen available but works without a token.read:entries