kokoirc 0.2.3 → 0.2.5

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.
Files changed (50) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +64 -39
  3. package/docs/commands/clear.md +26 -0
  4. package/docs/commands/image.md +47 -0
  5. package/docs/commands/invite.md +23 -0
  6. package/docs/commands/names.md +25 -0
  7. package/docs/commands/preview.md +31 -0
  8. package/docs/commands/topic.md +12 -6
  9. package/docs/commands/version.md +23 -0
  10. package/package.json +46 -3
  11. package/src/app/App.tsx +27 -3
  12. package/src/core/commands/help-formatter.ts +1 -1
  13. package/src/core/commands/helpers.ts +3 -1
  14. package/src/core/commands/registry.ts +182 -5
  15. package/src/core/config/defaults.ts +11 -0
  16. package/src/core/config/loader.ts +4 -2
  17. package/src/core/constants.ts +3 -0
  18. package/src/core/image-preview/cache.ts +108 -0
  19. package/src/core/image-preview/detect.ts +105 -0
  20. package/src/core/image-preview/encode.ts +116 -0
  21. package/src/core/image-preview/fetch.ts +174 -0
  22. package/src/core/image-preview/index.ts +6 -0
  23. package/src/core/image-preview/render.ts +222 -0
  24. package/src/core/image-preview/stdin-guard.ts +33 -0
  25. package/src/core/init.ts +2 -1
  26. package/src/core/irc/antiflood.ts +5 -4
  27. package/src/core/irc/client.ts +13 -2
  28. package/src/core/irc/events.ts +140 -109
  29. package/src/core/irc/netsplit.ts +2 -1
  30. package/src/core/scripts/api.ts +13 -3
  31. package/src/core/state/selectors.ts +1 -2
  32. package/src/core/state/store.ts +384 -18
  33. package/src/core/storage/index.ts +2 -2
  34. package/src/core/storage/writer.ts +12 -10
  35. package/src/core/theme/index.ts +1 -1
  36. package/src/core/theme/parser.ts +3 -1
  37. package/src/core/theme/renderer.tsx +46 -16
  38. package/src/core/utils/id.ts +2 -0
  39. package/src/types/config.ts +13 -0
  40. package/src/types/index.ts +1 -2
  41. package/src/types/theme.ts +1 -0
  42. package/src/ui/chat/ChatView.tsx +21 -10
  43. package/src/ui/chat/MessageLine.tsx +44 -6
  44. package/src/ui/input/CommandInput.tsx +56 -1
  45. package/src/ui/layout/AppLayout.tsx +6 -4
  46. package/src/ui/overlay/ImagePreview.tsx +77 -0
  47. package/src/ui/sidebar/BufferList.tsx +16 -7
  48. package/src/ui/sidebar/NickList.tsx +15 -7
  49. package/src/ui/splash/SplashScreen.tsx +6 -2
  50. package/src/ui/statusbar/StatusLine.tsx +4 -2
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 kofany
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -2,23 +2,20 @@
2
2
 
