@super-hands/connect 0.1.9 → 0.1.14

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 +22 -2
  2. package/client.mjs +184 -30
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -27,7 +27,27 @@ variable too.
27
27
  carries no credential.
28
28
 
29
29
  With no flags it connects every client it finds; `--cursor`, `--claude` or
30
- `--codex` narrow it to one.
30
+ `--codex` narrow it to one. Re-running it later to pick up a newer skill needs
31
+ no credential — see `update`, below.
32
+
33
+ ## Updating it
34
+
35
+ ```
36
+ npx -y @super-hands/connect@latest update
37
+ ```
38
+
39
+ Brings the skill on an already-connected machine up to whatever this build
40
+ ships. It takes **no token**: it reads the endpoint and the credential back out
41
+ of the configs the install wrote, so there is nothing to fetch from Superhands
42
+ and nothing new is minted. It touches no MCP entry — the entry is how the
43
+ credential was found, so it is already right.
44
+
45
+ A client counts as connected when its own config names the `superhands` server;
46
+ anything else is skipped, and a machine with no Superhands entry at all is told
47
+ to install first rather than half-connected. An installed skill at the same
48
+ version or newer is left alone, so your own edits survive an update and the run
49
+ says which files it left as they were. The same `--cursor` / `--claude` /
50
+ `--codex` flags narrow it to one client.
31
51
 
32
52
  ## Removing it
33
53
 
@@ -57,6 +77,6 @@ into the client configs above. The token can be revoked at any time from the
57
77
  Superhands MCP page.
58
78
 
59
79
  This file is generated from
