@super-hands/connect 0.1.17 → 0.1.19
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 +42 -6
- package/client.mjs +152 -7
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -3,16 +3,51 @@
|
|
|
3
3
|
Connect the coding agents on this machine to your team's
|
|
4
4
|
[Superhands](https://app.superhands.ai) MCP server, in one command.
|
|
5
5
|
|
|
6
|
-
Superhands
|
|
6
|
+
Superhands generates the command for you, with a short-lived setup code on
|
|
7
|
+
the end of it:
|
|
7
8
|
|
|
8
9
|
```
|
|
9
|
-
|
|
10
|
+
npx -y @super-hands/connect@latest --code shsetup_…
|
|
10
11
|
```
|
|
11
12
|
|
|
13
|
+
The client exchanges that code for the credential over one request to your
|
|
14
|
+
deployment and writes the credential into the clients below. The code is
|
|
15
|
+
worth nothing twenty minutes later; the token it becomes never appears in the
|
|
16
|
+
command, the prompt you pasted it from, or your shell history. A command
|
|
17
|
+
written by hand around a token from the Agents page still works:
|
|
18
|
+
`SUPERHANDS_MCP_TOKEN="…" npx -y @super-hands/connect@latest`.
|
|
19
|
+
|
|
12
20
|
It connects to `https://app.superhands.ai/api/mcp` unless `SUPERHANDS_MCP_URL`
|
|
13
21
|
says otherwise — a command from a preview or local deployment carries that
|
|
14
22
|
variable too.
|
|
15
23
|
|
|
24
|
+
## Starting without an account
|
|
25
|
+
|
|
26
|
+
If you have never used Superhands, you do not need a setup code, a browser or
|
|
27
|
+
an account. This command works on its own:
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
npx -y @super-hands/connect@latest start --claude
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
It creates a Superhands team for this machine, with the baseline Instructions
|
|
34
|
+
already in it, mints the credential, and writes the configs and the skill just
|
|
35
|
+
as an install does. Nobody owns the team yet: after your agent's first
|
|
36
|
+
Superhands tool call, its reply carries a link you can open to claim the team
|
|
37
|
+
whenever you want it in the app. `--cursor` and `--codex` do the same for those
|
|
38
|
+
clients; with no client flag it connects every client it finds.
|
|
39
|
+
|
|
40
|
+
Because this makes a team, it refuses to run twice by accident: on a machine
|
|
41
|
+
that already holds a Superhands credential it stops and tells you to use
|
|
42
|
+
`update`, or `install` with a setup code, instead. Pass `--new` if you really
|
|
43
|
+
do want a separate team.
|
|
44
|
+
|
|
45
|
+
The homepage adds one more thing to this command — `--code shwatch_…`. That is
|
|
46
|
+
a **watch handle**, not a credential and not a setup code. It buys exactly one
|
|
47
|
+
thing: the page you copied the command from can show your agent arriving and
|
|
48
|
+
list its first few calls. The team you get is identical without it, and every
|
|
49
|
+
run carrying the same handle lands on one team rather than making several.
|
|
50
|
+
|
|
16
51
|
## What it does
|
|
17
52
|
|
|
18
53
|
- **Cursor** — merges the `superhands` server into `~/.cursor/mcp.json`.
|
|
@@ -71,10 +106,11 @@ remove one client and leave the rest.
|
|
|
71
106
|
|
|
72
107
|
## What it does not do
|
|
73
108
|
|
|
74
|
-
It reads no repository, runs none of your code, and uploads nothing
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
109
|
+
It reads no repository, runs none of your code, and uploads nothing but the
|
|
110
|
+
setup code it was given, once, to the deployment that issued it. The one
|
|
111
|
+
secret it then holds is the token that came back, and it is written only into
|
|
112
|
+
the client configs above. The token can be revoked at any time from the
|
|
113
|
+
Superhands Agents page.
|
|
78
114
|
|
|
79
115
|
This file is generated from
|
|
80
116
|
[`lib/connect-client-entry.ts`](https://github.com/superhandsai/superhands/blob/main/lib/connect-client-entry.ts)
|
package/client.mjs
CHANGED
|
@@ -21,6 +21,7 @@ var MCP_ACCESS_TOKEN_TTL_SECONDS = 30 * 24 * 60 * 60;
|
|
|
21
21
|
// lib/mcp-clients.ts
|
|
22
22
|
var CONNECT_TOKEN_ENV = "SUPERHANDS_MCP_TOKEN";
|
|
23
23
|
var CONNECT_URL_ENV = "SUPERHANDS_MCP_URL";
|
|
24
|
+
var CONNECT_CODE_FLAG = "--code";
|
|
24
25
|
var CONNECT_DEFAULT_MCP_URL = "https://app.superhands.ai/api/mcp";
|
|
25
26
|
var CONNECT_CLIENT_PACKAGE = "@super-hands/connect";
|
|
26
27
|
var CONNECT_CLIENT_SPEC = `${CONNECT_CLIENT_PACKAGE}@latest`;
|
|
@@ -328,6 +329,95 @@ function writeSkill(clientDir) {
|
|
|
328
329
|
function connectReportUrl(endpoint) {
|
|
329
330
|
return `${endpoint.replace(/\/+$/, "")}/connect`;
|
|
330
331
|
}
|
|
332
|
+
function connectExchangeUrl(endpoint) {
|
|
333
|
+
return `${connectReportUrl(endpoint)}/exchange`;
|
|
334
|
+
}
|
|
335
|
+
function connectStartUrl(endpoint) {
|
|
336
|
+
return `${connectReportUrl(endpoint)}/start`;
|
|
337
|
+
}
|
|
338
|
+
function appOrigin(endpoint) {
|
|
339
|
+
return endpoint.replace(/\/api\/mcp\/?$/, "") || endpoint;
|
|
340
|
+
}
|
|
341
|
+
function takeCodeArgument(argv) {
|
|
342
|
+
const rest = [];
|
|
343
|
+
let code = null;
|
|
344
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
345
|
+
const arg = argv[i];
|
|
346
|
+
if (arg === CONNECT_CODE_FLAG) {
|
|
347
|
+
const next = argv[i + 1];
|
|
348
|
+
if (next !== void 0 && !next.startsWith("--")) {
|
|
349
|
+
code = next;
|
|
350
|
+
i += 1;
|
|
351
|
+
} else {
|
|
352
|
+
code = "";
|
|
353
|
+
}
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
if (arg.startsWith(`${CONNECT_CODE_FLAG}=`)) {
|
|
357
|
+
code = arg.slice(CONNECT_CODE_FLAG.length + 1);
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
rest.push(arg);
|
|
361
|
+
}
|
|
362
|
+
return { code, rest };
|
|
363
|
+
}
|
|
364
|
+
async function exchangeSetupCode(args) {
|
|
365
|
+
let response;
|
|
366
|
+
try {
|
|
367
|
+
response = await fetch(connectExchangeUrl(args.endpoint), {
|
|
368
|
+
method: "POST",
|
|
369
|
+
headers: { "content-type": "application/json" },
|
|
370
|
+
body: JSON.stringify({ code: args.code }),
|
|
371
|
+
signal: AbortSignal.timeout(1e4)
|
|
372
|
+
});
|
|
373
|
+
} catch {
|
|
374
|
+
return stop(
|
|
375
|
+
`Superhands at ${args.endpoint} could not be reached to turn the setup code into a credential. Check the connection and run this command again.`
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
const body = await response.json().catch(() => null);
|
|
379
|
+
if (response.ok && body?.ok && typeof body.token === "string" && body.token) {
|
|
380
|
+
return body.token;
|
|
381
|
+
}
|
|
382
|
+
if (response.status === 401 || response.status === 400) {
|
|
383
|
+
return stop(
|
|
384
|
+
"this setup code has expired or has been used up. Get a fresh command from Superhands and run it unchanged."
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
return stop(
|
|
388
|
+
`Superhands answered ${response.status} to the setup code. Try again in a moment, or get a fresh command from Superhands.`
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
async function startTeam(args) {
|
|
392
|
+
let response;
|
|
393
|
+
try {
|
|
394
|
+
response = await fetch(connectStartUrl(args.endpoint), {
|
|
395
|
+
method: "POST",
|
|
396
|
+
headers: { "content-type": "application/json" },
|
|
397
|
+
body: JSON.stringify(args.code ? { code: args.code } : {}),
|
|
398
|
+
signal: AbortSignal.timeout(1e4)
|
|
399
|
+
});
|
|
400
|
+
} catch {
|
|
401
|
+
return stop(
|
|
402
|
+
`Superhands at ${args.endpoint} could not be reached. Nothing was created. Check the connection and run this command again.`
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
const body = await response.json().catch(() => null);
|
|
406
|
+
if (response.ok && body?.ok && typeof body.token === "string" && body.token) {
|
|
407
|
+
return body.token;
|
|
408
|
+
}
|
|
409
|
+
if (response.status === 429) {
|
|
410
|
+
return stop(
|
|
411
|
+
`Superhands has started too many teams from this network in the last hour. Nothing was created. Try again later, or create an account at ${appOrigin(args.endpoint)} and use the connect command from Setup.`
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
if (response.status === 401) {
|
|
415
|
+
return stop(
|
|
416
|
+
`that watch code has expired or is not one Superhands issued. Run the same command without ${CONNECT_CODE_FLAG} \u2014 you get the same team, the homepage just will not show it arriving.`
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
return stop("Superhands could not start a team just now. Nothing was created \u2014 try again in a moment.");
|
|
420
|
+
}
|
|
331
421
|
async function reportSkillVersion(args) {
|
|
332
422
|
try {
|
|
333
423
|
await fetch(connectReportUrl(args.endpoint), {
|
|
@@ -427,26 +517,81 @@ function chosenClients(flags) {
|
|
|
427
517
|
return explicit ? { cursor, claude, codex, explicit } : { cursor: true, claude: true, codex: true, explicit };
|
|
428
518
|
}
|
|
429
519
|
async function main() {
|
|
430
|
-
const argv = process.argv.slice(2);
|
|
520
|
+
const { code, rest: argv } = takeCodeArgument(process.argv.slice(2));
|
|
431
521
|
const flags = new Set(argv.filter((arg) => arg.startsWith("--")));
|
|
432
522
|
const command = argv.find((arg) => !arg.startsWith("--")) ?? "install";
|
|
433
523
|
if (command === "uninstall") return uninstall(flags);
|
|
434
524
|
if (command === "update") return update(flags);
|
|
525
|
+
if (command === "start") return start(flags, code);
|
|
435
526
|
if (command !== "install") {
|
|
436
527
|
stop(
|
|
437
|
-
`unknown command "${command}". This tool takes "install" (the default), "update" or "uninstall".`
|
|
528
|
+
`unknown command "${command}". This tool takes "install" (the default), "start", "update" or "uninstall".`
|
|
438
529
|
);
|
|
439
530
|
}
|
|
440
|
-
return install(flags);
|
|
531
|
+
return install(flags, code);
|
|
532
|
+
}
|
|
533
|
+
function chosenEndpoint() {
|
|
534
|
+
return process.env[CONNECT_URL_ENV]?.trim() || CONNECT_DEFAULT_MCP_URL;
|
|
535
|
+
}
|
|
536
|
+
async function start(flags, code) {
|
|
537
|
+
const endpoint = chosenEndpoint();
|
|
538
|
+
if (code === "") {
|
|
539
|
+
stop(
|
|
540
|
+
`${CONNECT_CODE_FLAG} needs the watch code after it. Copy the whole command from the Superhands homepage and run it unchanged, or drop ${CONNECT_CODE_FLAG} entirely \u2014 the command works without it.`
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
const existing = existingConnections(endpoint);
|
|
544
|
+
if (!code && existing.length > 0 && !flags.has("--new")) {
|
|
545
|
+
stop(
|
|
546
|
+
`this machine is already connected to Superhands at ${endpoint}. Run \`update\` to refresh it, add the client flag to \`install\` with a setup code from your Agents page to connect another client to the same team, or pass --new to start a separate team.`
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
const token = await startTeam({ endpoint, code });
|
|
550
|
+
await connectWithToken(flags, token, endpoint);
|
|
551
|
+
if (existing.length === 0) {
|
|
552
|
+
say(
|
|
553
|
+
"A Superhands team was created for this machine, with the baseline Instructions already in it. Nobody owns it yet: your agent's first answer after calling a Superhands tool carries the link to claim it."
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
function existingConnections(endpoint) {
|
|
558
|
+
const found = [];
|
|
559
|
+
let cursorText = null;
|
|
560
|
+
try {
|
|
561
|
+
cursorText = readFileSync(join(homedir(), ".cursor", "mcp.json"), "utf8");
|
|
562
|
+
} catch {
|
|
563
|
+
cursorText = null;
|
|
564
|
+
}
|
|
565
|
+
const cursor = cursorConnection(cursorText);
|
|
566
|
+
if (cursor) found.push(cursor);
|
|
567
|
+
const claude = claudeRegistration().connection;
|
|
568
|
+
if (claude) found.push(claude);
|
|
569
|
+
let codexText = "";
|
|
570
|
+
try {
|
|
571
|
+
codexText = readFileSync(join(homedir(), ".codex", "config.toml"), "utf8");
|
|
572
|
+
} catch {
|
|
573
|
+
codexText = "";
|
|
574
|
+
}
|
|
575
|
+
const codex = codexConnection(codexText);
|
|
576
|
+
if (codex) found.push(codex);
|
|
577
|
+
return found.filter((connection) => connection.endpoint === endpoint);
|
|
441
578
|
}
|
|
442
|
-
async function install(flags) {
|
|
443
|
-
const
|
|
444
|
-
|
|
579
|
+
async function install(flags, code) {
|
|
580
|
+
const endpoint = chosenEndpoint();
|
|
581
|
+
if (code === "") {
|
|
582
|
+
stop(
|
|
583
|
+
`${CONNECT_CODE_FLAG} needs the setup code after it. Copy the whole command from Superhands and run it unchanged.`
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
const token = code ? await exchangeSetupCode({ endpoint, code }) : process.env[CONNECT_TOKEN_ENV]?.trim();
|
|
445
587
|
if (!token) {
|
|
446
588
|
stop(
|
|
447
|
-
`this command needs ${CONNECT_TOKEN_ENV} set on the same line. Copy the whole command from Superhands
|
|
589
|
+
`this command needs a setup code after ${CONNECT_CODE_FLAG}, or ${CONNECT_TOKEN_ENV} set on the same line. Copy the whole command from Superhands and run it unchanged. If you have no Superhands account yet, run \`start\` instead \u2014 it needs nothing.`
|
|
448
590
|
);
|
|
449
591
|
}
|
|
592
|
+
return connectWithToken(flags, token, endpoint);
|
|
593
|
+
}
|
|
594
|
+
async function connectWithToken(flags, token, endpoint) {
|
|
450
595
|
const wanted = chosenClients(flags);
|
|
451
596
|
let connected = 0;
|
|
452
597
|
let attempted = 0;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@super-hands/connect",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Connect the coding agents on this machine to your team's Superhands MCP server
|
|
3
|
+
"version": "0.1.19",
|
|
4
|
+
"description": "Connect the coding agents on this machine to your team's Superhands MCP server — with `start`, from a machine that has no Superhands account at all. Refresh it later with `update` (no token needed), and take it back off again with `uninstall`. Writes each client's own config; reads no repository, uploads nothing.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"superhands-connect": "client.mjs"
|
|
7
7
|
},
|