@insitue/claude-plugin 0.6.0 → 0.6.2

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "insitue",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
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
  "mcpServers": {
6
6
  "insitue": {
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @insitue/claude-plugin
2
2
 
3
+ ## 0.6.2
4
+
5
+ - **Echo the request before fixing.** The cloud-issue instructions (connect.md + the `claim_cloud_issue` tool) now require Claude to print the exact request verbatim ("Fixing locally: …") before any analysis or edits, so you always see what it's about to act on.
6
+
7
+ ## 0.6.1
8
+
9
+ - **Fix: refuse to reuse a stale companion.** `ensureCompanion` now checks a reachable companion's version via the handshake and, if it is older than the cloud-feature floor (0.7.0), tears it down and spawns the current one — instead of silently attaching to a stale companion left on the port.
10
+
3
11
  ## 0.6.0
4
12
 
5
13
  - **Sign in from Claude.** New `/insitue:login` command + `authenticate` / `complete_authentication` MCP tools open a browser, you approve, and a token is stored automatically — no manual paste.
@@ -160,8 +160,14 @@ action.
160
160
  **Typical loop:**
161
161
 
162
162
  1. `list_cloud_issues` → pick an issue with the user.
163
- 2. `claim_cloud_issue <id>` → read `source.file` around `source.line`,
164
- understand the repro from `note` + `consoleErrors`, make the fix.
163
+ 2. `claim_cloud_issue <id>` → **before doing anything else**, echo the
164
+ exact request verbatim to the developer:
165
+
166
+ > **Fixing locally:** [verbatim `note` from the claim response]
167
+ > (Source: `source.file:source.line`)
168
+
169
+ Then read `source.file` around `source.line`, understand the repro
170
+ from `note` + `consoleErrors`, make the fix.
165
171
  3. Open a PR.
166
172
  4. `resolve_cloud_issue <id> <prUrl>`.
167
173
 
@@ -169,6 +175,18 @@ The cloud-issue workflow follows the same guardrails as browser picks:
169
175
  never auto-apply writes, always wait for explicit user approval before
170
176
  editing files.
171
177
 
178
+ **Echo guardrail (cloud-issue path).** Mirroring the pick-loop rule
179
+ ("Always echo the prompt back first"): after a successful
180
+ `claim_cloud_issue`, the FIRST thing you output to the developer MUST
181
+ be the exact issue `note` in the format above — verbatim, not
182
+ paraphrased. The CLI transcript only shows your output, not the
183
+ dashboard description the reporter wrote. Without the echo the
184
+ developer cannot confirm you're acting on the right issue before edits
185
+ begin. Note: when an issue arrives via the browser "Fix locally"
186
+ button it flows through the normal pick loop and the existing
187
+ pick-echo rule already applies — but the same requirement holds when
188
+ you claim via the cloud-issue tool path directly.
189
+
172
190
  ## Failure modes to handle gracefully
173
191
 
174
192
  - **`source.file` doesn't exist**: tell the user the path the
@@ -291,30 +291,64 @@ var PickBuffer = class {
291
291
  this.waiters.length = 0;
292
292
  }
293
293
  };
294
+ var MIN_COMPANION = "0.7.0";
295
+ function semverGte(version, floor) {
296
+ function parse(v) {
297
+ const m = /^(\d+)\.(\d+)\.(\d+)/.exec(v);
298
+ if (!m) return [0, 0, 0];
299
+ return [parseInt(m[1], 10), parseInt(m[2], 10), parseInt(m[3], 10)];
300
+ }
301
+ const [vMaj, vMin, vPat] = parse(version);
302
+ const [fMaj, fMin, fPat] = parse(floor);
303
+ if (vMaj !== fMaj) return vMaj > fMaj;
304
+ if (vMin !== fMin) return vMin > fMin;
305
+ return vPat >= fPat;
306
+ }
294
307
  async function probeCompanion(session2) {
295
308
  try {
296
309
  process.kill(session2.pid, 0);
297
310
  } catch {
298
- return false;
311
+ return null;
299
312
  }
300
313
  return new Promise((resolve) => {
314
+ let rawBody = "";
301
315
  const req = httpRequest(
302
316
  {
303
317
  host: "127.0.0.1",
304
318
  port: session2.port,
305
319
  path: "/insitue/handshake",
306
320
  method: "GET",
321
+ // Include an Origin so the handshake endpoint returns 200 + JSON
322
+ // instead of 403 (which it returns when Origin is absent). We
323
+ // use the loopback Origin — the companion's allowLocalhost flag
324
+ // accepts any localhost:* Origin, or else this falls back to 403
325
+ // which we still treat as "reachable" (non-null return).
326
+ headers: { origin: "http://localhost:5747" },
307
327
  timeout: 1500
308
328
  },
309
329
  (res) => {
310
- res.resume();
311
- resolve(true);
330
+ res.setEncoding("utf8");
331
+ res.on("data", (chunk) => {
332
+ rawBody += chunk;
333
+ });
334
+ res.on("end", () => {
335
+ if (res.statusCode === 200) {
336
+ try {
337
+ const body = JSON.parse(rawBody);
338
+ resolve(body.companionVersion ?? "0.0.0");
339
+ } catch {
340
+ resolve("0.0.0");
341
+ }
342
+ } else {
343
+ resolve("0.0.0");
344
+ }
345
+ });
312
346
  }
313
347
  );
314
- req.on("error", () => resolve(false));
348
+ req.on("error", () => resolve(null));
315
349
  req.on("timeout", () => {
316
350
  req.destroy();
317
- resolve(false);
351
+ resolve(null);
318
352
  });
319
353
  req.end();
320
354
  });
@@ -322,12 +356,30 @@ async function probeCompanion(session2) {
322
356
  var ownedChild = null;
323
357
  async function ensureCompanion(projectDir2) {
324
358
  const existing = findSession(projectDir2);
325
- if (existing && await probeCompanion(existing.session)) {
326
- process.stderr.write(
327
- `[insitue-mcp] reusing companion at :${existing.session.port} (pid ${existing.session.pid})
359
+ if (existing) {
360
+ const companionVersion = await probeCompanion(existing.session);
361
+ if (companionVersion !== null) {
362
+ if (semverGte(companionVersion, MIN_COMPANION)) {
363
+ process.stderr.write(
364
+ `[insitue-mcp] reusing companion at :${existing.session.port} (pid ${existing.session.pid}, v${companionVersion})
328
365
  `
329
- );
330
- return existing.session;
366
+ );
367
+ return existing.session;
368
+ }
369
+ process.stderr.write(
370
+ `[insitue-mcp] replacing stale companion (v${companionVersion} < ${MIN_COMPANION})
371
+ `
372
+ );
373
+ try {
374
+ process.kill(existing.session.pid);
375
+ } catch {
376
+ }
377
+ const sessionPath = join3(projectDir2, ".insitue", "session.json");
378
+ try {
379
+ rmSync(sessionPath);
380
+ } catch {
381
+ }
382
+ }
331
383
  }
332
384
  process.stderr.write(
333
385
  `[insitue-mcp] starting companion via \`npx -y @insitue/companion@latest dev\` in ${projectDir2}\u2026
@@ -947,7 +999,7 @@ server.registerTool(
947
999
  source,
948
1000
  url,
949
1001
  consoleErrors,
950
- instructions: "Read the source file around the line, reproduce the issue from the description, make the fix, open a PR, then call resolve_cloud_issue with the PR url."
1002
+ instructions: 'FIRST, echo the exact issue note verbatim to the developer before any analysis or edits: output "**Fixing locally:** <note>" followed by "(Source: <file>:<line>)" so they can confirm what is being acted on. THEN read the source file around the line, reproduce the issue from the description, make the fix, open a PR, then call resolve_cloud_issue with the PR url.'
951
1003
  })
952
1004
  }
953
1005
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@insitue/claude-plugin",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "description": "Drive Claude (Code AND Desktop) from the InSitue browser overlay — pick an element in your app, claude reads the file and proposes the edit.",
5
5
  "keywords": [
6
6
  "insitue",