@super-hands/connect 0.1.17 → 0.1.18

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.
Files changed (3) hide show
  1. package/README.md +15 -6
  2. package/client.mjs +64 -5
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -3,12 +3,20 @@
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 setup generates the command for you, credential included:
6
+ Superhands generates the command for you, with a short-lived setup code on
7
+ the end of it:
7
8
 
8
9
  ```
9
- SUPERHANDS_MCP_TOKEN="…" npx -y @super-hands/connect@latest
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.
@@ -71,10 +79,11 @@ remove one client and leave the rest.
71
79
 
72
80
  ## What it does not do
73
81
 
74
- It reads no repository, runs none of your code, and uploads nothing. The one
75
- secret it holds is the token in its own environment, and it is written only
76
- into the client configs above. The token can be revoked at any time from the
77
- Superhands MCP page.
82
+ It reads no repository, runs none of your code, and uploads nothing but the
83
+ setup code it was given, once, to the deployment that issued it. The one
84
+ secret it then holds is the token that came back, and it is written only into
85
+ the client configs above. The token can be revoked at any time from the
86
+ Superhands Agents page.
78
87
 
79
88
  This file is generated from
80
89
  [`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,59 @@ 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 takeCodeArgument(argv) {
336
+ const rest = [];
337
+ let code = null;
338
+ for (let i = 0; i < argv.length; i += 1) {
339
+ const arg = argv[i];
340
+ if (arg === CONNECT_CODE_FLAG) {
341
+ const next = argv[i + 1];
342
+ if (next !== void 0 && !next.startsWith("--")) {
343
+ code = next;
344
+ i += 1;
345
+ } else {
346
+ code = "";
347
+ }
348
+ continue;
349
+ }
350
+ if (arg.startsWith(`${CONNECT_CODE_FLAG}=`)) {
351
+ code = arg.slice(CONNECT_CODE_FLAG.length + 1);
352
+ continue;
353
+ }
354
+ rest.push(arg);
355
+ }
356
+ return { code, rest };
357
+ }
358
+ async function exchangeSetupCode(args) {
359
+ let response;
360
+ try {
361
+ response = await fetch(connectExchangeUrl(args.endpoint), {
362
+ method: "POST",
363
+ headers: { "content-type": "application/json" },
364
+ body: JSON.stringify({ code: args.code }),
365
+ signal: AbortSignal.timeout(1e4)
366
+ });
367
+ } catch {
368
+ return stop(
369
+ `Superhands at ${args.endpoint} could not be reached to turn the setup code into a credential. Check the connection and run this command again.`
370
+ );
371
+ }
372
+ const body = await response.json().catch(() => null);
373
+ if (response.ok && body?.ok && typeof body.token === "string" && body.token) {
374
+ return body.token;
375
+ }
376
+ if (response.status === 401 || response.status === 400) {
377
+ return stop(
378
+ "this setup code has expired or has been used up. Get a fresh command from Superhands and run it unchanged."
379
+ );
380
+ }
381
+ return stop(
382
+ `Superhands answered ${response.status} to the setup code. Try again in a moment, or get a fresh command from Superhands.`
383
+ );
384
+ }
331
385
  async function reportSkillVersion(args) {
332
386
  try {
333
387
  await fetch(connectReportUrl(args.endpoint), {
@@ -427,7 +481,7 @@ function chosenClients(flags) {
427
481
  return explicit ? { cursor, claude, codex, explicit } : { cursor: true, claude: true, codex: true, explicit };
428
482
  }
429
483
  async function main() {
430
- const argv = process.argv.slice(2);
484
+ const { code, rest: argv } = takeCodeArgument(process.argv.slice(2));
431
485
  const flags = new Set(argv.filter((arg) => arg.startsWith("--")));
432
486
  const command = argv.find((arg) => !arg.startsWith("--")) ?? "install";
433
487
  if (command === "uninstall") return uninstall(flags);
@@ -437,14 +491,19 @@ async function main() {
437
491
  `unknown command "${command}". This tool takes "install" (the default), "update" or "uninstall".`
438
492
  );
439
493
  }
440
- return install(flags);
494
+ return install(flags, code);
441
495
  }
442
- async function install(flags) {
443
- const token = process.env[CONNECT_TOKEN_ENV]?.trim();
496
+ async function install(flags, code) {
444
497
  const endpoint = process.env[CONNECT_URL_ENV]?.trim() || CONNECT_DEFAULT_MCP_URL;
498
+ if (code === "") {
499
+ stop(
500
+ `${CONNECT_CODE_FLAG} needs the setup code after it. Copy the whole command from Superhands and run it unchanged.`
501
+ );
502
+ }
503
+ const token = code ? await exchangeSetupCode({ endpoint, code }) : process.env[CONNECT_TOKEN_ENV]?.trim();
445
504
  if (!token) {
446
505
  stop(
447
- `this command needs ${CONNECT_TOKEN_ENV} set on the same line. Copy the whole command from Superhands setup and run it unchanged.`
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.`
448
507
  );
449
508
  }
450
509
  const wanted = chosenClients(flags);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@super-hands/connect",
3
- "version": "0.1.17",
3
+ "version": "0.1.18",
4
4
  "description": "Connect the coding agents on this machine to your team's Superhands MCP server, 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"