@tloncorp/tlon-skill 0.1.11 → 0.2.1

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.
package/README.md CHANGED
@@ -26,7 +26,10 @@ curl -L https://registry.npmjs.org/@tloncorp/tlon-skill-linux-x64/-/tlon-skill-l
26
26
 
27
27
  **Option 1: CLI flags (highest priority)**
28
28
  ```bash
29
- # Pass credentials directly
29
+ # Cookie-based auth (fastest - ship parsed from cookie)
30
+ tlon --url https://your-ship.tlon.network --cookie "urbauth-~your-ship=0v..." contacts self
31
+
32
+ # Code-based auth (requires all three)
30
33
  tlon --url https://your-ship.tlon.network --ship ~your-ship --code sampel-ticlyt-migfun-falmel contacts self
31
34
 
32
35
  # Or use a config file
@@ -35,11 +38,20 @@ tlon --config ~/ships/my-ship.json contacts self
35
38
 
36
39
  Config file format:
37
40
  ```json
41
+ // Cookie-based (ship derived from cookie)
42
+ {"url": "https://your-ship.tlon.network", "cookie": "urbauth-~your-ship=0v..."}
43
+
44
+ // Code-based
38
45
  {"url": "https://your-ship.tlon.network", "ship": "~your-ship", "code": "sampel-ticlyt-migfun-falmel"}
39
46
  ```
40
47
 
41
48
  **Option 2: Environment variables**
42
49
  ```bash
50
+ # Cookie-based (ship derived from cookie)
51
+ export URBIT_URL="https://your-ship.tlon.network"
52
+ export URBIT_COOKIE="urbauth-~your-ship=0v..."
53
+
54
+ # Code-based
43
55
  export URBIT_URL="https://your-ship.tlon.network"
44
56
  export URBIT_SHIP="~your-ship"
45
57
  export URBIT_CODE="sampel-ticlyt-migfun-falmel"
@@ -49,16 +61,41 @@ export URBIT_CODE="sampel-ticlyt-migfun-falmel"
49
61
 
50
62
  If you have OpenClaw configured with a Tlon channel, credentials are loaded automatically.
51
63
 
64
+ ## Cookie Caching
65
+
66
+ The skill automatically caches auth cookies to `~/.tlon/cache/<ship>.json` after successful authentication.
67
+
68
+ ```bash
69
+ # First time - auth and cache
70
+ $ tlon --url https://zod.tlon.network --ship ~zod --code abcd-efgh contacts self
71
+ Note: Credentials cached for ~zod. Next time just run: tlon <command>
72
+
73
+ # After that - no flags needed!
74
+ $ tlon contacts self
75
+
76
+ # Multiple cached ships? Specify which one:
77
+ $ tlon --ship ~zod contacts self
78
+ ```
79
+
80
+ Clear cache: `rm ~/.tlon/cache/*.json`
81
+
82
+ ## Cookie vs Code Authentication
83
+
84
+ - **Cookie-based auth**: Uses a pre-authenticated session cookie. Faster since it skips login.
85
+ - **Code-based auth**: Performs a login request to get a session cookie.
86
+
87
+ The ship name is embedded in the cookie (`urbauth-~ship=...`), so you don't need to specify it separately with cookie auth.
88
+
52
89
  ## Multi-Ship Usage
53
90
 
54
- If you have credentials for multiple ships, you can operate on behalf of any of them by passing their credentials via CLI flags. This is useful for managing multiple identities, bot operations, or moon management:
91
+ If you have credentials for multiple ships, you can operate on behalf of any of them:
55
92
 
56
93
  ```bash
57
94
  # Act as a different ship
58
95
  tlon --config ~/ships/bot.json channels groups
59
96
 
60
97
  # Or pass credentials directly
61
- tlon --url https://bot.tlon.network --ship ~bot-ship --code bot-code contacts self
98
+ tlon --url https://bot.tlon.network --cookie "urbauth-~bot=0v..." contacts self
62
99
  ```
63
100
 
64
101
  ## Usage
@@ -82,11 +119,12 @@ tlon groups create "My Group" --description "A cool group"
82
119
 
83
120
  ## Features
84
121
 
85
- - **Activity**: Mentions, replies, unreads
86
- - **Channels**: List DMs, group DMs, subscribed groups
122
+ - **Activity**: Mentions, replies, unreads (with nicknames)
123
+ - **Channels**: List DMs, group DMs, subscribed groups (nicknames shown), reader/writer permissions
87
124
  - **Contacts**: List, get, update profiles
88
- - **Groups**: Create, join, invite, roles, privacy
89
- - **Messages**: History, search
125
+ - **Groups**: Create, join, invite, roles, privacy (member nicknames shown)
126
+ - **Hooks**: Manage channel hooks (add, edit, delete, order, config, cron)
127
+ - **Messages**: History, search (author nicknames shown)
90
128
  - **DMs**: Send, react, accept/decline
91
129
  - **Posts**: React, delete
92
130
  - **Notebook**: Post to diary channels
package/SKILL.md CHANGED
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: tlon
3
- description: Interact with Tlon/Urbit API. Use for contacts (get/update profiles), listing channels/groups, fetching message history, posting to channels, sending DMs, and group management.
3
+ description: Interact with Tlon/Urbit API. Use for contacts (get/update profiles), listing channels/groups, fetching message history, posting to channels, sending DMs, group management, and exposing content to the clearweb.
4
4
  ---
