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.
- package/README.md +52 -30
- package/index.ts +18 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,38 +1,60 @@
|
|
|
1
1
|
# moshi-opencode-hooks
|
|
2
2
|
|
|
3
|
-
OpenCode plugin
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
|
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
|
},
|