worktrust 0.6.2 → 0.6.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "worktrust",
3
- "version": "0.6.2",
3
+ "version": "0.6.4",
4
4
  "description": "Couple this computer to WorkTrust: approve a code in your browser, and every AI app here reports what you built. Metadata only, no dependencies.",
5
5
  "type": "module",
6
6
  "bin": {
package/setup-mcp.mjs CHANGED
@@ -111,7 +111,8 @@ for (const client of CLIENTS) {
111
111
  continue;
112
112
  }
113
113
  const text = existsSync(client.file) ? readFileSync(client.file, "utf8") : "";
114
- const already = text.includes(URL_) || (BRIDGE !== undefined && text.includes(JSON.stringify(BRIDGE).slice(1, -1))) || (REMOVE && /worktrust\.mjs/.test(text));
114
+ // With the bridge, coupled means the bridge is in AND no entry with a key in it is left beside it.
115
+ const already = REMOVE ? text.includes(URL_) || /worktrust\.mjs/.test(text) : BRIDGE !== undefined ? text.includes(JSON.stringify(BRIDGE).slice(1, -1)) && !text.includes(URL_) : text.includes(URL_);
115
116
  // THE WAY OUT, ON THE MACHINE. Uncoupling in the app ends the TOKEN; the config that names the
116
117
  // door stays behind on every client here, so the next person to read this file believes the
117
118
  // machine is coupled. `--remove` takes the entry out; the account side is a separate act and
@@ -132,7 +133,10 @@ for (const client of CLIENTS) {
132
133
  } else if (client.shape === "toml") {
133
134
  const text = readFileSync(client.file, "utf8");
134
135
  // The block and nothing after it: from its own header to the next one, or the end.
135
- const stripped = text.replace(new RegExp(`\\n?\\[mcp_servers\\.${NAME}\\][^\\[]*`, "g"), "\n");
136
+ // Every block that names the door, under any name (a hand-made one too, 2026-10-04), or ours.
137
+ // A block runs to the next table header at the START of a line, never to the next "[" — the
138
+ // bridge's own `args = ["…", "mcp"]` holds one, and cutting there left half a block behind.
139
+ const stripped = text.replace(/\n?\[mcp_servers\.("?)([A-Za-z0-9_.-]+)\1\][\s\S]*?(?=\n\[|$)/g, (block, _q, name) => (name === NAME || block.includes(URL_) || /worktrust\.mjs/.test(block) ? "" : block));
136
140
  console.log(` − ${client.label}: ${WRITE ? "removed from" : "would remove from"} ${client.file}`);
137
141
  if (WRITE) writeFileSync(client.file, stripped);
138
142
  }
package/worktrust.mjs CHANGED
@@ -9,6 +9,7 @@
9
9
  * npx worktrust --device a computer without a browser (SSH): type a code on another device
10
10
  * npx worktrust connect couple again, also on a computer already coupled
11
11
  * npx worktrust history send this computer's earlier sessions as history (asks first)
12
+ * npx worktrust web connect claude.ai or ChatGPT on the web (copies the address, opens the page)
12
13
  * npx worktrust disconnect show what comes out, ask, take it out again
13
14
  * npx worktrust status what is coupled here
14
15
  *
@@ -401,6 +402,7 @@ async function connect() {
401
402
  const hookArgs = ["--install", ...(BUNDLED ? ["--counter", paths["count-behaviour.mjs"]] : []), ...(direct ? [] : ["--via", STABLE])];
402
403
  if ((await run(paths["log-session.mjs"], hookArgs, token)).code !== 0) fail("installing the session hook failed; the output above says where.");
403
404
  await offerHistory(paths, token, device ? device.privateKey.export({ format: "pem", type: "pkcs8" }) : null);
405
+ await offerWeb();
404
406
  say();
405
407
  say(" Done. Quit your AI apps completely and open them again. This computer appears in WorkTrust");
406
408
  say(" under Sources → Devices once one of them has spoken.");
@@ -426,6 +428,40 @@ async function offerHistory(paths, token, devicePem, url = MCP) {
426
428
  say(sent.code === 0 ? " ✓ Sent as history. It shows in WorkTrust as earlier work, never as verified hours." : " Not all of it arrived. Run npx worktrust history again: a line already received is kept once.");
427
429
  }
428
430
 
431
+ /**
432
+ * CLAUDE AND CHATGPT ON THE WEB, FROM HERE (owner, 2026-10-04). A web connector lives in the person's
433
+ * own account at Anthropic or OpenAI, and neither lets a program add one: the person adds it and
434
+ * approves it there. What this can do is the rest: copy the door's address, open the right page,
435
+ * say the three steps, and wait. Enter, --yes and a closed terminal mean No.
436
+ */
437
+ const WEB = [
438
+ { name: "Claude (claude.ai)", page: "https://claude.ai/settings/connectors", steps: ["Add custom connector", "Name: WorkTrust.io · URL: paste (it is on your clipboard) · leave the OAuth fields empty", "Connect, then approve on the WorkTrust page that opens"] },
439
+ { name: "ChatGPT (chatgpt.com)", page: "https://chatgpt.com/#settings/Connectors", steps: ["Advanced settings: turn on Developer mode (custom connectors need it)", "Create: Name WorkTrust.io · MCP server URL: paste (it is on your clipboard) · Authentication: OAuth", "Create, then approve on the WorkTrust page that opens"] },
440
+ ];
441
+ function copyToClipboard(text) {
442
+ const [cmd, cmdArgs] = OS === "macos" ? ["pbcopy", []] : OS === "windows" ? ["clip", []] : ["xclip", ["-selection", "clipboard"]];
443
+ return new Promise((resolve) => {
444
+ try {
445
+ const child = spawn(cmd, cmdArgs, { stdio: ["pipe", "ignore", "ignore"] });
446
+ child.on("error", () => resolve(false)).on("exit", (code) => resolve(code === 0));
447
+ child.stdin.end(text);
448
+ } catch { resolve(false); }
449
+ });
450
+ }
451
+ async function offerWeb() {
452
+ if (has("dry-run")) return;
453
+ say();
454
+ for (const web of WEB) {
455
+ if (!(await ask(`Also connect ${web.name} in your browser?`, false))) continue;
456
+ const copied = await copyToClipboard(MCP);
457
+ say(` ${copied ? "✓ Copied" : "The address is"} ${MCP}${copied ? " to your clipboard" : ""}`);
458
+ say(` Opening ${web.page}. There:`);
459
+ web.steps.forEach((step, i) => say(` ${i + 1}. ${step}`));
460
+ openBrowser(web.page);
461
+ if (process.stdin.isTTY) { const prompt = createInterface({ input: process.stdin, output: process.stdout }); await prompt.question(" Press Enter when it is connected (or to skip). "); prompt.close(); }
462
+ }
463
+ }
464
+
429
465
  async function history() {
430
466
  const key = await freshKey();
431
467
  if (!key) fail("this computer is not coupled through the key file. Run npx worktrust first.");
@@ -484,6 +520,7 @@ function help() {
484
520
  say(" --direct put the key in each app's settings instead of the key file");
485
521
  say(" worktrust connect couple again, also when this computer is already coupled");
486
522
  say(" worktrust history send this computer's earlier sessions as history (asks first)");
523
+ say(" worktrust web connect Claude or ChatGPT on the web: copies the address, opens the page");
487
524
  say(" worktrust disconnect take WorkTrust out of every AI app here (asks first)");
488
525
  say(" worktrust status what is coupled here");
489
526
  }
@@ -496,6 +533,7 @@ else if (command === "mcp") await bridge();
496
533
  else if (command === "hook") await hook();
497
534
  else if (command === "connect") await connect();
498
535
  else if (command === "history") await history();
536
+ else if (command === "web") await offerWeb();
499
537
  else if (command === "disconnect") await disconnect();
500
538
  else if (command === "status") status();
501
539
  else help();