5
5
 
6
6
  # Tlon Skill
@@ -23,21 +23,37 @@ curl -L https://registry.npmjs.org/@tloncorp/tlon-skill-darwin-arm64/-/tlon-skil
23
23
 
24
24
  Replace `darwin-arm64` with `darwin-x64` or `linux-x64` as needed.
25
25
 
26
+
26
27
  ## Configuration
27
28
 
28
29
  **CLI Flags (highest priority):**
29
30
  ```bash
30
- # Pass all three credentials directly
31
+ # Cookie-based auth (fastest - ship parsed from cookie name)
32
+ tlon --url https://your-ship.tlon.network --cookie "urbauth-~your-ship=0v..." <command>
33
+
34
+ # Code-based auth (requires url + ship + code)
31
35
  tlon --url https://your-ship.tlon.network --ship ~your-ship --code sampel-ticlyt-migfun-falmel <command>
32
36
 
33
37
  # Or load from a JSON config file
34
38
  tlon --config ~/ships/my-ship.json <command>
35
39
  ```
36
40
 
37
- Config file format: `{"url": "...", "ship": "~...", "code": "..."}`
41
+ Config file format:
42
+ ```json
43
+ // Cookie-based (ship derived from cookie)
44
+ {"url": "...", "cookie": "urbauth-~ship=..."}
45
+
46
+ // Code-based
47
+ {"url": "...", "ship": "~...", "code": "..."}
48
+ ```
38
49
 
39
50
  **Environment Variables:**
