@insitue/claude-plugin 0.0.3 → 0.1.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/.claude-plugin/plugin.json +1 -1
- package/commands/connect.md +5 -2
- package/dist/mcp-server.js +56 -1
- package/package.json +1 -1
package/commands/connect.md
CHANGED
|
@@ -19,14 +19,17 @@ Connect this Claude Code session to the running InSitue companion and turn each
|
|
|
19
19
|
|
|
20
20
|
- **First**, call `mcp__insitue__list_recent_picks` once to see if the user already picked things before attaching. If there are recent picks the user hasn't acted on, summarise them so they can choose which to address.
|
|
21
21
|
- **Then enter the loop**: call `mcp__insitue__next_pick`. It blocks for ~5 min by default. When it returns:
|
|
22
|
-
- If `status: "ok"`:
|
|
23
|
-
|
|
22
|
+
- If `status: "ok"`:
|
|
23
|
+
- **If `pick.userNote` is set**: the user typed an instruction in the browser panel before sending. Read the `pick.source.file` at `pick.source.line`, propose an edit, show the diff, wait for "approve"/"yes" before writing.
|
|
24
|
+
- **If `pick.userNote` is null** (user clicked without typing): the user might still be about to type in the browser panel — they have an ASK textbox in the panel that streams here as a `broadcast-ask` event. **Call `next_pick` AGAIN with a 30-second timeout** to wait for the follow-up note. The MCP bridge re-delivers the same pick with `userNote` populated once the user sends it. If that second call returns `status: "timeout"` instead, ONLY THEN fall back to asking "What would you like to change at `<componentName>` (`<file>:<line>`)?" in the terminal.
|
|
24
25
|
- Propose an edit. Show a small diff in chat. Wait for the user to say "go" / "approve" / "yes" before writing.
|
|
25
26
|
- On approval, apply with Edit/Write. Confirm what changed.
|
|
26
27
|
- Loop back to `next_pick`.
|
|
27
28
|
- **If a pick comes through with `target` starting with `[insitue]`**: the companion disconnected (HMR / restart). Tell the user, then call `next_pick` again — the bridge auto-reconnects.
|
|
28
29
|
- **Exit the loop** when the user says "stop", "done", "quit", or similar.
|
|
29
30
|
|
|
31
|
+
**Why the double-poll on note-less picks**: the user's hands are on the browser, not the terminal. Their natural workflow is pick → type intent in the panel's ASK textbox → click Send. That ASK arrives via MCP as a `broadcast-ask` joined to the previous pick. If you ask in the terminal while the user is typing in the browser, both messages land at once and the user is confused. Default to "user is about to type in the browser" — re-poll first, only ask in the terminal as a last resort.
|
|
32
|
+
|
|
30
33
|
## Guardrails
|
|
31
34
|
|
|
32
35
|
- Don't touch any file outside the pick's `pick.source.file` unless the user explicitly asks for cross-file changes.
|
package/dist/mcp-server.js
CHANGED
|
@@ -61,19 +61,65 @@ var PickBuffer = class {
|
|
|
61
61
|
if (this.picks.length > MAX_BUFFERED_PICKS) {
|
|
62
62
|
this.picks.shift();
|
|
63
63
|
}
|
|
64
|
+
this.flushWaiters();
|
|
65
|
+
}
|
|
66
|
+
/** #162: merge a `broadcast-ask` into the matching `pick.userNote`.
|
|
67
|
+
* Two ordering modes the user might create:
|
|
68
|
+
* 1. Pick first (auto on click), THEN type + Send → ask lands
|
|
69
|
+
* AFTER pick. If the pick is still queued (not yet delivered
|
|
70
|
+
* to claude), update it in place. If already delivered, the
|
|
71
|
+
* ask arrives as a standalone update — surfaced as a synthetic
|
|
72
|
+
* pick whose `userNote` is set so claude treats it as new
|
|
73
|
+
* input on the most-recent target.
|
|
74
|
+
* 2. Ask before pick is impossible (the panel needs a pick first
|
|
75
|
+
* to enable the ASK textbox), so we don't handle that. */
|
|
76
|
+
attachAsk(bundleId, text) {
|
|
77
|
+
const idx = this.picks.findIndex((p) => p.id === bundleId);
|
|
78
|
+
if (idx >= 0) {
|
|
79
|
+
this.picks[idx] = { ...this.picks[idx], userNote: text };
|
|
80
|
+
this.flushWaiters();
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (this.lastDelivered === bundleId && this.lastDeliveredPick) {
|
|
84
|
+
this.picks.push({
|
|
85
|
+
...this.lastDeliveredPick,
|
|
86
|
+
userNote: text,
|
|
87
|
+
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
88
|
+
});
|
|
89
|
+
this.flushWaiters();
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const orphan = {
|
|
93
|
+
id: bundleId,
|
|
94
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
95
|
+
source: null,
|
|
96
|
+
confidence: "n/a",
|
|
97
|
+
target: "[insitue] note without pick",
|
|
98
|
+
selector: null,
|
|
99
|
+
userNote: text,
|
|
100
|
+
url: null,
|
|
101
|
+
componentStack: []
|
|
102
|
+
};
|
|
103
|
+
this.picks.push(orphan);
|
|
104
|
+
this.flushWaiters();
|
|
105
|
+
}
|
|
106
|
+
flushWaiters() {
|
|
64
107
|
while (this.waiters.length && this.picks.length) {
|
|
65
108
|
const w = this.waiters.shift();
|
|
66
109
|
clearTimeout(w.timer);
|
|
67
110
|
const next = this.picks.shift();
|
|
68
111
|
this.lastDelivered = next.id;
|
|
112
|
+
this.lastDeliveredPick = next;
|
|
69
113
|
w.resolve(next);
|
|
70
114
|
}
|
|
71
115
|
}
|
|
116
|
+
lastDeliveredPick = null;
|
|
72
117
|
/** Resolve with the next pick to land OR null on timeout. */
|
|
73
118
|
next(timeoutMs) {
|
|
74
119
|
if (this.picks.length) {
|
|
75
120
|
const next = this.picks.shift();
|
|
76
121
|
this.lastDelivered = next.id;
|
|
122
|
+
this.lastDeliveredPick = next;
|
|
77
123
|
return Promise.resolve(next);
|
|
78
124
|
}
|
|
79
125
|
return new Promise((resolve2) => {
|
|
@@ -120,7 +166,9 @@ function connectToCompanion(session) {
|
|
|
120
166
|
t: "hello",
|
|
121
167
|
// Pin to the published companion protocol version — bumped
|
|
122
168
|
// when the wire format breaks, NOT for every release.
|
|
123
|
-
|
|
169
|
+
// v5 added broadcast-ask + subscribers-attached + agent-
|
|
170
|
+
// ask-external for the external-claude routing flow.
|
|
171
|
+
protocolVersion: 5,
|
|
124
172
|
token: session.token
|
|
125
173
|
})
|
|
126
174
|
);
|
|
@@ -136,6 +184,13 @@ function connectToCompanion(session) {
|
|
|
136
184
|
ws.send(JSON.stringify({ t: "subscribe" }));
|
|
137
185
|
return;
|
|
138
186
|
}
|
|
187
|
+
if (m && typeof m === "object" && m.t === "broadcast-ask") {
|
|
188
|
+
const ask = m;
|
|
189
|
+
if (typeof ask.bundleId === "string" && typeof ask.text === "string") {
|
|
190
|
+
buffer.attachAsk(ask.bundleId, ask.text);
|
|
191
|
+
}
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
139
194
|
if (m && typeof m === "object" && m.t === "broadcast-capture") {
|
|
140
195
|
try {
|
|
141
196
|
buffer.push(summariseBundle(m));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@insitue/claude-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Drive a Claude Code session from the InSitue browser overlay — pick an element in your app, claude reads the file and proposes the edit.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|