clearotron 0.3.0-beta.3 → 0.3.0-beta.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/INSTALL.md +13 -6
- package/bin/connect.mjs +68 -25
- package/bin/disconnect.mjs +16 -11
- package/build-info.json +2 -2
- package/driver/CHANGELOG.md +16 -0
- package/driver/package.json +1 -1
- package/driver/suite-census.json +6 -6
- package/mcp-server/CHANGELOG.md +4 -0
- package/mcp-server/package.json +1 -1
- package/package.json +1 -1
- package/portal-ui/dist/assets/{index-CsCuPshD.css → index-Cv-E_agg.css} +199 -86
- package/portal-ui/dist/assets/{index-C1gpdkyH.js → index-DWYCsOCJ.js} +500 -375
- package/portal-ui/dist/index.html +2 -2
- package/portal-ui/package.json +1 -1
- package/providers/oauth-mcp-bridge/CHANGELOG.md +4 -0
- package/providers/oauth-mcp-bridge/package.json +1 -1
- package/shared/connect-clients.mjs +282 -321
- package/shared/names-in-force.mjs +1 -0
- package/shared/stdio-connect.mjs +63 -0
package/INSTALL.md
CHANGED
|
@@ -922,12 +922,19 @@ their own integration work.
|
|
|
922
922
|
**Two shapes, and the line between them is what your assistant can do — not where it runs.** Ruling
|
|
923
923
|
2026-09-03, on the vendor's own documented behaviour:
|
|
924
924
|
|
|
925
|
-
1. **
|
|
926
|
-
Codex
|
|
927
|
-
network, no ingress. `npx clearotron start` prints the one line to
|
|
928
|
-
connect` hands it over per assistant.
|
|
929
|
-
2. **
|
|
930
|
-
|
|
925
|
+
1. **Clearotron is installed on the machine the assistant runs on** — Claude's desktop app, Claude Code,
|
|
926
|
+
Codex, the ChatGPT desktop app, an agent that runs commands. The assistant spawns the server over
|
|
927
|
+
stdio. No address, no key, no network, no ingress. `npx clearotron start` prints the one line to
|
|
928
|
+
paste, and `npx clearotron connect --where here` hands it over per assistant.
|
|
929
|
+
2. **Clearotron is running somewhere else** — a server, a cloud machine, anywhere the assistant is
|
|
930
|
+
not. These need **a publicly reachable HTTPS address**, plus a key made for the person connecting.
|
|
931
|
+
Always. Claude (app, web, Cowork and mobile), ChatGPT on the web, Perplexity and other agents only
|
|
932
|
+
ever connect this way; Claude Code and Codex can connect either way. `npx clearotron connect
|
|
933
|
+
--where elsewhere` makes the key and prints the steps for the assistant you name.
|
|
934
|
+
|
|
935
|
+
Without `--where`, `connect` asks when both answers are possible, and `--client <name>` on its own keeps
|
|
936
|
+
the answer that assistant had before: a key for Claude, the local line for Claude Code and Codex.
|
|
937
|
+
`npx clearotron disconnect` takes the same `--where`.
|
|
931
938
|
|
|
932
939
|
**A loopback address is never an answer for shape 2, and that is not about your network.** A remote MCP
|
|
933
940
|
connector is reached **from the vendor's cloud**, never from the reader's device. Anthropic's own help
|
package/bin/connect.mjs
CHANGED
|
@@ -45,7 +45,7 @@ import { homedir, userInfo } from "node:os";
|
|
|
45
45
|
import { fileURLToPath } from "node:url";
|
|
46
46
|
import { execFileSync } from "node:child_process";
|
|
47
47
|
import { createServer } from "node:net";
|
|
48
|
-
import { CONNECT_CLIENTS, clientById, whatItNeeds } from "../shared/connect-clients.mjs";
|
|
48
|
+
import { CONNECT_CLIENTS, WHERE_FLAG, clientById, leadRouteFor, plainStep, whatItNeeds } from "../shared/connect-clients.mjs";
|
|
49
49
|
import { stdioConnectFor, STDIO_SHAPES } from "../shared/stdio-connect.mjs";
|
|
50
50
|
import { defaultDenylistPath, clientDoorAddress, clientDoorPort, clientDoorState, enablePlan, applyEnablePlan, describeChange, recordConnectKey, CLIENT_DOOR_UNIT } from "../shared/client-door.mjs";
|
|
51
51
|
import { mintToken, tokenId, resolvePerson, loadGrants } from "../shared/scope.mjs";
|
|
@@ -555,17 +555,12 @@ async function render(offer, have, { dryRun, running, allowMove = false }) {
|
|
|
555
555
|
return 1;
|
|
556
556
|
}
|
|
557
557
|
|
|
558
|
-
if (offer.route === "disk"
|
|
559
|
-
//
|
|
560
|
-
// block is how a reader pastes four lines into a shell
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
? ` Add this to ${s.where}:`
|
|
564
|
-
: " Run this once, on this machine:");
|
|
558
|
+
if (offer.route === "disk") {
|
|
559
|
+
// THE ROW'S OWN STEPS, and nothing written here. A command and a settings block are not the same
|
|
560
|
+
// instruction — saying "run this" over a TOML block is how a reader pastes four lines into a shell —
|
|
561
|
+
// and the steps already say which each one is and where it goes.
|
|
562
|
+
printSteps(offer, null);
|
|
565
563
|
say("");
|
|
566
|
-
for (const line of String(offer.command).split("\n")) say(` ${line}`);
|
|
567
|
-
say("");
|
|
568
|
-
if (s?.after) { say(` ${s.after}`); say(""); }
|
|
569
564
|
say(` ${offer.note}`);
|
|
570
565
|
return 0;
|
|
571
566
|
}
|
|
@@ -621,22 +616,40 @@ async function render(offer, have, { dryRun, running, allowMove = false }) {
|
|
|
621
616
|
}
|
|
622
617
|
|
|
623
618
|
say(` Address: ${offer.address}`);
|
|
624
|
-
|
|
625
|
-
//
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
619
|
+
// Printed once, stored nowhere — and only here when no step hands it over. A step whose copy carries
|
|
620
|
+
// the key prints it inside the line the reader pastes, which is the one place it is needed.
|
|
621
|
+
const keyInSteps = (offer.steps ?? []).some((s) => s.copy?.kind === "secret");
|
|
622
|
+
if (key && !keyInSteps) say(` Key: ${key}`);
|
|
623
|
+
printSteps(offer, key);
|
|
624
|
+
say("");
|
|
625
|
+
say(` ${offer.note}`);
|
|
626
|
+
return 0;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* The offer's own steps, numbered, with each copy printed under the step that hands it over.
|
|
631
|
+
*
|
|
632
|
+
* ── AND WHERE TO PUT THEM ( — F35) ───────────────────────────────────────
|
|
633
|
+
*
|
|
634
|
+
* The owner was left with two strings and no destination: *"I don't know how to connect it in Claude
|
|
635
|
+
* Cowork with those details."* The steps were DEFINED IN THE PRODUCT the whole time and this verb simply
|
|
636
|
+
* never printed them. Nothing is authored here; a second set of instructions written at the CLI would
|
|
637
|
+
* drift from the page's, which is the defect connect-clients-are-data exists against. A secret copy is
|
|
638
|
+
* printed with the key this press minted put in its slot — the page does the same substitution into the
|
|
639
|
+
* clipboard — and with no key (a dry run) the step prints and its copy does not.
|
|
640
|
+
*/
|
|
641
|
+
function printSteps(offer, key) {
|
|
632
642
|
if (offer.steps?.length) {
|
|
633
643
|
say("");
|
|
634
644
|
say(` In ${offer.client?.name ?? "your assistant"}:`);
|
|
635
|
-
offer.steps.forEach((step, n) =>
|
|
645
|
+
offer.steps.forEach((step, n) => {
|
|
646
|
+
say(` ${n + 1}. ${plainStep(step.text)}`);
|
|
647
|
+
const c = step.copy;
|
|
648
|
+
const text = !c ? null : c.kind === "secret" ? (key ? c.template.split(c.slot).join(key) : null) : c.text;
|
|
649
|
+
if (text) { say(""); for (const line of text.split("\n")) say(` ${line}`); say(""); }
|
|
650
|
+
if (step.hint) say(` ${plainStep(step.hint)}`);
|
|
651
|
+
});
|
|
636
652
|
}
|
|
637
|
-
say("");
|
|
638
|
-
say(` ${offer.note}`);
|
|
639
|
-
return 0;
|
|
640
653
|
}
|
|
641
654
|
|
|
642
655
|
async function main() {
|
|
@@ -654,6 +667,9 @@ async function main() {
|
|
|
654
667
|
say(" not about you.");
|
|
655
668
|
say("");
|
|
656
669
|
say(" --client <name> skip the question (see --list for the names)");
|
|
670
|
+
say(" --where here your assistant runs on this machine: it starts the software itself");
|
|
671
|
+
say(" --where elsewhere your assistant runs somewhere else and reaches this install over the");
|
|
672
|
+
say(" internet, with a key made for you now");
|
|
657
673
|
say(" --list the assistants this build knows");
|
|
658
674
|
say(" --dry-run say what would change, change nothing");
|
|
659
675
|
say(" --allow-checkout-move");
|
|
@@ -663,7 +679,7 @@ async function main() {
|
|
|
663
679
|
say("");
|
|
664
680
|
return 0;
|
|
665
681
|
}
|
|
666
|
-
const known = new Set(["--client", "--list", "--dry-run", "--allow-checkout-move", "--help", "-h"]);
|
|
682
|
+
const known = new Set(["--client", "--where", "--list", "--dry-run", "--allow-checkout-move", "--help", "-h"]);
|
|
667
683
|
const unknown = argv.filter((a) => a.startsWith("--") && !known.has(a));
|
|
668
684
|
if (unknown.length) {
|
|
669
685
|
console.error(`connect: unrecognised flag(s): ${unknown.join(", ")}`);
|
|
@@ -671,6 +687,12 @@ async function main() {
|
|
|
671
687
|
process.exit(2);
|
|
672
688
|
}
|
|
673
689
|
const dryRun = argv.includes("--dry-run");
|
|
690
|
+
const w = argv.indexOf("--where");
|
|
691
|
+
if (w >= 0 && !Object.hasOwn(WHERE_FLAG, argv[w + 1] ?? "")) {
|
|
692
|
+
console.error(`connect: --where takes one of: ${Object.keys(WHERE_FLAG).join(", ")}`);
|
|
693
|
+
process.exit(2);
|
|
694
|
+
}
|
|
695
|
+
let route = w >= 0 ? WHERE_FLAG[argv[w + 1]] : null;
|
|
674
696
|
// F40 — reason about the RUNNING product from the units' own environment, not this CLI's env file.
|
|
675
697
|
const running = runningEnv();
|
|
676
698
|
const have = deploymentHas(running.env);
|
|
@@ -712,9 +734,30 @@ async function main() {
|
|
|
712
734
|
chosen = CONNECT_CLIENTS[Number(answer) - 1] ?? clientById(answer);
|
|
713
735
|
} finally { rl.close(); }
|
|
714
736
|
if (!chosen) { console.error("connect: not one of the listed assistants."); process.exit(2); }
|
|
737
|
+
// THE SECOND QUESTION, ASKED ONLY WHEN IT HAS TWO ANSWERS — the page's own, in the terminal's words.
|
|
738
|
+
// Where only one route is served here, asking would offer a choice whose other half cannot work.
|
|
739
|
+
const both = ["disk", "public-http"].filter((r) => whatItNeeds(chosen, have, r)?.served);
|
|
740
|
+
if (!route && both.length > 1) {
|
|
741
|
+
say("");
|
|
742
|
+
say(" Where does it run?");
|
|
743
|
+
say("");
|
|
744
|
+
say(" 1) On this machine — it starts the software itself, nothing to open up");
|
|
745
|
+
say(" 2) Somewhere else — it reaches this install over the internet, with a key made for you now");
|
|
746
|
+
say("");
|
|
747
|
+
const rl2 = createInterface({ input: stdin, output: stdout });
|
|
748
|
+
try {
|
|
749
|
+
const answer = (await rl2.question(" 1-2: ")).trim();
|
|
750
|
+
route = answer === "1" ? "disk" : answer === "2" ? "public-http" : null;
|
|
751
|
+
} finally { rl2.close(); }
|
|
752
|
+
if (!route) { console.error("connect: answer 1 or 2."); process.exit(2); }
|
|
753
|
+
}
|
|
754
|
+
route ??= both[0] ?? chosen.lead;
|
|
715
755
|
}
|
|
716
756
|
|
|
717
|
-
|
|
757
|
+
// NAMED WITHOUT --where: the route this id had before every row took both, so a scripted
|
|
758
|
+
// `--client cowork` still mints a key and `--client codex` still prints a settings block.
|
|
759
|
+
route ??= leadRouteFor(argv[i + 1]);
|
|
760
|
+
return await render(whatItNeeds(chosen, have, route), have, { dryRun, running, allowMove: argv.includes("--allow-checkout-move") });
|
|
718
761
|
}
|
|
719
762
|
|
|
720
763
|
// THE DISPATCH RUNS ONLY WHEN THIS FILE IS THE COMMAND. Without the guard, importing
|
package/bin/disconnect.mjs
CHANGED
|
@@ -57,7 +57,7 @@ import { stdin, stdout } from "node:process";
|
|
|
57
57
|
import { readFileSync, writeFileSync, existsSync, mkdirSync, appendFileSync } from "node:fs";
|
|
58
58
|
import { join, dirname } from "node:path";
|
|
59
59
|
import { homedir, userInfo } from "node:os";
|
|
60
|
-
import { CONNECT_CLIENTS, clientById } from "../shared/connect-clients.mjs";
|
|
60
|
+
import { CONNECT_CLIENTS, WHERE_FLAG, clientById, leadRouteFor } from "../shared/connect-clients.mjs";
|
|
61
61
|
import { defaultDenylistPath, disablePlan, revokeEveryonePlan, applyDisablePlan, describeClosure, recordedKeysFor, removeRecordedKeys } from "../shared/client-door.mjs";
|
|
62
62
|
import { loadGrants } from "../shared/scope.mjs";
|
|
63
63
|
import { envFrom } from "../shared/env-aliases.mjs";
|
|
@@ -83,6 +83,8 @@ async function main() {
|
|
|
83
83
|
say(" `clearotron grant` manages that.");
|
|
84
84
|
say("");
|
|
85
85
|
say(" --client <name> which assistant you connected (see --list for the names)");
|
|
86
|
+
say(" --where here|elsewhere");
|
|
87
|
+
say(" how you connected it: on this machine, or over the internet with a key");
|
|
86
88
|
say(" --list the assistants this build knows");
|
|
87
89
|
say(" --everyone the admin act: revoke EVERY issued key on this install. It says");
|
|
88
90
|
say(" how many keys and how many people that is before doing it.");
|
|
@@ -90,7 +92,7 @@ async function main() {
|
|
|
90
92
|
say("");
|
|
91
93
|
return 0;
|
|
92
94
|
}
|
|
93
|
-
const known = new Set(["--client", "--list", "--dry-run", "--everyone", "--help", "-h"]);
|
|
95
|
+
const known = new Set(["--client", "--where", "--list", "--dry-run", "--everyone", "--help", "-h"]);
|
|
94
96
|
const unknown = argv.filter((a) => a.startsWith("--") && !known.has(a));
|
|
95
97
|
if (unknown.length) {
|
|
96
98
|
console.error(`disconnect: unrecognised flag(s): ${unknown.join(", ")}`);
|
|
@@ -137,18 +139,21 @@ async function main() {
|
|
|
137
139
|
say(` ${chosen.name}`);
|
|
138
140
|
say("");
|
|
139
141
|
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
// the
|
|
143
|
-
|
|
142
|
+
// THE ROUTE DECIDES THE SIDE, not the assistant: every row takes both now. Connected on this machine,
|
|
143
|
+
// it never touched this install; connected over the internet, it holds a key and the key is what goes.
|
|
144
|
+
// Unnamed, the route is the one `connect` gives the same id unnamed, so the two verbs pair up.
|
|
145
|
+
const w = argv.indexOf("--where");
|
|
146
|
+
if (w >= 0 && !Object.hasOwn(WHERE_FLAG, argv[w + 1] ?? "")) {
|
|
147
|
+
console.error(`disconnect: --where takes one of: ${Object.keys(WHERE_FLAG).join(", ")}`);
|
|
148
|
+
process.exit(2);
|
|
149
|
+
}
|
|
150
|
+
const route = w >= 0 ? WHERE_FLAG[argv[w + 1]] : (i >= 0 ? leadRouteFor(argv[i + 1]) : chosen.lead);
|
|
151
|
+
if (route === "disk") {
|
|
144
152
|
say(" Connecting this assistant changed nothing on this install — it runs the software itself,");
|
|
145
153
|
say(" from the configuration you added on its side. To disconnect it, remove that entry in the");
|
|
146
154
|
say(" assistant's own settings.");
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
say(" If you connected it by address instead, the door and key are shared — disconnect the");
|
|
150
|
-
say(" assistant you named when the door was opened, and the closure covers this one too.");
|
|
151
|
-
}
|
|
155
|
+
say("");
|
|
156
|
+
say(" If you connected it over the internet instead, run this again with --where elsewhere.");
|
|
152
157
|
return 0;
|
|
153
158
|
}
|
|
154
159
|
|
package/build-info.json
CHANGED
package/driver/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# clearotron-driver
|
|
2
2
|
|
|
3
|
+
## 0.3.0-beta.4
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- a9f5375: New: Use your own AI asks first where Clearotron is running, then shows the steps for your app beside the list. The same five apps are offered either way.
|
|
8
|
+
|
|
9
|
+
New: Claude Code and Codex can connect to an installation running elsewhere, and the ChatGPT desktop app to one on the same machine.
|
|
10
|
+
|
|
11
|
+
Fixed: Codex was told to paste a settings block into a terminal. Its steps now name the file the block goes in.
|
|
12
|
+
|
|
13
|
+
For operators: `clearotron connect` and `clearotron disconnect` take `--where here` or `--where elsewhere`. Assistant names used before, such as `cowork`, still work.
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- a9f5375: New: the company switcher in the sidebar ends with `+ New company`. Making a company is now one click from every screen, whether or not a company is selected.
|
|
18
|
+
|
|
3
19
|
## 0.3.0-beta.3
|
|
4
20
|
|
|
5
21
|
### Patch Changes
|
package/driver/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "clearotron-driver",
|
|
3
3
|
"private": true,
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "0.3.0-beta.
|
|
5
|
+
"version": "0.3.0-beta.4",
|
|
6
6
|
"license": "AGPL-3.0-only",
|
|
7
7
|
"description": "Deterministic driver for the trademark clearance workflow: orchestration in code (fan-out, fan-in barrier, gating, retries); the model does judgment leaves only, through a reasoning CLI spawned per stage.",
|
|
8
8
|
"engines": {
|
package/driver/suite-census.json
CHANGED
|
@@ -1366,8 +1366,8 @@
|
|
|
1366
1366
|
"todos": 0
|
|
1367
1367
|
},
|
|
1368
1368
|
"connect-clients-are-data.test.mjs": {
|
|
1369
|
-
"tests":
|
|
1370
|
-
"asserts":
|
|
1369
|
+
"tests": 15,
|
|
1370
|
+
"asserts": 75,
|
|
1371
1371
|
"skips": 0,
|
|
1372
1372
|
"todos": 0
|
|
1373
1373
|
},
|
|
@@ -1397,7 +1397,7 @@
|
|
|
1397
1397
|
},
|
|
1398
1398
|
"connector-annotations-and-pack.test.mjs": {
|
|
1399
1399
|
"tests": 7,
|
|
1400
|
-
"asserts":
|
|
1400
|
+
"asserts": 12,
|
|
1401
1401
|
"skips": 0,
|
|
1402
1402
|
"todos": 0
|
|
1403
1403
|
},
|
|
@@ -5774,8 +5774,8 @@
|
|
|
5774
5774
|
"todos": 0
|
|
5775
5775
|
},
|
|
5776
5776
|
"newCompanyReachableFromProfile.test.ts": {
|
|
5777
|
-
"tests":
|
|
5778
|
-
"asserts":
|
|
5777
|
+
"tests": 5,
|
|
5778
|
+
"asserts": 13,
|
|
5779
5779
|
"skips": 0,
|
|
5780
5780
|
"todos": 0
|
|
5781
5781
|
},
|
|
@@ -5913,7 +5913,7 @@
|
|
|
5913
5913
|
},
|
|
5914
5914
|
"useYourAI.test.ts": {
|
|
5915
5915
|
"tests": 13,
|
|
5916
|
-
"asserts":
|
|
5916
|
+
"asserts": 41,
|
|
5917
5917
|
"skips": 0,
|
|
5918
5918
|
"todos": 0
|
|
5919
5919
|
},
|
package/mcp-server/CHANGELOG.md
CHANGED
package/mcp-server/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trademark-artifacts-mcp",
|
|
3
|
-
"version": "0.3.0-beta.
|
|
3
|
+
"version": "0.3.0-beta.4",
|
|
4
4
|
"license": "AGPL-3.0-only",
|
|
5
5
|
"private": true,
|
|
6
6
|
"description": "MCP server to interrogate clearotron trademark-clearance runs — list/read artifacts, trace the full decision flow, telemetry/cost, coverage, single-run search, and a gated single-step what-if. Imports the clearotron-driver read-only; touches no driver/template/deploy files.",
|