60
- [`lib/connect-client-entry.ts`](https://github.com/superhandsai/superhandsmcp/blob/main/lib/connect-client-entry.ts)
80
+ [`lib/connect-client-entry.ts`](https://github.com/superhandsai/superhands/blob/main/lib/connect-client-entry.ts)
61
81
  in the Superhands repository — the published bytes are that commit's, and are
62
82
  not minified.
package/client.mjs CHANGED
@@ -24,6 +24,7 @@ var CONNECT_URL_ENV = "SUPERHANDS_MCP_URL";
24
24
  var CONNECT_DEFAULT_MCP_URL = "https://app.superhands.ai/api/mcp";
25
25
  var CONNECT_CLIENT_PACKAGE = "@super-hands/connect";
26
26
  var CONNECT_CLIENT_SPEC = `${CONNECT_CLIENT_PACKAGE}@latest`;
27
+ var CONNECT_UPDATE_COMMAND = `npx -y ${CONNECT_CLIENT_SPEC} update`;
27
28
  var MCP_SERVER_KEY = "superhands";
28
29
  function mcpAuthorizationHeader(token) {
29
30
  return `Bearer ${token}`;
@@ -40,11 +41,11 @@ function codexConfigBlock(args) {
40
41
  }
41
42
 
42
43
  // lib/connect-skill.ts
43
- var CONNECT_SKILL_VERSION = 4;
44
+ var CONNECT_SKILL_VERSION = 9;
44
45
  var CONNECT_SKILL_DIR = MCP_SERVER_KEY;
45
46
  var CONNECT_SKILL_FILENAME = "SKILL.md";
46
47
  var VERSION_MARKER = `[//]: # (superhands-skill-version: ${CONNECT_SKILL_VERSION})`;
47
- var CONNECT_SKILL_DESCRIPTION = `Build UI the way this team has decided it should be built. Use BEFORE building, changing, or restyling any page, screen, view, form, or component \u2014 "build a page", "add a screen", "create a form", "make a dashboard", "redesign this", "new landing page" \u2014 and when asked how this product should look or behave. Reads the team's written product decisions from their Superhands MCP server.`;
48
+ var CONNECT_SKILL_DESCRIPTION = `Build UI the way this team has decided it should be built. Use BEFORE building, changing, or restyling any page, screen, view, form, or component \u2014 "build a page", "add a screen", "create a form", "make a dashboard", "redesign this", "new landing page" \u2014 and when asked how this product should look or behave. Reads the team's Standards and Playbooks from their Superhands MCP server.`;
48
49
  var CONNECT_SKILL_CONTENT = `---
49
50
  name: ${CONNECT_SKILL_DIR}
50
51
  description: ${CONNECT_SKILL_DESCRIPTION}
@@ -55,35 +56,91 @@ ${VERSION_MARKER}
55
56
  # Superhands
56
57
 
57
58
  This machine is connected to the team's Superhands server over MCP (server
58
- key \`${MCP_SERVER_KEY}\`). It holds the decisions the team has written down
59
- about how their product should look and behave \u2014 the same decisions their
60
- engineers' pull requests are reviewed against.
59
+ key \`${MCP_SERVER_KEY}\`). It holds the team's **Instructions** in two kinds:
60
+ their **Standards**, the decisions they have written about how their product
61
+ should look and behave \u2014 the same decisions their engineers' pull requests are
62
+ reviewed against \u2014 and their **Playbooks**, procedures they imported for their
63
+ agents to follow.
61
64
 
62
65
  Before you build, change, or restyle any UI:
63
66
 
64
- 1. Call the \`superhands_get_guidance\` MCP tool with \`intent\` set to what
67
+ 1. Call the \`superhands_get_instructions\` MCP tool with \`intent\` set to what
65
68
  you have been asked to build, in plain language \u2014 for example "a sign-in
66
69
  page with email and password and an error state", and
67
70
  \`skill_version: ${CONNECT_SKILL_VERSION}\` \u2014 the version of this file, so
68
- the server can tell you when this file is out of date. The decisions that
69
- govern that work come back in full, ahead of the rest.
70
- 2. Build to what comes back. Where a decision names a case it deliberately
71
- does not govern, your own judgment resumes there.
72
- 3. When you have finished \u2014 not part-way through \u2014 send the files you wrote or
71
+ the server can tell you when this file is out of date. Both sections come
72
+ back narrowed to that work, with everything not chosen still named.
73
+ - Pass \`paths\` too, when you already know which files this work touches \u2014
74
+ the handful you have open or are about to edit, repository-relative, for
75
+ example \`["app/marketing/pricing.tsx"]\`. The team writes down where each
76
+ Standard applies, and a path answers that far better than a description
77
+ does. Leave it out if you do not know yet; it narrows nothing away.
78
+ - If that tool is not available on this server, it is an older deployment:
79
+ call \`superhands_get_guidance\` and \`superhands_get_skill\` instead,
80
+ with the same \`intent\` \u2014 those two are older and take no \`paths\`.
81
+ 2. Plan against what comes back, and note which parts of your plan each set of
82
+ keys covers. The Playbooks section renders what to read first, then names
83
+ under **Reachable** the modules judged to matter to parts you have not
84
+ started \u2014 those keys are what the next step asks with \u2014 and ends with an
85
+ index of the modules it did **not** choose, for anything the selection
86
+ missed. A part of your plan with no keys against it is a real gap in what
87
+ the team has written down; say so rather than quietly using your own
88
+ defaults.
89
+ 3. As you start each part \u2014 the sidebar, then the table, then the form \u2014 call
90
+ \`superhands_get_playbook_modules\` with the reachable keys covering the
91
+ part you are starting now. A key ending in \`*\` takes everything under
92
+ it, so \`better-ui/*\` is a whole skill and \`better-ui/SKILL#*\` is one
93
+ file. It runs no selection and makes no model call, so it is a plain, fast
94
+ lookup and it returns exactly what you named.
95
+ - Fetch each part's modules **when you reach that part**, not all of them at
96
+ the start. These are instructions to follow, not facts to know, so modules
97
+ for a part you are not building are a page of directions about the wrong
98
+ thing sitting in your context while you work.
99
+ - The Standards from step 1 need no such loop. They are a short list of
100
+ constraints on the whole task, so one read at the start is the right read.
101
+ - If that tool is not available on this server, it is an older deployment:
102
+ build from what step 1 already returned and skip this step. Do not ask
103
+ for the whole library instead \u2014 that is the wall of text step 1 avoided.
104
+ 4. Build to what comes back. Apply both sections. Where imported Playbook text
105
+ directly contradicts a Standard, the Standard governs. Where a Standard
106
+ names a case it deliberately does not govern, your own judgment resumes
107
+ there.
108
+ 5. When you have finished \u2014 not part-way through \u2014 send the files you wrote or
73
109
  changed with \`superhands_report_completed_build\`, naming the \`route\` if
74
110
  you know it. The team looks at what their agents build there; it records
75
111
  and does not review, so a failed report costs the work nothing.
76
- 4. When you finish, say you followed the team's guidance \u2014 never that your
77
- work passes it or was checked. Reading guidance is not a review, reporting
78
- a build is not a review, and nothing has looked at what you built.
112
+ 6. When you finish, say you followed the team's guidance \u2014 never that your
113
+ work passes it or was checked. Reading Instructions is not a review,
114
+ reporting a build is not a review, and nothing has looked at what you built.
79
115
 
80
- If the tool answers \`no-guidance\`, carry on with what you were asked for and
81
- mention that the team writes its guidance on the Superhands Guidance page.
116
+ If the tool answers \`no-instructions\` (or \`no-guidance\` from an older
117
+ server), carry on with what you were asked for and mention that the team
118
+ writes its Standards and imports its Playbooks on the Superhands Instructions
119
+ page.
82
120
 
83
121
  If the \`${MCP_SERVER_KEY}\` MCP server is not reachable in this session, say
84
122
  so rather than guessing at the team's decisions \u2014 it is configured on this
85
123
  machine, so a missing connection is worth telling the person about.
86
124
 
125
+ ## Updating Superhands on this machine
126
+
127
+ If the person asks to update or upgrade Superhands, or if a Superhands tool
128
+ tells you this file is out of date, run:
129
+
130
+ \`\`\`
131
+ ${CONNECT_UPDATE_COMMAND}
132
+ \`\`\`
133
+
134
+ and tell them what it printed. It reuses the credential already configured on
135
+ this machine, so it needs no token and mints nothing \u2014 it only brings this file
136
+ up to whatever the team's server now expects. Add \`--claude\`, \`--cursor\` or
137
+ \`--codex\` to update one client and leave the rest.
138
+
139
+ Run it when you are asked, or when you have finished the work in front of you \u2014
140
+ never in the middle of a build. The guidance you were given came from the
141
+ server, not from this file, so it is current either way and there is nothing to
142
+ redo.
143
+
87
144
  ## Removing Superhands from this machine
88
145
 
89
146
  If the person asks to uninstall, remove, or disconnect Superhands, run:
@@ -171,6 +228,21 @@ function cursorConfigWithoutServer(existing) {
171
228
  return { text: `${JSON.stringify(config, null, 2)}
172
229
  `, removed: true };
173
230
  }
231
+ function cursorNamesServer(existing) {
232
+ if (!existing || existing.trim() === "") return false;
233
+ let parsed;
234
+ try {
235
+ parsed = JSON.parse(existing);
236
+ } catch {
237
+ return false;
238
+ }
239
+ const servers = parsed?.mcpServers;
240
+ if (typeof servers !== "object" || servers === null || Array.isArray(servers)) return false;
241
+ return MCP_SERVER_KEY in servers;
242
+ }
243
+ function codexNamesServer(existing) {
244
+ return existing.includes(`[mcp_servers.${MCP_SERVER_KEY}]`);
245
+ }
174
246
  function cursorConnection(existing) {
175
247
  if (!existing || existing.trim() === "") return null;
176
248
  let parsed;
@@ -220,24 +292,30 @@ function appendedCodexConfig(existing, args) {
220
292
  return { text: `${existing}${sep}${codexConfigBlock(args)}
221
293
  `, alreadyPresent: false, repaired: false };
222
294
  }
223
- function writeSkill(clientDir) {
224
- const skillPath = join(clientDir, "skills", CONNECT_SKILL_DIR, CONNECT_SKILL_FILENAME);
225
- let existing = "";
295
+ function skillPathIn(clientDir) {
296
+ return join(clientDir, "skills", CONNECT_SKILL_DIR, CONNECT_SKILL_FILENAME);
297
+ }
298
+ function installedSkillVersionIn(clientDir) {
226
299
  try {
227
- existing = readFileSync(skillPath, "utf8");
300
+ return installedSkillVersion(readFileSync(skillPathIn(clientDir), "utf8"));
228
301
  } catch {
229
- existing = "";
302
+ return 0;
303
+ }
304
+ }
305
+ function writeSkill(clientDir) {
306
+ const skillPath = skillPathIn(clientDir);
307
+ const existingVersion = installedSkillVersionIn(clientDir);
308
+ if (existingVersion >= CONNECT_SKILL_VERSION) {
309
+ return { version: existingVersion, state: "current" };
230
310
  }
231
- const existingVersion = existing === "" ? 0 : installedSkillVersion(existing);
232
- if (existingVersion >= CONNECT_SKILL_VERSION) return existingVersion;
233
311
  try {
234
312
  mkdirSync(dirname(skillPath), { recursive: true });
235
313
  writeFileSync(skillPath, CONNECT_SKILL_CONTENT);
236
314
  say(` Wrote the ${CONNECT_SKILL_DIR} skill to ${skillPath} \u2014 it routes UI work through the team's guidance.`);
237
- return CONNECT_SKILL_VERSION;
315
+ return { version: CONNECT_SKILL_VERSION, state: "wrote" };
238
316
  } catch {
239
317
  say(` Could not write the ${CONNECT_SKILL_DIR} skill at ${skillPath}. The connection works without it.`);
240
- return existingVersion;
318
+ return { version: existingVersion, state: "blocked" };
241
319
  }
242
320
  }
243
321
  function connectReportUrl(endpoint) {
@@ -310,6 +388,11 @@ function claudeConnection() {
310
388
  return null;
311
389
  }
312
390
  }
391
+ function claudeRegistration() {
392
+ const result = spawnSync("claude", ["mcp", "get", MCP_SERVER_KEY], { encoding: "utf8" });
393
+ if (result.error || result.status !== 0) return { registered: false, connection: null };
394
+ return { registered: true, connection: claudeConnectionFrom(result.stdout ?? "") };
395
+ }
313
396
  function hasCli(bin) {
314
397
  try {
315
398
  execFileSync(bin, ["--version"], { stdio: "ignore" });
@@ -341,8 +424,11 @@ async function main() {
341
424
  const flags = new Set(argv.filter((arg) => arg.startsWith("--")));
342
425
  const command = argv.find((arg) => !arg.startsWith("--")) ?? "install";
343
426
  if (command === "uninstall") return uninstall(flags);
427
+ if (command === "update") return update(flags);
344
428
  if (command !== "install") {
345
- stop(`unknown command "${command}". This tool takes "install" (the default) or "uninstall".`);
429
+ stop(
430
+ `unknown command "${command}". This tool takes "install" (the default), "update" or "uninstall".`
431
+ );
346
432
  }
347
433
  return install(flags);
348
434
  }
@@ -389,7 +475,7 @@ async function install(flags) {
389
475
  } else {
390
476
  say(` Open Cursor (restart it if it was running), then enable ${MCP_SERVER_KEY} under Settings \u2192 MCP.`);
391
477
  }
392
- skillVersions.push(writeSkill(cursorDir));
478
+ skillVersions.push(writeSkill(cursorDir).version);
393
479
  connected += 1;
394
480
  } catch {
395
481
  say(`Cursor \u2014 ${configPath} could not be written, so it was left as it was.`);
@@ -430,7 +516,7 @@ async function install(flags) {
430
516
  );
431
517
  say(`Claude Code \u2014 added the ${MCP_SERVER_KEY} server (user scope).`);
432
518
  say(" Open a new claude session and it connects on start.");
433
- skillVersions.push(writeSkill(join(homedir(), ".claude")));
519
+ skillVersions.push(writeSkill(join(homedir(), ".claude")).version);
434
520
  connected += 1;
435
521
  } catch {
436
522
  say("Claude Code \u2014 `claude mcp add` failed. Run it by hand from the Superhands MCP page.");
@@ -450,7 +536,7 @@ async function install(flags) {
450
536
  if (result.alreadyPresent) {
451
537
  say(`Codex \u2014 ${configPath} already names a ${MCP_SERVER_KEY} server, so it was left as it is.`);
452
538
  say(" If that connection is stale, update the http_headers Authorization value there by hand.");
453
- skillVersions.push(writeSkill(codexDir));
539
+ skillVersions.push(writeSkill(codexDir).version);
454
540
  connected += 1;
455
541
  } else {
456
542
  try {
@@ -460,7 +546,7 @@ async function install(flags) {
460
546
  result.repaired ? `Codex \u2014 replaced the ${MCP_SERVER_KEY} entry in ${configPath}: its old bearer_token spelling makes current Codex reject the whole config.` : `Codex \u2014 added the ${MCP_SERVER_KEY} server to ${configPath}.`
461
547
  );
462
548
  say(" The app and the CLI both read this config \u2014 open either and it connects when a session starts.");
463
- skillVersions.push(writeSkill(codexDir));
549
+ skillVersions.push(writeSkill(codexDir).version);
464
550
  connected += 1;
465
551
  } catch {
466
552
  say(`Codex \u2014 ${configPath} could not be written, so it was left as it was.`);
@@ -483,6 +569,74 @@ async function install(flags) {
483
569
  }
484
570
  say("Done. Superhands notices the moment an agent connects \u2014 setup ticks by itself.");
485
571
  }
572
+ async function update(flags) {
573
+ const wanted = chosenClients(flags);
574
+ const cursorDir = join(homedir(), ".cursor");
575
+ const claudeDir = join(homedir(), ".claude");
576
+ const codexDir = join(homedir(), ".codex");
577
+ const found = [];
578
+ const skillVersions = [];
579
+ let configured = 0;
580
+ let rewritten = 0;
581
+ function refresh(product, clientDir, connection) {
582
+ configured += 1;
583
+ if (connection) found.push(connection);
584
+ const before = installedSkillVersionIn(clientDir);
585
+ if (before >= CONNECT_SKILL_VERSION) {
586
+ say(
587
+ `${product} \u2014 its ${CONNECT_SKILL_DIR} skill is already at version ${before}, so it was left as it is.`
588
+ );
589
+ } else if (before === 0) {
590
+ say(
591
+ `${product} \u2014 connected, but has no ${CONNECT_SKILL_DIR} skill; writing version ${CONNECT_SKILL_VERSION}.`
592
+ );
593
+ } else {
594
+ say(`${product} \u2014 updating its ${CONNECT_SKILL_DIR} skill from version ${before} to ${CONNECT_SKILL_VERSION}.`);
595
+ }
596
+ const result = writeSkill(clientDir);
597
+ skillVersions.push(result.version);
598
+ if (result.state === "wrote") rewritten += 1;
599
+ }
600
+ if (wanted.cursor) {
601
+ let text = null;
602
+ try {
603
+ text = readFileSync(join(cursorDir, "mcp.json"), "utf8");
604
+ } catch {
605
+ text = null;
606
+ }
607
+ if (cursorNamesServer(text)) refresh("Cursor", cursorDir, cursorConnection(text));
608
+ }
609
+ if (wanted.claude) {
610
+ const claude = claudeRegistration();
611
+ if (claude.registered) refresh("Claude Code", claudeDir, claude.connection);
612
+ }
613
+ if (wanted.codex) {
614
+ let text = "";
615
+ try {
616
+ text = readFileSync(join(codexDir, "config.toml"), "utf8");
617
+ } catch {
618
+ text = "";
619
+ }
620
+ if (codexNamesServer(text)) refresh("Codex", codexDir, codexConnection(text));
621
+ }
622
+ if (configured === 0) {
623
+ say(
624
+ wanted.explicit ? "Nothing to update \u2014 none of the clients you named has a Superhands server configured." : "Nothing to update \u2014 no client on this machine has a Superhands server configured."
625
+ );
626
+ say(" Connect one first with the command on the Superhands Agents page; it carries the credential.");
627
+ return;
628
+ }
629
+ const installed = skillVersions.length > 0 ? Math.min(...skillVersions) : 0;
630
+ if (installed > 0) {
631
+ const unique = new Map(found.map((c) => [`${c.endpoint}\0${c.token}`, c]));
632
+ for (const connection of unique.values()) {
633
+ await reportSkillVersion({ ...connection, version: installed });
634
+ }
635
+ }
636
+ say(
637
+ rewritten > 0 ? "Done. Open a new session in any client that was already running \u2014 a skill is read when a session starts." : "Done. Everything on this machine was already up to date."
638
+ );
639
+ }
486
640
  async function uninstall(flags) {
487
641
  const wanted = chosenClients(flags);
488
642
  const cursorDir = join(homedir(), ".cursor");
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@super-hands/connect",
3
- "version": "0.1.9",
4
- "description": "Connect the coding agents on this machine to your team's Superhands MCP server, and take it back off again with `uninstall`. Writes each client's own config; reads no repository, uploads nothing.",
3
+ "version": "0.1.14",
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"
7
7
  },
@@ -16,7 +16,7 @@
16
16
  "license": "MIT",
17
17
  "repository": {
18
18
  "type": "git",
19
- "url": "git+https://github.com/superhandsai/superhandsmcp.git",
19
+ "url": "git+https://github.com/superhandsai/superhands.git",
20
20
  "directory": "packages/connect-client"
21
21
  },
22
22
  "homepage": "https://app.superhands.ai",