3
3
  A modern terminal IRC client built with [OpenTUI](https://github.com/anomalyco/opentui), React, and Bun. Inspired by irssi, designed for the future.
4
4
 
5
- ```
6
- ┌─ Buffers ─┬─────────────────────────────────────────┬─ Users ──┐
7
- IRCnet │ 12:34:05 <kofany> hey everyone │ @kofany │
8
- │ #chat │ 12:34:08 <alice> welcome back! │ +alice │
9
- │ #dev │ 12:34:12 * kofany waves │ bob │
10
- │ #music │ 12:34:15 <bob> what's new? │ charlie │
11
- │ │ │ │
12
- │ ├──────────────────────────────────────────┤ │
13
- │ │ [IRCnet❱ /msg alice check this out │ │
14
- │ ├──────────────────────────────────────────┤ │
15
- │ │ 12:34 | kofany(+i) | #chat(+nt) | lag:2 │ │
16
- └───────────┴──────────────────────────────────────────┴──────────┘
17
- ```
5
+ **[Documentation](https://kofany.github.io/kokoIRC/)** | **[GitHub](https://github.com/kofany/kokoIRC)**
6
+
7
+ ![Splash screen](docs/screenshots/splash.png)
8
+
9
+ ![Chat view with multiple networks](docs/screenshots/chat.png)
10
+
11
+ ![Help command list](docs/screenshots/help.png)
12
+
13
+ ![Configuration and settings](docs/screenshots/config.png)
18
14
 
19
15
  ## Features
20
16
 
21
17
  - **Full IRC protocol** — channels, queries, CTCP, SASL, TLS, channel modes, ban lists
18
+ - **Inline image preview** — kitty, iTerm2, sixel, and Unicode fallback with tmux support
22
19
  - **irssi-style navigation** — `Esc+1-9` window switching, `/commands`, aliases
23
20
  - **Mouse support** — click buffers/nicks, drag to resize sidepanels
24
21
  - **Netsplit detection** — batches join/part floods into single events
@@ -35,21 +32,35 @@ A modern terminal IRC client built with [OpenTUI](https://github.com/anomalyco/o
35
32
 
36
33
  ## Install
37
34
 
35
+ ### From npm
36
+
38
37
  ```bash
39
- git clone https://github.com/kofany/kokoIRC.git
40
- cd kokoIRC
41
- bun install
38
+ # Global install (adds `kokoirc` to your PATH)
39
+ bun install -g kokoirc
40
+
41
+ # Then run from anywhere
42
+ kokoirc
42
43
  ```
43
44
 
44
- ## Usage
45
+ ```bash
46
+ # Local install (in a project)
47
+ bun add kokoirc
48
+ bunx kokoirc
49
+ ```
50
+
51
+ ### From source
45
52
 
46
53
  ```bash
54
+ git clone https://github.com/kofany/kokoIRC.git
55
+ cd kokoIRC
56
+ bun install
57
+
47
58
  # Run directly
48
- bun run src/index.tsx
59
+ bun run start
49
60
 
50
61
  # Or build a standalone binary
51
62
  bun run build
52
- ./openirc
63
+ ./kokoirc
53
64
  ```
54
65
 
55
66
  ## Configuration
@@ -79,6 +90,12 @@ autosendcmd = "MSG NickServ identify pass; WAIT 2000; MODE $N +i"
79
90
  # sasl_user = "mynick"
80
91
  # sasl_pass = "hunter2"
81
92
 
93
+ [image_preview]
94
+ enabled = true
95
+ max_width = 800
96
+ max_height = 400
97
+ protocol = "auto" # "auto", "kitty", "iterm2", "sixel", "unicode"
98
+
82
99
  [logging]
83
100
  enabled = true
84
101
  encrypt = false # AES-256-GCM (key auto-generated in ~/.kokoirc/.env)
@@ -92,17 +109,18 @@ j = "/join"
92
109
 
93
110
  ## Commands
94
111
 
95
- 38 built-in commands. Type `/help` for the full list, `/help <command>` for details.
112
+ 44 built-in commands. Type `/help` for the full list, `/help <command>` for details.
96
113
 
97
114
  | Category | Commands |
98
115
  |----------|----------|
99
116
  | Connection | `/connect`, `/disconnect`, `/quit`, `/server` |
100
- | Channel | `/join`, `/part`, `/close`, `/topic`, `/list` |
117
+ | Channel | `/join`, `/part`, `/close`, `/clear`, `/topic`, `/names`, `/invite`, `/list` |
101
118
  | Messaging | `/msg`, `/me`, `/notice`, `/action`, `/slap`, `/wallops` |
102
119
  | Moderation | `/kick`, `/ban`, `/unban`, `/kb`, `/kill`, `/mode` |
103
120
  | Nick/Ops | `/nick`, `/op`, `/deop`, `/voice`, `/devoice` |
121
+ | Media | `/image`, `/preview` |
104
122
  | User | `/whois`, `/wii`, `/ignore`, `/unignore` |
105
- | Info | `/stats` |
123
+ | Info | `/version`, `/stats` |
106
124
  | Server | `/quote` (`/raw`), `/oper` |
107
125
  | Config | `/set`, `/alias`, `/unalias`, `/reload` |
108
126
  | Logging | `/log status`, `/log search <query>` |
@@ -194,25 +212,32 @@ Messages are stored in `~/.kokoirc/logs.db` (SQLite WAL mode). The log system su
194
212
 
195
213
  ```
196
214
  src/
197
- ├── index.tsx # Entry point — creates OpenTUI renderer
215
+ ├── index.tsx # Entry point — creates OpenTUI renderer
198
216
  ├── app/
199
- │ └── App.tsx # Lifecycle: config → storage → theme → connect
217
+ │ └── App.tsx # Lifecycle: config → storage → theme → connect
200
218
  ├── core/
201
- │ ├── irc/ # IRC client, event binding, middlewares
202
- │ ├── state/ # Zustand store (UI-agnostic)
203
- │ ├── commands/ # Command registry and parser
204
- │ ├── config/ # TOML loader, defaults, merge logic
205
- │ ├── storage/ # SQLite logging, encryption, queries
206
- │ ├── scripts/ # Script loader, API, event bus
207
- └── theme/ # Format string parser, theme loader
208
- ├── ui/ # React components (no IRC imports)
209
- │ ├── AppLayout.tsx # 3-column layout with resizable panels
210
- │ ├── ChatView.tsx # Message scrollback
211
- ├── BufferList.tsx # Left sidebar
212
- ├── NickList.tsx # Right sidebar
213
- │ ├── CommandInput.tsx # Input line with prompt
214
- └── StatusLine.tsx # Status bar
215
- └── types/ # TypeScript definitions
219
+ │ ├── init.ts # Application initialization
220
+ │ ├── constants.ts # Global constants
221
+ │ ├── irc/ # IRC client, event binding, middlewares
222
+ │ ├── state/ # Zustand store (UI-agnostic)
223
+ │ ├── commands/ # Command registry, parser, help system
224
+ │ ├── config/ # TOML loader, defaults, merge logic
225
+ ├── storage/ # SQLite logging, encryption, queries
226
+ ├── scripts/ # Script loader, API, event bus
227
+ │ ├── theme/ # Format string parser, theme loader
228
+ │ ├── image-preview/ # Inline image display (kitty, iTerm2, sixel)
229
+ └── utils/ # Message ID generator
230
+ ├── ui/ # React components (no IRC imports)
231
+ │ ├── layout/ # AppLayout, TopicBar
232
+ ├── chat/ # ChatView, MessageLine
233
+ │ ├── input/ # CommandInput
234
+ │ ├── sidebar/ # BufferList, NickList
235
+ │ ├── statusbar/ # StatusLine
236
+ │ ├── overlay/ # ImagePreview
237
+ │ ├── splash/ # SplashScreen
238
+ │ ├── hooks/ # useStatusbarColors
239
+ │ └── ErrorBoundary.tsx # React error boundary
240
+ └── types/ # TypeScript definitions
216
241
  ```
217
242
 
218
243
  The UI layer never imports from `core/irc/` — all communication goes through the Zustand store. This keeps the architecture clean and makes it possible to drive the same store from a web frontend.
@@ -0,0 +1,26 @@
1
+ ---
2
+ category: Channel
3
+ description: Clear current buffer's messages
4
+ ---
5
+
6
+ # /clear
7
+
8
+ ## Syntax
9
+
10
+ /clear
11
+
12
+ ## Description
13
+
14
+ Clears all messages from the current buffer's display. This is a visual-only
15
+ operation — chat logs in the database are not affected. Messages remain
16
+ searchable via `/log search`.
17
+
18
+ Works on any buffer type: channels, queries, and server buffers.
19
+
20
+ ## Examples
21
+
22
+ /clear
23
+
24
+ ## See Also
25
+
26
+ /close, /log
@@ -0,0 +1,47 @@
1
+ ---
2
+ category: Media
3
+ description: Manage image preview cache
4
+ ---
5
+
6
+ # /image
7
+
8
+ ## Syntax
9
+
10
+ /image [stats|clear]
11
+
12
+ ## Description
13
+
14
+ Manage the image preview cache. Without arguments, shows current status.
15
+
16
+ All image preview settings are persistent via `/set`:
17
+
18
+ /set image_preview.enabled true|false
19
+ /set image_preview.protocol auto|kitty|iterm2|sixel|symbols
20
+ /set image_preview.max_width 0
21
+ /set image_preview.cache_max_mb 100
22
+
23
+ ## Subcommands
24
+
25
+ ### stats
26
+
27
+ Show cache file count and disk usage.
28
+
29
+ /image stats
30
+
31
+ ### clear
32
+
33
+ Delete all cached images.
34
+
35
+ /image clear
36
+
37
+ ## Examples
38
+
39
+ /image
40
+ /image stats
41
+ /image clear
42
+ /set image_preview.enabled false
43
+ /set image_preview.protocol kitty
44
+
45
+ ## See Also
46
+
47
+ /preview, /set
@@ -0,0 +1,23 @@
1
+ ---
2
+ category: Channel
3
+ description: Invite a user to a channel
4
+ ---
5
+
6
+ # /invite
7
+
8
+ ## Syntax
9
+
10
+ /invite <nick> [channel]
11
+
12
+ ## Description
13
+
14
+ Invite a user to join a channel. Without a channel argument, invites to the current channel. You typically need channel operator status to send invites.
15
+
16
+ ## Examples
17
+
18
+ /invite friend # invite to current channel
19
+ /invite friend #secret # invite to #secret
20
+
21
+ ## See Also
22
+
23
+ /kick, /ban
@@ -0,0 +1,25 @@
1
+ ---
2
+ category: Channel
3
+ description: List users in a channel
4
+ ---
5
+
6
+ # /names
7
+
8
+ ## Syntax
9
+
10
+ /names [channel]
11
+
12
+ ## Description
13
+
14
+ Display the list of users in a channel. Without arguments, lists users in the current channel. Also sends a NAMES request to the server to refresh the nick list panel.
15
+
16
+ The output shows each user with their mode prefix (@, +, etc.) and a total user count.
17
+
18
+ ## Examples
19
+
20
+ /names # list users in current channel
21
+ /names #help # list users in #help
22
+
23
+ ## See Also
24
+
25
+ /who, /whois
@@ -0,0 +1,31 @@
1
+ ---
2
+ category: Media
3
+ description: Preview an image URL in the terminal
4
+ ---
5
+
6
+ # /preview
7
+
8
+ ## Syntax
9
+
10
+ /preview <url>
11
+
12
+ ## Description
13
+
14
+ Fetches an image URL and displays it as a popup overlay in the terminal.
15
+ Supports direct image links (jpg, png, gif, webp) and pages with og:image
16
+ metadata (imgur, imgbb, etc.).
17
+
18
+ The display protocol is auto-detected based on your terminal:
19
+ kitty, iTerm2, sixel, or Unicode half-block fallback.
20
+ Works through tmux with DCS passthrough.
21
+
22
+ Press any key or click to dismiss the preview.
23
+
24
+ ## Examples
25
+
26
+ /preview https://i.imgur.com/abc123.jpg
27
+ /preview https://imgur.com/gallery/xyz
28
+
29
+ ## See Also
30
+
31
+ /image
@@ -7,18 +7,24 @@ description: Set or view channel topic
7
7
 
8
8
  ## Syntax
9
9
 
10
- /topic [channel] <text>
10
+ /topic
11
+ /topic <text>
12
+ /topic <channel>
13
+ /topic <channel> <text>
11
14
 
12
15
  ## Description
13
16
 
14
- Set the topic for a channel. With just text, sets the topic for the
15
- current channel. Specify a channel name to set a different channel's topic.
17
+ View or set the channel topic.
18
+
19
+ Without arguments, displays the current topic for the active channel. With only a channel name, requests the topic from the server. With text, sets the topic on the active channel (or on the specified channel).
16
20
 
17
21
  ## Examples
18
22
 
19
- /topic Welcome to the channel!
20
- /topic #linux Welcome to #linux
23
+ /topic # show current topic
24
+ /topic #help # request topic for #help
25
+ /topic Welcome to the channel # set topic on current channel
26
+ /topic #help Welcome! # set topic on #help
21
27
 
22
28
  ## See Also
23
29
 
24
- /join, /mode
30
+ /mode, /names
@@ -0,0 +1,23 @@
1
+ ---
2
+ category: Info
3
+ description: Query server or user client version
4
+ ---
5
+
6
+ # /version
7
+
8
+ ## Syntax
9
+
10
+ /version [nick]
11
+
12
+ ## Description
13
+
14
+ Without arguments, queries the IRC server version. With a nick, sends a CTCP VERSION request to that user to find out what client they are using. The reply is displayed in the active buffer.
15
+
16
+ ## Examples
17
+
18
+ /version # query server version
19
+ /version someone # query someone's client version
20
+
21
+ ## See Also
22
+
23
+ /whois
package/package.json CHANGED
@@ -1,7 +1,46 @@
1
1
  {
2
2
  "name": "kokoirc",
3
- "version": "0.2.3",
4
- "description": "Terminal IRC client built with OpenTUI and React",
3
+ "version": "0.2.5",
4
+ "description": "Modern terminal IRC client with inline image preview, SASL, scripting, encrypted logging, and theming — built with OpenTUI, React, and Bun",
5
+ "license": "MIT",
6
+ "author": {
7
+ "name": "kofany",
8
+ "url": "https://github.com/kofany"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/kofany/kokoIRC.git"
13
+ },
14
+ "homepage": "https://kofany.github.io/kokoIRC/",
15
+ "bugs": {
16
+ "url": "https://github.com/kofany/kokoIRC/issues"
17
+ },
18
+ "keywords": [
19
+ "irc",
20
+ "irc-client",
21
+ "terminal",
22
+ "tui",
23
+ "cli",
24
+ "client",
25
+ "chat",
26
+ "opentui",
27
+ "react",
28
+ "bun",
29
+ "sasl",
30
+ "sixel",
31
+ "image-preview",
32
+ "theming",
33
+ "scripting",
34
+ "sqlite",
35
+ "encrypted-logging"
36
+ ],
37
+ "engines": {
38
+ "bun": ">=1.2.0"
39
+ },
40
+ "os": [
41
+ "darwin",
42
+ "linux"
43
+ ],
5
44
  "module": "src/index.tsx",
6
45
  "type": "module",
7
46
  "bin": {
@@ -17,11 +56,13 @@
17
56
  "start": "bun run src/index.tsx",
18
57
  "dev": "bun --watch run src/index.tsx",
19
58
  "test": "bun test",
20
- "build": "bun build --compile src/index.tsx --outfile openirc"
59
+ "build": "bun build --compile src/index.tsx --outfile kokoirc",
60
+ "docs:build": "bun run docs/build.ts"
21
61
  },
22
62
  "devDependencies": {
23
63
  "@types/bun": "^1.3.9",
24
64
  "@types/react": "^19.2.14",
65
+ "@types/sharp": "^0.32.0",
25
66
  "typescript": "^5.9.3"
26
67
  },
27
68
  "imports": {
@@ -32,6 +73,8 @@
32
73
  "@opentui/react": "^0.1.83",
33
74
  "kofany-irc-framework": "^4.14.1",
34
75
  "react": "^19.2.4",
76
+ "sharp": "^0.34.5",
77
+ "sixel": "^0.16.0",
35
78
  "smol-toml": "^1.6.0",
36
79
  "zustand": "^5.0.11"
37
80
  }
package/src/app/App.tsx CHANGED
@@ -10,6 +10,7 @@ import { initHomeDir } from "@/core/init"
10
10
  import { autoloadScripts } from "@/core/scripts/manager"
11
11
  import { initStorage, shutdownStorage } from "@/core/storage"
12
12
  import { BufferType, ActivityLevel, makeBufferId, getSortGroup } from "@/types"
13
+ import { nextMsgId } from "@/core/utils/id"
13
14
  import { SplashScreen } from "@/ui/splash/SplashScreen"
14
15
  import { AppLayout } from "@/ui/layout/AppLayout"
15
16
  import { TopicBar } from "@/ui/layout/TopicBar"
@@ -18,6 +19,7 @@ import { NickList } from "@/ui/sidebar/NickList"
18
19
  import { ChatView } from "@/ui/chat/ChatView"
19
20
  import { CommandInput } from "@/ui/input/CommandInput"
20
21
  import { StatusLine } from "@/ui/statusbar/StatusLine"
22
+ import { ImagePreview } from "@/ui/overlay/ImagePreview"
21
23
 
22
24
  export function App() {
23
25
  const renderer = useRenderer()
@@ -33,7 +35,17 @@ export function App() {
33
35
 
34
36
  useKeyboard((key) => {
35
37
  if (key.name === "q" && key.ctrl) {
36
- shutdownStorage().finally(() => renderer.destroy())
38
+ shutdownStorage().finally(() => {
39
+ renderer.destroy()
40
+ process.exit(0)
41
+ })
42
+ return
43
+ }
44
+
45
+ // Dismiss image preview on any keypress
46
+ if (useStore.getState().imagePreview) {
47
+ useStore.getState().hideImagePreview()
48
+ key.preventDefault()
37
49
  return
38
50
  }
39
51
 
@@ -112,7 +124,10 @@ export function App() {
112
124
  // Register shutdown handler so commands can close the app
113
125
  useEffect(() => {
114
126
  useStore.getState().setShutdownHandler(() => {
115
- shutdownStorage().finally(() => renderer.destroy())
127
+ shutdownStorage().finally(() => {
128
+ renderer.destroy()
129
+ process.exit(0)
130
+ })
116
131
  })
117
132
  }, [renderer])
118
133
 
@@ -131,6 +146,14 @@ export function App() {
131
146
  setTheme(theme)
132
147
 
133
148
  await loadAllDocs()
149
+
150
+ // Run image cache cleanup (fire-and-forget)
151
+ const imgConfig = config.image_preview
152
+ if (imgConfig?.enabled) {
153
+ import("@/core/image-preview/cache").then(({ cleanupCache }) => {
154
+ cleanupCache(imgConfig.cache_max_mb ?? 100, imgConfig.cache_max_days ?? 7).catch(() => {})
155
+ })
156
+ }
134
157
  }
135
158
  init().catch((err) => console.error("[init]", err))
136
159
  }, [])
@@ -152,7 +175,7 @@ export function App() {
152
175
  type: BufferType.Server,
153
176
  name: "Status",
154
177
  messages: [{
155
- id: crypto.randomUUID(),
178
+ id: nextMsgId(),
156
179
  timestamp: new Date(),
157
180
  type: "event" as const,
158
181
  text: "Welcome to kokoIRC. Type /connect to connect to a server.",
@@ -200,6 +223,7 @@ export function App() {
200
223
  nicklist={<NickList />}
201
224
  input={<CommandInput />}
202
225
  statusline={statusbarEnabled ? <StatusLine /> : undefined}
226
+ overlay={<ImagePreview />}
203
227
  />
204
228
  )
205
229
  }
@@ -7,7 +7,7 @@ import { commands, findByAlias, getCanonicalName } from "./registry"
7
7
 
8
8
  const CATEGORY_ORDER = [
9
9
  "Connection", "Channel", "Messaging", "Moderation",
10
- "Configuration", "Statusbar", "Info",
10
+ "Media", "Configuration", "Statusbar", "Info",
11
11
  ]
12
12
 
13
13
  // ─── /help (no args) — categorized command list ─────────────
@@ -1,6 +1,7 @@
1
1
  import { useStore } from "@/core/state/store"
2
2
  import { makeBufferId, BufferType } from "@/types"
3
3
  import type { AppConfig } from "@/types/config"
4
+ import { nextMsgId } from "@/core/utils/id"
4
5
  import { CREDENTIAL_FIELDS } from "./types"
5
6
  import type { ResolvedConfig } from "./types"
6
7
 
@@ -10,7 +11,7 @@ export function addLocalEvent(text: string) {
10
11
  const buf = s.activeBufferId
11
12
  if (!buf) return
12
13
  s.addMessage(buf, {
13
- id: crypto.randomUUID(),
14
+ id: nextMsgId(),
14
15
  timestamp: new Date(),
15
16
  type: "event",
16
17
  text,
@@ -155,6 +156,7 @@ export function listAllSettings(config: AppConfig): void {
155
156
  showSection("sidepanel.left", "sidepanel.left", config.sidepanel.left)
156
157
  showSection("sidepanel.right", "sidepanel.right", config.sidepanel.right)
157
158
  showSection("statusbar", "statusbar", config.statusbar)
159
+ showSection("image_preview", "image_preview", config.image_preview)
158
160
 
159
161
  if (Object.keys(config.aliases).length > 0) {
160
162
  showSection("aliases", "aliases", config.aliases)