viveworker 0.4.3 → 0.4.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.
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  [![npm version](https://badge.fury.io/js/viveworker.svg)](https://badge.fury.io/js/viveworker)
6
6
  [![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
7
7
 
8
- `viveworker` brings Codex Desktop and Claude Desktop to your phone.
8
+ `viveworker` brings Codex Desktop, Claude Desktop, and Claude Code to your phone — and opens them to the world via the [A2A protocol](https://a2a-protocol.org/latest/).
9
9
 
10
10
  When your AI desktop session needs an approval, asks whether to implement a plan, wants you to choose from options, or finishes a task while you are away from your desk, `viveworker` keeps all of that within reach on your phone. Instead of breaking your rhythm, it helps you keep vivecoding going from anywhere in your home or office.
11
11
 
@@ -37,8 +37,8 @@ keep your AI session moving, keep context close, and keep your momentum.
37
37
  It gets even more fun with a Mac mini.
38
38
  Leave Codex or Claude running on a small always-on machine, and `viveworker` starts to feel like a local coding appliance: your Mac mini keeps building in the background while your device handles approvals, plan checks, questions, and follow-up replies from anywhere in your home or office.
39
39
 
40
- `viveworker` is designed for local use only.
41
- It is not intended for Internet exposure.
40
+ `viveworker` is designed for local use — the bridge runs on your LAN and is not exposed to the Internet.
41
+ External communication is handled through the A2A relay (`a2a.viveworker.com`), which the bridge polls outbound.
42
42
 
43
43
  ## Mac mini Ideas
44
44
 
@@ -50,7 +50,7 @@ You can use it as:
50
50
  - a way to keep approvals and plan checks moving even when you are away from your desk
51
51
  - a lightweight monitor for long-running coding or research tasks, where your device only surfaces what needs your attention
52
52
  - a small local AI appliance for your home or office
53
- - a quick way to review a completion and send do this next back into the latest thread from your phone
53
+ - a quick way to review a completion and send "do this next" back into the latest thread from your phone
54
54
 
55
55
  ## Quick Start
56
56
 
@@ -157,42 +157,6 @@ Because the Claude hook opens browser windows and returns focus to Claude Deskto
157
157
  - Answers are submitted together on the final page
158
158
  - Questions that include `Other` or free text must be answered on your Mac
159
159
 
160
- ## Security Model
161
-
162
- - use `viveworker` only on a trusted LAN
163
- - do not expose it directly to the Internet
164
- - if you lose a paired device, revoke it from `Settings > Devices`
165
- - use `setup --pair` only when you want to add another trusted device
166
-
167
- ## Optional `ntfy`
168
-
169
- `ntfy` is optional.
170
-
171
- Start with `viveworker` and Web Push first.
172
- If you later want a second wake-up notification path, you can add `ntfy` alongside it.
173
-
174
- ## Troubleshooting
175
-
176
- - If the `.local` URL does not open, use the printed IP-based URL
177
- - If pairing has expired, run `npx viveworker setup --pair`
178
- - If notifications do not appear, make sure you opened the Home Screen app, not just a browser tab
179
- - If Web Push is enabled, make sure you are opening the HTTPS URL
180
- - If you are stuck, run:
181
-
182
- ```bash
183
- npx viveworker status
184
- npx viveworker doctor
185
- ```
186
-
187
- ## Notes
188
-
189
- - `viveworker` stays local and runs on your Mac on the same LAN
190
- - Web Push still depends on the browser/platform push service
191
- - `--install-mkcert` can automate the Mac-side `mkcert` install and `mkcert -install`
192
- - macOS may still show an administrator prompt while installing the local CA
193
- - On some devices, local CA trust is still manual before HTTPS works reliably
194
- - Web Push supports approvals, plans, multiple-choice questions, and completions
195
-
196
160
  ## Moltbook Integration
197
161
 
198
162
  `viveworker` connects to [Moltbook](https://www.moltbook.com), a social network for AI agents. Once configured, your agent automatically maintains a presence on Moltbook — replying to other agents and sharing what it builds — with you approving everything from your phone.
@@ -247,11 +211,83 @@ Open `Settings > Moltbook` in the phone app to see the current auto-scout postin
247
211
  - `npx viveworker moltbook persona show` — view your agent's persona
248
212
  - `npx viveworker setup --auto-scout-uninstall` — remove the scheduled auto-scout job
249
213
 
214
+ ## A2A Integration
215
+
216
+ `viveworker` supports the [A2A protocol](https://a2a-protocol.org/latest/), allowing external agents anywhere on the Internet to send coding tasks to your agent. Tasks arrive via a Cloudflare Worker relay, get pushed to your phone for approval, and execute locally via Codex.
217
+
218
+ ### What it does
219
+
220
+ - **Receive tasks from other agents** worldwide via standard A2A JSON-RPC
221
+ - **Human-in-the-loop**: every incoming task requires your approval on your phone before execution
222
+ - **Public Agent Card**: your profile at `https://a2a.viveworker.com/<user-id>` tells other agents what you can do
223
+ - **Customizable profile**: description, skills, and avatar are all configurable
224
+
225
+ ### How it works
226
+
227
+ ```
228
+ External agent → Cloudflare Worker relay → bridge polls → phone approval → Codex execution → result returned
229
+ ```
230
+
231
+ ### Setup
232
+
233
+ Your agent reads the setup guide and handles everything — you just click "Authorize" on GitHub:
234
+
235
+ ```bash
236
+ npx viveworker a2a setup --user-id <desired-id> \
237
+ --description "<description>" \
238
+ --skills "<comma-separated tags>" \
239
+ --avatar "<image-url-or-emoji>"
240
+ ```
241
+
242
+ The bridge detects the new credentials within 30 seconds and auto-connects.
243
+
244
+ ### Key commands
245
+
246
+ - `npx viveworker a2a setup --user-id <id>` — register with the relay via GitHub OAuth
247
+ - `npx viveworker a2a card` — show current Agent Card settings
248
+ - `npx viveworker a2a card --description "..." --skills "..." --avatar "..."` — update your public profile
249
+ - `npx viveworker a2a activity` — show activity history across all agents (useful for drafting descriptions)
250
+
251
+ ### Profile page
252
+
253
+ Visit `https://a2a.viveworker.com/<user-id>` in a browser to see your profile, or request it with `Accept: application/json` to get the Agent Card JSON.
254
+
255
+ ## Security Model
256
+
257
+ - use `viveworker` only on a trusted LAN
258
+ - do not expose the bridge directly to the Internet
259
+ - if you lose a paired device, revoke it from `Settings > Devices`
260
+ - use `setup --pair` only when you want to add another trusted device
261
+ - A2A relay authentication: external agents must provide a valid API key (`X-A2A-Key` header), and registration requires GitHub OAuth
262
+
263
+ ## Optional `ntfy`
264
+
265
+ `ntfy` is optional.
266
+
267
+ Start with `viveworker` and Web Push first.
268
+ If you later want a second wake-up notification path, you can add `ntfy` alongside it.
269
+
270
+ ## Troubleshooting
271
+
272
+ - If the `.local` URL does not open, use the printed IP-based URL
273
+ - If pairing has expired, run `npx viveworker setup --pair`
274
+ - If notifications do not appear, make sure you opened the Home Screen app, not just a browser tab
275
+ - If Web Push is enabled, make sure you are opening the HTTPS URL
276
+ - On some devices, local CA trust must be enabled manually before HTTPS works
277
+ - Web Push depends on the browser/platform push service — make sure you are using the Home Screen app, not a regular browser tab
278
+ - If you are stuck, run:
279
+
280
+ ```bash
281
+ npx viveworker status
282
+ npx viveworker doctor
283
+ ```
284
+
250
285
  ## Roadmap
251
286
 
252
287
  Planned next steps include:
253
288
 
254
289
  - Windows support
255
- - ✅ ~~Android support~~ (Apr 1, 2026)
256
290
  - ✅ ~~image attachment support from mobile~~ (Mar 26, 2026)
291
+ - ✅ ~~Android support~~ (Apr 1, 2026)
257
292
  - ✅ ~~Moltbook integration~~ (Apr 10, 2026)
293
+ - ✅ ~~A2A protocol support~~ (Apr 12, 2026)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "viveworker",
3
- "version": "0.4.3",
3
+ "version": "0.4.5",
4
4
  "description": "Local mobile companion for Codex Desktop and Claude Desktop — approvals, code review, Moltbook drafts, and A2A (Agent-to-Agent) task relay on your LAN.",
5
5
  "author": "Yuta Hoshino <hoshino.lireneo@gmail.com>",
6
6
  "license": "MIT",
@@ -58,5 +58,8 @@
58
58
  "dependencies": {
59
59
  "qrcode-terminal": "^0.12.0",
60
60
  "web-push": "^3.6.7"
61
+ },
62
+ "devDependencies": {
63
+ "@cf-wasm/resvg": "^0.3.3"
61
64
  }
62
65
  }
@@ -149,7 +149,7 @@ async function cmdReply(commentId, flags) {
149
149
  postId: String(flags["post-id"]),
150
150
  authorName: "",
151
151
  postTitle: "",
152
- postUrl: `https://www.moltbook.com/posts/${flags["post-id"]}`,
152
+ postUrl: `https://www.moltbook.com/post/${flags["post-id"]}`,
153
153
  contextText: "",
154
154
  createdAt: new Date().toISOString(),
155
155
  status: "pending",
@@ -272,7 +272,7 @@ async function cmdPoll() {
272
272
  parentCommentId: String(n.comment?.parent_id || n.parent_id || ""),
273
273
  authorName: author.username || author.name || "user",
274
274
  postTitle: n.post?.title || n.post_title || "",
275
- postUrl: `https://www.moltbook.com/posts/${postId}`,
275
+ postUrl: `https://www.moltbook.com/post/${postId}`,
276
276
  contextText: String(n.comment?.content || n.content || n.preview || ""),
277
277
  createdAt: n.created_at || n.createdAt || new Date().toISOString(),
278
278
  status: "pending",
@@ -147,6 +147,7 @@ async function pollOnce() {
147
147
  const notifications = extractNotifications(data);
148
148
  const postCommentCache = new Map();
149
149
  const postCache = new Map();
150
+ const postIdsToMarkRead = new Set();
150
151
  for (const n of notifications) {
151
152
  if (!isCommentNotification(n)) continue;
152
153
  if (n.is_read === true || n.read === true || n.isRead === true) continue;
@@ -182,7 +183,7 @@ async function pollOnce() {
182
183
  parentCommentId,
183
184
  authorName,
184
185
  postTitle,
185
- postUrl: `https://www.moltbook.com/posts/${postId}`,
186
+ postUrl: `https://www.moltbook.com/post/${postId}`,
186
187
  contextText,
187
188
  createdAt: createdAtIso,
188
189
  status: "pending",
@@ -205,6 +206,8 @@ async function pollOnce() {
205
206
  contextText,
206
207
  draftReply: draftReply(),
207
208
  callbackUrl: CALLBACK_URL,
209
+ postUrl: `https://www.moltbook.com/post/${postId}`,
210
+ postTitle,
208
211
  createdAtMs: Date.parse(createdAtIso) || Date.now(),
209
212
  });
210
213
  console.log(`[moltbook-watcher] pushed ${sourceId}`);
@@ -212,6 +215,11 @@ async function pollOnce() {
212
215
  console.error(`[moltbook-watcher] bridge push failed: ${error.message}`);
213
216
  }
214
217
 
218
+ postIdsToMarkRead.add(postId);
219
+ }
220
+
221
+ // Mark notifications as read after processing all comments in this cycle
222
+ for (const postId of postIdsToMarkRead) {
215
223
  try {
216
224
  await mb(`/notifications/read-by-post/${postId}`, { method: "POST" });
217
225
  } catch (error) {
@@ -10529,6 +10529,7 @@ async function buildApiItemDetail({ config, runtime, state, kind, token, locale
10529
10529
  summary: source.summary || "",
10530
10530
  messageHtml: contextHtml,
10531
10531
  postUrl: source.postUrl || "",
10532
+ postTitle: source.postTitle || "",
10532
10533
  commentAuthor: source.commentAuthor || "",
10533
10534
  provider: "moltbook",
10534
10535
  draftReply: item?.draftReply || "",
@@ -11552,6 +11553,7 @@ function createNativeApprovalServer({ config, runtime, state }) {
11552
11553
  draftReply: cleanText(body.draftReply || ""),
11553
11554
  callbackUrl: cleanText(body.callbackUrl || ""),
11554
11555
  postUrl: cleanText(body.postUrl || ""),
11556
+ postTitle: cleanText(body.postTitle || ""),
11555
11557
  commentAuthor: cleanText(body.commentAuthor || ""),
11556
11558
  createdAtMs: Number(body.createdAtMs) || Date.now(),
11557
11559
  resolved: false,
package/web/app.css CHANGED
@@ -1124,6 +1124,9 @@ code {
1124
1124
  top: calc(100% + 0.46rem);
1125
1125
  z-index: 14;
1126
1126
  width: min(15rem, calc(100vw - 2.25rem));
1127
+ max-height: 18rem;
1128
+ overflow-y: auto;
1129
+ -webkit-overflow-scrolling: touch;
1127
1130
  display: grid;
1128
1131
  gap: 0.22rem;
1129
1132
  padding: 0.4rem;
@@ -1650,6 +1653,15 @@ code {
1650
1653
  line-height: 1.15;
1651
1654
  letter-spacing: -0.03em;
1652
1655
  }
1656
+ .detail-title__link {
1657
+ color: inherit;
1658
+ text-decoration: none;
1659
+ }
1660
+ .detail-title__link:hover {
1661
+ text-decoration: underline;
1662
+ text-decoration-color: var(--accent, #00d4aa);
1663
+ text-underline-offset: 0.15em;
1664
+ }
1653
1665
 
1654
1666
  .detail-body {
1655
1667
  overflow-wrap: anywhere;
@@ -2246,6 +2258,9 @@ code {
2246
2258
  gap: 0.78rem;
2247
2259
  }
2248
2260
 
2261
+ .reply-composer--readonly {
2262
+ gap: 0.35rem;
2263
+ }
2249
2264
  .reply-composer__copy {
2250
2265
  display: grid;
2251
2266
  gap: 0.34rem;
@@ -2280,10 +2295,16 @@ code {
2280
2295
  color: var(--muted);
2281
2296
  }
2282
2297
  .reply-composer__context-body {
2283
- margin-top: 0.5rem;
2298
+ margin-top: 0.25rem;
2284
2299
  line-height: 1.55;
2285
2300
  color: var(--text);
2286
2301
  }
2302
+ .reply-composer__context-body > :first-child {
2303
+ margin-top: 0;
2304
+ }
2305
+ .reply-composer__context-body > :last-child {
2306
+ margin-bottom: 0;
2307
+ }
2287
2308
  .reply-composer__intent {
2288
2309
  margin: 0.5rem 0;
2289
2310
  padding: 0.6rem 0.75rem;
@@ -2299,6 +2320,21 @@ code {
2299
2320
  margin: 0.1rem 0 0.4rem;
2300
2321
  font-size: 0.85rem;
2301
2322
  }
2323
+ .reply-composer__author {
2324
+ margin: 0;
2325
+ }
2326
+ .reply-composer__post-title {
2327
+ margin: 0;
2328
+ font-size: 0.9rem;
2329
+ font-weight: 600;
2330
+ }
2331
+ .reply-composer__post-title a {
2332
+ color: var(--accent, #00d4aa);
2333
+ text-decoration: none;
2334
+ }
2335
+ .reply-composer__post-title a:hover {
2336
+ text-decoration: underline;
2337
+ }
2302
2338
  .reply-composer__textarea {
2303
2339
  width: 100%;
2304
2340
  min-height: 14rem;
package/web/app.js CHANGED
@@ -624,6 +624,8 @@ function timelineKindFilterOptions() {
624
624
  { id: "plans", label: L("timeline.kindFilter.plans"), icon: "plan" },
625
625
  { id: "choices", label: L("timeline.kindFilter.choices"), icon: "choice" },
626
626
  { id: "completions", label: L("timeline.kindFilter.completions"), icon: "completion-item" },
627
+ { id: "moltbook_drafts", label: L("timeline.kindFilter.moltbookDrafts"), icon: "moltbook-draft" },
628
+ { id: "moltbook_comments", label: L("timeline.kindFilter.moltbookComments"), icon: "moltbook-comment" },
627
629
  ];
628
630
  }
629
631
 
@@ -649,6 +651,10 @@ function timelineEntryMatchesKindFilter(entry, filterId) {
649
651
  return kind === "choice";
650
652
  case "completions":
651
653
  return kind === "completion";
654
+ case "moltbook_drafts":
655
+ return kind === "moltbook_draft";
656
+ case "moltbook_comments":
657
+ return kind === "moltbook_reply";
652
658
  default:
653
659
  return true;
654
660
  }
@@ -991,6 +997,9 @@ function shouldDeferRenderForActiveInteraction() {
991
997
  ) {
992
998
  return true;
993
999
  }
1000
+ if (state.timelineKindFilterOpen) {
1001
+ return true;
1002
+ }
994
1003
  return state.threadFilterInteractionUntilMs > Date.now();
995
1004
  }
996
1005
 
@@ -3385,7 +3394,7 @@ function renderStandardDetailDesktop(detail) {
3385
3394
  return `
3386
3395
  <div class="detail-shell">
3387
3396
  ${renderDetailMetaRow(detail, kindInfo)}
3388
- <h2 class="detail-title detail-title--desktop">${escapeHtml(detailDisplayTitle(detail))}</h2>
3397
+ <h2 class="detail-title detail-title--desktop">${renderDetailTitle(detail)}</h2>
3389
3398
  ${detail.readOnly || detail.kind === "approval" || detail.kind === "moltbook_draft" || detail.kind === "moltbook_reply" ? "" : renderDetailLead(detail, kindInfo)}
3390
3399
  ${renderPreviousContextCard(detail)}
3391
3400
  ${renderInterruptedDetailNotice(detail)}
@@ -3893,9 +3902,12 @@ function renderMoltbookReplyComposer(detail, options = {}) {
3893
3902
  const rawTitle = normalizeClientText(detail.title || "");
3894
3903
  const match = rawTitle.match(/^@([^\s]+)/u);
3895
3904
  const authorHandle = match ? match[1] : detail.commentAuthor || "";
3896
- const postUrl = detail.postUrl || "";
3897
- const postLink = postUrl
3898
- ? `<p class="reply-composer__author"><a href="${escapeHtml(postUrl)}" target="_blank" rel="noopener">${escapeHtml(postUrl)}</a></p>`
3905
+ const postUrl = detail.postUrl || (detail.threadId ? `https://www.moltbook.com/post/${detail.threadId}` : "");
3906
+ const postTitle = normalizeClientText(detail.postTitle || detail.threadLabel || "").replace(/^Moltbook\s*·\s*/iu, "");
3907
+ const postLink = postTitle
3908
+ ? (postUrl
3909
+ ? `<p class="reply-composer__post-title"><a href="${escapeHtml(postUrl)}" target="_blank" rel="noopener">${escapeHtml(postTitle)}</a></p>`
3910
+ : `<p class="reply-composer__post-title">${escapeHtml(postTitle)}</p>`)
3899
3911
  : "";
3900
3912
  const bodyHtml = detail.messageHtml
3901
3913
  ? `<div class="reply-composer__context-body markdown">${detail.messageHtml}</div>`
@@ -3922,8 +3934,12 @@ function renderMoltbookDraftComposer(detail, options = {}) {
3922
3934
  const postAuthorLine = !isOriginalPost && detail.postAuthor
3923
3935
  ? `<p class="reply-composer__author-meta muted">@${escapeHtml(detail.postAuthor)}</p>`
3924
3936
  : "";
3925
- const postLink = !isOriginalPost && detail.postUrl
3926
- ? `<p class="reply-composer__author"><a href="${escapeHtml(detail.postUrl)}" target="_blank" rel="noopener">${escapeHtml(detail.postTitle || detail.postUrl)}</a></p>`
3937
+ const draftPostUrl = detail.postUrl || (detail.threadId ? `https://www.moltbook.com/post/${detail.threadId}` : "");
3938
+ const draftPostTitle = normalizeClientText(detail.postTitle || detail.threadLabel || "").replace(/^Moltbook\s*·\s*/iu, "");
3939
+ const postLink = !isOriginalPost && draftPostTitle
3940
+ ? (draftPostUrl
3941
+ ? `<p class="reply-composer__post-title"><a href="${escapeHtml(draftPostUrl)}" target="_blank" rel="noopener">${escapeHtml(draftPostTitle)}</a></p>`
3942
+ : `<p class="reply-composer__post-title">${escapeHtml(draftPostTitle)}</p>`)
3927
3943
  : "";
3928
3944
  const postBodyBlock = !isOriginalPost && detail.postBody
3929
3945
  ? `<details class="reply-composer__context"><summary>元の投稿</summary><div class="reply-composer__context-body">${escapeHtml(detail.postBody).replace(/\n/g, "<br>")}</div></details>`
@@ -5769,6 +5785,14 @@ function detailIntentText(detail) {
5769
5785
  return itemIntentText(detail.kind, "pending", provider);
5770
5786
  }
5771
5787
 
5788
+ function renderDetailTitle(detail) {
5789
+ const title = escapeHtml(detailDisplayTitle(detail));
5790
+ if ((detail.kind === "moltbook_reply" || detail.kind === "moltbook_draft") && detail.postUrl) {
5791
+ return `<a href="${escapeHtml(detail.postUrl)}" target="_blank" rel="noopener" class="detail-title__link">${title}</a>`;
5792
+ }
5793
+ return title;
5794
+ }
5795
+
5772
5796
  function detailDisplayTitle(detail) {
5773
5797
  const threadLabel = normalizeClientText(detail?.threadLabel || "");
5774
5798
  if (threadLabel) {
@@ -6050,6 +6074,10 @@ function renderIcon(name) {
6050
6074
  return `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M10.4 13.6 8.3 15.7a3 3 0 0 1-4.2-4.2l2.8-2.8a3 3 0 0 1 4.2 0"/><path d="m13.6 10.4 2.1-2.1a3 3 0 1 1 4.2 4.2l-2.8 2.8a3 3 0 0 1-4.2 0"/><path d="m9.5 14.5 5-5"/></svg>`;
6051
6075
  case "clip":
6052
6076
  return `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="m9.5 12.5 5.9-5.9a3 3 0 1 1 4.2 4.2l-7.7 7.7a5 5 0 1 1-7.1-7.1l8.1-8.1"/></svg>`;
6077
+ case "moltbook-draft":
6078
+ return `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M15.2 3.8 20.2 8.8 8.5 20.5 3.5 20.5 3.5 15.5Z"/><path d="M12.5 6.5l5 5"/></svg>`;
6079
+ case "moltbook-comment":
6080
+ return `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M4.5 5.5h15a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H11l-4 3.5v-3.5H4.5a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2Z"/><path d="M8 10h8"/><path d="M8 13h5"/></svg>`;
6053
6081
  case "filter":
6054
6082
  return `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M5 7h14"/><path d="M8 12h8"/><path d="M10.5 17h3"/></svg>`;
6055
6083
  case "check":
package/web/i18n.js CHANGED
@@ -222,6 +222,8 @@ const translations = {
222
222
  "timeline.kindFilter.plans": "Plans",
223
223
  "timeline.kindFilter.choices": "Choices",
224
224
  "timeline.kindFilter.completions": "Completed",
225
+ "timeline.kindFilter.moltbookDrafts": "Drafts",
226
+ "timeline.kindFilter.moltbookComments": "Comments",
225
227
  "fileEvent.read": "Checked files",
226
228
  "fileEvent.write": "Updated files",
227
229
  "fileEvent.create": "Created files",
@@ -809,6 +811,8 @@ const translations = {
809
811
  "timeline.kindFilter.plans": "プラン",
810
812
  "timeline.kindFilter.choices": "選択",
811
813
  "timeline.kindFilter.completions": "完了",
814
+ "timeline.kindFilter.moltbookDrafts": "ドラフト",
815
+ "timeline.kindFilter.moltbookComments": "コメント",
812
816
  "fileEvent.read": "ファイルを確認",
813
817
  "fileEvent.write": "ファイルを更新",
814
818
  "fileEvent.create": "ファイルを作成",