@insitue/claude-plugin 0.7.2 → 0.7.4
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/CHANGELOG.md +10 -0
- package/README.md +15 -3
- package/commands/connect.md +43 -26
- package/commands/login.md +19 -6
- package/dist/{chunk-RS3B7P4N.js → chunk-B2OV76P2.js} +121 -3
- package/dist/cloud/login.js +1 -1
- package/dist/cloud-cli.js +25 -8
- package/dist/mcp-server.js +21 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @insitue/claude-plugin
|
|
2
2
|
|
|
3
|
+
## 0.7.4
|
|
4
|
+
|
|
5
|
+
- **`/insitue:connect` no longer blocks the session.** In poll mode the agent silently re-looped `next_pick` forever, freezing the chat (queued messages never processed). It now polls once then hands control back, drains buffered picks on your next message, and points you at channel mode (`--dangerously-load-development-channels --channels plugin:insitue`) for truly hands-free background picks.
|
|
6
|
+
- **Brand the sign-in success page.** The loopback login success page uses the brand purple gradient heading instead of the off-brand green.
|
|
7
|
+
|
|
8
|
+
## 0.7.3
|
|
9
|
+
|
|
10
|
+
- **Device-flow login for SSH/remote.** `/insitue:login` (+ `authenticate` tool) auto-detects an SSH session and uses a device flow: shows a verification URL + pairing code to approve in any browser; `complete_authentication` polls for the token. `login --device` forces it. Loopback stays the default locally.
|
|
11
|
+
- **Project-scope awareness.** Persists the token scope; cloud errors guide you to re-login for the current project when scoped elsewhere.
|
|
12
|
+
|
|
3
13
|
## 0.7.2
|
|
4
14
|
|
|
5
15
|
- **Consistent slash commands.** Removed the duplicate MCP prompts for connect/login/logout, which Claude Code surfaced as differently-named `…insitue:<name> (MCP)` entries alongside the clean `/insitue:*` slash commands. Now there is one consistent surface: `/insitue:connect`, `/insitue:disconnect`, `/insitue:login`, `/insitue:logout` (from `commands/*.md`). On Claude Desktop, use the equivalent tools (`start_session`, `authenticate`+`complete_authentication`, `logout`).
|
package/README.md
CHANGED
|
@@ -47,6 +47,15 @@ auto-start the InSitue companion process in the background of
|
|
|
47
47
|
your `claude` session — no separate terminal to babysit. The
|
|
48
48
|
slash command `/insitue:connect` enters the loop.
|
|
49
49
|
|
|
50
|
+
The plugin ships four slash commands:
|
|
51
|
+
|
|
52
|
+
| Command | What it does |
|
|
53
|
+
|---|---|
|
|
54
|
+
| `/insitue:connect` | Enter the pick→edit loop — receive browser picks and act on them. |
|
|
55
|
+
| `/insitue:disconnect` | Leave the loop: close the WS, stop receiving picks, and kill the companion the plugin spawned. |
|
|
56
|
+
| `/insitue:login` | Browser sign-in (PKCE) to InSitue Cloud, then auto-link this repo to its project. |
|
|
57
|
+
| `/insitue:logout` | Revoke the token server-side and clear local credentials. |
|
|
58
|
+
|
|
50
59
|
### 1B. Claude Desktop — one-command setup
|
|
51
60
|
|
|
52
61
|
Claude Desktop doesn't have a plugin marketplace, but it does
|
|
@@ -275,8 +284,8 @@ If your project uses InSitue Cloud, bug reports captured in production land in y
|
|
|
275
284
|
|
|
276
285
|
### Prerequisites
|
|
277
286
|
|
|
278
|
-
1. **Authenticate:** run `insitue login` (from the `@insitue/companion` CLI).
|
|
279
|
-
2. **Link your project:** run `insitue link <projectId>` in your project root. The tools read the linked project from `.insitue/project.json`.
|
|
287
|
+
1. **Authenticate:** run `insitue login` (from the `@insitue/companion` CLI), or `/insitue:login` inside Claude Code. Bare `insitue login` is a tokenless **browser** sign-in via PKCE — it opens your browser, prints an authorize URL + a short code to confirm, and on success prints `✓ Signed in as <login>`. Credentials are saved to `~/.insitue/auth.json`. A token minted this way from inside a repo is **project-scoped** (it can only act on that project's issues). On SSH/remote where no browser or loopback redirect is available, the flow auto-switches to the device flow — or force it with `insitue login --device`. As a CI / headless fallback, paste a Personal Access Token directly with `insitue login --token pat_live_…` (mint PATs at <https://app.insitue.com/app/settings/developer>); PATs are account-wide.
|
|
288
|
+
2. **Link your project:** `insitue login` already best-effort auto-links the current repo to its InSitue project, so this is usually unnecessary. If you need to link manually, run `insitue link` (auto-detects the project from the git remote) or `insitue link <projectId>` in your project root. The tools read the linked project from `.insitue/project.json`.
|
|
280
289
|
|
|
281
290
|
### The loop
|
|
282
291
|
|
|
@@ -352,7 +361,7 @@ The plugin is a stdio MCP server that:
|
|
|
352
361
|
|
|
353
362
|
| Tool | Purpose |
|
|
354
363
|
|---|---|
|
|
355
|
-
| `next_pick` | Long-poll for the next browser pick (default
|
|
364
|
+
| `next_pick` | Long-poll for the next browser pick (default 8 s; max 30 min). |
|
|
356
365
|
| `list_recent_picks` | Up to N buffered picks since the MCP server started. |
|
|
357
366
|
| `start_session` | Returns the operating instructions + current state. Desktop entry point. |
|
|
358
367
|
| `end_session` | Cleanly disconnect: close WS, suppress reconnect, kill spawned companion, drop session file. |
|
|
@@ -364,6 +373,9 @@ The plugin is a stdio MCP server that:
|
|
|
364
373
|
| `claim_cloud_issue` | Claim an issue by `id`; returns note, source `file:line`, page URL, and top console errors. |
|
|
365
374
|
| `resolve_cloud_issue` | Mark an issue resolved; attach `prUrl` and optional `branch`. |
|
|
366
375
|
| `release_cloud_issue` | Return a claimed issue to the open queue. |
|
|
376
|
+
| `authenticate` | Start browser sign-in (PKCE): chooses the device vs loopback flow automatically and returns the authorize URL + code to confirm. |
|
|
377
|
+
| `complete_authentication` | Poll for approval (~5 min), then save credentials and best-effort auto-link the current repo to its project. |
|
|
378
|
+
| `logout` | Revoke the token server-side and clear local credentials. |
|
|
367
379
|
|
|
368
380
|
All file tools resolve paths against the project dir and refuse
|
|
369
381
|
anything that resolves outside it (realpath-checked, so `..` games
|
package/commands/connect.md
CHANGED
|
@@ -64,18 +64,33 @@ your context and skip any id you've already processed.
|
|
|
64
64
|
1. Call `mcp__insitue__list_recent_picks` once. If there are
|
|
65
65
|
any picks the user made before you attached, summarise them
|
|
66
66
|
("you picked X but haven't sent a description yet — make sure
|
|
67
|
-
to click Send in the InSitue panel"). Otherwise
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
67
|
+
to click Send in the InSitue panel"). Otherwise say:
|
|
68
|
+
|
|
69
|
+
> Connected. Pick an element in the browser and I'll act on it
|
|
70
|
+
> here. You can also just type to ask me anything — I'll answer
|
|
71
|
+
> right away, then keep watching.
|
|
72
|
+
>
|
|
73
|
+
> Tip: for hands-free background picks (so I act the moment you
|
|
74
|
+
> pick, without you waiting on me), relaunch with
|
|
75
|
+
> `claude --dangerously-load-development-channels --channels plugin:insitue`.
|
|
76
|
+
|
|
77
|
+
2. **Watch for a pick — ONE poll, then hand control back.** Call
|
|
78
|
+
`mcp__insitue__next_pick` (it long-polls ~8s).
|
|
79
|
+
- On `status: "timeout"` (no pick this window): **STOP. Do NOT
|
|
80
|
+
silently call `next_pick` again.** Parking yourself in back-to-back
|
|
81
|
+
polls FREEZES the session — a tool call can't be interrupted, so the
|
|
82
|
+
user types a message and you never see it (this is the #1 cause of
|
|
83
|
+
"InSitue is blocking me"). Instead, end your turn and hand control
|
|
84
|
+
back with one short line, e.g.:
|
|
85
|
+
|
|
86
|
+
> 👀 Watching. Pick something in the browser, or type anything — I'll
|
|
87
|
+
> grab whatever you picked.
|
|
88
|
+
|
|
89
|
+
Then wait for the user. You resume watching on their next message
|
|
90
|
+
(step 3). In plain poll mode you catch picks when the user next
|
|
91
|
+
interacts — for truly hands-free push *between* messages, they need
|
|
92
|
+
channel mode (the tip in step 1).
|
|
93
|
+
- On `status: "ok"` (a pick arrived):
|
|
79
94
|
- **Always echo the prompt back first.** Before any action,
|
|
80
95
|
diff, or follow-up question, lead with:
|
|
81
96
|
|
|
@@ -105,20 +120,22 @@ your context and skip any id you've already processed.
|
|
|
105
120
|
Don't auto-apply.
|
|
106
121
|
- On approval, write with the Edit tool (Code) or
|
|
107
122
|
`mcp__insitue__apply_edit` (Desktop). Confirm what changed.
|
|
108
|
-
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
`/insitue:connect`
|
|
123
|
+
- Then call `next_pick` once more to catch a rapid follow-up pick.
|
|
124
|
+
When it times out (picks have stopped), fall back to the
|
|
125
|
+
hand-back rule above — yield to the user; do NOT loop forever.
|
|
126
|
+
3. **On each user message: drain picks first, then answer, then resume.**
|
|
127
|
+
Whenever the user sends a message, before responding call `next_pick`
|
|
128
|
+
with a short `timeout_ms` (e.g. `1500`), repeatedly, until it returns
|
|
129
|
+
`status: "timeout"` — handle each pick it returns (de-dupe by `id`).
|
|
130
|
+
This catches anything the user picked while you were idle; picks are
|
|
131
|
+
buffered server-side, so nothing is lost. Then answer the user's
|
|
132
|
+
message fully and do whatever they ask. Then resume watching with a
|
|
133
|
+
single `next_pick` poll (step 2's one-poll-then-hand-back rule).
|
|
134
|
+
|
|
135
|
+
The user's message is ALWAYS the priority. Never tell them to cancel
|
|
136
|
+
or re-run `/insitue:connect` to talk to you, and never narrate the
|
|
137
|
+
watch ("still waiting…", "polling again…") — the browser already shows
|
|
138
|
+
`[insitue] 📥 pick received` the moment a pick lands.
|
|
122
139
|
4. **End the session properly.** When the user says "stop",
|
|
123
140
|
"done", "quit", "thanks", "exit", "disconnect", "stop
|
|
124
141
|
insitue", or anything else that clearly ends the InSitue
|
package/commands/login.md
CHANGED
|
@@ -10,9 +10,13 @@ Cloud issues will become available via the claude-plugin tools once signed in.
|
|
|
10
10
|
|
|
11
11
|
## Your behaviour
|
|
12
12
|
|
|
13
|
-
1. Call `mcp__insitue__authenticate` (no arguments).
|
|
13
|
+
1. Call `mcp__insitue__authenticate` (no arguments). It auto-detects the
|
|
14
|
+
environment: it opens a loopback browser flow locally, and automatically
|
|
15
|
+
switches to the device flow when an SSH session is detected or no loopback
|
|
16
|
+
port is available. Branch on the returned shape:
|
|
14
17
|
|
|
15
|
-
2.
|
|
18
|
+
2. **Loopback flow** — the tool returns
|
|
19
|
+
`{ status: "browser_opened", url, userCode, message }`.
|
|
16
20
|
Show the user:
|
|
17
21
|
|
|
18
22
|
> I've opened your browser to sign in to InSitue. Please:
|
|
@@ -21,11 +25,20 @@ Cloud issues will become available via the claude-plugin tools once signed in.
|
|
|
21
25
|
>
|
|
22
26
|
> (If your browser didn't open, visit this URL manually: `<url>`)
|
|
23
27
|
|
|
24
|
-
|
|
25
|
-
|
|
28
|
+
**Device flow** — the tool instead returns a device-flow shape with a
|
|
29
|
+
`verificationUrl` and `userCode` (no loopback redirect). This is what you'll
|
|
30
|
+
get over SSH or when no loopback port is free. Show the user:
|
|
26
31
|
|
|
27
|
-
|
|
28
|
-
|
|
32
|
+
> To sign in to InSitue, open this URL in any browser:
|
|
33
|
+
> **`<verificationUrl>`**
|
|
34
|
+
>
|
|
35
|
+
> Then confirm the code matches: **`<userCode>`**
|
|
36
|
+
|
|
37
|
+
In both flows, do NOT proceed to step 3 until the user says they've approved
|
|
38
|
+
or you detect they're ready. Wait for the user to confirm.
|
|
39
|
+
|
|
40
|
+
3. Call `mcp__insitue__complete_authentication` (no arguments). This polls
|
|
41
|
+
for the browser approval (up to ~5 minutes) for either flow.
|
|
29
42
|
|
|
30
43
|
4. On `{ status: "ok" }`:
|
|
31
44
|
- If `linked: true`: reply "Signed in as **@`<login>`** and linked to
|
|
@@ -19,8 +19,8 @@ var SUCCESS_HTML = `<!DOCTYPE html>
|
|
|
19
19
|
<head><meta charset="utf-8"><title>InSitue \u2014 signed in</title>
|
|
20
20
|
<style>body{font-family:system-ui,sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh;margin:0;background:#f9fafb;}
|
|
21
21
|
.card{background:#fff;border-radius:12px;padding:40px 48px;text-align:center;box-shadow:0 1px 4px rgba(0,0,0,.12);}
|
|
22
|
-
h1{font-size:1.4rem;margin:0 0 8px;}p{color:#6b7280;margin:0;}</style></head>
|
|
23
|
-
<body><div class="card"><h1>You're signed in to InSitue
|
|
22
|
+
h1{font-size:1.4rem;margin:0 0 8px;background:linear-gradient(180deg,#6b63ff,#5751e6);-webkit-background-clip:text;background-clip:text;color:transparent;}p{color:#6b7280;margin:0;}</style></head>
|
|
23
|
+
<body><div class="card"><h1>You're signed in to InSitue!</h1>
|
|
24
24
|
<p>You can close this tab and return to your terminal.</p></div></body></html>`;
|
|
25
25
|
var ERROR_HTML = `<!DOCTYPE html>
|
|
26
26
|
<html lang="en">
|
|
@@ -34,7 +34,113 @@ function openBrowserUrl(url) {
|
|
|
34
34
|
} catch {
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
|
-
|
|
37
|
+
function isRemoteSession() {
|
|
38
|
+
return !!(process.env["SSH_CONNECTION"] || process.env["SSH_TTY"]);
|
|
39
|
+
}
|
|
40
|
+
async function startDeviceFlow(opts) {
|
|
41
|
+
const { host, label, repo, openBrowser = true } = opts;
|
|
42
|
+
const { verifier, challenge } = genPkce();
|
|
43
|
+
const startRes = await fetch(`${host}/api/v1/cli/authorize/start`, {
|
|
44
|
+
method: "POST",
|
|
45
|
+
headers: { "content-type": "application/json" },
|
|
46
|
+
body: JSON.stringify({
|
|
47
|
+
code_challenge: challenge,
|
|
48
|
+
code_challenge_method: "S256",
|
|
49
|
+
...label ? { label } : {},
|
|
50
|
+
...repo ? { repo } : {}
|
|
51
|
+
})
|
|
52
|
+
});
|
|
53
|
+
if (!startRes.ok) {
|
|
54
|
+
const text = await startRes.text().catch(() => "");
|
|
55
|
+
throw new Error(`authorize/start failed: HTTP ${startRes.status} ${text}`);
|
|
56
|
+
}
|
|
57
|
+
const startJson = await startRes.json();
|
|
58
|
+
const { request_id: requestId, user_code: userCode, expires_in } = startJson;
|
|
59
|
+
const verificationUrl = `${host}/cli/authorize?request_id=${encodeURIComponent(requestId)}`;
|
|
60
|
+
if (openBrowser) {
|
|
61
|
+
openBrowserUrl(verificationUrl);
|
|
62
|
+
}
|
|
63
|
+
let cancelled = false;
|
|
64
|
+
let cancelFn = null;
|
|
65
|
+
function cancel() {
|
|
66
|
+
cancelled = true;
|
|
67
|
+
if (cancelFn) cancelFn();
|
|
68
|
+
}
|
|
69
|
+
function wait() {
|
|
70
|
+
return new Promise((resolve, reject) => {
|
|
71
|
+
const timeoutMs = Math.min((expires_in ?? 600) * 1e3, 6e5);
|
|
72
|
+
let pollIntervalMs = 5e3;
|
|
73
|
+
let timer;
|
|
74
|
+
let settled = false;
|
|
75
|
+
const overallTimeout = setTimeout(() => {
|
|
76
|
+
if (settled) return;
|
|
77
|
+
settled = true;
|
|
78
|
+
if (timer) clearTimeout(timer);
|
|
79
|
+
reject(new Error("timed out waiting for device approval (10 min)"));
|
|
80
|
+
}, timeoutMs);
|
|
81
|
+
cancelFn = () => {
|
|
82
|
+
if (settled) return;
|
|
83
|
+
settled = true;
|
|
84
|
+
if (timer) clearTimeout(timer);
|
|
85
|
+
clearTimeout(overallTimeout);
|
|
86
|
+
reject(new Error("cancelled"));
|
|
87
|
+
};
|
|
88
|
+
const poll = async () => {
|
|
89
|
+
if (settled || cancelled) return;
|
|
90
|
+
try {
|
|
91
|
+
const tokenRes = await fetch(`${host}/api/v1/cli/token`, {
|
|
92
|
+
method: "POST",
|
|
93
|
+
headers: { "content-type": "application/json" },
|
|
94
|
+
body: JSON.stringify({
|
|
95
|
+
request_id: requestId,
|
|
96
|
+
code_verifier: verifier
|
|
97
|
+
})
|
|
98
|
+
});
|
|
99
|
+
if (tokenRes.ok) {
|
|
100
|
+
if (settled) return;
|
|
101
|
+
settled = true;
|
|
102
|
+
clearTimeout(overallTimeout);
|
|
103
|
+
const tokenJson = await tokenRes.json();
|
|
104
|
+
resolve({
|
|
105
|
+
token: tokenJson.token,
|
|
106
|
+
host: tokenJson.host,
|
|
107
|
+
login: tokenJson.login,
|
|
108
|
+
projectId: tokenJson.projectId ?? null
|
|
109
|
+
});
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
let errBody = {};
|
|
113
|
+
try {
|
|
114
|
+
errBody = await tokenRes.json();
|
|
115
|
+
} catch {
|
|
116
|
+
}
|
|
117
|
+
const errCode = errBody.error ?? "";
|
|
118
|
+
if (errCode === "authorization_pending") {
|
|
119
|
+
} else if (errCode === "slow_down") {
|
|
120
|
+
pollIntervalMs = Math.min(pollIntervalMs + 5e3, 3e4);
|
|
121
|
+
} else {
|
|
122
|
+
if (settled) return;
|
|
123
|
+
settled = true;
|
|
124
|
+
clearTimeout(overallTimeout);
|
|
125
|
+
reject(
|
|
126
|
+
new Error(
|
|
127
|
+
errCode === "invalid_grant" ? "sign-in failed: token request rejected (invalid_grant). The request may have expired." : `sign-in failed: ${errCode || `HTTP ${tokenRes.status}`}`
|
|
128
|
+
)
|
|
129
|
+
);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
} catch {
|
|
133
|
+
}
|
|
134
|
+
if (!settled && !cancelled) {
|
|
135
|
+
timer = setTimeout(() => void poll(), pollIntervalMs);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
timer = setTimeout(() => void poll(), pollIntervalMs);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
return { authorizeUrl: verificationUrl, userCode, port: 0, wait, cancel };
|
|
142
|
+
}
|
|
143
|
+
async function startLoopbackFlow(opts) {
|
|
38
144
|
const { host, label, repo, openBrowser = true } = opts;
|
|
39
145
|
const { verifier, challenge } = genPkce();
|
|
40
146
|
const state = toBase64Url(randomBytes(16));
|
|
@@ -157,6 +263,18 @@ async function startLogin(opts) {
|
|
|
157
263
|
}
|
|
158
264
|
return { authorizeUrl, userCode, port, wait, cancel };
|
|
159
265
|
}
|
|
266
|
+
async function startLogin(opts) {
|
|
267
|
+
const { mode, ...rest } = opts;
|
|
268
|
+
const effectiveMode = (() => {
|
|
269
|
+
if (mode === "device") return "device";
|
|
270
|
+
if (mode === "loopback") return "loopback";
|
|
271
|
+
return isRemoteSession() ? "device" : "loopback";
|
|
272
|
+
})();
|
|
273
|
+
if (effectiveMode === "device") {
|
|
274
|
+
return startDeviceFlow(rest);
|
|
275
|
+
}
|
|
276
|
+
return startLoopbackFlow(rest);
|
|
277
|
+
}
|
|
160
278
|
function parseRemoteToOwnerName(remote) {
|
|
161
279
|
remote = remote.trim();
|
|
162
280
|
const sshMatch = remote.match(
|
package/dist/cloud/login.js
CHANGED
package/dist/cloud-cli.js
CHANGED
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
detectGitRemote,
|
|
17
17
|
pickProjectIdForRepo,
|
|
18
18
|
startLogin
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-B2OV76P2.js";
|
|
20
20
|
|
|
21
21
|
// src/cloud-cli.ts
|
|
22
22
|
import { hostname } from "os";
|
|
@@ -55,14 +55,14 @@ async function cmdLogin(argv) {
|
|
|
55
55
|
);
|
|
56
56
|
return 0;
|
|
57
57
|
}
|
|
58
|
+
const mode = flags.has("device") ? "device" : flags.has("loopback") ? "loopback" : "auto";
|
|
58
59
|
const auth = loadAuth();
|
|
59
60
|
const host = resolveHost(auth);
|
|
60
61
|
const projectDir = resolveProjectDir();
|
|
61
62
|
const repo = detectGitRemote(projectDir.dir);
|
|
62
|
-
process.stdout.write("Opening your browser to sign in to InSitue\u2026\n");
|
|
63
63
|
let flow;
|
|
64
64
|
try {
|
|
65
|
-
flow = await startLogin({ host, label: hostname(), ...repo ? { repo } : {} });
|
|
65
|
+
flow = await startLogin({ host, label: hostname(), mode, ...repo ? { repo } : {} });
|
|
66
66
|
} catch (err) {
|
|
67
67
|
process.stderr.write(
|
|
68
68
|
`error: could not start login: ${err.message}
|
|
@@ -70,20 +70,33 @@ async function cmdLogin(argv) {
|
|
|
70
70
|
);
|
|
71
71
|
return 1;
|
|
72
72
|
}
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
if (mode === "device" || mode === "auto" && flow.port === 0) {
|
|
74
|
+
process.stdout.write(
|
|
75
|
+
`
|
|
76
|
+
Open this URL in any browser to approve:
|
|
77
|
+
${flow.authorizeUrl}
|
|
78
|
+
Confirm the code matches: ${flow.userCode}
|
|
79
|
+
|
|
80
|
+
Waiting for approval\u2026
|
|
81
|
+
`
|
|
82
|
+
);
|
|
83
|
+
} else {
|
|
84
|
+
process.stdout.write("Opening your browser to sign in to InSitue\u2026\n");
|
|
85
|
+
process.stdout.write(
|
|
86
|
+
`
|
|
75
87
|
Browser URL: ${flow.authorizeUrl}
|
|
76
88
|
Pairing code (confirm your browser shows this): ${flow.userCode}
|
|
77
89
|
|
|
78
90
|
Waiting for approval\u2026
|
|
79
91
|
`
|
|
80
|
-
|
|
92
|
+
);
|
|
93
|
+
}
|
|
81
94
|
let result;
|
|
82
95
|
try {
|
|
83
96
|
result = await flow.wait();
|
|
84
97
|
} catch (err) {
|
|
85
98
|
const msg = err.message ?? String(err);
|
|
86
|
-
if (msg === "timeout") {
|
|
99
|
+
if (msg === "timeout" || msg.startsWith("timed out")) {
|
|
87
100
|
process.stderr.write("error: timed out waiting for browser approval\n");
|
|
88
101
|
} else {
|
|
89
102
|
process.stderr.write(`error: ${msg}
|
|
@@ -91,9 +104,13 @@ Waiting for approval\u2026
|
|
|
91
104
|
}
|
|
92
105
|
return 1;
|
|
93
106
|
}
|
|
94
|
-
saveAuth({ token: result.token, host: result.host, login: result.login });
|
|
107
|
+
saveAuth({ token: result.token, host: result.host, login: result.login, projectId: result.projectId });
|
|
95
108
|
process.stdout.write(`Signed in as ${result.login}.
|
|
96
109
|
`);
|
|
110
|
+
if (result.projectId) {
|
|
111
|
+
process.stdout.write(`Token scoped to project ${result.projectId}.
|
|
112
|
+
`);
|
|
113
|
+
}
|
|
97
114
|
const projectDir2 = resolveProjectDir();
|
|
98
115
|
let linkedProjectId = result.projectId ?? null;
|
|
99
116
|
if (!linkedProjectId && repo) {
|
package/dist/mcp-server.js
CHANGED
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
detectGitRemote,
|
|
27
27
|
pickProjectIdForRepo,
|
|
28
28
|
startLogin
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-B2OV76P2.js";
|
|
30
30
|
|
|
31
31
|
// src/mcp-server.ts
|
|
32
32
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -826,10 +826,11 @@ server.registerTool(
|
|
|
826
826
|
}
|
|
827
827
|
);
|
|
828
828
|
var pendingLoginFlow = null;
|
|
829
|
+
var pendingLoginIsDevice = false;
|
|
829
830
|
server.registerTool(
|
|
830
831
|
"authenticate",
|
|
831
832
|
{
|
|
832
|
-
description: "Start
|
|
833
|
+
description: "Start an InSitue sign-in (PKCE). Auto-detects the best method: on SSH or headless environments uses device-flow (returns a URL + code for you to open in any browser); on a local machine opens your browser directly. After approving in the browser, call `complete_authentication` to finish.",
|
|
833
834
|
inputSchema: {}
|
|
834
835
|
},
|
|
835
836
|
async () => {
|
|
@@ -839,9 +840,26 @@ server.registerTool(
|
|
|
839
840
|
const flow = await startLogin({
|
|
840
841
|
host,
|
|
841
842
|
label: hostname(),
|
|
843
|
+
mode: "auto",
|
|
842
844
|
...repo ? { repo } : {}
|
|
843
845
|
});
|
|
844
846
|
pendingLoginFlow = flow;
|
|
847
|
+
pendingLoginIsDevice = flow.port === 0;
|
|
848
|
+
if (pendingLoginIsDevice) {
|
|
849
|
+
return {
|
|
850
|
+
content: [
|
|
851
|
+
{
|
|
852
|
+
type: "text",
|
|
853
|
+
text: JSON.stringify({
|
|
854
|
+
status: "device",
|
|
855
|
+
verificationUrl: flow.authorizeUrl,
|
|
856
|
+
userCode: flow.userCode,
|
|
857
|
+
message: "Open the URL in any browser, confirm the code, then I'll finish."
|
|
858
|
+
})
|
|
859
|
+
}
|
|
860
|
+
]
|
|
861
|
+
};
|
|
862
|
+
}
|
|
845
863
|
return {
|
|
846
864
|
content: [
|
|
847
865
|
{
|
|
@@ -894,7 +912,7 @@ server.registerTool(
|
|
|
894
912
|
]
|
|
895
913
|
};
|
|
896
914
|
}
|
|
897
|
-
saveAuth({ token: result.token, host: result.host, login: result.login });
|
|
915
|
+
saveAuth({ token: result.token, host: result.host, login: result.login, projectId: result.projectId });
|
|
898
916
|
let linkedProjectId = result.projectId ?? null;
|
|
899
917
|
let linked = false;
|
|
900
918
|
if (!linkedProjectId) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@insitue/claude-plugin",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.4",
|
|
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",
|