@super-hands/connect 0.1.18 → 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.
Files changed (3) hide show
  1. package/README.md +27 -0
  2. package/client.mjs +89 -3
  3. 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
@@ -332,6 +332,12 @@ function connectReportUrl(endpoint) {
332
332
  function connectExchangeUrl(endpoint) {
333
333
  return `${connectReportUrl(endpoint)}/exchange`;
334
334
  }
335
+ function connectStartUrl(endpoint) {
336
+ return `${connectReportUrl(endpoint)}/start`;
337
+ }
338
+ function appOrigin(endpoint) {
339
+ return endpoint.replace(/\/api\/mcp\/?$/, "") || endpoint;
340
+ }
335
341
  function takeCodeArgument(argv) {
336
342
  const rest = [];
337
343
  let code = null;
@@ -382,6 +388,36 @@ async function exchangeSetupCode(args) {
382
388
  `Superhands answered ${response.status} to the setup code. Try again in a moment, or get a fresh command from Superhands.`
383
389
  );
384
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
+ }
385
421
  async function reportSkillVersion(args) {
386
422
  try {
387
423
  await fetch(connectReportUrl(args.endpoint), {
@@ -486,15 +522,62 @@ async function main() {
486
522
  const command = argv.find((arg) => !arg.startsWith("--")) ?? "install";
487
523
  if (command === "uninstall") return uninstall(flags);
488
524
  if (command === "update") return update(flags);
525
+ if (command === "start") return start(flags, code);
489
526
  if (command !== "install") {
490
527
  stop(
491
- `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".`
492
529
  );
493
530
  }
494
531
  return install(flags, code);
495
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);
578
+ }
496
579
  async function install(flags, code) {
497
- const endpoint = process.env[CONNECT_URL_ENV]?.trim() || CONNECT_DEFAULT_MCP_URL;
580
+ const endpoint = chosenEndpoint();
498
581
  if (code === "") {
499
582
  stop(
500
583
  `${CONNECT_CODE_FLAG} needs the setup code after it. Copy the whole command from Superhands and run it unchanged.`
@@ -503,9 +586,12 @@ async function install(flags, code) {
503
586
  const token = code ? await exchangeSetupCode({ endpoint, code }) : process.env[CONNECT_TOKEN_ENV]?.trim();
504
587
  if (!token) {
505
588
  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.`
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.`
507
590
  );
508
591
  }
592
+ return connectWithToken(flags, token, endpoint);
593
+ }
594
+ async function connectWithToken(flags, token, endpoint) {
509
595
  const wanted = chosenClients(flags);
510
596
  let connected = 0;
511
597
  let attempted = 0;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@super-hands/connect",
3
- "version": "0.1.18",
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.",
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
  },