clearotron 0.3.0-beta.4 → 0.3.0-beta.6
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 +9 -0
- package/INSTALL.md +13 -0
- package/bin/connect.mjs +3 -1
- package/bin/onboard.mjs +80 -28
- package/bin/start.mjs +82 -18
- package/bin/update.mjs +38 -6
- package/build-info.json +2 -2
- package/docs/architecture/04-configuration-reference.md +1 -0
- package/driver/CHANGELOG.md +36 -10
- package/driver/contract-e3-backlog.mjs +1 -1
- package/driver/demo-container.mjs +30 -2
- package/driver/engine/openai-agent.mjs +60 -2
- package/driver/enqueue-schema.mjs +3 -1
- package/driver/gateway.mjs +12 -0
- package/driver/package.json +1 -1
- package/driver/portal-local-auth.mjs +35 -3
- package/driver/portal-service.mjs +80 -18
- package/driver/portal-upstream.mjs +20 -1
- package/driver/search-policy.mjs +7 -2
- package/driver/suite-census.json +71 -23
- package/mcp-server/CHANGELOG.md +12 -2
- package/mcp-server/CONNECT.md +3 -2
- package/mcp-server/lib/options.mjs +13 -5
- package/mcp-server/package.json +1 -1
- package/package.json +1 -1
- package/portal-ui/dist/assets/{index-DWYCsOCJ.js → index-DBUVdQT-.js} +6 -0
- package/portal-ui/dist/index.html +1 -1
- package/portal-ui/package.json +1 -1
- package/providers/oauth-mcp-bridge/CHANGELOG.md +8 -0
- package/providers/oauth-mcp-bridge/package.json +1 -1
- package/scripts/e2e.mjs +84 -12
- package/scripts/engine-probe.mjs +2 -2
- package/scripts/env-classify.mjs +3 -0
- package/shared/connect-clients.mjs +8 -0
- package/shared/names-in-force.mjs +1 -0
- package/shared/permanent-install.mjs +167 -0
- package/shared/stdio-connect.mjs +32 -14
- package/shared/verb-shim.mjs +10 -1
- package/shared/wsl.mjs +23 -0
package/.env.example
CHANGED
|
@@ -399,6 +399,15 @@ CLEAROTRON_DEMO=
|
|
|
399
399
|
# effect: deployment
|
|
400
400
|
# CLEAROTRON_INVOKED_AS=
|
|
401
401
|
|
|
402
|
+
# Marks an install that has already moved itself out of npm's npx cache. `npx clearotron install` first
|
|
403
|
+
# installs the same version under ~/.local and then runs that copy's install, with this set to 1, so the
|
|
404
|
+
# copy does not try to move itself a second time. Unset, an install running from npx's cache moves.
|
|
405
|
+
# Set by bin/onboard.mjs on the one process it starts, never by an operator, so the row is left
|
|
406
|
+
# commented. It is listed for the reason CLEAROTRON_DEMO is: the catalogue is about what an operator can
|
|
407
|
+
# FIND. Read by bin/onboard.mjs.
|
|
408
|
+
# effect: deployment
|
|
409
|
+
# CLEAROTRON_RELOCATED=
|
|
410
|
+
|
|
402
411
|
# How many client what-ifs the runner drains at once (default 1). A what-if re-runs ONE stage of a run
|
|
403
412
|
# that already exists, in a sandbox, at a client's request — the owner opened it to clients on
|
|
404
413
|
# 2026-08-27 and ruled SPEND controls out with it ("ignore the call spend"). This is not one.
|
package/INSTALL.md
CHANGED
|
@@ -64,6 +64,15 @@ run is [mcp-server/CONNECT.md](mcp-server/CONNECT.md), and why something is the
|
|
|
64
64
|
npx clearotron install
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
+
Run from `npx`, the install first installs Clearotron under `~/.local`, as `npm install -g --prefix
|
|
68
|
+
~/.local` would, and finishes from there. That way the `clearotron` command and your assistant's connection
|
|
69
|
+
do not point into npm's temporary cache, which npm replaces on an update and deletes when it cleans up.
|
|
70
|
+
Later, the `update` command updates that copy in place.
|
|
71
|
+
|
|
72
|
+
On WSL, a program on the Windows side can hold a port that WSL reports as free, and the browser reaches
|
|
73
|
+
it first. VS Code's Remote-SSH port forwarding is the common case. If the page that opens is not this
|
|
74
|
+
install's sign-in, run the same command again with `--port 28802`, or any free number.
|
|
75
|
+
|
|
67
76
|
A *hosted* deployment needs Linux for one further thing, the systemd outbox trigger —
|
|
68
77
|
[driver/systemd/README.md](driver/systemd/README.md).
|
|
69
78
|
- **A reasoning CLI on your `PATH`, signed in.** This is the prerequisite people miss. Every stage runs
|
|
@@ -749,9 +758,13 @@ Use the demo to see what this system produces. Use `npx clearotron start` to run
|
|
|
749
758
|
|
|
750
759
|
- Generates `PORTAL_SECRET` and `TRADEMARK_MCP_TOKEN_SECRET` and **appends** them to `~/.config/clearotron/.env` at
|
|
751
760
|
mode 600. Append, never rewrite: that file also holds the credentials `npx clearotron install` collected.
|
|
761
|
+
It also records where saved searches are kept, `CLEAROTRON_RECIPES_DIR` and `RECIPE_REPO_ROOT`, so
|
|
762
|
+
`clearotron doctor` and a connected assistant read the same saved searches as the portal.
|
|
752
763
|
- Creates `~/trademark/` — `pool/`, `workspace/`, `queue/`, `outbox/`, `locks/`, an empty grants file,
|
|
753
764
|
and a small git repository for saved searches. Same base directory `npx clearotron install` uses, so whichever
|
|
754
765
|
of the two you ran first, the other finds the same install. Move it with `npx clearotron start --base <dir>`.
|
|
766
|
+
That does not move anything the env file already names: the saved-search lines above, and the data
|
|
767
|
+
directories `npx clearotron install` wrote, keep pointing at the old place until you edit them.
|
|
755
768
|
- Mints your sign-in passphrase and **prints it once**. Write it down. It is stored as a scrypt digest in
|
|
756
769
|
`~/trademark/portal-local-credential.json`, nothing can read it back, and no later start reprints it. To
|
|
757
770
|
get a new one, run `clearotron passphrase --reset`. An install that has been signing in with
|
package/bin/connect.mjs
CHANGED
|
@@ -47,6 +47,7 @@ import { execFileSync } from "node:child_process";
|
|
|
47
47
|
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
|
+
import { isWsl } from "../shared/wsl.mjs";
|
|
50
51
|
import { defaultDenylistPath, clientDoorAddress, clientDoorPort, clientDoorState, enablePlan, applyEnablePlan, describeChange, recordConnectKey, CLIENT_DOOR_UNIT } from "../shared/client-door.mjs";
|
|
51
52
|
import { mintToken, tokenId, resolvePerson, loadGrants } from "../shared/scope.mjs";
|
|
52
53
|
import { envFrom } from "../shared/env-aliases.mjs";
|
|
@@ -205,7 +206,8 @@ function deploymentHas(env = process.env) {
|
|
|
205
206
|
return {
|
|
206
207
|
// EVERY SHAPE, RESOLVED ONCE. A row picks its own; nothing here knows a client's name.
|
|
207
208
|
stdioRoutes: Object.fromEntries(Object.keys(STDIO_SHAPES).map((shape) =>
|
|
208
|
-
[shape, stdioConnectFor(shape, { workDir: env.CLEAROTRON_WORK_DIR || null })])),
|
|
209
|
+
[shape, stdioConnectFor(shape, { workDir: env.CLEAROTRON_WORK_DIR || null, reportsDir: env.CLEAROTRON_REPORTS_DIR || null })])),
|
|
210
|
+
wsl: isWsl({ env }),
|
|
209
211
|
// WHERE THE DOOR BINDS — not an address handed to any assistant. It is the loopback address the
|
|
210
212
|
// unit listens on, and `enablePlan` needs it to write the unit. It used to be passed to the
|
|
211
213
|
// resolver as `localAddress` and served to Cowork as somewhere to connect, which is the false
|
package/bin/onboard.mjs
CHANGED
|
@@ -68,6 +68,8 @@ import { nodeFloorVerdict } from "../shared/node-floor.mjs"; // — the floor
|
|
|
68
68
|
import { invocationForm } from "../shared/invocation.mjs"; // — and WHY that form
|
|
69
69
|
import { standFrom } from "../shared/invocation.mjs"; // is this tree one npm replaces?
|
|
70
70
|
import { installShim } from "../shared/verb-shim.mjs"; // — the verb goes on PATH
|
|
71
|
+
import { relocationPlan } from "../shared/permanent-install.mjs"; // — and the program out of npx's cache
|
|
72
|
+
import { isWsl } from "../shared/wsl.mjs"; // — one answer to "is this WSL", shared with the connect lines
|
|
71
73
|
import { styleFor, banner } from "../shared/tty-style.mjs"; // — weight where the meaning is
|
|
72
74
|
import { bracketAsciiCells, BRAND } from "../shared/brand.mjs"; // F18 — the mark, from the geometry the SVG already uses
|
|
73
75
|
// THE REFUSALS ABOUT THE SIGN-IN ADDRESS ITSELF, shared with `bin/start.mjs`. Two copies would be a
|
|
@@ -99,7 +101,15 @@ import { entrypointOf } from "../driver/systemd/install-census.mjs"; //
|
|
|
99
101
|
import { overlayReport, renderOverlayReport } from "../shared/doctrine-overlay.mjs"; // — the doctor reports the overlay
|
|
100
102
|
import { whereSavesGo, storeCommitRefusal, storeInRepo, storeOutsideRepoMessage, resolveStoreRepoRoot } from "../shared/store-in-repo.mjs"; // — doctor says where a portal save goes once it is committed, and why saved searches are off
|
|
101
103
|
import { engineInventory, engineMode, ENGINE_MODES } from "../driver/config-inventory.mjs"; //
|
|
102
|
-
import { probeEngineTurn, probeFailureText,
|
|
104
|
+
import { probeEngineTurn, probeFailureText, PROBE_TIMEOUT_SEC, engineEnvKeys } from "../driver/engine/probe.mjs";
|
|
105
|
+
|
|
106
|
+
// THE PROVING SENTENCES NAME NO MODEL. They printed the driver's tier word, which is an Anthropic model's
|
|
107
|
+
// name, on both engines, so a codex user was told setup was about to spend on a model family they do not
|
|
108
|
+
// use. "Its cheapest model" is true of either engine and brands neither.
|
|
109
|
+
export const probingLine = (engineId) =>
|
|
110
|
+
`Probing ${engineId} with one turn on its cheapest model (this SPENDS; ${PROBE_TIMEOUT_SEC}s ceiling)…`;
|
|
111
|
+
export const proveQuestion = ({ engineId, lane }) =>
|
|
112
|
+
`Prove ${engineId} on the ${lane} lane now with one turn on its cheapest model (a few tokens, ${PROBE_TIMEOUT_SEC}s ceiling)?`;
|
|
103
113
|
import { runRequiredNames, missingRequirements, REGISTER_ENV, ENGINE_ENV } from "../driver/run-requirements.mjs"; // the order-time gate's own question, asked here rather than restated
|
|
104
114
|
import { pinEnv, envFrom } from "../shared/env-aliases.mjs";
|
|
105
115
|
import { isEntrypoint } from "../shared/is-entrypoint.mjs"; // — one entry-point test, all spellings
|
|
@@ -695,23 +705,8 @@ export function resolveEngineBin(bin, { env = process.env, wsl = null, onWindows
|
|
|
695
705
|
/** A path on a Windows drive as WSL mounts it. */
|
|
696
706
|
export const ON_A_WINDOWS_DRIVE = /^\/mnt\/[a-z]\//i;
|
|
697
707
|
|
|
698
|
-
/**
|
|
699
|
-
|
|
700
|
-
*
|
|
701
|
-
* BOTH SIGNALS INJECTABLE, for the reason `platformEngineRefusal` gives: the readers this protects
|
|
702
|
-
* are the ones who cannot run this suite to find out, so a Linux runner has to be able to drive both
|
|
703
|
-
* answers rather than read the source and agree with it.
|
|
704
|
-
*
|
|
705
|
-
* A READ THAT FAILS ANSWERS "NOT WSL", and that is the direction that changes nothing: it leaves the
|
|
706
|
-
* resolution exactly as it was before this existed. Claiming WSL on a could-not-read would start
|
|
707
|
-
* refusing candidates under /mnt on an ordinary Linux box with an ordinary mount.
|
|
708
|
-
*/
|
|
709
|
-
export function isWsl({ env = process.env, procVersion = null } = {}) {
|
|
710
|
-
if (String(env.WSL_DISTRO_NAME ?? "").trim()) return true;
|
|
711
|
-
if (String(env.WSL_INTEROP ?? "").trim()) return true;
|
|
712
|
-
const v = procVersion ?? (() => { try { return readFileSync("/proc/version", "utf8"); } catch { return ""; } })();
|
|
713
|
-
return /microsoft|wsl/i.test(v);
|
|
714
|
-
}
|
|
708
|
+
/** Whether this is a Linux running under Windows: the one answer, from shared/wsl.mjs. */
|
|
709
|
+
export { isWsl };
|
|
715
710
|
|
|
716
711
|
/**
|
|
717
712
|
* What to say about candidates passed over because they sit on a Windows drive — or `null` when none
|
|
@@ -1206,7 +1201,7 @@ export async function runCheck() {
|
|
|
1206
1201
|
// deliberate — a second reader would drift from this one exactly as the composer and the checker did
|
|
1207
1202
|
// in F41, and the drift is invisible because both sides keep passing their own arms.
|
|
1208
1203
|
const unitDir = join(homedir(), ".config", "systemd", "user");
|
|
1209
|
-
const { BACKGROUND_UNITS } = await import(pathToFileURL(join(REPO, "bin", "start.mjs")).href);
|
|
1204
|
+
const { BACKGROUND_UNITS, startPaths } = await import(pathToFileURL(join(REPO, "bin", "start.mjs")).href);
|
|
1210
1205
|
const hosted = BACKGROUND_UNITS.some((u) => existsSync(join(unitDir, u)));
|
|
1211
1206
|
const unitEnv = hosted
|
|
1212
1207
|
? unitEnvironment({
|
|
@@ -1440,7 +1435,7 @@ export async function runCheck() {
|
|
|
1440
1435
|
} else if (!bin.executable || bin.relative) {
|
|
1441
1436
|
info("not probed — there is no usable binary to probe. Fix the line above first.");
|
|
1442
1437
|
} else {
|
|
1443
|
-
say(`\n
|
|
1438
|
+
say(`\n ${probingLine(engineId)}`);
|
|
1444
1439
|
// The engine as a RUN would see it: environment first, .env behind it. Only the engine-selection
|
|
1445
1440
|
// keys — the probe must bill exactly the way this box bills and moves no other variable.
|
|
1446
1441
|
const probeEnv = { ...process.env };
|
|
@@ -1628,6 +1623,13 @@ export async function runCheck() {
|
|
|
1628
1623
|
// because a reader debugging `clearotron run` by hand IS this process.
|
|
1629
1624
|
info(`this command's own process ${where} — a CLI is not started by the units' EnvironmentFile, so `
|
|
1630
1625
|
+ "that differs by design and is not what a run uses");
|
|
1626
|
+
} else if (!hosted && svcCustomers?.from === serviceEnvLabel) {
|
|
1627
|
+
// NAMED IN THE ENV FILE ALONE, ON AN INSTALL WITH NO UNITS. `clearotron start` loads that file and
|
|
1628
|
+
// its services inherit the store; this command does not load it, so its own process fell back to
|
|
1629
|
+
// the bundled roster and printed that as the answer, one line below a ✓ for the same variable.
|
|
1630
|
+
ok(`the services resolve profiles from ${svcCustomers.v} (${svcCustomers.from})`);
|
|
1631
|
+
info(`this command's own process ${where} — it does not load ${serviceEnvFile}, so that differs by `
|
|
1632
|
+
+ `design and is not what \`${invoke("start")}\` hands the services`);
|
|
1631
1633
|
} else {
|
|
1632
1634
|
if (r.situation === "overlay" && !r.findings.length) ok(`${where} — the configured store`);
|
|
1633
1635
|
else if (r.situation === "bundled-fallback") info(`${where} — THE BUNDLED DEMO ROSTER, because CLEAROTRON_CUSTOMERS_DIR is unset. Legitimate on a generic-defaults install; a fallback either way, and it is what a misconfigured deployment also looks like`);
|
|
@@ -1638,7 +1640,8 @@ export async function runCheck() {
|
|
|
1638
1640
|
info(`the units are installed but their environment could not be read (${unitEnv?.why ?? "no reason given"}) — `
|
|
1639
1641
|
+ "what the services resolve is not judged here, and the line above is this process's own answer");
|
|
1640
1642
|
info("what a RUN used is its own `profile-store` journal line — this command reports what the "
|
|
1641
|
-
+ (hosted ? "units' file says, which the running services read at their own start"
|
|
1643
|
+
+ (hosted ? "units' file says, which the running services read at their own start"
|
|
1644
|
+
: `environment you are typing in and your environment file say, which \`${invoke("start")}\` reads at its own start`));
|
|
1642
1645
|
// ── AND WHO IS ACTUALLY IN IT ─────────────────────────────────────────
|
|
1643
1646
|
//
|
|
1644
1647
|
// The line above names the STORE. An operator who has just configured one wants to know their
|
|
@@ -1807,16 +1810,32 @@ export async function runCheck() {
|
|
|
1807
1810
|
// be read, the lines above already said so and nothing is judged here. A store that is configured but
|
|
1808
1811
|
// holds a file that cannot be read fails every company's saved searches, in the portal and the
|
|
1809
1812
|
// connector alike, so that is read here too.
|
|
1813
|
+
//
|
|
1814
|
+
// WITH NO UNITS, THE SERVICES ARE `clearotron start`'s CHILDREN, and it hands every one of them a store
|
|
1815
|
+
// whether or not the env file names it. So "off" is only ever true of a hosted box. This read the env
|
|
1816
|
+
// file alone, and on a local install started before `start` wrote the store there, it told a portal
|
|
1817
|
+
// that was listing saved searches that they were off.
|
|
1810
1818
|
if (!hosted || serviceKnown) {
|
|
1811
|
-
|
|
1819
|
+
// Layered as effectiveForService layers it — this command's environment over the services' file — so
|
|
1820
|
+
// the repository root is read from the same place the store directory is.
|
|
1821
|
+
let storeEnv = { ...(serviceFileEnv ?? {}), ...process.env };
|
|
1822
|
+
let recipesSet = effectiveForService("CLEAROTRON_RECIPES_DIR");
|
|
1823
|
+
let handedBy = "";
|
|
1824
|
+
if (!hosted && !recipesSet?.v) {
|
|
1825
|
+
const handed = startPaths({ env: storeEnv });
|
|
1826
|
+
recipesSet = { v: handed.recipes, from: "clearotron start", name: "CLEAROTRON_RECIPES_DIR" };
|
|
1827
|
+
storeEnv = { ...storeEnv, RECIPE_REPO_ROOT: handed.configStore };
|
|
1828
|
+
handedBy = ` — where \`${invoke("start")}\` puts them`;
|
|
1829
|
+
}
|
|
1812
1830
|
const recipesDir = recipesSet?.v || null;
|
|
1813
1831
|
if (!recipesDir) {
|
|
1814
1832
|
info("saved searches are off: CLEAROTRON_RECIPES_DIR is not set, so Custom searches in the portal and the "
|
|
1815
1833
|
+ "connector offer none. Name a directory inside a git repository to switch them on");
|
|
1834
|
+
} else if (handedBy && !existsSync(join(storeEnv.RECIPE_REPO_ROOT, ".git"))) {
|
|
1835
|
+
// NOT CREATED YET IS NOT UNREADABLE. The store does not exist until the first start makes it, and
|
|
1836
|
+
// reading it now would report a missing directory as a broken one.
|
|
1837
|
+
info(`saved searches switch on at the first \`${invoke("start")}\`, which creates their store in ${recipesDir}`);
|
|
1816
1838
|
} else {
|
|
1817
|
-
// Layered as effectiveForService layers it — this command's environment over the services' file — so
|
|
1818
|
-
// the repository root is read from the same place the store directory above was.
|
|
1819
|
-
const storeEnv = { ...(serviceFileEnv ?? {}), ...process.env };
|
|
1820
1839
|
const resolved = resolveStoreRepoRoot({ names: ["RECIPE_REPO_ROOT", "PROFILE_REPO_ROOT"], fallback: REPO, env: storeEnv });
|
|
1821
1840
|
const reach = storeInRepo(recipesDir, resolved.root);
|
|
1822
1841
|
if (!reach.ok) {
|
|
@@ -1828,8 +1847,8 @@ export async function runCheck() {
|
|
|
1828
1847
|
try { loadRecipes({ dir: recipesDir, force: true }); } catch (e) { unreadable = String(e?.message ?? e).split("\n")[0]; }
|
|
1829
1848
|
if (unreadable) {
|
|
1830
1849
|
warn(`saved searches cannot be read from ${recipesDir}: ${unreadable}. Every company's saved searches fail `
|
|
1831
|
-
+ "to load, in the portal and the connector, until
|
|
1832
|
-
} else ok(`saved searches are read from ${recipesDir}, and saves are committed in ${reach.repo}`);
|
|
1850
|
+
+ "to load, in the portal and the connector, until it is fixed");
|
|
1851
|
+
} else ok(`saved searches are read from ${recipesDir}${handedBy}, and saves are committed in ${reach.repo}`);
|
|
1833
1852
|
}
|
|
1834
1853
|
}
|
|
1835
1854
|
}
|
|
@@ -2901,6 +2920,39 @@ if (!input.isTTY) {
|
|
|
2901
2920
|
process.exit(2);
|
|
2902
2921
|
}
|
|
2903
2922
|
|
|
2923
|
+
// ── OUT OF NPX'S CACHE, BEFORE ANYTHING IS WRITTEN ─────────────────────────────────────────────────────
|
|
2924
|
+
//
|
|
2925
|
+
// Run from npx, this program lives in npm's cache, and everything below would be wired to a directory npm
|
|
2926
|
+
// deletes: the launcher, and the connect line an assistant is registered with. So the install first puts
|
|
2927
|
+
// this same version somewhere permanent (shared/permanent-install.mjs) and runs itself from there. Nothing
|
|
2928
|
+
// has been written yet, so a failure here costs nothing, and it stops rather than carrying on: an install
|
|
2929
|
+
// finished from the cache is the defect, not a fallback.
|
|
2930
|
+
const move = relocationPlan();
|
|
2931
|
+
if (move && !move.skip && process.env.CLEAROTRON_RELOCATED !== "1") {
|
|
2932
|
+
say(`\n This is running from npm's temporary npx cache. Installing clearotron ${move.version} to ${move.prefix}`);
|
|
2933
|
+
say(" first, so the launcher and your assistants keep working after npm cleans that cache or you update.\n");
|
|
2934
|
+
// The npm that launched this, when npm says which: no second npm is guessed at.
|
|
2935
|
+
const npmCli = process.env.npm_execpath;
|
|
2936
|
+
const r = npmCli && existsSync(npmCli)
|
|
2937
|
+
? spawnSync(process.execPath, [npmCli, ...move.npmArgs], { stdio: "inherit" })
|
|
2938
|
+
: spawnSync("npm", move.npmArgs, { stdio: "inherit" });
|
|
2939
|
+
if (r.status !== 0 || !existsSync(move.entry)) {
|
|
2940
|
+
console.error(`\n Could not install clearotron to ${move.prefix}${r.error ? ` (${r.error.message})` : ""}. Nothing was installed, and nothing of yours was changed.`);
|
|
2941
|
+
console.error(` Run \`npm install --global --prefix ${move.prefix} clearotron@${move.version}\`, then \`${join(move.prefix, "bin", "clearotron")} install\`.\n`);
|
|
2942
|
+
process.exit(1);
|
|
2943
|
+
}
|
|
2944
|
+
// THE REST OF THE INSTALL RUNS FROM THE PERMANENT COPY, with npm's marks of an npx arrival taken off, so
|
|
2945
|
+
// it prints the commands of the install it now is.
|
|
2946
|
+
const env = { ...process.env, CLEAROTRON_RELOCATED: "1" };
|
|
2947
|
+
for (const k of ["npm_command", "npm_lifecycle_event", "npm_execpath"]) delete env[k];
|
|
2948
|
+
const moved = spawnSync(process.execPath, [move.entry, "install", ...process.argv.slice(2)], { stdio: "inherit", env });
|
|
2949
|
+
process.exit(moved.status ?? 1);
|
|
2950
|
+
}
|
|
2951
|
+
if (move?.skip) {
|
|
2952
|
+
console.error(`\n Note: this is running from npm's temporary npx cache and cannot be moved out of it here (${move.skip}).`);
|
|
2953
|
+
console.error(" It will stop working when npm cleans that cache. `npm install -g clearotron` installs it permanently.\n");
|
|
2954
|
+
}
|
|
2955
|
+
|
|
2904
2956
|
// A credential typed at a prompt is echoed by the terminal and then sits in scrollback, in tmux history,
|
|
2905
2957
|
// in whatever the reader pastes into a bug report. So the echo is muted while a secret is being typed:
|
|
2906
2958
|
// the output stream readline writes through drops everything while `muted` is set.
|
|
@@ -3281,7 +3333,7 @@ try {
|
|
|
3281
3333
|
// skipping it costs an hour later.
|
|
3282
3334
|
info("A file that exists is not an engine that works — so setup tries one before writing anything.");
|
|
3283
3335
|
info("It takes a few seconds here. Skipped, a broken engine surfaces an hour into a real search.");
|
|
3284
|
-
if (!await confirm(
|
|
3336
|
+
if (!await confirm(proveQuestion({ engineId: pick.id, lane: authPick.id }), true)) {
|
|
3285
3337
|
info("Not proven, so not written. Pick again — the last row configures no engine at all.");
|
|
3286
3338
|
continue;
|
|
3287
3339
|
}
|
package/bin/start.mjs
CHANGED
|
@@ -300,6 +300,22 @@ export function resolvePorts(env = {}) {
|
|
|
300
300
|
client: one("CLIENT_MCP_HTTP_PORT", clientDoorPort({})) };
|
|
301
301
|
}
|
|
302
302
|
|
|
303
|
+
/**
|
|
304
|
+
* WHAT TO DO WHEN THE PAGE THAT OPENS IS NOT OURS. Printed under every "Open" line.
|
|
305
|
+
*
|
|
306
|
+
* A port can be free where this runs and taken where the browser runs: on WSL, a Windows-side listener
|
|
307
|
+
* (VS Code's Remote-SSH forwarding is the one measured, 2026-09-11) answers 127.0.0.1 before WSL does. The
|
|
308
|
+
* doors bind cleanly, the in-use detection has nothing to see, and the browser shows somebody else's page
|
|
309
|
+
* with nothing on this screen saying so. `--port` already moves all three doors; the reader has to be told
|
|
310
|
+
* about it at the moment the address is handed over, which is here.
|
|
311
|
+
*/
|
|
312
|
+
export function foreignPageHint(verb) {
|
|
313
|
+
return [
|
|
314
|
+
"If the page that opens is not this install's sign-in, another program on this machine holds that",
|
|
315
|
+
`port from outside this environment. Run \`${invoke(verb)} --port 28802\` (or any free number) instead.`,
|
|
316
|
+
];
|
|
317
|
+
}
|
|
318
|
+
|
|
303
319
|
/**
|
|
304
320
|
* Apply `--port <n>` to the three doors.
|
|
305
321
|
*
|
|
@@ -355,6 +371,50 @@ export function defaultGrantsPath({ env = process.env, demo = false } = {}) {
|
|
|
355
371
|
return installPaths(join(homedir(), demo ? "trademark-demo" : "trademark")).grants;
|
|
356
372
|
}
|
|
357
373
|
|
|
374
|
+
/**
|
|
375
|
+
* The paths `clearotron start` hands its services: the install's layout under `base`, with whatever the
|
|
376
|
+
* environment already names winning over the layout's default, as `start` has always applied it.
|
|
377
|
+
*
|
|
378
|
+
* EXPORTED FOR THE ONE OTHER READER THAT MUST AGREE WITH IT. On an install with no background units,
|
|
379
|
+
* `clearotron doctor` has no unit file to read the services' environment from: the services are this
|
|
380
|
+
* command's children and get the saved-search store from here. An install started before the store was
|
|
381
|
+
* written to the env file has no line there to read, so doctor asks this function for it, and the
|
|
382
|
+
* answer it prints and the one the services were given have one author.
|
|
383
|
+
*
|
|
384
|
+
* NOT IN A DEMO. Nothing the environment says about an install is the demo's: with the reader's settings
|
|
385
|
+
* in force, 0.3.0-beta.1's demo seeded its example reports into their real archive.
|
|
386
|
+
*/
|
|
387
|
+
export function startPaths({ env = process.env, base = join(homedir(), "trademark"), demo = false } = {}) {
|
|
388
|
+
const paths = installPaths(base);
|
|
389
|
+
if (demo) return paths;
|
|
390
|
+
for (const [k, name] of [["pool", "CLEAROTRON_REPORTS_DIR"], ["workspace", "CLEAROTRON_WORK_DIR"], ["queue", "CLEAROTRON_QUEUE_DIR"],
|
|
391
|
+
["outbox", "CLEAROTRON_OUTBOX_DIR"], ["locks", "CLEAROTRON_RUN_LOCK_DIR"], ["grants", "CLEAROTRON_ACCESS_FILE"],
|
|
392
|
+
["recipes", "CLEAROTRON_RECIPES_DIR"]]) if (env[name]) paths[k] = env[name];
|
|
393
|
+
if (env.RECIPE_REPO_ROOT) paths.configStore = env.RECIPE_REPO_ROOT;
|
|
394
|
+
if (env.PORTAL_AUDIT) paths.audit = env.PORTAL_AUDIT;
|
|
395
|
+
return paths;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* The saved-search store, as the env file must record it for every reader OUTSIDE this command's tree.
|
|
400
|
+
*
|
|
401
|
+
* The services are handed the store by `childEnv`. A connector an assistant launches, `clearotron doctor`
|
|
402
|
+
* and every other hand-run command are not this command's children: they read the env file, and the
|
|
403
|
+
* file never named the store. So on a local install the portal listed a company's saved searches, the
|
|
404
|
+
* connected assistant was told there were none, and doctor said saved searches were off. Written
|
|
405
|
+
* add-only beside the secrets, as `npx clearotron install` writes the reports and work directories: a
|
|
406
|
+
* line an operator wrote wins.
|
|
407
|
+
*/
|
|
408
|
+
export function storesForOtherReaders(paths) {
|
|
409
|
+
return { CLEAROTRON_RECIPES_DIR: paths.recipes, RECIPE_REPO_ROOT: paths.configStore };
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Written above each line, so a reader who moves the install sees that these two move with it.
|
|
413
|
+
const STORE_NOTES = {
|
|
414
|
+
CLEAROTRON_RECIPES_DIR: "Where saved searches are kept. `clearotron start` created it; edit both lines if you move the install.",
|
|
415
|
+
RECIPE_REPO_ROOT: "The git repository saved searches are committed in, which holds the directory above.",
|
|
416
|
+
};
|
|
417
|
+
|
|
358
418
|
/** Everything this install keeps on disk, under one base directory. */
|
|
359
419
|
export function installPaths(base) {
|
|
360
420
|
return {
|
|
@@ -846,20 +906,11 @@ if (isMain) {
|
|
|
846
906
|
const DEMO = argv.includes("--demo");
|
|
847
907
|
// The same base `npm run setup` writes under, so whichever of the two a reader ran first, the other
|
|
848
908
|
// finds the same install rather than a second one beside it.
|
|
849
|
-
const paths = installPaths(flag("--base", join(homedir(), DEMO ? "trademark-demo" : "trademark")));
|
|
850
909
|
// Whatever the environment already says wins over the base-derived default, for every path — a reader
|
|
851
|
-
// who ran `npm run setup` has these in .env already and this must not move their data.
|
|
852
|
-
//
|
|
853
|
-
//
|
|
854
|
-
|
|
855
|
-
// rather than deleted from the environment, so a real start cannot be reached by this branch at all.
|
|
856
|
-
if (!DEMO) {
|
|
857
|
-
for (const [k, name] of [["pool", "CLEAROTRON_REPORTS_DIR"], ["workspace", "CLEAROTRON_WORK_DIR"], ["queue", "CLEAROTRON_QUEUE_DIR"],
|
|
858
|
-
["outbox", "CLEAROTRON_OUTBOX_DIR"], ["locks", "CLEAROTRON_RUN_LOCK_DIR"], ["grants", "CLEAROTRON_ACCESS_FILE"],
|
|
859
|
-
["recipes", "CLEAROTRON_RECIPES_DIR"]]) if (process.env[name]) paths[k] = process.env[name];
|
|
860
|
-
if (process.env.RECIPE_REPO_ROOT) paths.configStore = process.env.RECIPE_REPO_ROOT;
|
|
861
|
-
if (process.env.PORTAL_AUDIT) paths.audit = process.env.PORTAL_AUDIT;
|
|
862
|
-
}
|
|
910
|
+
// who ran `npm run setup` has these in .env already and this must not move their data. Not in a demo,
|
|
911
|
+
// which `startPaths` says why. One author, because `doctor` asks the same function what the services
|
|
912
|
+
// were handed.
|
|
913
|
+
const paths = startPaths({ env: process.env, base: flag("--base", join(homedir(), DEMO ? "trademark-demo" : "trademark")), demo: DEMO });
|
|
863
914
|
// ── THIS INSTALL'S FIRST START, read before this start writes either file that answers it ────────────
|
|
864
915
|
//
|
|
865
916
|
// The grants file and the config store's repository are both written further down, on every start
|
|
@@ -1117,14 +1168,19 @@ if (isMain) {
|
|
|
1117
1168
|
const portalSecret = secretFor("PORTAL_SECRET");
|
|
1118
1169
|
const tokenSecret = secretFor("TRADEMARK_MCP_TOKEN_SECRET");
|
|
1119
1170
|
if (!process.env.PORTAL_LOCAL_USER) generated.PORTAL_LOCAL_USER = user;
|
|
1171
|
+
const stores = DEMO ? {} : storesForOtherReaders(paths);
|
|
1120
1172
|
|
|
1121
1173
|
// A DEMO WRITES NO SECRETS AND NO ADDRESS. They are generated per run and live in memory only, which
|
|
1122
1174
|
// is the same posture the ops key already has here — and it is what makes "removing the demo is one
|
|
1123
1175
|
// directory" true rather than nearly true.
|
|
1124
|
-
|
|
1176
|
+
//
|
|
1177
|
+
// THE STORES ARE WRITTEN EVEN WHEN NO SECRET IS. An install whose secrets are already in the file is
|
|
1178
|
+
// exactly the install whose connector and doctor could not see its saved searches, and a gate on the
|
|
1179
|
+
// secrets alone would never reach it.
|
|
1180
|
+
if ((Object.keys(generated).length || Object.keys(stores).length) && !DEMO) {
|
|
1125
1181
|
let existing = "";
|
|
1126
1182
|
try { existing = readFileSync(ENV_PATH, "utf8"); } catch (e) { if (e.code !== "ENOENT") fatal(`${ENV_PATH} exists but could not be read (${e.code}).`); }
|
|
1127
|
-
const merged = mergeEnvFile(existing, generated);
|
|
1183
|
+
const merged = mergeEnvFile(existing, { ...generated, ...stores }, { notes: STORE_NOTES });
|
|
1128
1184
|
if (merged.added.length) {
|
|
1129
1185
|
// ONE WRITER FOR EVERY FILE THAT HOLDS CREDENTIALS, and it creates the directory. This site had its
|
|
1130
1186
|
// own copy of the write and did not learn what the wizard's copy learned when `.env` moved under
|
|
@@ -1135,7 +1191,7 @@ if (isMain) {
|
|
|
1135
1191
|
} catch (e) {
|
|
1136
1192
|
fatal(`could not write ${ENV_PATH} (${String(e?.message ?? e)}).`);
|
|
1137
1193
|
}
|
|
1138
|
-
// NAMES only.
|
|
1194
|
+
// NAMES only. Some of what this file holds is credentials.
|
|
1139
1195
|
say(` wrote ${merged.added.join(", ")} to ${ENV_PATH} (mode 600)`);
|
|
1140
1196
|
}
|
|
1141
1197
|
}
|
|
@@ -1330,8 +1386,12 @@ if (isMain) {
|
|
|
1330
1386
|
// SEEDED FROM A COPY, for the reason the player publishes from one: republishing writes a receipt
|
|
1331
1387
|
// into the run directory it reads, and `demo/` is tracked. This is the path a reader actually takes
|
|
1332
1388
|
// — `clearotron demo` hands over to this — so fixing the player alone left the defect where it was.
|
|
1333
|
-
const { publishSource } = await import("../driver/demo-container.mjs");
|
|
1389
|
+
const { publishSource, seedDemoRuns } = await import("../driver/demo-container.mjs");
|
|
1334
1390
|
const seed = await seedPool({ pool: paths.pool, examplesDir: publishSource(join(REPO, "demo"), { repoRoot: REPO }), republish: republishRun });
|
|
1391
|
+
// AND AS RUNS, so the assistant this demo's connect line wires has them to list, brief and open. Under
|
|
1392
|
+
// the demo's own workspace only: nothing of it reaches an install started afterwards.
|
|
1393
|
+
const runs = seedDemoRuns({ workspace: paths.workspace, examplesDir: join(REPO, "demo") });
|
|
1394
|
+
if (runs.seeded.length) say(` runs ${runs.seeded.length} sample run(s) your assistant can list, brief and open`);
|
|
1335
1395
|
// WHAT WAS ALREADY THERE IS SAID TOO. This branch used to run only when the pool
|
|
1336
1396
|
// was empty; it now tops a stale pool up to the package's set, so "seeded 1" on an upgrade is a fact
|
|
1337
1397
|
// about what was MISSING and says nothing on its own about how many are now listed.
|
|
@@ -1839,6 +1899,7 @@ if (isMain) {
|
|
|
1839
1899
|
}
|
|
1840
1900
|
say("");
|
|
1841
1901
|
say(` Open: ${envs.url}`);
|
|
1902
|
+
for (const line of foreignPageHint(DEMO ? "demo" : "start")) say(` ${line}`);
|
|
1842
1903
|
say(" This SURVIVES the terminal — close the window, the product keeps running.");
|
|
1843
1904
|
say(` Stop it: ${invoke("stop")} (stops and removes the units; issued connect keys survive — \`${invoke("disconnect")}\` revokes those)`);
|
|
1844
1905
|
say(` Is it up? ${invoke("status")}`);
|
|
@@ -2072,6 +2133,7 @@ if (isMain) {
|
|
|
2072
2133
|
|
|
2073
2134
|
say("");
|
|
2074
2135
|
say(` Open ${envs.url}`);
|
|
2136
|
+
for (const line of foreignPageHint(DEMO ? "demo" : "start")) say(` ${line}`);
|
|
2075
2137
|
say("");
|
|
2076
2138
|
// ── TWO DOORS, TWO AUDIENCES, BOTH NAMED ( — F26) ─────────────────────────
|
|
2077
2139
|
//
|
|
@@ -2169,7 +2231,9 @@ if (isMain) {
|
|
|
2169
2231
|
//
|
|
2170
2232
|
// The string comes from the ONE composer, not from a literal here: three surfaces state this route
|
|
2171
2233
|
// and a line of instruction with more than one author drifts silently.
|
|
2172
|
-
|
|
2234
|
+
// THE WORKSPACE AND POOL THE SERVICES WERE HANDED, not this process's environment: a demo reads no env
|
|
2235
|
+
// file, so its own line named no workspace and the connector fell back to the real install's.
|
|
2236
|
+
const connect = stdioConnectOffer({ workDir: paths.workspace, reportsDir: paths.pool });
|
|
2173
2237
|
say(" Connect your assistant to this install — one line, no address and no sign-in:");
|
|
2174
2238
|
say("");
|
|
2175
2239
|
say(` ${connect.command}`);
|
package/bin/update.mjs
CHANGED
|
@@ -59,6 +59,8 @@ import { isEntrypoint } from "../shared/is-entrypoint.mjs"; // — one entry-p
|
|
|
59
59
|
import { readEnvFile } from "./onboard.mjs";
|
|
60
60
|
import { invoke, invocationPrefix } from "../shared/invocation.mjs"; // — name a command the reader can actually type
|
|
61
61
|
import { rebuildIfStale } from "../shared/bundle-rebuild.mjs"; // a pull cannot update an untracked bundle
|
|
62
|
+
import { packagedUpdate } from "../shared/permanent-install.mjs"; // — a packaged install updates at its own prefix
|
|
63
|
+
import { installShim, inspectShim, shimPath } from "../shared/verb-shim.mjs"; // — npm's link replaces the launcher
|
|
62
64
|
|
|
63
65
|
const REPO = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
64
66
|
const ENV_PATH = envLocalPath({ repoRoot: REPO }); // resolved, never composed: one resolver, so moving this file later is one line
|
|
@@ -246,13 +248,22 @@ export async function update(argv = process.argv.slice(2)) {
|
|
|
246
248
|
// not succeed` — true, useless, and pointing at the wrong thing entirely. `update` is the verb a
|
|
247
249
|
// stranger reaches for, so the one install we expect most people to have must not be answered with
|
|
248
250
|
// a git error about a directory that was never a repository.
|
|
251
|
+
//
|
|
252
|
+
// A PACKAGED INSTALL NOW UPDATES ITSELF, the way it was installed: npm, at the prefix it lives under, on
|
|
253
|
+
// the channel it came from (shared/permanent-install.mjs). It runs AFTER the live-run refusal below,
|
|
254
|
+
// because npm replaces the program's files as surely as `npm ci` does. Only a layout that cannot be
|
|
255
|
+
// named is still refused.
|
|
256
|
+
let packaged = null;
|
|
249
257
|
if (!isGitCheckout()) {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
258
|
+
packaged = packagedUpdate();
|
|
259
|
+
if (!packaged) {
|
|
260
|
+
console.error("\n This install is not a git checkout, so there is nothing to pull.");
|
|
261
|
+
console.error(" It was installed from a package rather than cloned, which is the ordinary way.");
|
|
262
|
+
console.error("\n Update it the way it was installed:\n");
|
|
263
|
+
console.error(" npm install -g clearotron@latest\n");
|
|
264
|
+
console.error(" Nothing was touched.");
|
|
265
|
+
return 4;
|
|
266
|
+
}
|
|
256
267
|
}
|
|
257
268
|
|
|
258
269
|
// ── AND THE SECOND REFUSAL: NOT OVER A LIVE RUN ──────────────────────────────────────────────
|
|
@@ -289,6 +300,27 @@ export async function update(argv = process.argv.slice(2)) {
|
|
|
289
300
|
return 4;
|
|
290
301
|
}
|
|
291
302
|
|
|
303
|
+
if (packaged) {
|
|
304
|
+
if (packaged.current) {
|
|
305
|
+
say(`\n This install is ${packaged.installed}, and nothing newer is published (${packaged.tag}: ${packaged.version}). Nothing was touched.\n`);
|
|
306
|
+
return 0;
|
|
307
|
+
}
|
|
308
|
+
if (packaged.unread) say(`\n npm did not say which versions are published, so this follows the ${packaged.tag} channel.`);
|
|
309
|
+
say(`\n Updating this install at ${packaged.prefix} from ${packaged.installed ?? "an unreadable version"} to clearotron@${packaged.spec}.`);
|
|
310
|
+
const rc = runInCheckout("npm", packaged.npmArgs);
|
|
311
|
+
if (rc !== 0) return rc;
|
|
312
|
+
// npm puts its own link back at `<prefix>/bin/clearotron` on every install, over the launcher the
|
|
313
|
+
// install wrote, and that link runs whichever `node` is first on PATH. Put the launcher back, but only
|
|
314
|
+
// over npm's link or our own: anything else there was not ours before this update either.
|
|
315
|
+
const kind = inspectShim(shimPath()).kind;
|
|
316
|
+
if (kind === "npm-link" || kind === "ours" || kind === "ours-other-install") {
|
|
317
|
+
const shim = installShim();
|
|
318
|
+
if (!shim.ok) console.error(`\n The update worked, but the launcher at ${shim.path ?? "~/.local/bin/clearotron"} could not be written back: ${shim.detail}.`);
|
|
319
|
+
}
|
|
320
|
+
say("\n Updated. An assistant starts the new version the next time it launches Clearotron; restart the services for the portal.\n");
|
|
321
|
+
return 0;
|
|
322
|
+
}
|
|
323
|
+
|
|
292
324
|
say("\n Configuration store is outside the checkout. Updating the product.");
|
|
293
325
|
for (const e of entries) say(` ${e.name}=${e.value} (${e.from})`);
|
|
294
326
|
if (!entries.length) {
|
package/build-info.json
CHANGED
|
@@ -291,6 +291,7 @@ because the rule is about what PRODUCT CODE reads, not about what a run reads.
|
|
|
291
291
|
| `PORTAL_URL` | `http://127.0.0.1:18802`, or built from `PORTAL_SERVICE_HOST`/`PORTAL_SERVICE_PORT` | Where the deploy tick's live-surface check expects to reach the portal. |
|
|
292
292
|
| `PORTAL_OPS_TOKEN_FILE` | `~/.config/systemd/user/trademark-portal.service.d/secrets.conf` | The systemd drop-in the live-surface check reads `PORTAL_OPS_TOKEN` out of. It reads the FILE rather than the environment so a check run by hand sees the same token the service does. |
|
|
293
293
|
| `CLEAROTRON_INVOKED_AS` | unset (⇒ the verb's own `argv[1]`) | How the reader typed the command, so every command a verb prints for them to type next is spelled the way they type it: `clearotron …` after a global install, `npx clearotron …` otherwise. The dispatcher runs each verb as a process of its own, whose `argv[1]` is always `bin/<verb>.mjs`, so `bin/clearotron.mjs` passes its own `argv[1]` down in this name. **Set by the dispatcher, never by an operator.** Effect class `deployment`; the full contract is its row in `.env.example`. |
|
|
294
|
+
| `CLEAROTRON_RELOCATED` | unset (⇒ an install running from npx's cache moves itself to `~/.local` first) | Marks an install that has already made that move. `npx clearotron install` installs the same version under `~/.local`, then runs that copy's install with this set to `1`, so the copy does not try to move itself again. **Set by the install on the one process it starts, never by an operator.** Effect class `deployment`; the full contract is its row in `.env.example`. |
|
|
294
295
|
| `CLEAROTRON_REQUIRE_EXPLICIT_PORTS` | unset | `1` makes a service that would listen on a built-in default port refuse to start instead of warning. For a box that runs more than one instance, where one instance's default is another's port on the day that other instance is down. Unset, a service on a default port still says so as it starts. Effect class `deployment`. |
|
|
295
296
|
| `CLEAROTRON_UPDATER_STAMP` | `_updater-identity.json` beside the update script, in the directory the updater runs from | The full path of the file in which the updater that deploys this box records which copy of itself ran. The updater writes it and deploy health reads it under this one name, so a box that moves the stamp sets it once for both. On a box with no updater unit, setting it says an updater exists elsewhere and is to be judged. Effect class `deployment`. |
|
|
296
297
|
| `CLEAROTRON_CUT_REF` | `HEAD` | Which ref the cut decision reads the version from. **Read only by the release workflow, never set on a deployment.** The jobs that ask about `main` set it to `origin/main` explicitly, because their checkout is pinned to the run's own ref and `HEAD` there is that ref rather than the branch they are deciding about. A job that asks the wrong subject gets a confident wrong answer. |
|
package/driver/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# clearotron-driver
|
|
2
2
|
|
|
3
|
+
## 0.3.0-beta.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 2ca849d: Fixed: When codex's sign-in can no longer be refreshed, the search stops and says to run `codex login`. It used to retry with a bare exit code.
|
|
8
|
+
- a81279f: Fixed: When the portal address opens someone else's page, for example a port forwarded from outside WSL, Clearotron now says so and points you to `--port`.
|
|
9
|
+
- a81279f: Fixed: The sign-in page clears a session left by another Clearotron on the same address and says so. A refusal that is not about the passphrase now says what it is about.
|
|
10
|
+
- f133f7d: Fixed: A passphrase pasted with a space or line break at either end now signs in, instead of being refused as wrong.
|
|
11
|
+
- 2ca849d: Fixed: A manager who does not run the installation no longer sees server file paths when a new company cannot be recorded or filed.
|
|
12
|
+
- a81279f: Fixed: The local sign-in page no longer invites the browser to fill in a saved password from another install.
|
|
13
|
+
- a81279f: Fixed: `npx clearotron install` now installs Clearotron permanently under `~/.local` before setting up. The `clearotron` command and your assistants' connections keep working after npm cleans its cache.
|
|
14
|
+
- 2ca849d: Fixed: When the saved-search store exists but cannot be read, the connector and `clearotron doctor` now say so, instead of reporting no saved searches.
|
|
15
|
+
- a81279f: Fixed: On WSL, the "on this computer" connect steps now say to run them inside WSL. The Claude Code line registers Clearotron for every project and works in Windows PowerShell.
|
|
16
|
+
- a81279f: New: An assistant connected to `clearotron demo` can now list, brief and open the demo's four sample runs. They stay inside the demo's own folder.
|
|
17
|
+
- a81279f: Fixed: The sign-in page's reset line now runs for a demo started with npx, and resets that demo's own passphrase.
|
|
18
|
+
- a81279f: New: `clearotron update` now updates an npm-installed copy itself, and a beta install moves on to the release once it is published.
|
|
19
|
+
|
|
20
|
+
## 0.3.0-beta.5
|
|
21
|
+
|
|
22
|
+
### Patch Changes
|
|
23
|
+
|
|
24
|
+
- 5f7e295: Fixed: A Clearotron install signed in to OpenAI's `codex` with a subscription keeps working after codex refreshes its login. Before, every search after the first refresh failed within seconds until you signed in again.
|
|
25
|
+
- 2878809: Fixed: When the company store cannot record a new company, the New company page now says why and what fixes it, instead of "Try again shortly". Someone who does not run the installation is told to ask whoever does.
|
|
26
|
+
- e7cd9e1: Fixed: On an install run with `clearotron start`, a connected assistant now lists the same saved searches as the portal, including Generic's. `clearotron doctor` no longer says working saved searches are off, and it names one place profiles come from.
|
|
27
|
+
- e7cd9e1: Fixed: When setup offers to try your engine, it no longer names an Anthropic model to someone who chose OpenAI's `codex`.
|
|
28
|
+
|
|
3
29
|
## 0.3.0-beta.4
|
|
4
30
|
|
|
5
31
|
### Minor Changes
|
|
@@ -23,9 +49,9 @@
|
|
|
23
49
|
- a782aad: Fixed: Creating a company is refused, with nothing left behind, when the configuration store cannot record it. The company used to be created anyway, with no record of who made it or when, and its organisation was given access to it.
|
|
24
50
|
|
|
25
51
|
A store with no git identity is the usual cause on a new machine, and the refusal names the command that fixes it. Setup and `clearotron start` now check a store they adopt for this straight away.
|
|
26
|
-
- d1ef225: Fixed: A
|
|
52
|
+
- d1ef225: Fixed: A search step stopped at its time limit now records the output it actually produced. It used to record a small fraction, so a step that was working read as one that had stalled.
|
|
27
53
|
|
|
28
|
-
The token totals `clearotron tokens` reports for runs with a stopped
|
|
54
|
+
The token totals `clearotron tokens` reports for runs with a stopped step now include that output.
|
|
29
55
|
- 0ff42d1: Fixed: `clearotron doctor` now says when saved searches are switched off and why, and when a saved search file cannot be read.
|
|
30
56
|
|
|
31
57
|
An assistant asking for saved searches is told when they could not be read, instead of being told there are none.
|
|
@@ -79,11 +105,11 @@
|
|
|
79
105
|
|
|
80
106
|
Fixed: A search now records any of your default territories that the engine cannot search, in the record of that search. A mistyped default no longer narrows a search silently.
|
|
81
107
|
- eacfce4: Fixed: An assistant connected to a local install now sees the saved searches the portal shows, and can plan a run from one of them. Before, outside the demo, only the portal was told where saved searches are kept, so an assistant was told the install had none.
|
|
82
|
-
- 34cc1c7: For operators: A health check that could not
|
|
108
|
+
- 34cc1c7: For operators: A deployment health check that could not finish now fails instead of reporting success. Two parts of the service check could skip themselves. One skipped when the installation did not say which installation it is; the other when the scan of the service files did not finish. Both used to note that they had not run and then pass.
|
|
83
109
|
|
|
84
110
|
For operators: Set `CLEAROTRON_BOX` to `prod` or `test`. Those are the only two values the check accepts; anything else, including any other name, reads as unnamed and fails. The failure names the setting and says what went unchecked.
|
|
85
111
|
|
|
86
|
-
For operators: The
|
|
112
|
+
For operators: The part that could skip itself is the one that notices a service that has stopped and stayed stopped. The other part lists what is running, so it cannot see something that is no longer there. While the first part is skipped, a service can disappear without the check saying anything.
|
|
87
113
|
- b45a5cf: Fixed: `clearotron doctor` now reports everything a search would be refused for, checked against the environment the search will run in. So an installation it passes is one that can run a search.
|
|
88
114
|
|
|
89
115
|
Fixed: A token set in the installation's own configuration file now reaches the engine check. A headless server set up the documented way proves its engine instead of reporting it signed out.
|
|
@@ -128,7 +154,7 @@
|
|
|
128
154
|
Fixed: When a feature is switched off on your installation, the screen says so and what to change. It used to suggest trying again shortly.
|
|
129
155
|
|
|
130
156
|
Fixed: The link to the risk-framework guide opens in a new tab and goes straight to the section on writing your own. It also appears on the Company profile screen.
|
|
131
|
-
- ad088d6: Fixed: The audit workbook's "What was searched" sheet is now in plain words. Its Result and Note columns carry the search log's own notes.
|
|
157
|
+
- ad088d6: Fixed: The audit workbook's "What was searched" sheet is now in plain words. Its Result and Note columns carry the search log's own notes. Internal terms could reach them: a status such as "deferred", a full web address, a bare HTTP code. Those words are now replaced as the workbook is built, a republished report included. A search recorded as not run still reads as not searched. Search terms and names stay exactly as written, because they record what was searched.
|
|
132
158
|
- ff16a3b: For operators: The deployment health check now reports whether the component that updates an installation is itself up to date. It was the one part of a deployment the check could not identify. An installation kept current by an out-of-date updater could report healthy while serving stale code.
|
|
133
159
|
|
|
134
160
|
For operators: An updater that cannot be identified is now reported as a failure rather than passed over. A copy old enough to predate this reporting writes nothing at all. That silence is the case worth knowing about, so it is treated as a finding.
|
|
@@ -158,7 +184,7 @@
|
|
|
158
184
|
|
|
159
185
|
The product now says company throughout. It used to say brand owner, account, client and customer for the same thing. The firm running the installation is named separately, in the top bar.
|
|
160
186
|
|
|
161
|
-
Companies created through the settings page were saved without a risk framework. Their matters were then rated under the
|
|
187
|
+
Companies created through the settings page were saved without a risk framework. Their matters were then rated under the default risk framework, with nothing on screen saying so. Every company created now carries one, and says which.
|
|
162
188
|
- f75266d: New: The setup wizard now asks for the organisation's name after the sign-in address, and the person who installs starts with access to everything.
|
|
163
189
|
|
|
164
190
|
`clearotron grant add` sets a person's two permissions with `--run` and `--manage`; with neither, the person can look and start nothing.
|
|
@@ -180,18 +206,18 @@
|
|
|
180
206
|
|
|
181
207
|
Getting the deck's shape wrong used to fail quietly. The profile screen showed the framework's title and your band colours, and silently omitted the box saying what the bands mean. The new command answers that question directly, using the screen's own read of the deck.
|
|
182
208
|
|
|
183
|
-
Fixed: a framework
|
|
209
|
+
Fixed: When you have a configuration store set and a risk framework comes from the product's own files instead, the product now says so.
|
|
184
210
|
|
|
185
211
|
Your store is looked in first, and the product's files answer when it is silent. The product ships decks under names you may also have chosen. So a deck that went missing from your store was replaced by ours rather than reported absent. Same band words, different rubric, nothing raised anywhere. The profile screen now writes one line naming what happened, and the new command reports it.
|
|
186
212
|
|
|
187
|
-
Fixed:
|
|
213
|
+
Fixed: The built-in triage framework's profile page explains its bands again.
|
|
188
214
|
|
|
189
215
|
Its band sections stated their meanings as plain paragraphs, which the screen does not read. Every company without a framework of its own saw band colours and no explanation. The wording is unchanged.
|
|
190
|
-
- cfb9a9f: Fixed: `clearotron doctor` now
|
|
216
|
+
- cfb9a9f: Fixed: `clearotron doctor` now lists the companies your portal's key may start runs for.
|
|
191
217
|
|
|
192
218
|
The key carries a list of the companies it may start runs for. A company added after the key was minted is outside it. Doctor reported the key's expiry and never its coverage. So the one command whose job is to tell you what a machine is configured for said nothing about it.
|
|
193
219
|
|
|
194
|
-
It reads the
|
|
220
|
+
It reads the company list the background services read, not the one a command run in a terminal would find, and it says which. The two can differ, and when they do, that difference is the answer.
|
|
195
221
|
|
|
196
222
|
The line is a note, not a failure. Your portal takes a fresh credential at the start of every call, so a company outside the key is not normally refused. It is refused when the portal cannot take a fresh one, and the line says so and gives you the command to widen the key.
|
|
197
223
|
- ba2899b: Fixed: On reports searched through Signa, each register finding now links to the office's own page for that record, where the office publishes one. Singapore publishes no such page, so its registrations are cited by number, and the report says why. A number that an office's page cannot take is cited the same way.
|