clearotron 0.3.2-beta.10 → 0.3.2-beta.12
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/.env.example +1 -1
- package/CONTRIBUTING.md +1 -0
- package/INSTALL.md +49 -46
- package/README.md +8 -7
- package/bin/clearotron.mjs +14 -0
- package/bin/connect.mjs +68 -3
- package/bin/key.mjs +6 -1
- package/bin/onboard.mjs +51 -14
- package/bin/passphrase.mjs +4 -2
- package/bin/start.mjs +43 -9
- package/build-info.json +2 -2
- package/docs/architecture/05-config-governance.md +1 -1
- package/driver/CHANGELOG.md +416 -388
- package/driver/contract-vocabulary.mjs +5 -5
- package/driver/engine/mcp/recording-server.mjs +21 -1
- package/driver/engine/mcp/supplemental.mjs +22 -5
- package/driver/knockout-next-step.mjs +72 -0
- package/driver/named-band.mjs +1 -1
- package/driver/package.json +1 -1
- package/driver/phase0.mjs +16 -7
- package/driver/pipeline-knockout.mjs +26 -0
- package/driver/pipeline.mjs +48 -1
- package/driver/portal-local-auth.mjs +14 -4
- package/driver/portal-report.mjs +21 -2
- package/driver/portal-service.mjs +20 -11
- package/driver/publish/attr.mjs +36 -0
- package/driver/publish/index.mjs +10 -10
- package/driver/publish/parse.mjs +1 -1
- package/driver/publish/render-knockout.mjs +14 -9
- package/driver/publish/render.mjs +9 -9
- package/driver/publish/xlsx.mjs +7 -1
- package/driver/queue-watch-verdict.mjs +1 -1
- package/driver/register-availability.mjs +1 -1
- package/driver/register-plan.mjs +81 -11
- package/driver/result-noun-fields.mjs +3 -1
- package/driver/stages-knockout.mjs +1 -1
- package/driver/suite-census.json +104 -38
- package/driver/unit-inventory.mjs +7 -7
- package/driver/verify-knockout.mjs +0 -27
- package/mcp-server/CHANGELOG.md +27 -19
- package/mcp-server/lib/audit-view.mjs +4 -4
- package/mcp-server/lib/trace.mjs +1 -1
- package/mcp-server/package.json +1 -1
- package/package.json +2 -2
- package/portal-ui/dist/assets/{index-CtvwLCti.css → index-7Lq-dXDV.css} +12 -9
- package/portal-ui/dist/assets/{index-DXSRxPV_.js → index-w8GFZftk.js} +110 -69
- package/portal-ui/dist/index.html +2 -2
- package/portal-ui/package.json +1 -1
- package/providers/jx/README.md +2 -2
- package/providers/jx-subclass/README.md +1 -1
- package/providers/oauth-mcp-bridge/CHANGELOG.md +8 -0
- package/providers/oauth-mcp-bridge/package.json +1 -1
- package/scripts/README.md +5 -10
- package/scripts/ask-ai-render-check.mjs +26 -1
- package/scripts/citation-line-check.mjs +2 -2
- package/scripts/dead-names.mjs +17 -16
- package/scripts/e2e.mjs +8 -8
- package/scripts/env-audit.mjs +6 -0
- package/scripts/generated-files-are-current.mjs +35 -4
- package/scripts/live-surface-check.mjs +17 -17
- package/scripts/release-code-scanning-check.mjs +114 -0
- package/scripts/release-visible-check.mjs +117 -0
- package/scripts/repo-writes.mjs +42 -0
- package/scripts/report-sections-render-check.mjs +245 -0
- package/scripts/report-theme-render-check.mjs +31 -3
- package/scripts/settings-render-check.mjs +10 -7
- package/scripts/test-run.mjs +63 -4
- package/shared/client-door.mjs +20 -0
- package/shared/invocation.mjs +2 -2
- package/shared/parent-watch.mjs +33 -0
- package/shared/register-selection.mjs +4 -1
- package/shared/root-doc-commands.mjs +10 -4
- package/shared/running-start.mjs +14 -3
- package/shared/scope.mjs +24 -5
package/.env.example
CHANGED
|
@@ -578,7 +578,7 @@ TRADEMARK_MCP_KEY_SOCKET=
|
|
|
578
578
|
# names, read by code that ships.
|
|
579
579
|
|
|
580
580
|
# WHO the chat notice is addressed to: a JSON object of requester email or handle → number, e.g.
|
|
581
|
-
# {"
|
|
581
|
+
# {"robin@tenant.example":"+41...","jordan":"+41..."}. Before this, the notice went to a map keyed by
|
|
582
582
|
# AGENT id, and every user of a deployment shares one agent — so the operator was notified about work
|
|
583
583
|
# other people ordered and the requester was told nothing.
|
|
584
584
|
#
|
package/CONTRIBUTING.md
CHANGED
package/INSTALL.md
CHANGED
|
@@ -16,7 +16,7 @@ after §5 is needed to produce a report.
|
|
|
16
16
|
| | Sections | For |
|
|
17
17
|
|---|---|---|
|
|
18
18
|
| **Installing** | §1 Prerequisites · §2 Install · §3 Configuration · §3a Free register route · §3b Paying through a cloud account · §4 Config store · §5 Run a clearance | Anyone |
|
|
19
|
-
| **Operating** | §6
|
|
19
|
+
| **Operating** | §6 Start the product · §7 The MCP server · §8 Access control and isolation | Running it as a service for other people |
|
|
20
20
|
| **Reference** | §9 What an integrator supplies · §10 Licence | — |
|
|
21
21
|
|
|
22
22
|
Before §3, decide which register you are using — it is the first real choice and the fastest route is
|
|
@@ -113,7 +113,7 @@ run is [mcp-server/CONNECT.md](mcp-server/CONNECT.md), and why something is the
|
|
|
113
113
|
pay for Claude through your own Google Cloud, Microsoft Azure or Amazon Bedrock account (§3b).
|
|
114
114
|
|
|
115
115
|
**Installed is not usable.** `npx clearotron install` proves the engine can complete a turn before it
|
|
116
|
-
writes anything, and `
|
|
116
|
+
writes anything, and `clearotron doctor --probe-engine` re-proves it on a configured box. Both
|
|
117
117
|
spend one cheap turn; plain `doctor` spends nothing.
|
|
118
118
|
- **A register credential**, and **`PERPLEXITY_API_KEY`**. Both are required for a real run and both
|
|
119
119
|
fail closed at preflight — before a stage has spent, never at the grid after. The one exception is a
|
|
@@ -202,8 +202,8 @@ tarball as a dependency.** Not an unpacked archive; there is no step here that u
|
|
|
202
202
|
mkdir ~/app && cd ~/app
|
|
203
203
|
npm init -y
|
|
204
204
|
npm install /path/to/clearotron-<version>.tgz
|
|
205
|
-
npx clearotron doctor # reads; writes nothing, calls nobody
|
|
206
205
|
npx clearotron install # the wizard
|
|
206
|
+
clearotron doctor # reads; writes nothing, calls nobody
|
|
207
207
|
```
|
|
208
208
|
|
|
209
209
|
`npx` works from `~/app` because npm links a **dependency's** `bin` into the consuming project — the
|
|
@@ -264,7 +264,10 @@ there. On the repository route "the project" is the checkout; on the packaged ro
|
|
|
264
264
|
you ran `npm init` in — and on that route `clearotron` IS a dependency, so `npx` finds it in
|
|
265
265
|
`node_modules/.bin` exactly as the paragraph above describes.
|
|
266
266
|
|
|
267
|
-
**`clearotron install` (§3) puts the short form on your `PATH
|
|
267
|
+
**`clearotron install` (§3) puts the short form on your `PATH`**, and every command after it in this
|
|
268
|
+
document uses that form. Use it rather than a fresh `npx clearotron`: `npx` resolves whichever version npm
|
|
269
|
+
picks at that moment — the stable, unless you name a channel — and does not hand over to the copy the
|
|
270
|
+
install placed. You can stop typing `npx` after it:
|
|
268
271
|
it writes a small shim to `~/.local/bin/clearotron` pointing at this checkout. That directory needs no
|
|
269
272
|
root — `npm link` would want npm's global prefix, which on a default install is `/usr` and refuses
|
|
270
273
|
without it. Most login profiles add `~/.local/bin` to `PATH` only if it already existed when the shell
|
|
@@ -413,7 +416,7 @@ If you would rather not write this file by hand, `npx clearotron install` asks f
|
|
|
413
416
|
these paths under it, checks what it can check without billing you before it writes anything, and never
|
|
414
417
|
touches a path you did not name — an engine turn, an EUIPO token exchange, and an offered (not assumed)
|
|
415
418
|
Perplexity ping. A paid register key is deliberately not probed: a call against a metered subscription is
|
|
416
|
-
a charge you did not ask for, so that credential is written on your word. `
|
|
419
|
+
a charge you did not ask for, so that credential is written on your word. `clearotron doctor`
|
|
417
420
|
reports what is set and what is missing, and writes nothing.
|
|
418
421
|
|
|
419
422
|
Only the integrator-set knobs are shown — copy what you need:
|
|
@@ -597,9 +600,9 @@ points on it.
|
|
|
597
600
|
| What it is | What the product calls it | Where it lives | What creates it |
|
|
598
601
|
|---|---|---|---|
|
|
599
602
|
| A group of people and the companies they clear for — a firm, a brand team, one company on a hosted install | **organisation** (`tenant` in `grants.json`) | a key under `tenants` in `grants.json`, with its `name` | setup creates the first; after that, a key you add to `grants.json` |
|
|
600
|
-
| A company you do clearances for | **company** (`account` in `grants.json` and on the wire; the CLI calls it **brand owner**) | a bundle in the company store, keyed by an account key, and listed under exactly one organisation | the portal's `+ New company`, or `
|
|
601
|
-
| One engagement under that company — its classes, jurisdictions, platforms | **project** | inside that company's bundle | `
|
|
602
|
-
| Someone who may see some of it | **person** | `grants.json`: their access under each organisation's `users`, their two switches under `people` | the portal's People page, or `
|
|
603
|
+
| A company you do clearances for | **company** (`account` in `grants.json` and on the wire; the CLI calls it **brand owner**) | a bundle in the company store, keyed by an account key, and listed under exactly one organisation | the portal's `+ New company`, or `clearotron brandowner add <key>` |
|
|
604
|
+
| One engagement under that company — its classes, jurisdictions, platforms | **project** | inside that company's bundle | `clearotron project add` |
|
|
605
|
+
| Someone who may see some of it | **person** | `grants.json`: their access under each organisation's `users`, their two switches under `people` | the portal's People page, or `clearotron grant add` |
|
|
603
606
|
|
|
604
607
|
Nesting, in one line: **an organisation contains companies; a company contains projects; a person is
|
|
605
608
|
given access to points on that tree — the whole install, an organisation, or one company — and sees
|
|
@@ -613,7 +616,7 @@ Three consequences worth stating, because each has surprised someone:
|
|
|
613
616
|
stops them; **Manage** adds people, adds companies and changes settings. Viewing is not a permission:
|
|
614
617
|
access is viewing. A person with no entry under `people` sees what their access covers and starts
|
|
615
618
|
nothing.
|
|
616
|
-
- **A key grants no reach of its own.** `
|
|
619
|
+
- **A key grants no reach of its own.** `clearotron key issue` mints the identity a person's assistant
|
|
617
620
|
presents; what that identity may see and do is decided by the guest list at the moment of each call.
|
|
618
621
|
Enrol first, issue second — a key for someone with no access reaches nothing, and is not an error
|
|
619
622
|
anywhere.
|
|
@@ -710,7 +713,7 @@ Copy or author the companies, context packs and project overlays you want; assum
|
|
|
710
713
|
## 5. Run a headless clearance report
|
|
711
714
|
|
|
712
715
|
1. Make sure `CLEAROTRON_AI` names the engine you want (`anthropic-agent` or `openai-agent`), its CLI is
|
|
713
|
-
authenticated, and your active register provider's credential is set. `
|
|
716
|
+
authenticated, and your active register provider's credential is set. `clearotron doctor
|
|
714
717
|
--probe-engine` answers all three, and the engine half of it by actually running a turn — an
|
|
715
718
|
executable on `PATH` that is signed out passes every other check and fails at the first stage.
|
|
716
719
|
|
|
@@ -760,7 +763,7 @@ Copy or author the companies, context packs and project overlays you want; assum
|
|
|
760
763
|
3. Run the pipeline:
|
|
761
764
|
|
|
762
765
|
```
|
|
763
|
-
|
|
766
|
+
clearotron run --job job.json
|
|
764
767
|
```
|
|
765
768
|
|
|
766
769
|
**The example above is sized in minutes; a real clearance is sized in hours.**
|
|
@@ -834,7 +837,7 @@ A run stopped by a **provider rate limit** or parked for **automatic recovery**
|
|
|
834
837
|
that is the systemd units in `driver/systemd/`. Everywhere else, run the watcher yourself:
|
|
835
838
|
|
|
836
839
|
```
|
|
837
|
-
|
|
840
|
+
clearotron run-queue --watch
|
|
838
841
|
```
|
|
839
842
|
|
|
840
843
|
That polls every 90 seconds for queued jobs and for parked runs whose window has elapsed, and it resumes
|
|
@@ -847,18 +850,18 @@ stops it, and anything still parked waits for the next time you start it.
|
|
|
847
850
|
Everything above runs the engine from a job file. This is the product: a portal you sign in to, order a
|
|
848
851
|
clearance from, and read the report in.
|
|
849
852
|
|
|
850
|
-
**Plain `
|
|
853
|
+
**Plain `clearotron start` runs in the foreground and stops when this terminal closes** — Ctrl-C, a
|
|
851
854
|
dropped SSH session, a shut laptop lid ending the session: the portal goes with it. That is the right
|
|
852
855
|
shape for trying things. To keep it running when the window is gone:
|
|
853
856
|
|
|
854
857
|
```
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
+
clearotron start --background # the same product, as user services that survive the terminal
|
|
859
|
+
clearotron status # is it up, and on which ports
|
|
860
|
+
clearotron stop # stop it and give the box back — plain `start` works again
|
|
858
861
|
```
|
|
859
862
|
|
|
860
|
-
`--background` never touches the assistant connector: `
|
|
861
|
-
and `
|
|
863
|
+
`--background` never touches the assistant connector: `clearotron connect` opens that door
|
|
864
|
+
and `clearotron disconnect` closes it, separately and on purpose.
|
|
862
865
|
|
|
863
866
|
**If anything else on this host already runs this product, set its ports first.** The portal (18802)
|
|
864
867
|
and the engine door (18790) are **fixed defaults shared by every checkout on a machine**, so a second
|
|
@@ -866,14 +869,14 @@ instance collides with the first and `start` refuses rather than quietly moving.
|
|
|
866
869
|
before the first start:
|
|
867
870
|
|
|
868
871
|
```
|
|
869
|
-
PORTAL_SERVICE_PORT=18820 TRADEMARK_MCP_HTTP_PORT=18821
|
|
872
|
+
PORTAL_SERVICE_PORT=18820 TRADEMARK_MCP_HTTP_PORT=18821 clearotron start
|
|
870
873
|
```
|
|
871
874
|
|
|
872
875
|
A test instance beside a live one needs more than two ports — §8, *Two instances on one machine*, is the
|
|
873
876
|
whole boundary. On a host running nothing else, ignore this and carry on:
|
|
874
877
|
|
|
875
878
|
```
|
|
876
|
-
|
|
879
|
+
clearotron start
|
|
877
880
|
```
|
|
878
881
|
|
|
879
882
|
One command. It starts the portal and the engine door the portal's Start button calls, waits until both
|
|
@@ -881,14 +884,14 @@ answer, and prints one address to open. `Ctrl-C` stops both. The second run asks
|
|
|
881
884
|
|
|
882
885
|
**This is not `npx clearotron demo`, and the two are not interchangeable.**
|
|
883
886
|
|
|
884
|
-
| | `npx clearotron demo` | `
|
|
887
|
+
| | `npx clearotron demo` | `clearotron start` |
|
|
885
888
|
|---|---|---|
|
|
886
889
|
| what it is | a finished report, replayed | the running product |
|
|
887
890
|
| credentials | none | whatever a real run needs (§3) |
|
|
888
891
|
| model calls | none | yes, once you order a clearance |
|
|
889
892
|
| what you can do | read | sign in, configure, order, read |
|
|
890
893
|
|
|
891
|
-
Use the demo to see what this system produces. Use `
|
|
894
|
+
Use the demo to see what this system produces. Use `clearotron start` to run it.
|
|
892
895
|
|
|
893
896
|
### What the first start does, once
|
|
894
897
|
|
|
@@ -898,7 +901,7 @@ Use the demo to see what this system produces. Use `npx clearotron start` to run
|
|
|
898
901
|
`clearotron doctor` and a connected assistant read the same saved searches as the portal.
|
|
899
902
|
- Creates `~/trademark/` — `pool/`, `workspace/`, `queue/`, `outbox/`, `locks/`, an empty grants file,
|
|
900
903
|
and a small git repository for saved searches. Same base directory `npx clearotron install` uses, so whichever
|
|
901
|
-
of the two you ran first, the other finds the same install. Move it with `
|
|
904
|
+
of the two you ran first, the other finds the same install. Move it with `clearotron start --base <dir>`.
|
|
902
905
|
That does not move anything the env file already names: the saved-search lines above, and the data
|
|
903
906
|
directories `npx clearotron install` wrote, keep pointing at the old place until you edit them.
|
|
904
907
|
- Mints your sign-in passphrase and **prints it once**. Write it down. It is stored as a scrypt digest in
|
|
@@ -911,7 +914,7 @@ Use the demo to see what this system produces. Use `npx clearotron start` to run
|
|
|
911
914
|
You sign in as `<your-username>@localhost` unless you say otherwise:
|
|
912
915
|
|
|
913
916
|
```
|
|
914
|
-
|
|
917
|
+
clearotron start --user you@example.com
|
|
915
918
|
```
|
|
916
919
|
|
|
917
920
|
Setup does not ask for the address: it writes the local-account form to `.env` and shows it once, in
|
|
@@ -923,7 +926,7 @@ nobody else at its domain; enrolling anyone else is that same file, exactly as o
|
|
|
923
926
|
|
|
924
927
|
**No authentication is switched off to make this work, and none can be.** Both doors prove who the
|
|
925
928
|
caller is — the portal by passphrase and a signed session cookie, the engine door by a mandatory
|
|
926
|
-
access key that `
|
|
929
|
+
access key that `clearotron start` mints in memory at every start and never writes down. The key is scoped to
|
|
927
930
|
two verbs and capped to the companies this install knows about. The `*_AUTH_DISABLED` switches
|
|
928
931
|
elsewhere in this repository are for something else and are written into the child environment as `0`.
|
|
929
932
|
|
|
@@ -941,7 +944,7 @@ no proxy in front is the one shape to avoid — the portal refuses to start with
|
|
|
941
944
|
|
|
942
945
|
### It drains its own queue
|
|
943
946
|
|
|
944
|
-
`
|
|
947
|
+
`clearotron start` supervises a worker alongside the portal, so ordering a clearance from the portal
|
|
945
948
|
runs it.
|
|
946
949
|
|
|
947
950
|
The consent did not move and did not weaken. The portal prices the run, quotes how long it takes, and
|
|
@@ -953,8 +956,8 @@ If you want the old separation — order here, drain deliberately over there —
|
|
|
953
956
|
run the queue yourself:
|
|
954
957
|
|
|
955
958
|
```
|
|
956
|
-
|
|
957
|
-
|
|
959
|
+
clearotron start --no-worker
|
|
960
|
+
clearotron run-queue --watch
|
|
958
961
|
```
|
|
959
962
|
|
|
960
963
|
A queued job whose worker is not running says so on the portal rather than sitting at "Waiting to start".
|
|
@@ -982,7 +985,7 @@ systemctl --user daemon-reload && systemctl --user enable --now clearotron-deplo
|
|
|
982
985
|
|
|
983
986
|
A busy box simply skips: the service exits clean when a run is live and the next firing tries again.
|
|
984
987
|
|
|
985
|
-
**`
|
|
988
|
+
**`clearotron doctor` reports how far behind this install is**, beside everything else it checks. It
|
|
986
989
|
does not fetch, so the count is against your last fetch — which it says. An install that is not a git
|
|
987
990
|
checkout, or a branch with no upstream, says that instead of reporting a number it cannot compute.
|
|
988
991
|
|
|
@@ -997,7 +1000,7 @@ yours to do, and now yours to know about.
|
|
|
997
1000
|
|
|
998
1001
|
The portal is on 18802 and the engine door on 18790, both loopback, and both are **fixed defaults for
|
|
999
1002
|
every checkout on the host** rather than per-install values. They move separately: the portal with
|
|
1000
|
-
`
|
|
1003
|
+
`clearotron start --port <n>` or `PORTAL_SERVICE_PORT=<n>`, the engine door with
|
|
1001
1004
|
`TRADEMARK_MCP_HTTP_PORT=<n>`. A port already in use produces a sentence saying which port and which
|
|
1002
1005
|
variable to change, before anything starts — it will not quietly pick another, because whatever sits in
|
|
1003
1006
|
front of it is still addressed to the old one.
|
|
@@ -1073,17 +1076,17 @@ their own integration work.
|
|
|
1073
1076
|
|
|
1074
1077
|
1. **Clearotron is installed on the machine the assistant runs on** — Claude's desktop app, Claude Code,
|
|
1075
1078
|
Codex, the ChatGPT desktop app, an agent that runs commands. The assistant spawns the server over
|
|
1076
|
-
stdio. No address, no key, no network, no ingress. `
|
|
1077
|
-
paste, and `
|
|
1079
|
+
stdio. No address, no key, no network, no ingress. `clearotron start` prints the one line to
|
|
1080
|
+
paste, and `clearotron connect --where here` hands it over per assistant.
|
|
1078
1081
|
2. **Clearotron is running somewhere else** — a server, a cloud machine, anywhere the assistant is
|
|
1079
1082
|
not. These need **a publicly reachable HTTPS address**, plus a key made for the person connecting.
|
|
1080
1083
|
Always. Claude (app, web, Cowork and mobile), ChatGPT on the web, Perplexity and other agents only
|
|
1081
|
-
ever connect this way; Claude Code and Codex can connect either way. `
|
|
1084
|
+
ever connect this way; Claude Code and Codex can connect either way. `clearotron connect
|
|
1082
1085
|
--where elsewhere` makes the key and prints the steps for the assistant you name.
|
|
1083
1086
|
|
|
1084
1087
|
Without `--where`, `connect` asks when both answers are possible, and `--client <name>` on its own keeps
|
|
1085
1088
|
the answer that assistant had before: a key for Claude, the local line for Claude Code and Codex.
|
|
1086
|
-
`
|
|
1089
|
+
`clearotron disconnect` takes the same `--where`.
|
|
1087
1090
|
|
|
1088
1091
|
**A loopback address is never an answer for shape 2, and that is not about your network.** A remote MCP
|
|
1089
1092
|
connector is reached **from the vendor's cloud**, never from the reader's device. Anthropic's own help
|
|
@@ -1091,7 +1094,7 @@ centre states it: *"Claude connects to your remote MCP server from Anthropic's c
|
|
|
1091
1094
|
rather than from your local device. This is true across every Claude client, including claude.ai, Claude
|
|
1092
1095
|
Desktop, Cowork, and the mobile apps … Your MCP server must be reachable over the public internet."*
|
|
1093
1096
|
So an install behind `ssh -L` or an editor's port forward serves shape 1 perfectly and cannot serve
|
|
1094
|
-
shape 2 at all, however the reader reaches the portal. `
|
|
1097
|
+
shape 2 at all, however the reader reaches the portal. `clearotron connect` says so plainly rather
|
|
1095
1098
|
than printing an address that will be rejected.
|
|
1096
1099
|
|
|
1097
1100
|
**The address is set once, at install.** `npx clearotron install` asks for it — *"the address companies'
|
|
@@ -1100,7 +1103,7 @@ Use-your-AI page, a report's Ask-your-AI control and `doctor` all read. Leave it
|
|
|
1100
1103
|
install: every one of those surfaces then shows its honest empty state, which is correct for a machine
|
|
1101
1104
|
with no public address. Changing it later is editing that one setting and restarting.
|
|
1102
1105
|
|
|
1103
|
-
Whatever you provision, `
|
|
1106
|
+
Whatever you provision, `clearotron doctor` will tell you whether the published address actually
|
|
1104
1107
|
answers — being set is not the same as being reachable, and the page and the report both render from
|
|
1105
1108
|
the value being present alone. An address that does not answer is reported as a problem with the
|
|
1106
1109
|
reason, never as configured.
|
|
@@ -1168,7 +1171,7 @@ curl -sS -o /dev/null -w '%{http_code}\n' https://<your-host>/mcp
|
|
|
1168
1171
|
|
|
1169
1172
|
Anything that comes back — including a 401 or a 405 — proves the route reaches the connector; a
|
|
1170
1173
|
connection error or a login page does not. Put the address that answered into the installer's question
|
|
1171
|
-
(or `CLEAROTRON_CLIENT_MCP_URL`), then `
|
|
1174
|
+
(or `CLEAROTRON_CLIENT_MCP_URL`), then `clearotron doctor` and read the connector line.
|
|
1172
1175
|
|
|
1173
1176
|
Smoke-test either face offline:
|
|
1174
1177
|
|
|
@@ -1191,28 +1194,28 @@ bills Claude through an API key or a cloud account, never a Claude subscription,
|
|
|
1191
1194
|
require.
|
|
1192
1195
|
|
|
1193
1196
|
**The guest list.** `CLEAROTRON_ACCESS_FILE` turns account scoping on for **every face at once** — the
|
|
1194
|
-
portal, the MCP read face, and the client connector. `
|
|
1197
|
+
portal, the MCP read face, and the client connector. `clearotron start` (§6) writes one into its
|
|
1195
1198
|
state directory the first time it runs: you, with access to everything, your organisation if setup was
|
|
1196
1199
|
told its name, and nobody else yet.
|
|
1197
1200
|
[examples/grants.example.json](examples/grants.example.json) is a runnable guest list over the demo
|
|
1198
1201
|
companies.
|
|
1199
1202
|
|
|
1200
|
-
**Giving someone access.** `
|
|
1203
|
+
**Giving someone access.** `clearotron grant add` writes the same file the portal's People page
|
|
1201
1204
|
writes:
|
|
1202
1205
|
|
|
1203
1206
|
```
|
|
1204
|
-
|
|
1207
|
+
clearotron grant add <email> --tenant <organisation> --accounts <key,key|*> [--run] [--manage]
|
|
1205
1208
|
```
|
|
1206
1209
|
|
|
1207
1210
|
`--accounts '*'` is the whole organisation, including companies filed under it later. `--run` lets the
|
|
1208
1211
|
person start and stop clearances; `--manage` lets them add people and companies and change settings.
|
|
1209
1212
|
With neither, they can see what their access covers and start nothing.
|
|
1210
1213
|
|
|
1211
|
-
**Keys for people.** `
|
|
1214
|
+
**Keys for people.** `clearotron grant` enrols someone; it decides what they may see and issues
|
|
1212
1215
|
nothing. The key their assistant actually presents comes from a different verb:
|
|
1213
1216
|
|
|
1214
1217
|
```
|
|
1215
|
-
|
|
1218
|
+
clearotron key issue <email> [--accounts a,b] [--ttl-days 90]
|
|
1216
1219
|
```
|
|
1217
1220
|
|
|
1218
1221
|
It is printed once, on stdout, and stored nowhere — possession is the credential. Enrol first: the key
|
|
@@ -1227,7 +1230,7 @@ node mcp-server/mint-token.mjs --scope ops --sub <name> --ttl-days 30 --verbs st
|
|
|
1227
1230
|
```
|
|
1228
1231
|
|
|
1229
1232
|
Said plainly rather than dressed as a verb, because the distinction costs real time: **`npx clearotron
|
|
1230
|
-
key issue` mints ACCOUNT keys only**, and `
|
|
1233
|
+
key issue` mints ACCOUNT keys only**, and `clearotron grant` mints nothing at all. This page
|
|
1231
1234
|
previously sent readers to `grant` for an ops token, which is why the sentence is now this long.
|
|
1232
1235
|
|
|
1233
1236
|
**When you have to re-mint one.** Not on the ordinary path any more, and this paragraph used to say
|
|
@@ -1307,7 +1310,7 @@ https://claude.com/api/mcp/auth_callback
|
|
|
1307
1310
|
https://chatgpt.com/connector_platform_oauth_redirect
|
|
1308
1311
|
```
|
|
1309
1312
|
|
|
1310
|
-
`
|
|
1313
|
+
`clearotron doctor --probe-connector` asks your own door whether each of those can register, with a
|
|
1311
1314
|
localhost control first so a broken endpoint is never reported as a policy refusal. It is opt-in because
|
|
1312
1315
|
each successful attempt creates a throwaway OAuth client on your account — every other `doctor` check
|
|
1313
1316
|
writes nothing.
|
|
@@ -1318,7 +1321,7 @@ writes nothing.
|
|
|
1318
1321
|
> whether the application was recreated before you change anything else.
|
|
1319
1322
|
|
|
1320
1323
|
**Seeing what a company sees.** There is no "view as" screen. The documented route is a **client-scoped
|
|
1321
|
-
connector key**: issue one for that company with `
|
|
1324
|
+
connector key**: issue one for that company with `clearotron key issue`, point an assistant at the
|
|
1322
1325
|
client connector with it, and you get exactly that company's scope. Changing `PORTAL_LOCAL_USER` to
|
|
1323
1326
|
impersonate someone is not the answer — local sign-in is one user by design, and the service refuses to
|
|
1324
1327
|
start if the credential does not match the configured address, so you lose your own access and take the
|
|
@@ -1391,7 +1394,7 @@ the orchestrator, the provider adapters and the documentation.
|
|
|
1391
1394
|
|
|
1392
1395
|
AGPL §13 matters if you run it as a network service: anyone who interacts with your instance is owed
|
|
1393
1396
|
the source of **that** instance. Every network face answers with its own running commit — the portal's
|
|
1394
|
-
About page, the MCP server's `server_info`, and `
|
|
1397
|
+
About page, the MCP server's `server_info`, and `clearotron start --license`.
|
|
1395
1398
|
|
|
1396
1399
|
**Everything §1 told you to bring is outside it.** Read this before you count the licence as your
|
|
1397
1400
|
answer on any of them:
|
package/README.md
CHANGED
|
@@ -43,8 +43,9 @@ npx clearotron install
|
|
|
43
43
|
|
|
44
44
|
Node 22.13 or newer, on macOS or Linux. It needs no root: it puts the program under `~/.local`, with the
|
|
45
45
|
`clearotron` command in `~/.local/bin`, then asks one question at a time and checks each credential
|
|
46
|
-
before it saves it.
|
|
47
|
-
|
|
46
|
+
before it saves it. Every command below uses the `clearotron` it installed — the same channel you
|
|
47
|
+
installed from — so if `~/.local/bin` is not on your `PATH` yet, type the full path,
|
|
48
|
+
`~/.local/bin/clearotron`, instead. **On Windows the demo above runs natively; a
|
|
48
49
|
real clearance needs WSL2.** Native Windows clearances are planned for a later release. Until then the
|
|
49
50
|
engine does not run on native Windows: it spawns each stage with POSIX path and process semantics, so
|
|
50
51
|
a clearance is refused there before it starts.
|
|
@@ -71,13 +72,13 @@ With it installed, check what it found before it does anything. `doctor` only re
|
|
|
71
72
|
calls nobody, and names whatever is still missing:
|
|
72
73
|
|
|
73
74
|
```bash
|
|
74
|
-
|
|
75
|
+
clearotron doctor
|
|
75
76
|
```
|
|
76
77
|
|
|
77
78
|
Then start the product and open the portal address it prints:
|
|
78
79
|
|
|
79
80
|
```bash
|
|
80
|
-
|
|
81
|
+
clearotron start
|
|
81
82
|
```
|
|
82
83
|
|
|
83
84
|
That is the portal everyone at your company uses. Ordering a clearance is the same screen — describe it
|
|
@@ -99,7 +100,7 @@ rights-holders behind them by jurisdiction:
|
|
|
99
100
|
Then run your own: order it in the portal, or hand the engine a job file:
|
|
100
101
|
|
|
101
102
|
```bash
|
|
102
|
-
|
|
103
|
+
clearotron run --job my-job.json
|
|
103
104
|
```
|
|
104
105
|
|
|
105
106
|
## How it fits together
|
|
@@ -158,8 +159,8 @@ npx clearotron demo # replays finished clearances into a local portal
|
|
|
158
159
|
|
|
159
160
|
From a clone the commands are `npx clearotron …`, run from that directory.
|
|
160
161
|
|
|
161
|
-
`npm test` is the
|
|
162
|
-
|
|
162
|
+
`npm test` is the offline suite and the first thing [CONTRIBUTING.md](CONTRIBUTING.md) asks of a
|
|
163
|
+
contributor; `npm run test:full`, also free, is the merge gate.
|
|
163
164
|
|
|
164
165
|
## Project documents
|
|
165
166
|
|
package/bin/clearotron.mjs
CHANGED
|
@@ -24,6 +24,7 @@ import { constants as SIG } from "node:os";
|
|
|
24
24
|
import { isEntrypoint } from "../shared/is-entrypoint.mjs";
|
|
25
25
|
import { nodeFloorVerdict, nodeFloorRefusal } from "../shared/node-floor.mjs"; // — one floor, read from package.json
|
|
26
26
|
import { invocationPrefix } from "../shared/invocation.mjs"; // — print a command the reader can type
|
|
27
|
+
import { watchParent } from "../shared/parent-watch.mjs";
|
|
27
28
|
|
|
28
29
|
export const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
29
30
|
|
|
@@ -229,6 +230,19 @@ const [verb, ...rest] = process.argv.slice(2);
|
|
|
229
230
|
};
|
|
230
231
|
process.on(sig, forward);
|
|
231
232
|
}
|
|
233
|
+
|
|
234
|
+
// ── AND THE DEMO STOPS WHEN WHATEVER STARTED IT IS GONE ─────────────────────────────────────────────
|
|
235
|
+
//
|
|
236
|
+
// The forwarding above covers a signal sent to THIS pid. Through npx it is not this pid a reader holds:
|
|
237
|
+
// npm → `sh -c` → this launcher, and a TERM to npm dies at that `sh` (shared/parent-watch.mjs). This
|
|
238
|
+
// launcher is then reparented, and the demo runs on holding its three ports while the reader believes
|
|
239
|
+
// it stopped. So the demo takes its parent's death as a TERM, and stops everything it started.
|
|
240
|
+
//
|
|
241
|
+
// THE DEMO ONLY. A foreground `start` or a `run` outliving the shell that started it can be what the
|
|
242
|
+
// reader meant: `nohup clearotron start &` on a server they are about to log out of, or an hours-long
|
|
243
|
+
// clearance they will not babysit. The demo is a replay nobody leaves running on purpose, and its own
|
|
244
|
+
// banner already says it lasts only as long as the command that started it.
|
|
245
|
+
if (verb === "demo") watchParent(() => { try { child.kill("SIGTERM"); } catch { /* already gone */ } });
|
|
232
246
|
}
|
|
233
247
|
|
|
234
248
|
// RESOLVE BOTH SIDES THROUGH SYMLINKS. `npm install` puts a symlink at node_modules/.bin/clearotron, so
|
package/bin/connect.mjs
CHANGED
|
@@ -40,7 +40,7 @@ import { createInterface } from "node:readline/promises";
|
|
|
40
40
|
import { requireInteractive } from "../shared/invocation.mjs"; // — a prompt with nobody to answer it
|
|
41
41
|
import { stdin, stdout } from "node:process";
|
|
42
42
|
import { readFileSync, writeFileSync, existsSync, copyFileSync, mkdirSync } from "node:fs";
|
|
43
|
-
import { join, dirname } from "node:path";
|
|
43
|
+
import { join, dirname, resolve } from "node:path";
|
|
44
44
|
import { homedir, userInfo } from "node:os";
|
|
45
45
|
import { fileURLToPath } from "node:url";
|
|
46
46
|
import { execFileSync } from "node:child_process";
|
|
@@ -48,7 +48,11 @@ import { createServer } from "node:net";
|
|
|
48
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 { isWsl, wslTarget } from "../shared/wsl.mjs"; // — and which distribution a row should start the server in
|
|
51
|
-
import { defaultDenylistPath, clientDoorAddress, clientDoorPort, clientDoorState, enablePlan, applyEnablePlan, describeChange, recordConnectKey, CLIENT_DOOR_UNIT } from "../shared/client-door.mjs";
|
|
51
|
+
import { defaultDenylistPath, clientDoorAddress, clientDoorPort, clientDoorState, enablePlan, applyEnablePlan, describeChange, recordConnectKey, CLIENT_DOOR_UNIT, demoTokenSecretPath, keyPurposeLine, demoConnectCommand } from "../shared/client-door.mjs";
|
|
52
|
+
import { readRunning } from "../shared/running-start.mjs";
|
|
53
|
+
import { readLocalCredential, passphraseResetCommand, INSTALL_CREDENTIAL_FILE } from "../driver/portal-local-auth.mjs";
|
|
54
|
+
import { invocationPrefix } from "../shared/invocation.mjs";
|
|
55
|
+
import { BRAND } from "../shared/brand.mjs";
|
|
52
56
|
import { mintToken, tokenId, resolvePerson, loadGrants } from "../shared/scope.mjs";
|
|
53
57
|
import { envFrom } from "../shared/env-aliases.mjs";
|
|
54
58
|
import { atomicWrite } from "../driver/progress.mjs";
|
|
@@ -654,6 +658,59 @@ function printSteps(offer, key) {
|
|
|
654
658
|
}
|
|
655
659
|
}
|
|
656
660
|
|
|
661
|
+
/**
|
|
662
|
+
* ── A RUNNING DEMO, NAMED BY ITS FOLDER (owner, 2026-09-19) ─────────────────────────────────────────
|
|
663
|
+
*
|
|
664
|
+
* The demo is how a newcomer meets the product, and the next thing they ask an assistant is to connect.
|
|
665
|
+
* The route that works is the demo's own client door with an account key, and reaching it took a key
|
|
666
|
+
* issued by hand and an address read out of the startup log. This does both: it finds the demo that is
|
|
667
|
+
* running from that folder, mints a key with the demo's own secret for the demo's one user, and prints
|
|
668
|
+
* the address and the key, and nothing else is changed. No unit is written and no setting touched: the
|
|
669
|
+
* demo's door is already open, and everything of the demo goes when it stops.
|
|
670
|
+
*
|
|
671
|
+
* The address route's steps are not printed. They are written for a door behind a sign-in service,
|
|
672
|
+
* reached over the web, and a demo's door is on this machine and takes the key instead.
|
|
673
|
+
*/
|
|
674
|
+
async function connectDemo({ base, dryRun, clientName = null, running = readRunning() } = {}) {
|
|
675
|
+
const at = resolve(base);
|
|
676
|
+
const prefix = invocationPrefix();
|
|
677
|
+
say("");
|
|
678
|
+
if (clientName) { say(` ${clientName}`); say(""); }
|
|
679
|
+
const demo = running.find((r) => r.demo && resolve(String(r.base ?? "")) === at);
|
|
680
|
+
if (!demo) {
|
|
681
|
+
say(` Not available here — no demo is running from ${at}.`);
|
|
682
|
+
say(` What would change it: start it with \`${prefix}clearotron demo --base ${/\s/.test(at) ? `"${at}"` : at}\`, then run this again.`);
|
|
683
|
+
return 1;
|
|
684
|
+
}
|
|
685
|
+
if (!demo.ports?.client) {
|
|
686
|
+
say(` Not available here — the demo running from ${at} has no client door open.`);
|
|
687
|
+
say(" What would change it: stop the demo and start it again; its output says why the door did not start.");
|
|
688
|
+
return 1;
|
|
689
|
+
}
|
|
690
|
+
const address = `http://${!demo.host || demo.host === "0.0.0.0" ? "127.0.0.1" : demo.host}:${demo.ports.client}/mcp`;
|
|
691
|
+
const credentialPath = join(at, INSTALL_CREDENTIAL_FILE);
|
|
692
|
+
let email = "demo@localhost";
|
|
693
|
+
try { email = readLocalCredential(credentialPath)?.email || email; } catch { /* the demo's own user stands */ }
|
|
694
|
+
if (dryRun) {
|
|
695
|
+
say(" (dry run — nothing was changed)");
|
|
696
|
+
say(` Address: ${address}`);
|
|
697
|
+
return 0;
|
|
698
|
+
}
|
|
699
|
+
let secret = "";
|
|
700
|
+
try { secret = readFileSync(demoTokenSecretPath(at), "utf8").trim(); } catch { /* said below */ }
|
|
701
|
+
if (!secret) {
|
|
702
|
+
say(` Not available here — the demo running from ${at} keeps no key secret this command can read.`);
|
|
703
|
+
say(" What would change it: run this as the user who started the demo.");
|
|
704
|
+
return 1;
|
|
705
|
+
}
|
|
706
|
+
const key = withSecret(secret, () => mintToken({ scope: "account", sub: email, ttlSec: 90 * 24 * 3600 }));
|
|
707
|
+
say(` ${keyPurposeLine({ email, brand: BRAND.name, reset: passphraseResetCommand({ prefix, credentialPath }) })}`);
|
|
708
|
+
say("");
|
|
709
|
+
say(` Address: ${address}`);
|
|
710
|
+
say(` Key: ${key}`);
|
|
711
|
+
return 0;
|
|
712
|
+
}
|
|
713
|
+
|
|
657
714
|
async function main() {
|
|
658
715
|
const argv = process.argv.slice(2);
|
|
659
716
|
// `--help` IS ANSWERED, not rejected. Running this bare drops the reader into a question, so "run it
|
|
@@ -674,6 +731,7 @@ async function main() {
|
|
|
674
731
|
say(" internet, with a key made for you now");
|
|
675
732
|
say(" --list the assistants this build knows");
|
|
676
733
|
say(" --dry-run say what would change, change nothing");
|
|
734
|
+
say(" --base <dir> a running demo's folder: mint a key for its client door, and print both");
|
|
677
735
|
say(" --allow-checkout-move");
|
|
678
736
|
say(" write this checkout's path even though services are running from");
|
|
679
737
|
say(" another one. Every unit's ExecStart follows that value, so the ones");
|
|
@@ -681,7 +739,7 @@ async function main() {
|
|
|
681
739
|
say("");
|
|
682
740
|
return 0;
|
|
683
741
|
}
|
|
684
|
-
const known = new Set(["--client", "--where", "--list", "--dry-run", "--allow-checkout-move", "--help", "-h"]);
|
|
742
|
+
const known = new Set(["--client", "--where", "--list", "--dry-run", "--base", "--allow-checkout-move", "--help", "-h"]);
|
|
685
743
|
const unknown = argv.filter((a) => a.startsWith("--") && !known.has(a));
|
|
686
744
|
if (unknown.length) {
|
|
687
745
|
console.error(`connect: unrecognised flag(s): ${unknown.join(", ")}`);
|
|
@@ -689,6 +747,13 @@ async function main() {
|
|
|
689
747
|
process.exit(2);
|
|
690
748
|
}
|
|
691
749
|
const dryRun = argv.includes("--dry-run");
|
|
750
|
+
const b = argv.indexOf("--base");
|
|
751
|
+
if (b >= 0) {
|
|
752
|
+
const base = argv[b + 1];
|
|
753
|
+
if (!base || base.startsWith("--")) { console.error("connect: --base needs the demo's folder."); process.exit(2); }
|
|
754
|
+
const c = argv.indexOf("--client");
|
|
755
|
+
return await connectDemo({ base, dryRun, clientName: c >= 0 ? clientById(argv[c + 1])?.name ?? null : null });
|
|
756
|
+
}
|
|
692
757
|
const w = argv.indexOf("--where");
|
|
693
758
|
if (w >= 0 && !Object.hasOwn(WHERE_FLAG, argv[w + 1] ?? "")) {
|
|
694
759
|
console.error(`connect: --where takes one of: ${Object.keys(WHERE_FLAG).join(", ")}`);
|
package/bin/key.mjs
CHANGED
|
@@ -28,9 +28,10 @@
|
|
|
28
28
|
// question would be two answers, and the wrong one would be the one nobody read.
|
|
29
29
|
import "../shared/env-local.mjs"; // side effect: apply the install's .env — FIRST, before anything reads process.env
|
|
30
30
|
import { invocationPrefix } from "../shared/invocation.mjs";
|
|
31
|
+
import { BRAND } from "../shared/brand.mjs";
|
|
31
32
|
import { existsSync, readFileSync } from "node:fs";
|
|
32
33
|
import { defaultGrantsPath, installPaths } from "./start.mjs";
|
|
33
|
-
import { demoTokenSecretPath } from "../shared/client-door.mjs";
|
|
34
|
+
import { demoTokenSecretPath, keyPurposeLine } from "../shared/client-door.mjs";
|
|
34
35
|
import { resolvePerson } from "../shared/scope.mjs"; // the door's own reading of the guest list, so one answer serves both
|
|
35
36
|
import { mintFromOptions } from "../mcp-server/mint-token.mjs";
|
|
36
37
|
|
|
@@ -96,6 +97,10 @@ try {
|
|
|
96
97
|
die(e.message);
|
|
97
98
|
}
|
|
98
99
|
|
|
100
|
+
// WHAT THIS KEY IS FOR, FIRST, in the owner's words (2026-09-19): a person who found this verb before the
|
|
101
|
+
// passphrase minted a key and could not sign in to the portal with it. Standard error, as every line here
|
|
102
|
+
// is, so the token stays alone on standard output.
|
|
103
|
+
console.error(keyPurposeLine({ email, brand: BRAND.name, reset: `${p}clearotron passphrase --reset` }));
|
|
99
104
|
for (const line of minted.notes) console.error(line);
|
|
100
105
|
|
|
101
106
|
// — found in review. A KEY FOR AN IDENTITY ON NO LIST IS A KEY THAT 403s. This command
|