@insitue/claude-plugin 0.4.1 → 0.4.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.
- package/commands/connect.md +19 -3
- package/commands/disconnect.md +37 -0
- package/dist/mcp-server.js +74 -10
- package/package.json +1 -1
package/commands/connect.md
CHANGED
|
@@ -81,9 +81,25 @@ Either path is fine; pick whichever your runtime has.
|
|
|
81
81
|
`[insitue]` (e.g. "companion disconnected"), tell the user
|
|
82
82
|
what happened in one sentence and call `next_pick` again —
|
|
83
83
|
the bridge auto-reconnects.
|
|
84
|
-
5.
|
|
85
|
-
"thanks", "exit",
|
|
86
|
-
|
|
84
|
+
5. **End the session properly.** When the user says "stop",
|
|
85
|
+
"done", "quit", "thanks", "exit", "disconnect", "stop
|
|
86
|
+
insitue", or anything else that clearly ends the InSitue
|
|
87
|
+
session, do BOTH of these:
|
|
88
|
+
a. Call `mcp__insitue__end_session` ONCE. This is non-
|
|
89
|
+
optional. Without it the browser launcher stays purple
|
|
90
|
+
forever — the user sees you as "still listening" when
|
|
91
|
+
you're not. The teardown is cheap (closes a WS, drops a
|
|
92
|
+
file) and safe to repeat.
|
|
93
|
+
b. Stop calling `next_pick`. Acknowledge the disconnect in
|
|
94
|
+
one short line.
|
|
95
|
+
|
|
96
|
+
If the user's "stop" is clearly scoped to *the current task*
|
|
97
|
+
("stop reading that file", "stop, that's not what I meant")
|
|
98
|
+
— i.e. they're not signalling end-of-InSitue — leave the
|
|
99
|
+
subscriber attached and keep the loop alive. Read the room.
|
|
100
|
+
|
|
101
|
+
On Claude Code the user can also run `/insitue:disconnect`
|
|
102
|
+
directly; that hits `end_session` the same way.
|
|
87
103
|
|
|
88
104
|
## Guardrails
|
|
89
105
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Cleanly disconnect this Claude session from InSitue — mutes the browser launcher, frees the companion port.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# /insitue:disconnect
|
|
6
|
+
|
|
7
|
+
Tears down the InSitue session in this Claude Code window
|
|
8
|
+
without exiting claude. Use when you're done picking for now —
|
|
9
|
+
the launcher in the browser goes muted, the companion process
|
|
10
|
+
the MCP server spawned is killed, the stale session file is
|
|
11
|
+
removed.
|
|
12
|
+
|
|
13
|
+
## Your behaviour
|
|
14
|
+
|
|
15
|
+
1. Call `mcp__insitue__end_session` once.
|
|
16
|
+
2. Summarise what was torn down in a single line — e.g.
|
|
17
|
+
*"Disconnected. Companion stopped, session cleared. Run
|
|
18
|
+
`/insitue:connect` again whenever you want to start picking."*
|
|
19
|
+
Don't narrate every field of the response; the user just
|
|
20
|
+
needs to know it worked.
|
|
21
|
+
3. **Exit any active pick loop** — stop calling `next_pick`.
|
|
22
|
+
4. Stay in the chat. The user may have more for you here that
|
|
23
|
+
isn't pick-related. Don't `/exit`; that's their call.
|
|
24
|
+
|
|
25
|
+
## Symmetric with /insitue:connect
|
|
26
|
+
|
|
27
|
+
`/insitue:connect` attaches the subscriber → browser launcher
|
|
28
|
+
goes purple. `/insitue:disconnect` detaches → browser launcher
|
|
29
|
+
goes muted. Both safe to call any number of times, in any
|
|
30
|
+
order; the MCP holds the lifecycle straight.
|
|
31
|
+
|
|
32
|
+
## Reconnecting after disconnect
|
|
33
|
+
|
|
34
|
+
Just run `/insitue:connect` again. The MCP re-spawns the
|
|
35
|
+
companion if needed (the kill in `end_session` left `npx` ready
|
|
36
|
+
to restart), re-attaches the WS, and you're back in the loop.
|
|
37
|
+
No claude restart required.
|
package/dist/mcp-server.js
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
10
10
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
11
11
|
import { spawn } from "child_process";
|
|
12
|
-
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
|
|
12
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3, rmSync } from "fs";
|
|
13
13
|
import { request as httpRequest } from "http";
|
|
14
14
|
import { dirname as dirname3, join as join3 } from "path";
|
|
15
15
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
@@ -360,11 +360,15 @@ process.on("SIGTERM", () => {
|
|
|
360
360
|
process.exit(143);
|
|
361
361
|
});
|
|
362
362
|
var buffer = new PickBuffer();
|
|
363
|
-
|
|
364
|
-
|
|
363
|
+
var activeWs = null;
|
|
364
|
+
var reconnectTimer = null;
|
|
365
|
+
var disconnecting = false;
|
|
366
|
+
function connectToCompanion(s) {
|
|
367
|
+
const url = `ws://127.0.0.1:${s.port}/insitue/cli`;
|
|
365
368
|
const ws = new WebSocket(url, {
|
|
366
369
|
headers: { "user-agent": "insitue-claude-plugin" }
|
|
367
370
|
});
|
|
371
|
+
activeWs = ws;
|
|
368
372
|
ws.on("open", () => {
|
|
369
373
|
ws.send(
|
|
370
374
|
JSON.stringify({
|
|
@@ -372,7 +376,7 @@ function connectToCompanion(session2) {
|
|
|
372
376
|
// Pin to the companion's pinned protocol version. Bump
|
|
373
377
|
// when the wire format breaks.
|
|
374
378
|
protocolVersion: 5,
|
|
375
|
-
token:
|
|
379
|
+
token: s.token
|
|
376
380
|
})
|
|
377
381
|
);
|
|
378
382
|
});
|
|
@@ -413,8 +417,10 @@ function connectToCompanion(session2) {
|
|
|
413
417
|
}
|
|
414
418
|
});
|
|
415
419
|
ws.on("close", () => {
|
|
420
|
+
if (activeWs === ws) activeWs = null;
|
|
416
421
|
buffer.rejectAll("companion disconnected \u2014 restart `claude` to reconnect");
|
|
417
|
-
|
|
422
|
+
if (disconnecting) return;
|
|
423
|
+
reconnectTimer = setTimeout(() => connectToCompanion(s), 2e3);
|
|
418
424
|
});
|
|
419
425
|
ws.on("error", () => {
|
|
420
426
|
});
|
|
@@ -431,11 +437,51 @@ if (!session) {
|
|
|
431
437
|
);
|
|
432
438
|
}
|
|
433
439
|
var attached = false;
|
|
434
|
-
function ensureSubscriberAttached() {
|
|
435
|
-
if (attached
|
|
440
|
+
async function ensureSubscriberAttached() {
|
|
441
|
+
if (attached) return;
|
|
442
|
+
disconnecting = false;
|
|
443
|
+
if (!session) {
|
|
444
|
+
session = await ensureCompanion(projectDir.dir);
|
|
445
|
+
if (!session) return;
|
|
446
|
+
}
|
|
436
447
|
attached = true;
|
|
437
448
|
connectToCompanion(session);
|
|
438
449
|
}
|
|
450
|
+
function endSession() {
|
|
451
|
+
disconnecting = true;
|
|
452
|
+
attached = false;
|
|
453
|
+
let closedWs = false;
|
|
454
|
+
let killedCompanion = false;
|
|
455
|
+
let removedSessionFile = false;
|
|
456
|
+
if (reconnectTimer) {
|
|
457
|
+
clearTimeout(reconnectTimer);
|
|
458
|
+
reconnectTimer = null;
|
|
459
|
+
}
|
|
460
|
+
if (activeWs) {
|
|
461
|
+
try {
|
|
462
|
+
activeWs.close();
|
|
463
|
+
closedWs = true;
|
|
464
|
+
} catch {
|
|
465
|
+
}
|
|
466
|
+
activeWs = null;
|
|
467
|
+
}
|
|
468
|
+
if (ownedChild) {
|
|
469
|
+
killedCompanion = true;
|
|
470
|
+
cleanupOwnedChild();
|
|
471
|
+
}
|
|
472
|
+
if (killedCompanion) {
|
|
473
|
+
const f = join3(projectDir.dir, ".insitue", "session.json");
|
|
474
|
+
if (existsSync2(f)) {
|
|
475
|
+
try {
|
|
476
|
+
rmSync(f);
|
|
477
|
+
removedSessionFile = true;
|
|
478
|
+
} catch {
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
session = null;
|
|
483
|
+
return { closedWs, killedCompanion, removedSessionFile };
|
|
484
|
+
}
|
|
439
485
|
var server = new McpServer({
|
|
440
486
|
name: "insitue",
|
|
441
487
|
version: "0.3.0"
|
|
@@ -451,7 +497,7 @@ server.registerTool(
|
|
|
451
497
|
}
|
|
452
498
|
},
|
|
453
499
|
async ({ timeout_ms }) => {
|
|
454
|
-
ensureSubscriberAttached();
|
|
500
|
+
await ensureSubscriberAttached();
|
|
455
501
|
const ms = timeout_ms ?? NEXT_PICK_DEFAULT_TIMEOUT_MS;
|
|
456
502
|
const pick = await buffer.next(ms);
|
|
457
503
|
if (!pick) {
|
|
@@ -480,7 +526,7 @@ server.registerTool(
|
|
|
480
526
|
}
|
|
481
527
|
},
|
|
482
528
|
async ({ limit }) => {
|
|
483
|
-
ensureSubscriberAttached();
|
|
529
|
+
await ensureSubscriberAttached();
|
|
484
530
|
const picks = buffer.recent(limit ?? 10);
|
|
485
531
|
return {
|
|
486
532
|
content: [
|
|
@@ -499,7 +545,7 @@ server.registerTool(
|
|
|
499
545
|
inputSchema: {}
|
|
500
546
|
},
|
|
501
547
|
async () => {
|
|
502
|
-
ensureSubscriberAttached();
|
|
548
|
+
await ensureSubscriberAttached();
|
|
503
549
|
const instructions = loadInstructions();
|
|
504
550
|
const buffered = buffer.recent(32).length;
|
|
505
551
|
const status = `
|
|
@@ -520,6 +566,24 @@ Begin the loop by calling \`list_recent_picks\` once, then loop on \`next_pick\`
|
|
|
520
566
|
};
|
|
521
567
|
}
|
|
522
568
|
);
|
|
569
|
+
server.registerTool(
|
|
570
|
+
"end_session",
|
|
571
|
+
{
|
|
572
|
+
description: "Cleanly disconnect this MCP from the InSitue companion: close the WS subscriber (browser launcher mutes immediately), suppress auto-reconnect, kill the companion if we spawned it, and remove the stale session file. The user can reconnect later in the same claude session via `/insitue:connect` (Code) or by calling `start_session` again (Desktop). Safe to call repeatedly. Returns what was actually torn down.",
|
|
573
|
+
inputSchema: {}
|
|
574
|
+
},
|
|
575
|
+
async () => {
|
|
576
|
+
const r = endSession();
|
|
577
|
+
return {
|
|
578
|
+
content: [
|
|
579
|
+
{
|
|
580
|
+
type: "text",
|
|
581
|
+
text: JSON.stringify({ status: "disconnected", ...r })
|
|
582
|
+
}
|
|
583
|
+
]
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
);
|
|
523
587
|
server.registerTool(
|
|
524
588
|
"diagnose",
|
|
525
589
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@insitue/claude-plugin",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.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
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|