@opentil/cli 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,142 @@
1
+ # Local Drafts & Sync Protocol
2
+
3
+ When the API is unavailable or no token is configured, TIL entries are saved locally. This document covers the full local draft lifecycle.
4
+
5
+ ## Directory Structure
6
+
7
+ ```
8
+ ~/.til/
9
+ drafts/
10
+ 20260210-143022-go-interfaces.md
11
+ 20260210-150415-postgresql-gin-index.md
12
+ 20260211-091200-css-has-selector.md
13
+ ```
14
+
15
+ All platforms use `~/.til/drafts/`. Create the directory if it does not exist.
16
+
17
+ ## File Format
18
+
19
+ Filename: `YYYYMMDD-HHMMSS-<slug>.md`
20
+
21
+ The slug is derived from the title (lowercase, hyphens, no special chars, max 50 chars).
22
+
23
+ ```markdown
24
+ ---
25
+ title: "Go interfaces are satisfied implicitly"
26
+ tags: [go, interfaces]
27
+ lang: en
28
+ summary: "Go types implement interfaces implicitly by implementing their methods, with no explicit declaration needed."
29
+ source: human
30
+ agent_name: Claude Code
31
+ agent_model: Claude Opus 4.6
32
+ profile: personal
33
+ ---
34
+
35
+ In Go, a type implements an interface by implementing its methods.
36
+ There is no explicit `implements` keyword...
37
+ ```
38
+
39
+ ### Frontmatter Fields
40
+
41
+ | Field | Type | Description |
42
+ |-------|------|-------------|
43
+ | `title` | string | Entry title |
44
+ | `tags` | array | Tag list |
45
+ | `lang` | string | Language code |
46
+ | `summary` | string | AI-generated summary for listing pages (max 500 chars) |
47
+ | `source` | string | `human` (from `/til`) or `agent` (from auto-detection) |
48
+ | `agent_name` | string | Agent display name, e.g. `Claude Code` (optional) |
49
+ | `agent_model` | string | Human-readable model name, e.g. `Claude Opus 4.6` (optional) |
50
+ | `profile` | string | Active profile name at save time (optional). Used during sync to determine which account's token to use. Omitted when no profiles are configured. |
51
+
52
+ The `source`, `agent_name`, and `agent_model` fields preserve attribution so that when syncing to the API, the correct headers and tags can be applied.
53
+
54
+ The `profile` field ensures drafts are synced to the correct account in multi-profile setups.
55
+
56
+ ## Sync Protocol
57
+
58
+ When a successful API call is made (201 response), check for pending local drafts:
59
+
60
+ ### Step 1: Detect Pending Drafts
61
+
62
+ ```
63
+ List files in ~/.til/drafts/ matching *.md
64
+ ```
65
+
66
+ If no files exist, skip sync entirely.
67
+
68
+ ### Step 2: Offer to Sync
69
+
70
+ ```
71
+ Found 3 local drafts from before. Sync them to OpenTIL?
72
+ ```
73
+
74
+ Wait for user confirmation. If the user declines, do not ask again this session.
75
+
76
+ ### Step 3: Sync Each Draft
77
+
78
+ For each `.md` file in `~/.til/drafts/`:
79
+
80
+ 1. Parse the frontmatter (title, tags, lang, source, agent_name, agent_model, profile)
81
+ 2. **Resolve token for this draft** (profile matching):
82
+ - If `$OPENTIL_TOKEN` is set → always use it (env var overrides all profiles)
83
+ - If `profile` field is present → look up that profile's token in `~/.til/credentials`
84
+ - Profile found → use its token
85
+ - Profile not found → skip this draft, report: `Skipped: profile "work" not found (/til auth list)`
86
+ - If `profile` field is absent (old drafts) → use the current active profile's token
87
+ 3. Read the content body (everything after the second `---`)
88
+ 4. POST to API (using the resolved token):
89
+ - Set `published: false`
90
+ - Set `X-OpenTIL-Source` header based on `source` field
91
+ - Set `X-OpenTIL-Agent` header from `agent_name` field (if present)
92
+ - Set `X-OpenTIL-Model` header from `agent_model` field (if present)
93
+ - Add `agent-assisted` tag if `source` is `agent`
94
+ 5. On 201 success: delete the local file
95
+ 6. On failure: keep the local file, record the error
96
+
97
+ ### Step 4: Report Results
98
+
99
+ **All succeeded:**
100
+ ```
101
+ Synced 3 local drafts to OpenTIL
102
+
103
+ + Go defer runs in LIFO order
104
+ + PostgreSQL JSONB indexes support GIN operators
105
+ + CSS :has() selector enables parent selection
106
+ ```
107
+
108
+ **Partial failure:**
109
+ ```
110
+ Synced 2 of 3 local drafts
111
+
112
+ + Go defer runs in LIFO order
113
+ + PostgreSQL JSONB indexes support GIN operators
114
+ x CSS :has() selector enables parent selection (validation error)
115
+ Kept at: ~/.til/drafts/20260210-143022-css-has-selector.md
116
+ ```
117
+
118
+ ## First-Run Guide Template
119
+
120
+ On the first local save in a session (when no token is found):
121
+
122
+ ```
123
+ TIL captured
124
+
125
+ Title: Go interfaces are satisfied implicitly
126
+ Tags: go, interfaces
127
+ File: ~/.til/drafts/20260210-143022-go-interfaces.md
128
+
129
+ Sync to OpenTIL? Run: /til auth
130
+ ```
131
+
132
+ On subsequent local saves in the same session, use the short form:
133
+
134
+ ```
135
+ TIL captured
136
+
137
+ Title: Go interfaces are satisfied implicitly
138
+ Tags: go, interfaces
139
+ File: ~/.til/drafts/20260210-143022-go-interfaces.md
140
+ ```
141
+
142
+ Track "first save shown" as session state. Reset on each new session.