moshi-opencode-hooks 1.0.12 → 1.0.14

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 (3) hide show
  1. package/README.md +52 -30
  2. package/index.ts +18 -6
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,38 +1,60 @@
1
1
  # moshi-opencode-hooks
2
2
 
3
- OpenCode plugin for Moshi live activity integration. Provides real-time progress updates in the Moshi iOS app when using OpenCode.
3
+ OpenCode plugin that sends real-time events to the [Moshi](https://getmoshi.app) iOS app for live activity integration.
4
+
5
+ ## Motivation
6
+
7
+ [Moshi](https://getmoshi.app) is an iOS SSH app that provides live activity widgets on the lock screen and Dynamic Island. While it already supports Cloud Code, this plugin brings the same live activity experience to [OpenCode](https://opencode.ai) users.
8
+
9
+ Track your coding session progress in real-time:
10
+ - See which tool is currently running
11
+ - Get notified when permissions are needed
12
+ - Know when your task completes
4
13
 
5
14
  ## Setup
6
15
 
7
- 1. **Install the token** (if not already done with Claude Code):
8
- ```bash
9
- bunx moshi-hooks token Xjqyek7li0vXgBnIlvXEn3VJgdhWf8qW
10
- ```
11
-
12
- 2. **Add as npm plugin** in `~/.config/opencode/opencode.json`:
13
- ```json
14
- {
15
- "$schema": "https://opencode.ai/config.json",
16
- "plugin": ["moshi-opencode-hooks"]
17
- }
18
- ```
19
-
20
- Or use a local path for development:
21
- ```json
22
- {
23
- "$schema": "https://opencode.ai/config.json",
24
- "plugin": ["/path/to/moshi-opencode-hooks"]
25
- }
26
- ```
27
-
28
- ## Events Sent to Moshi
29
-
30
- - `tool.execute.before/after` - Tool execution (Bash, Edit, Write, Read, Glob, Grep, Task)
31
- - `permission.ask` - Permission prompts
32
- - `session.created` - Session start
33
- - `session.idle` - Task completion
16
+ ```bash
17
+ # 1. Install the plugin (pulls from npm)
18
+ bunx moshi-opencode-hooks setup
19
+
20
+ # 2. Add your Moshi token
21
+ bunx moshi-opencode-hooks token YOUR_TOKEN_HERE
22
+ ```
23
+
24
+ That's it! OpenCode will now send events to Moshi whenever you start a session or run a tool.
25
+
26
+ ### Manual Setup
27
+
28
+ Add to `~/.config/opencode/opencode.json`:
29
+
30
+ ```json
31
+ {
32
+ "$schema": "https://opencode.ai/config.json",
33
+ "plugin": ["moshi-opencode-hooks"]
34
+ }
35
+ ```
34
36
 
35
37
  ## Requirements
36
38
 
37
- - OpenCode
38
- - Moshi iOS app with Cloud Code hook token
39
+ - [OpenCode](https://opencode.ai)
40
+ - [Moshi iOS app](https://getmoshi.app) with Pro subscription
41
+
42
+ ## Moshi Token
43
+
44
+ Get your token from the Moshi iOS app:
45
+ 1. Open Moshi → Settings → Agent Hooks
46
+ 2. Copy the hook token
47
+
48
+ ## Uninstall
49
+
50
+ ```bash
51
+ bunx moshi-opencode-hooks uninstall
52
+ ```
53
+
54
+ ## How It Works
55
+
56
+ The plugin hooks into OpenCode's plugin system and subscribes to session and tool events. When events occur, they're normalized and POSTed to the Moshi API endpoint, which pushes updates to your live activity.
57
+
58
+ ## License
59
+
60
+ MIT
package/index.ts CHANGED
@@ -4,12 +4,13 @@ import type { Plugin } from "@opencode-ai/plugin"
4
4
 
5
5
  const TOKEN_PATH = `${homedir()}/.config/moshi/token`
6
6
  const API_URL = "https://api.getmoshi.app/api/v1/agent-events"
7
- const INTERESTING_TOOLS = new Set(["bash", "edit", "write", "read", "glob", "grep", "task", "question", "apply_patch"])
7
+ const INTERESTING_TOOLS = new Set(["bash", "edit", "write", "read", "glob", "grep", "task", "question", "apply_patch", "webfetch", "websearch"])
8
8
 
9
9
  interface HookState {
10
10
  model?: string
11
11
  lastToolName?: string
12
12
  lastStopTime?: number
13
+ isSubagent?: boolean
13
14
  }
14
15
 
15
16
  interface AgentEvent {
@@ -121,6 +122,11 @@ function formatToolName(toolName: string): string {
121
122
  return toolName.charAt(0).toUpperCase() + toolName.slice(1)
122
123
  }
123
124
 
125
+ function formatModelName(model: string | undefined): string | undefined {
126
+ if (!model) return undefined
127
+ return model.replace(/^claude-/, "")
128
+ }
129
+
124
130
  export const MoshiHooks: Plugin = async ({ client, directory }) => {
125
131
  const setupEventSubscription = async () => {
126
132
  try {
@@ -134,13 +140,17 @@ export const MoshiHooks: Plugin = async ({ client, directory }) => {
134
140
  const state = await readState(sessionId)
135
141
 
136
142
  if (event.type === "session.created") {
143
+ const isSubagent = await isSubagentSession(sessionId)
137
144
  await writeState(sessionId, {
138
145
  model: (event as any).properties?.model,
146
+ isSubagent,
139
147
  })
140
148
  continue
141
149
  }
142
150
 
143
151
  if (event.type === "session.idle") {
152
+ if (state.isSubagent) continue
153
+
144
154
  const now = Date.now() / 1000
145
155
  if (state.lastStopTime && now - state.lastStopTime < 5) continue
146
156
 
@@ -155,7 +165,7 @@ export const MoshiHooks: Plugin = async ({ client, directory }) => {
155
165
  message: "",
156
166
  eventId: crypto.randomUUID(),
157
167
  projectName,
158
- modelName: state.model,
168
+ modelName: formatModelName(state.model),
159
169
  toolName: state.lastToolName,
160
170
  }
161
171
  await sendAgentEvent(client, token, evt)
@@ -208,7 +218,7 @@ export const MoshiHooks: Plugin = async ({ client, directory }) => {
208
218
  message: lines.join("\n---\n").slice(0, 512),
209
219
  eventId: crypto.randomUUID(),
210
220
  projectName,
211
- modelName: state.model,
221
+ modelName: formatModelName(state.model),
212
222
  toolName: tool,
213
223
  }
214
224
  await sendAgentEvent(client, token, evt)
@@ -224,7 +234,7 @@ export const MoshiHooks: Plugin = async ({ client, directory }) => {
224
234
  message: "",
225
235
  eventId: crypto.randomUUID(),
226
236
  projectName,
227
- modelName: state.model,
237
+ modelName: formatModelName(state.model),
228
238
  toolName: tool,
229
239
  }
230
240
  await sendAgentEvent(client, token, evt)
@@ -252,7 +262,7 @@ export const MoshiHooks: Plugin = async ({ client, directory }) => {
252
262
  message: "",
253
263
  eventId: crypto.randomUUID(),
254
264
  projectName,
255
- modelName: state.model,
265
+ modelName: formatModelName(state.model),
256
266
  toolName: tool,
257
267
  }
258
268
  await sendAgentEvent(client, token, evt)
@@ -264,6 +274,8 @@ export const MoshiHooks: Plugin = async ({ client, directory }) => {
264
274
 
265
275
  const sessionID = (input as any).sessionID ?? "unknown"
266
276
  const state = await readState(sessionID)
277
+ if (state.isSubagent) return
278
+
267
279
  const projectName = directory ? basename(directory) : undefined
268
280
 
269
281
  const prompt = (input as any).prompt ?? ""
@@ -277,7 +289,7 @@ export const MoshiHooks: Plugin = async ({ client, directory }) => {
277
289
  message: prompt.slice(0, 256),
278
290
  eventId: crypto.randomUUID(),
279
291
  projectName,
280
- modelName: state.model,
292
+ modelName: formatModelName(state.model),
281
293
  }
282
294
  await sendAgentEvent(client, token, evt)
283
295
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "moshi-opencode-hooks",
3
- "version": "1.0.12",
3
+ "version": "1.0.14",
4
4
  "description": "OpenCode plugin for Moshi live activity integration",
5
5
  "repository": {
6
6
  "type": "git",