@super-hands/connect 0.1.18 → 0.1.20
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 +27 -0
- package/client.mjs +105 -6
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -21,6 +21,33 @@ It connects to `https://app.superhands.ai/api/mcp` unless `SUPERHANDS_MCP_URL`
|
|
|
21
21
|
says otherwise — a command from a preview or local deployment carries that
|
|
22
22
|
variable too.
|
|
23
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
|
+
|
|
24
51
|
## What it does
|
|
25
52
|
|
|
26
53
|
- **Cursor** — merges the `superhands` server into `~/.cursor/mcp.json`.
|
package/client.mjs
CHANGED
|
@@ -42,7 +42,7 @@ function codexConfigBlock(args) {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
// lib/connect-skill.ts
|
|
45
|
-
var CONNECT_SKILL_VERSION =
|
|
45
|
+
var CONNECT_SKILL_VERSION = 13;
|
|
46
46
|
var CONNECT_SKILL_DIR = MCP_SERVER_KEY;
|
|
47
47
|
var CONNECT_SKILL_FILENAME = "SKILL.md";
|
|
48
48
|
var VERSION_MARKER = `[//]: # (superhands-skill-version: ${CONNECT_SKILL_VERSION})`;
|
|
@@ -71,6 +71,10 @@ Before you build, change, or restyle any UI:
|
|
|
71
71
|
\`skill_version: ${CONNECT_SKILL_VERSION}\` \u2014 the version of this file, so
|
|
72
72
|
the server can tell you when this file is out of date. The corpus comes
|
|
73
73
|
back narrowed to that work, with everything not chosen still named.
|
|
74
|
+
- Keep the instruction activity visible in your working log as terse progress:
|
|
75
|
+
say "Searching instructions" while this call runs, then replace it with the
|
|
76
|
+
names selected when the answer arrives. Update that temporary status in
|
|
77
|
+
place instead of posting a second conclusion.
|
|
74
78
|
- Pass \`paths\` too, when you already know which files this work touches \u2014
|
|
75
79
|
the handful you have open or are about to edit, repository-relative, for
|
|
76
80
|
example \`["app/marketing/pricing.tsx"]\`. The team writes down where
|
|
@@ -91,6 +95,8 @@ Before you build, change, or restyle any UI:
|
|
|
91
95
|
everything under it, so \`better-ui/*\` is a whole skill and
|
|
92
96
|
\`better-ui/SKILL#*\` is one file. It runs no selection and makes no model
|
|
93
97
|
call, so it is a plain, fast lookup and it returns exactly what you named.
|
|
98
|
+
- In your working log, say "Checking: [part]", then replace that status with
|
|
99
|
+
"[module] picked up" when the call returns. Keep it factual and brief.
|
|
94
100
|
- **Expect to make this call several times in one task.** A contact form is
|
|
95
101
|
not one part: it is the form, then its inputs, then its buttons, then its
|
|
96
102
|
validation errors, then its loading and empty states. One read at the
|
|
@@ -117,8 +123,15 @@ Before you build, change, or restyle any UI:
|
|
|
117
123
|
6. When you have finished \u2014 not part-way through \u2014 send the files you wrote or
|
|
118
124
|
changed with \`superhands_report_completed_build\`, naming the \`route\` if
|
|
119
125
|
you know it. The team looks at what their agents build there; it records
|
|
120
|
-
and does not review, so a failed report costs the work nothing.
|
|
121
|
-
|
|
126
|
+
and does not review, so a failed report costs the work nothing. A successful
|
|
127
|
+
response ends with the run's Instructions receipt; include it once in your
|
|
128
|
+
final response so the person can see what was handed over up front, picked
|
|
129
|
+
up during the work, or only named for later. It is context provenance, not
|
|
130
|
+
proof that anything was applied.
|
|
131
|
+
7. In that final response, state the concrete output change each picked-up
|
|
132
|
+
Instruction caused. If one caused no change, say so. This is your account of
|
|
133
|
+
the work, not a Superhands review or verification.
|
|
134
|
+
8. When you finish, say you followed the team's Instructions \u2014 never that your
|
|
122
135
|
work was checked. Reading Instructions is not a check, reporting a build is
|
|
123
136
|
not a review, and nothing has looked at what you built.
|
|
124
137
|
|
|
@@ -332,6 +345,12 @@ function connectReportUrl(endpoint) {
|
|
|
332
345
|
function connectExchangeUrl(endpoint) {
|
|
333
346
|
return `${connectReportUrl(endpoint)}/exchange`;
|
|
334
347
|
}
|
|
348
|
+
function connectStartUrl(endpoint) {
|
|
349
|
+
return `${connectReportUrl(endpoint)}/start`;
|
|
350
|
+
}
|
|
351
|
+
function appOrigin(endpoint) {
|
|
352
|
+
return endpoint.replace(/\/api\/mcp\/?$/, "") || endpoint;
|
|
353
|
+
}
|
|
335
354
|
function takeCodeArgument(argv) {
|
|
336
355
|
const rest = [];
|
|
337
356
|
let code = null;
|
|
@@ -382,6 +401,36 @@ async function exchangeSetupCode(args) {
|
|
|
382
401
|
`Superhands answered ${response.status} to the setup code. Try again in a moment, or get a fresh command from Superhands.`
|
|
383
402
|
);
|
|
384
403
|
}
|
|
404
|
+
async function startTeam(args) {
|
|
405
|
+
let response;
|
|
406
|
+
try {
|
|
407
|
+
response = await fetch(connectStartUrl(args.endpoint), {
|
|
408
|
+
method: "POST",
|
|
409
|
+
headers: { "content-type": "application/json" },
|
|
410
|
+
body: JSON.stringify(args.code ? { code: args.code } : {}),
|
|
411
|
+
signal: AbortSignal.timeout(1e4)
|
|
412
|
+
});
|
|
413
|
+
} catch {
|
|
414
|
+
return stop(
|
|
415
|
+
`Superhands at ${args.endpoint} could not be reached. Nothing was created. Check the connection and run this command again.`
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
const body = await response.json().catch(() => null);
|
|
419
|
+
if (response.ok && body?.ok && typeof body.token === "string" && body.token) {
|
|
420
|
+
return body.token;
|
|
421
|
+
}
|
|
422
|
+
if (response.status === 429) {
|
|
423
|
+
return stop(
|
|
424
|
+
`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.`
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
if (response.status === 401) {
|
|
428
|
+
return stop(
|
|
429
|
+
`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.`
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
return stop("Superhands could not start a team just now. Nothing was created \u2014 try again in a moment.");
|
|
433
|
+
}
|
|
385
434
|
async function reportSkillVersion(args) {
|
|
386
435
|
try {
|
|
387
436
|
await fetch(connectReportUrl(args.endpoint), {
|
|
@@ -486,15 +535,62 @@ async function main() {
|
|
|
486
535
|
const command = argv.find((arg) => !arg.startsWith("--")) ?? "install";
|
|
487
536
|
if (command === "uninstall") return uninstall(flags);
|
|
488
537
|
if (command === "update") return update(flags);
|
|
538
|
+
if (command === "start") return start(flags, code);
|
|
489
539
|
if (command !== "install") {
|
|
490
540
|
stop(
|
|
491
|
-
`unknown command "${command}". This tool takes "install" (the default), "update" or "uninstall".`
|
|
541
|
+
`unknown command "${command}". This tool takes "install" (the default), "start", "update" or "uninstall".`
|
|
492
542
|
);
|
|
493
543
|
}
|
|
494
544
|
return install(flags, code);
|
|
495
545
|
}
|
|
546
|
+
function chosenEndpoint() {
|
|
547
|
+
return process.env[CONNECT_URL_ENV]?.trim() || CONNECT_DEFAULT_MCP_URL;
|
|
548
|
+
}
|
|
549
|
+
async function start(flags, code) {
|
|
550
|
+
const endpoint = chosenEndpoint();
|
|
551
|
+
if (code === "") {
|
|
552
|
+
stop(
|
|
553
|
+
`${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.`
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
const existing = existingConnections(endpoint);
|
|
557
|
+
if (!code && existing.length > 0 && !flags.has("--new")) {
|
|
558
|
+
stop(
|
|
559
|
+
`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.`
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
const token = await startTeam({ endpoint, code });
|
|
563
|
+
await connectWithToken(flags, token, endpoint);
|
|
564
|
+
if (existing.length === 0) {
|
|
565
|
+
say(
|
|
566
|
+
"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."
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
function existingConnections(endpoint) {
|
|
571
|
+
const found = [];
|
|
572
|
+
let cursorText = null;
|
|
573
|
+
try {
|
|
574
|
+
cursorText = readFileSync(join(homedir(), ".cursor", "mcp.json"), "utf8");
|
|
575
|
+
} catch {
|
|
576
|
+
cursorText = null;
|
|
577
|
+
}
|
|
578
|
+
const cursor = cursorConnection(cursorText);
|
|
579
|
+
if (cursor) found.push(cursor);
|
|
580
|
+
const claude = claudeRegistration().connection;
|
|
581
|
+
if (claude) found.push(claude);
|
|
582
|
+
let codexText = "";
|
|
583
|
+
try {
|
|
584
|
+
codexText = readFileSync(join(homedir(), ".codex", "config.toml"), "utf8");
|
|
585
|
+
} catch {
|
|
586
|
+
codexText = "";
|
|
587
|
+
}
|
|
588
|
+
const codex = codexConnection(codexText);
|
|
589
|
+
if (codex) found.push(codex);
|
|
590
|
+
return found.filter((connection) => connection.endpoint === endpoint);
|
|
591
|
+
}
|
|
496
592
|
async function install(flags, code) {
|
|
497
|
-
const endpoint =
|
|
593
|
+
const endpoint = chosenEndpoint();
|
|
498
594
|
if (code === "") {
|
|
499
595
|
stop(
|
|
500
596
|
`${CONNECT_CODE_FLAG} needs the setup code after it. Copy the whole command from Superhands and run it unchanged.`
|
|
@@ -503,9 +599,12 @@ async function install(flags, code) {
|
|
|
503
599
|
const token = code ? await exchangeSetupCode({ endpoint, code }) : process.env[CONNECT_TOKEN_ENV]?.trim();
|
|
504
600
|
if (!token) {
|
|
505
601
|
stop(
|
|
506
|
-
`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.`
|
|
602
|
+
`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.`
|
|
507
603
|
);
|
|
508
604
|
}
|
|
605
|
+
return connectWithToken(flags, token, endpoint);
|
|
606
|
+
}
|
|
607
|
+
async function connectWithToken(flags, token, endpoint) {
|
|
509
608
|
const wanted = chosenClients(flags);
|
|
510
609
|
let connected = 0;
|
|
511
610
|
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.20",
|
|
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
|
},
|