40
51
  ```bash
52
+ # Cookie-based (ship derived from cookie)
53
+ export URBIT_URL="https://your-ship.tlon.network"
54
+ export URBIT_COOKIE="urbauth-~your-ship=0v..."
55
+
56
+ # Code-based
41
57
  export URBIT_URL="https://your-ship.tlon.network"
42
58
  export URBIT_SHIP="~your-ship"
43
59
  export URBIT_CODE="sampel-ticlyt-migfun-falmel"
@@ -45,7 +61,43 @@ export URBIT_CODE="sampel-ticlyt-migfun-falmel"
45
61
 
46
62
  **OpenClaw:** If configured with a Tlon channel, credentials load automatically.
47
63
 
48
- **Resolution order:** CLI flags → `TLON_CONFIG_FILE` → `TLON_SHIP`+`TLON_SKILL_DIR` → `URBIT_*` env vars → OpenClaw config
64
+ **Resolution order:** CLI flags → `TLON_CONFIG_FILE` → `URL + COOKIE` → `URL + SHIP + CODE` `--ship` with cache → OpenClaw config → cached ships (auto-select if only one)
65
+
66
+ **Cookie vs Code:**
67
+ - **Cookie-based:** Uses pre-authenticated session cookie. Ship is parsed from the cookie name (`urbauth-~ship=...`). Fastest option.
68
+ - **Code-based:** Performs login to get session cookie. Requires URL + ship + code.
69
+
70
+ You can provide both cookie and code — cookie is used first, code serves as fallback if cookie expires.
71
+
72
+ ## Cookie Caching
73
+
74
+ The skill automatically caches auth cookies to `~/.tlon/cache/<ship>.json` after successful authentication. This makes subsequent invocations much faster by skipping the login request.
75
+
76
+ **How it works:**
77
+ ```bash
78
+ # First time - authenticates and caches
79
+ $ tlon --url https://zod.tlon.network --ship ~zod --code abcd-efgh contacts self
80
+ ~zod
81
+ Note: Credentials cached for ~zod. Next time just run: tlon <command>
82
+
83
+ # After that - no flags needed (if only one cached ship)
84
+ $ tlon contacts self
85
+ ~zod
86
+
87
+ # With multiple cached ships - specify which one
88
+ $ tlon --ship ~zod contacts self
89
+ $ tlon --ship ~bus contacts self
90
+ ```
91
+
92
+ **Cache behavior:**
93
+ - Cached cookies are URL-specific (won't use a cookie for the wrong host)
94
+ - If only one ship is cached, it's auto-selected (no flags needed)
95
+ - If multiple ships are cached, you'll be prompted to specify with `--ship`
96
+ - The skill reminds you when you pass credentials that aren't needed
97
+
98
+ **Clear cache:** `rm ~/.tlon/cache/*.json`
99
+
100
+
49
101
 
50
102
  ## Multi-Ship Usage
51
103
 
@@ -71,7 +123,7 @@ tlon --config ~/ships/moon.json contacts self
71
123
 
72
124
  ### Activity
73
125
 
74
- Check recent notifications and unread counts.
126
+ Check recent notifications and unread counts. Ships are shown with nicknames when available.
75
127
 
76
128
  ```bash
77
129
  tlon activity mentions --limit 10 # Recent mentions (max 25)
@@ -82,18 +134,32 @@ tlon activity unreads # Unread counts per channel
82
134
 
83
135
  ### Channels
84
136
 
85
- List and manage channels.
137
+ List and manage channels. DMs and group DMs show nicknames when available.
86
138
 
87
139
  ```bash
88
- tlon channels dms # List DM contacts
89
- tlon channels group-dms # List group DMs (clubs)
140
+ tlon channels dms # List DM contacts (with nicknames)
141
+ tlon channels group-dms # List group DMs (clubs, with nicknames)
90
142
  tlon channels groups # List subscribed groups
91
143
  tlon channels all # List everything
92
144
  tlon channels info chat/~host/slug # Get channel details
93
145
  tlon channels update chat/~host/slug --title "New Title" # Update metadata
94
146
  tlon channels delete chat/~host/slug # Delete a channel
147
+
148
+ # Writers (who can post)
149
+ tlon channels add-writers chat/~host/slug admin member # Add write access
150
+ tlon channels del-writers chat/~host/slug member # Remove write access
151
+
152
+ # Readers (who can view - requires group flag)
153
+ tlon channels add-readers ~host/group chat/~host/slug admin # Restrict viewing
154
+ tlon channels del-readers ~host/group chat/~host/slug admin # Open viewing
95
155
  ```
96
156
 
157
+ Notes on permissions:
158
+ - Empty writers list = anyone in the group can post (default for chat)
159
+ - Empty readers list = anyone in the group can view (default)
160
+ - Diaries default to admin-only writers
161
+ - Roles must exist in the group (use `tlon groups add-role` first)
162
+
97
163
  ### Contacts
98
164
 
99
165
  Manage contacts and profiles.
@@ -124,7 +190,7 @@ tlon groups leave ~host/slug # Leave a group
124
190
  tlon groups delete ~host/slug # Delete (host only)
125
191
  tlon groups update ~host/slug --title "..." [--description "..."]
126
192
 
127
- # Members
193
+ # Members (shown with nicknames when available)
128
194
  tlon groups invite ~host/slug ~ship1 ~ship2 # Invite members
129
195
  tlon groups kick ~host/slug ~ship1 # Kick members
130
196
  tlon groups ban ~host/slug ~ship1 # Ban members
@@ -146,9 +212,52 @@ tlon groups add-channel ~host/slug "Name" [--kind chat|diary|heap]
146
212
 
147
213
  Group format: `~host-ship/group-slug`
148
214
 
215
+ ### Hooks
216
+
217
+ Manage channel hooks — functions that run on triggers (posts, replies, reactions, crons).
218
+
219
+ ```bash
220
+ # List and inspect
221
+ tlon hooks list # List all hooks
222
+ tlon hooks get 0v1a.2b3c4 # Get hook details and source
223
+
224
+ # Manage hooks
225
+ tlon hooks init my-hook --type on-post # Create starter template (on-post|cron|moderation)
226
+ tlon hooks add my-hook ./my-hook.hoon # Add a new hook from file
227
+ tlon hooks edit 0v1a.2b3c4 --name "New Name" # Rename a hook
228
+ tlon hooks edit 0v1a.2b3c4 --src ./updated.hoon # Update source
229
+ tlon hooks delete 0v1a.2b3c4 # Delete a hook
230
+
231
+ # Configure for channels
232
+ tlon hooks order chat/~host/slug 0v1a 0v2b 0v3c # Set execution order
233
+ tlon hooks config 0v1a chat/~host/slug key1=val1 # Configure hook instance
234
+
235
+ # Scheduled execution
236
+ tlon hooks cron 0v1a ~h1 # Run hourly (global)
237
+ tlon hooks cron 0v1a ~m30 --nest chat/~host/slug # Run every 30m for channel
238
+ tlon hooks rest 0v1a # Stop cron job
239
+ ```
240
+
241
+ Notes:
242
+ - Hook IDs are @uv format (e.g., `0v1a.2b3c4...`)
243
+ - Schedules use @dr format: `~h1` (1 hour), `~m30` (30 minutes), `~d1` (1 day)
244
+ - Hooks run in order when triggered; use `order` to set priority
245
+ - Use `config` to pass channel-specific settings to a hook instance
246
+
247
+ **Writing Hooks:** See `references/hooks.md` for full documentation on writing hooks, including:
248
+ - Event types (`on-post`, `on-reply`, `cron`, `wake`)
249
+ - Bowl context (channel, group, config access)
250
+ - Effects (channel actions, group actions, scheduled wakes)
251
+ - Config handling with clam (`;;`)
252
+
253
+ **Examples:** See `references/hooks-examples/` for starter templates:
254
+ - `auto-react.hoon` — React to new posts with emoji
255
+ - `delete-old-posts.hoon` — Cron job to clean up old messages
256
+ - `word-filter.hoon` — Block posts containing banned words
257
+
149
258
  ### Messages
150
259
 
151
- Read and search message history.
260
+ Read and search message history. Authors are shown with nicknames when available.
152
261
 
153
262
  ```bash
154
263
  tlon messages dm ~sampel --limit 20 # DM history (max 50)
@@ -175,6 +284,24 @@ tlon dms accept ~sampel # Accept DM invite
175
284
  tlon dms decline ~sampel # Decline DM invite
176
285
  ```
177
286
 
287
+ ### Expose
288
+
289
+ Publish Tlon content to the clearweb via the %expose agent.
290
+
291
+ ```bash
292
+ tlon expose list # List all exposed content
293
+ tlon expose show chat/~host/slug/170.141... # Expose a post publicly
294
+ tlon expose hide chat/~host/slug/170.141... # Hide an exposed post
295
+ tlon expose check diary/~host/blog/170.141... # Check if a post is exposed
296
+ tlon expose url diary/~host/blog/170.141... # Get the public URL
297
+ ```
298
+
299
+ Cite path formats:
300
+ - Simplified: `chat/~host/channel/170.141...` (auto-expands)
301
+ - Full: `/1/chan/chat/~host/channel/msg/170.141...`
302
+
303
+ Channel kinds map to content types: chat→msg, diary→note, heap→curio
304
+
178
305
  ### Posts
179
306
 
180
307
  Manage channel posts (reactions, edits, deletes).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tloncorp/tlon-skill",
3
- "version": "0.1.11",
3
+ "version": "0.2.1",
4
4
  "description": "Tlon/Urbit skill for OpenClaw agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -9,7 +9,8 @@
9
9
  "files": [
10
10
  "bin/",
11
11
  "scripts/postinstall.js",
12
- "SKILL.md"
12
+ "SKILL.md",
13
+ "references/"
13
14
  ],
14
15
  "scripts": {
15
16
  "build": "node scripts/build.js",
@@ -20,13 +21,13 @@
20
21
  "postinstall": "node scripts/postinstall.js"
21
22
  },
22
23
  "optionalDependencies": {
23
- "@tloncorp/tlon-skill-darwin-arm64": "0.1.11",
24
- "@tloncorp/tlon-skill-darwin-x64": "0.1.11",
25
- "@tloncorp/tlon-skill-linux-x64": "0.1.11",
26
- "@tloncorp/tlon-skill-linux-arm64": "0.1.11"
24
+ "@tloncorp/tlon-skill-darwin-arm64": "0.2.1",
25
+ "@tloncorp/tlon-skill-darwin-x64": "0.2.1",
26
+ "@tloncorp/tlon-skill-linux-x64": "0.2.1",
27
+ "@tloncorp/tlon-skill-linux-arm64": "0.2.1"
27
28
  },
28
29
  "devDependencies": {
29
- "@tloncorp/api": "git+https://github.com/tloncorp/api-beta.git#main",
30
+ "@tloncorp/api": "git+https://github.com/tloncorp/api-beta.git#c5eb1720d94435b62503605acb294ef42a5440b3",
30
31
  "@urbit/aura": "^3.0.0",
31
32
  "@types/node": "^22.0.0",
32
33
  "typescript": "^5.9.3"
@@ -0,0 +1,24 @@
1
+ :: Auto-react hook: reacts to new posts with configured emoji
2
+ :: Config: emoji (default 👍)
3
+ ::
4
+ |= [=event:h =bowl:h]
5
+ ^- outcome:h
6
+ :: Extract config with defaults
7
+ =+ ;;(emoji=cord (~(gut by config.bowl) 'emoji' '👍'))
8
+ :: Only react to new posts
9
+ ?. ?=([%on-post %add *] event)
10
+ &+[[[%allowed event] ~] state.hook.bowl]
11
+ :: Don't react to our own posts
12
+ ?: =(author.post.event our.bowl)
13
+ &+[[[%allowed event] ~] state.hook.bowl]
14
+ :: Need channel context
15
+ ?~ channel.bowl
16
+ &+[[[%allowed event] ~] state.hook.bowl]
17
+ :: React to the post
18
+ =/ react-effect=effect:h
19
+ :* %channels
20
+ %channel
21
+ nest.u.channel.bowl
22
+ [%post [%add-react id.post.event our.bowl emoji]]
23
+ ==
24
+ &+[[[%allowed event] [react-effect ~]] state.hook.bowl]
@@ -0,0 +1,18 @@
1
+ :: Disappearing messages: deletes posts older than configured delay
2
+ :: From: https://github.com/tloncorp/hooks/blob/master/hooks/disappearing.hoon
3
+ :: Config: delay (default ~s30 = 30 seconds)
4
+ ::
5
+ |= [=event:h bowl:h]
6
+ ^- outcome:h
7
+ =- &+[[[%allowed event] -] state.hook]
8
+ ?. ?=(%cron -.event) ~
9
+ ^- (list effect:h)
10
+ =+ ;;(delay=@dr (~(gut by config) 'delay' ~s30))
11
+ =/ cutoff (sub now delay)
12
+ ?~ channel ~
13
+ %+ murn
14
+ (tap:on-v-posts:c (lot:on-v-posts:c posts.u.channel ~ `cutoff))
15
+ |= [=id-post:c post=(may:c v-post:c)]
16
+ ^- (unit effect:h)
17
+ ?: ?=(%| -.post) ~
18
+ `[%channels %channel nest.u.channel %post %del id-post]
@@ -0,0 +1,52 @@
1
+ :: Word filter hook: blocks posts containing banned words
2
+ :: Config: words (comma-separated list of words to block)
3
+ ::
4
+ |= [=event:h =bowl:h]
5
+ ^- outcome:h
6
+ |^
7
+ :: Only filter new posts
8
+ ?. ?=([%on-post %add *] event)
9
+ &+[[[%allowed event] ~] state.hook.bowl]
10
+ :: Get banned words from config (comma-separated)
11
+ =+ ;;(words-cord=cord (~(gut by config.bowl) 'words' ''))
12
+ :: Skip if no words configured
13
+ ?: =('' words-cord)
14
+ &+[[[%allowed event] ~] state.hook.bowl]
15
+ :: Split on commas into list of tapes
16
+ =/ banned=(list tape)
17
+ (split-on-comma (trip words-cord))
18
+ :: Get message content
19
+ =/ content=tape (extract-text content.post.event)
20
+ :: Check if any banned word appears in content
21
+ =/ has-banned=?
22
+ %+ lien banned
23
+ |= word=tape
24
+ !=(~ (find word content))
25
+ :: If found, deny
26
+ ?: has-banned
27
+ &+[[[%denied `'Message contains prohibited content'] ~] state.hook.bowl]
28
+ :: Otherwise allow
29
+ &+[[[%allowed event] ~] state.hook.bowl]
30
+ ::
31
+ ++ split-on-comma
32
+ |= txt=tape
33
+ ^- (list tape)
34
+ =/ idx (find "," txt)
35
+ ?~ idx
36
+ ?~ txt *(list tape)
37
+ ~[txt]
38
+ :- (scag u.idx txt)
39
+ $(txt (slag +(u.idx) txt))
40
+ ::
41
+ ++ extract-text
42
+ |= =story:c
43
+ ^- tape
44
+ ?~ story ""
45
+ =/ verse i.story
46
+ ?. ?=(%inline -.verse) ""
47
+ ?~ p.verse ""
48
+ =/ inl i.p.verse
49
+ ?@ inl
50
+ (trip inl)
51
+ ""
52
+ --
@@ -0,0 +1,366 @@
1
+ # Tlon Channel Hooks
2
+
3
+ Hooks are hoon functions that modify events, cause effects, and/or build state for channels.
4
+
5
+ ## Version Compatibility
6
+
7
+ Hooks are tightly coupled to `channels-server` / `tlon-apps` types.
8
+ When docs/examples drift from runtime types, compilation can fail even if logic is correct.
9
+
10
+ Recommended practice:
11
+ - Pin your local references to a known `tlon-apps` commit/tag
12
+ - Record that version in PRs when adding/updating hooks
13
+ - Prefer examples in this folder that were verified against the current runtime
14
+
15
+ ## Hook Structure
16
+
17
+ ```hoon
18
+ ++ hook
19
+ $: id=id-hook
20
+ version=%0
21
+ name=@t
22
+ meta=data:m
23
+ src=@t
24
+ compiled=(unit vase)
25
+ state=vase
26
+ config=(map nest config)
27
+ ==
28
+ ```
29
+
30
+ - `id` - unique identifier (@uv format)
31
+ - `version` - the version this hook was written for
32
+ - `name` - human-readable display name
33
+ - `meta` - standard metadata (title/image/desc/cover)
34
+ - `src` - the source code for the hook
35
+ - `compiled` - result of compiling hoon to nock
36
+ - `state` - container to collect/persist data
37
+ - `config` - configurations for each channel
38
+
39
+ ## Events
40
+
41
+ Hooks respond to four event types:
42
+
43
+ ```hoon
44
+ +$ event
45
+ $% [%on-post on-post]
46
+ [%on-reply on-reply]
47
+ [%cron ~]
48
+ [%wake waiting-hook]
49
+ ==
50
+ ```
51
+
52
+ ### on-post events
53
+ ```hoon
54
+ +$ on-post
55
+ $% [%add post=v-post]
56
+ [%edit original=v-post =essay]
57
+ [%del original=v-post]
58
+ [%react post=v-post =ship react=(unit react)]
59
+ ==
60
+ ```
61
+
62
+ ### on-reply events
63
+ ```hoon
64
+ +$ on-reply
65
+ $% [%add parent=v-post reply=v-reply]
66
+ [%edit parent=v-post original=v-reply =memo]
67
+ [%del parent=v-post original=v-reply]
68
+ [%react parent=v-post reply=v-reply =ship react=(unit react)]
69
+ ==
70
+ ```
71
+
72
+ ## Bowl (Context)
73
+
74
+ Hooks receive ambient state via the bowl:
75
+
76
+ ```hoon
77
+ +$ bowl
78
+ $: channel=(unit [=nest v-channel]) :: current channel (null for global cron)
79
+ group=(unit group-ui:g) :: group data
80
+ channels=v-channels :: all hosted channels
81
+ =hook :: this hook's data
82
+ =config :: channel-specific config
83
+ now=time :: current time
84
+ our=ship :: host ship
85
+ src=ship :: triggering ship
86
+ eny=@ :: entropy
87
+ ==
88
+ ```
89
+
90
+ **Important:** Access patterns depend on whether you use a face:
91
+ - `|= [=event:h =bowl:h]` (with face) → access via `config.bowl`, `channel.bowl`, `state.hook.bowl`
92
+ - `|= [=event:h bowl:h]` (no face) → access via `config`, `channel`, `state.hook`
93
+
94
+ ## Return Type
95
+
96
+ ```hoon
97
+ +$ outcome (each return tang)
98
+
99
+ +$ return
100
+ $: result=event-result
101
+ effects=(list effect)
102
+ new-state=vase
103
+ ==
104
+
105
+ +$ event-result
106
+ $% [%allowed =event] :: allow event, optionally transform it
107
+ [%denied msg=(unit cord)] :: block event with optional message
108
+ ==
109
+ ```
110
+
111
+ ## Effects
112
+
113
+ Hooks can trigger actions on other agents:
114
+
115
+ ```hoon
116
+ +$ effect
117
+ $% [%channels =a-channels] :: channel actions
118
+ [%groups =action:g] :: group actions (ban, kick, etc)
119
+ [%activity =action:a] :: activity actions
120
+ [%dm =action:dm:ch] :: DM actions
121
+ [%club =action:club:ch] :: group DM actions
122
+ [%contacts =action:co] :: contact actions
123
+ [%wait waiting-hook] :: schedule delayed execution
124
+ ==
125
+ ```
126
+
127
+ ### Channel Effects
128
+
129
+ The most common effect pattern for channel actions:
130
+
131
+ ```hoon
132
+ :: React to a post
133
+ [%channels %channel nest [%post [%add-react post-id ship emoji]]]
134
+
135
+ :: Delete a post
136
+ [%channels %channel nest %post %del post-id]
137
+ ```
138
+
139
+ **Note:** Use actual unicode emoji (`'👍'`, `'🔥'`) not shortcodes (`:thumbsup:`).
140
+
141
+ ## Config
142
+
143
+ Config is `(map @t *)` on the Hoon side.
144
+ From CLI, values are sent as text and then clammed/coerced in-hook.
145
+ Use clam (`;;`) to extract typed values with defaults:
146
+
147
+ ```hoon
148
+ :: With bowl face (=bowl:h)
149
+ =+ ;;(delay=@dr (~(gut by config.bowl) 'delay' ~s30))
150
+ =+ ;;(emoji=cord (~(gut by config.bowl) 'emoji' '👍'))
151
+
152
+ :: Without bowl face (bowl:h)
153
+ =+ ;;(delay=@dr (~(gut by config) 'delay' ~s30))
154
+ ```
155
+
156
+ CLI tips:
157
+ - Prefer simple text/cord values first (`password=owl-pass`)
158
+ - For booleans/durations, verify with `hooks get <id>` after setting config
159
+ - If a config poke fails, inspect the exact update payload from CLI output
160
+
161
+ ## Plaintext Helper Pattern
162
+
163
+ The hook subject includes a `flatten` gate via `channel-utils`.
164
+ For moderation/search-like use cases, prefer this default:
165
+
166
+ ```hoon
167
+ =/ text=tape
168
+ (trip (flatten content.post.event))
169
+ ```
170
+
171
+ `flatten` intentionally focuses on searchable/user-visible text and may skip some structures
172
+ (e.g. code blocks or other rich content forms depending on story shape).
173
+
174
+ If you need strict/full extraction (including code), implement a custom story walker gate.
175
+
176
+ ## Writing a Hook
177
+
178
+ Basic hook template (with bowl face):
179
+
180
+ ```hoon
181
+ |= [=event:h =bowl:h]
182
+ ^- outcome:h
183
+ :: Return success with: [allowed-result effects new-state]
184
+ &+[[[%allowed event] ~] state.hook.bowl]
185
+ ```
186
+
187
+ Basic hook template (without bowl face):
188
+
189
+ ```hoon
190
+ |= [=event:h bowl:h]
191
+ ^- outcome:h
192
+ :: Return success with: [allowed-result effects new-state]
193
+ &+[[[%allowed event] ~] state.hook]
194
+ ```
195
+
196
+ ### Example: Auto-react to new posts
197
+
198
+ ```hoon
199
+ :: Auto-react hook: reacts to new posts with configured emoji
200
+ :: Config: emoji (default 👍)
201
+ ::
202
+ |= [=event:h =bowl:h]
203
+ ^- outcome:h
204
+ :: Extract config with defaults
205
+ =+ ;;(emoji=cord (~(gut by config.bowl) 'emoji' '👍'))
206
+ :: Only react to new posts
207
+ ?. ?=([%on-post %add *] event)
208
+ &+[[[%allowed event] ~] state.hook.bowl]
209
+ :: Don't react to our own posts
210
+ ?: =(author.post.event our.bowl)
211
+ &+[[[%allowed event] ~] state.hook.bowl]
212
+ :: Need channel context
213
+ ?~ channel.bowl
214
+ &+[[[%allowed event] ~] state.hook.bowl]
215
+ :: React to the post
216
+ =/ react-effect=effect:h
217
+ :* %channels
218
+ %channel
219
+ nest.u.channel.bowl
220
+ [%post [%add-react id.post.event our.bowl emoji]]
221
+ ==
222
+ &+[[[%allowed event] [react-effect ~]] state.hook.bowl]
223
+ ```
224
+
225
+ **Key points:**
226
+ - Check `author.post.event` (not `src.bowl`) for self-detection
227
+ - Check `channel.bowl` for null before accessing `u.channel.bowl`
228
+ - Effect structure: `[%channels %channel nest [%post [%add-react ...]]]`
229
+ - Use actual emoji unicode, not shortcodes
230
+
231
+ ### Example: Disappearing messages (cron)
232
+
233
+ From [tloncorp/hooks](https://github.com/tloncorp/hooks/blob/master/hooks/disappearing.hoon):
234
+
235
+ ```hoon
236
+ :: Disappearing messages: deletes posts older than configured delay
237
+ :: Config: delay (default ~s30 = 30 seconds)
238
+ ::
239
+ |= [=event:h bowl:h]
240
+ ^- outcome:h
241
+ =- &+[[[%allowed event] -] state.hook]
242
+ ?. ?=(%cron -.event) ~
243
+ ^- (list effect:h)
244
+ =+ ;;(delay=@dr (~(gut by config) 'delay' ~s30))
245
+ =/ cutoff (sub now delay)
246
+ ?~ channel ~
247
+ %+ murn
248
+ (tap:on-v-posts:c (lot:on-v-posts:c posts.u.channel ~ `cutoff))
249
+ |= [=id-post:c post=(may:c v-post:c)]
250
+ ^- (unit effect:h)
251
+ ?: ?=(%| -.post) ~
252
+ `[%channels %channel nest.u.channel %post %del id-post]
253
+ ```
254
+
255
+ **Key points:**
256
+ - Uses `bowl:h` without face → access `config`, `channel`, `state.hook` directly
257
+ - Uses `on-v-posts:c` ordered map with `lot:` for cutoff filtering
258
+ - Uses `(may:c v-post:c)` type - posts can be deleted (`%|`) or present (`%&`)
259
+ - Check `?=(%| -.post)` to skip already-deleted posts
260
+
261
+ ### Example: Word filter
262
+
263
+ ```hoon
264
+ :: Word filter hook: blocks posts containing banned words
265
+ :: Config: words (comma-separated list of words to block)
266
+ ::
267
+ |= [=event:h =bowl:h]
268
+ ^- outcome:h
269
+ |^
270
+ :: Only filter new posts
271
+ ?. ?=([%on-post %add *] event)
272
+ &+[[[%allowed event] ~] state.hook.bowl]
273
+ :: Get banned words from config (comma-separated)
274
+ =+ ;;(words-cord=cord (~(gut by config.bowl) 'words' ''))
275
+ :: Skip if no words configured
276
+ ?: =('' words-cord)
277
+ &+[[[%allowed event] ~] state.hook.bowl]
278
+ :: Split on commas into list of tapes
279
+ =/ banned=(list tape)
280
+ (split-on-comma (trip words-cord))
281
+ :: Get message content
282
+ =/ content=tape (extract-text content.post.event)
283
+ :: Check if any banned word appears in content
284
+ =/ has-banned=?
285
+ %+ lien banned
286
+ |= word=tape
287
+ !=(~ (find word content))
288
+ :: If found, deny
289
+ ?: has-banned
290
+ &+[[[%denied `'Message contains prohibited content'] ~] state.hook.bowl]
291
+ :: Otherwise allow
292
+ &+[[[%allowed event] ~] state.hook.bowl]
293
+ ::
294
+ ++ split-on-comma
295
+ |= txt=tape
296
+ ^- (list tape)
297
+ =/ idx (find "," txt)
298
+ ?~ idx
299
+ ?~ txt *(list tape)
300
+ ~[txt]
301
+ :- (scag u.idx txt)
302
+ $(txt (slag +(u.idx) txt))
303
+ ::
304
+ ++ extract-text
305
+ |= =story:c
306
+ ^- tape
307
+ ?~ story ""
308
+ =/ verse i.story
309
+ ?. ?=(%inline -.verse) ""
310
+ ?~ p.verse ""
311
+ =/ inl i.p.verse
312
+ ?@ inl
313
+ (trip inl)
314
+ ""
315
+ --
316
+ ```
317
+
318
+ **Key points:**
319
+ - Access post content via `content.post.event`
320
+ - Use `lien` to check if any word in list matches
321
+ - Helper arms (`++`) for text processing go inside `|^` ... `--`
322
+ - Config supports comma-separated values
323
+
324
+ ## CLI Commands
325
+
326
+ ```bash
327
+ # Add a hook
328
+ tlon hooks add "my-hook" ./hook.hoon
329
+
330
+ # Configure for a channel
331
+ tlon hooks config <id> chat/~host/channel delay=~m5 emoji=👍
332
+
333
+ # Set execution order
334
+ tlon hooks order chat/~host/channel <id1> <id2>
335
+
336
+ # Schedule periodic execution
337
+ tlon hooks cron <id> ~h1 --nest chat/~host/channel
338
+
339
+ # List hooks
340
+ tlon hooks list
341
+
342
+ # Watch for hook updates (debugging)
343
+ tlon hooks watch
344
+ ```
345
+
346
+ ## Testing Hooks (Dojo)
347
+
348
+ Test without affecting channels:
349
+
350
+ ```
351
+ -groups!hooks-run <event> [%origin nest optional-state optional-config] <src>
352
+ ```
353
+
354
+ ## Common Pitfalls
355
+
356
+ 1. **Bowl face confusion** - `=bowl:h` vs `bowl:h` changes how you access fields
357
+ 2. **Wrong effect structure** - channel effects need exact nesting: `[%channels %channel nest ...]`
358
+ 3. **Emoji shortcodes** - use actual unicode (`'👍'`), not `:thumbsup:`
359
+ 4. **Self-detection** - check `author.post.event`, not `src.bowl`
360
+ 5. **Deleted posts** - in cron, posts can be `%|` (deleted) or `%&` (present)
361
+
362
+ ## Type References
363
+
364
+ - Full hooks types: https://github.com/tloncorp/tlon-apps/blob/develop/desk/sur/hooks.hoon
365
+ - Channel types (v-post, v-reply, etc): https://github.com/tloncorp/tlon-apps/blob/develop/desk/sur/channels.hoon
366
+ - Official hooks examples: https://github.com/tloncorp/hooks
@@ -0,0 +1,113 @@
1
+ # Urbit HTTP API Reference
2
+
3
+ Quick reference for the Urbit HTTP API used by this skill.
4
+
5
+ ## Authentication
6
+
7
+ ```typescript
8
+ import { Urbit } from "@urbit/http-api";
9
+
10
+ const api = await Urbit.authenticate({
11
+ ship: "sampel-palnet", // without ~
12
+ url: "https://myship.tlon.network",
13
+ code: "lidlut-tabwed-...",
14
+ verbose: false,
15
+ });
16
+ ```
17
+
18
+ ## Core Operations
19
+
20
+ ### Scry (Read)
21
+ ```typescript
22
+ const result = await api.scry<T>({ app: "app-name", path: "/path" });
23
+ ```
24
+
25
+ ### Poke (Write)
26
+ ```typescript
27
+ await api.poke({ app: "app-name", mark: "mark-name", json: { ... } });
28
+ ```
29
+
30
+ ### Subscribe (Real-time)
31
+ ```typescript
32
+ await api.subscribe({
33
+ app: "app-name",
34
+ path: "/path",
35
+ event: (data) => { ... },
36
+ quit: () => { ... },
37
+ });
38
+ ```
39
+
40
+ ## Contacts Agent
41
+
42
+ ### Scry Paths
43
+ - `/all` - All peers with merged profile data (ContactRolodex)
44
+ - `/v1/book` - All contacts with user overrides (ContactBookScryResult)
45
+
46
+ ### Poke Marks
47
+ - `contact-action` - Legacy profile edits
48
+ - `{ edit: [{ nickname: "name" }, { bio: "..." }] }`
49
+ - `contact-action-1` - New contact operations
50
+ - `{ meet: ["~ship1", "~ship2"] }` - Sync profiles
51
+ - `{ page: { kip: "~ship", contact: {} } }` - Add contact
52
+ - `{ wipe: ["~ship"] }` - Remove contact
53
+ - `{ edit: { kip: "~ship", contact: { nickname: {...} } } }` - Edit contact
54
+
55
+ ### Profile Fields
56
+ ```typescript
57
+ interface ContactBookProfile {
58
+ nickname?: { type: "text", value: string };
59
+ bio?: { type: "text", value: string };
60
+ status?: { type: "text", value: string };
61
+ avatar?: { type: "look", value: string }; // URL
62
+ cover?: { type: "look", value: string }; // URL
63
+ color?: { type: "tint", value: string }; // Hex without #
64
+ }
65
+ ```
66
+
67
+ ## Chat Agent
68
+
69
+ ### Scry Paths
70
+ - `/dm` - List of DM ship names (string[])
71
+ - `/clubs` - Group DMs (Clubs)
72
+ - `/blocked` - Blocked ships
73
+ - `/v3/dm/~ship/writs/newest/{count}/light` - DM message history (light = no replies)
74
+ - `/v3/dm/~ship/writs/newest/{count}/heavy` - DM message history (heavy = with replies)
75
+ - `/v3/club/{clubId}/writs/newest/{count}/light` - Group DM message history
76
+ - `/v2/dm/~ship/writs/writ/id/{author}/{postId}` - Single DM post with replies
77
+
78
+ ### Poke Marks
79
+ - `chat-dm-action-1` - Send DM
80
+ - `chat-club-action-1` - Group DM operations
81
+ - `chat-remark-action` - Mark read
82
+
83
+ ## Groups Agent
84
+
85
+ ### Scry Paths
86
+ - `/groups/v2` - All subscribed groups
87
+ - `/groups/v2/~host/group-name` - Specific group
88
+
89
+ ## Channels Agent
90
+
91
+ ### Scry Paths
92
+ - `/v1/hooks/preview/{nest}` - Channel preview
93
+ - `/{nest}/search/...` - Search messages
94
+
95
+ ### Poke Marks
96
+ - `channel-action` - Legacy channel operations
97
+ - `channel-action-1` - New channel operations
98
+
99
+ ## Common Types
100
+
101
+ ### Flag (Group ID)
102
+ Format: `~host/group-name`
103
+
104
+ ### Nest (Channel ID)
105
+ Format: `{kind}/~host/channel-name`
106
+ Kinds: `chat`, `diary`, `heap`
107
+
108
+ ## Tips
109
+
110
+ 1. Ship names in pokes/responses include `~` prefix
111
+ 2. Use `formatUd(unixToDa(timestamp))` for message IDs
112
+ 3. Profile changes sync automatically to peers
113
+ 4. Scries are synchronous reads; pokes are async writes