probe-research 0.10.6 → 0.12.0

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/README.md CHANGED
@@ -6,14 +6,33 @@ The Probe Research setup wizard.
6
6
  npx probe-research
7
7
  ```
8
8
 
9
- Installs and configures [Probe Research](https://research.prbe.ai) for Claude Code.
10
- It asks which capabilities you want, installs only those plugins, and finishes
11
- with a single browser approval.
9
+ Signs in to [Probe Research](https://research.prbe.ai) through your browser and
10
+ opens the main wizard menu. If you create an account, finish onboarding on the
11
+ website first. Its final step gives you a command like:
12
+
13
+ ```bash
14
+ npx probe-research install ABCD234567
15
+ ```
16
+
17
+ The 10-character code signs you in once without another browser approval, and
18
+ `install` opens the guided tooling setup for Claude Code, Codex, or pi. Choose
19
+ your agents and confirm the capabilities before anything is installed. Each
20
+ selected agent receives its own capture credential when capture is enabled.
21
+
22
+ `npx probe-research ABCD234567` also signs in, then opens the main menu.
23
+ `npx probe-research install` runs guided setup with browser sign-in. Expired or
24
+ already-used codes need to be replaced on the website.
12
25
 
13
26
  This package is a thin launcher. Probe Research is a Python CLI; this exists so
14
27
  the entry point matches the `npx <tool>` shape people already expect. It resolves
15
28
  a real `probe` (via an existing install, `uv`, or `pipx`) and hands over — there
16
29
  is no reimplementation here that could drift from the CLI.
17
30
 
18
- Re-run it any time to change what's enabled, diagnose a problem, update, print
19
- the manual steps, or remove Probe from the device.
31
+ The main menu requires a signed-in account. Choose **Sign out** to clear this
32
+ device's credentials, stop capture, and exit the wizard. Run the command again
33
+ to sign in to another account. You can also change what's enabled, diagnose a
34
+ problem, update, print the manual steps, or remove Probe from the device.
35
+
36
+ Publishing this launcher requires `probe-research>=0.147.0` to be available on
37
+ PyPI first. That CLI version introduces website sign-in codes; older installed
38
+ CLIs are upgraded before the launcher forwards the new commands.
@@ -27,6 +27,20 @@ const os = require("node:os");
27
27
 
28
28
  const DIST = "probe-research";
29
29
 
30
+ /**
31
+ * Where the published-version manifest lives. The SessionStart hook already
32
+ * reads this exact endpoint to decide whether to nudge, so the launcher and the
33
+ * nudge cannot disagree about what "latest" means.
34
+ *
35
+ * `PROBE_BASE_URL` is honoured for the same reason the hook honours it: a
36
+ * self-hosted tenant's latest is not this one's, and asking the wrong host
37
+ * would tell them they are behind forever.
38
+ */
39
+ const MANIFEST_PATH = "/v1/client-version";
40
+ const DEFAULT_BASE = "https://api.research.prbe.ai";
41
+ /** How long a currency check may delay a command that could already run. */
42
+ const MANIFEST_TIMEOUT_MS = 1500;
43
+
30
44
  /**
31
45
  * The minimum CLI version this launcher needs — NOT this package's own version.
32
46
  *
@@ -49,14 +63,37 @@ const DIST = "probe-research";
49
63
  * to repair the thing it installed. A healthy 0.26.x install pays one refetch,
50
64
  * which is the right trade against a broken one staying broken forever.
51
65
  *
52
- * (1) is why it is now 0.36.0. The dashboard's last onboarding step hands out
66
+ * (1) was why it was 0.36.0. The dashboard's last onboarding step hands out
53
67
  * `npx probe-research backfill`, and `probe backfill` does not exist before
54
68
  * 0.36.0. Arguments are forwarded to whatever `probe` this launcher resolves,
55
69
  * so under the old floor a user with 0.35.0 on PATH gets `No such command
56
70
  * 'backfill'` from a command the product just told them to run. A floor is the
57
71
  * only thing that can catch it: the copied command is identical either way.
72
+ *
73
+ * (1) also raised the floor to 0.56.0, and the failure is quieter. 0.56.0 adds the
74
+ * BULK verbs an automated caller reaches for rather than a human:
75
+ *
76
+ * probe artifact add --from-manifest FILE import many files in one process
77
+ * probe artifact move ID --to LEVEL promote/demote/lateral
78
+ * probe metrics backfill RUN --key K a whole derived curve, one request
79
+ *
80
+ * The first of those is the one that makes a floor load-bearing. It exists
81
+ * because per-file `artifact add` costs a process start and a slug resolution
82
+ * each, so 200k files is tens of CPU-hours -- which means whatever issues it is
83
+ * importing at that scale, unattended. Against a pre-0.56.0 `probe` on PATH,
84
+ * `--from-manifest` is an unknown OPTION: the CLI exits 2 having queued
85
+ * nothing, and there is no operator watching to read it. The old floor is
86
+ * satisfied by every one of those installs.
87
+ *
88
+ * PUBLISHING ORDER MATTERS. `${DIST}>=${target}` must resolve, so this package
89
+ * may only be `npm publish`ed once a CLI at or above the floor is live on PyPI.
90
+ * That ordering is not enforced anywhere: the PyPI release is automated and
91
+ * `npm publish` is manual, so it is a human who has to get it right. Raising
92
+ * the floor ahead of the release strands the from-zero entry point.
58
93
  */
59
- const MIN_CLI = "0.36.0";
94
+ // 0.147.0 adds positional website sign-in codes to wizard/install. Publish this
95
+ // launcher only after that CLI release is available on PyPI.
96
+ const MIN_CLI = "0.147.0";
60
97
  const UV_INSTALL = "curl -LsSf https://astral.sh/uv/install.sh | sh";
61
98
 
62
99
  /** Compare dotted numeric versions. Returns true when `a` >= `b`. */
@@ -86,10 +123,43 @@ function installedVersion() {
86
123
  return m ? m[1] : null;
87
124
  }
88
125
 
126
+ function baseUrl() {
127
+ const env = process.env.PROBE_BASE_URL;
128
+ if (env && /^https?:\/\//.test(env)) return env.replace(/\/+$/, "");
129
+ return DEFAULT_BASE;
130
+ }
131
+
132
+ /**
133
+ * The newest published CLI, or null if that cannot be established quickly.
134
+ *
135
+ * NULL IS THE IMPORTANT RETURN. Offline, behind a proxy, self-hosted with the
136
+ * route absent, or simply slow -- every one of those must leave a working local
137
+ * install runnable. A currency check that can strand a user offline is worse
138
+ * than the staleness it fixes, so every failure here falls open.
139
+ */
140
+ async function latestVersion() {
141
+ try {
142
+ const res = await fetch(`${baseUrl()}${MANIFEST_PATH}`, {
143
+ signal: AbortSignal.timeout(MANIFEST_TIMEOUT_MS),
144
+ headers: { accept: "application/json" },
145
+ });
146
+ if (!res.ok) return null;
147
+ const body = await res.json();
148
+ const found = body && body.cli && body.cli.latest;
149
+ return typeof found === "string" && /^\d+(\.\d+)+$/.test(found) ? found : null;
150
+ } catch {
151
+ return null;
152
+ }
153
+ }
154
+
89
155
  function has(cmd) {
90
- const probe = process.platform === "win32" ? "where" : "command";
91
- const args = process.platform === "win32" ? [cmd] : ["-v", cmd];
92
- const r = spawnSync(probe, args, { stdio: "ignore", shell: true });
156
+ // ONE shell string, never (command, args[], shell:true): Node deprecated that
157
+ // pairing in DEP0190 because the args are concatenated rather than escaped,
158
+ // and it printed a security warning on every single launcher run. `cmd` is a
159
+ // literal at all three call sites, so there is nothing to escape here -- but
160
+ // a warning users see on the from-zero entry point is its own problem.
161
+ const line = process.platform === "win32" ? `where ${cmd}` : `command -v ${cmd}`;
162
+ const r = spawnSync(line, { stdio: "ignore", shell: true });
93
163
  return r.status === 0;
94
164
  }
95
165
 
@@ -104,27 +174,61 @@ function run(cmd, args) {
104
174
  process.exit(r.status === null ? 1 : r.status);
105
175
  }
106
176
 
107
- function main() {
177
+ /** The optional one-time sign-in code is independent of the install action. */
178
+ function cliArgs(args) {
179
+ if (!args.length) return ["wizard"];
180
+ if (/^[A-Z0-9]{10}$/.test(args[0])) {
181
+ // `code install` and `install code` both retain the same two arguments.
182
+ if (args[1] === "install") return ["install", args[0], ...args.slice(2)];
183
+ return ["wizard", ...args];
184
+ }
185
+ return args;
186
+ }
187
+
188
+ async function main() {
108
189
  const args = process.argv.slice(2);
109
190
  // `npx probe-research` with no arguments runs the wizard — that is the entire
110
191
  // reason this package exists, so it should not require remembering a verb.
111
- const forwarded = args.length ? args : ["wizard"];
192
+ const forwarded = cliArgs(args);
112
193
  const wanted = MIN_CLI;
113
194
 
114
- // Only hand off to an existing install if it is at least as new as this
115
- // launcher. Otherwise fall through and let uv/pipx fetch a matching CLI.
195
+ // Hand off to an existing install only when it is BOTH above the floor and
196
+ // current. The floor alone is not enough, and that was the bug: `npx <tool>`
197
+ // means "run the latest", but a floor is satisfied forever, so anyone who had
198
+ // ever installed the CLI was frozen on it and npx silently never delivered
199
+ // another update.
200
+ //
201
+ // That is the same failure the `--refresh` flag below exists to fix -- uv
202
+ // serving whatever it resolved on day one -- in the other branch. It was
203
+ // fixed there and left standing here.
204
+ let latest = null;
116
205
  if (has("probe")) {
117
206
  const found = installedVersion();
118
- if (found && atLeast(found, wanted)) return run("probe", forwarded);
119
- console.error(
120
- `probe-research: installed probe ${found || "(unknown version)"} is older than ` +
121
- `${wanted}; fetching the latest…`,
122
- );
207
+ if (found && atLeast(found, wanted)) {
208
+ latest = await latestVersion();
209
+ // Unknown latest means run what we have: see latestVersion's contract.
210
+ if (!latest || atLeast(found, latest)) return run("probe", forwarded);
211
+ console.error(
212
+ `probe-research: probe ${found} is behind ${latest}; fetching the latest…`,
213
+ );
214
+ } else {
215
+ console.error(
216
+ `probe-research: installed probe ${found || "(unknown version)"} is older than ` +
217
+ `${wanted}; fetching the latest…`,
218
+ );
219
+ }
123
220
  }
124
221
  // A FLOOR, not an exact pin. Unpinned, `uv tool run --from probe-research`
125
222
  // reuses an already-installed 0.8.2 tool; pinned exactly, it demands a PyPI
126
223
  // version that may not exist. `>=` fixes both.
127
- const spec = `${DIST}>=${wanted}`;
224
+ // Resolve to the newest version we actually know about, not merely the floor.
225
+ // `uv tool run` reuses an installed tool that satisfies the constraint, so a
226
+ // stale 0.46.0 satisfies `>=0.36.0` and uv hands back the very version we
227
+ // just decided was out of date -- the currency check above would print
228
+ // "fetching the latest" and then change nothing. Measured exactly that before
229
+ // this line existed.
230
+ const target = latest && atLeast(latest, wanted) ? latest : wanted;
231
+ const spec = `${DIST}>=${target}`;
128
232
  // REFRESH. uv caches the ENVIRONMENT it built for a requirement, so a range
129
233
  // like `>=0.10.0` keeps serving whatever it resolved the FIRST time — every
130
234
  // user frozen on the version they happened to run on day one, with the
@@ -162,8 +266,13 @@ function main() {
162
266
  // because it was resolved before the install ran.
163
267
  const uv = `${os.homedir()}/.local/bin/uv`;
164
268
  return run(uv, [
165
- "tool", "run", "--refresh", "--from", `${DIST}>=${wanted}`, "probe", ...forwarded,
269
+ "tool", "run", "--refresh", "--from", spec, "probe", ...forwarded,
166
270
  ]);
167
271
  }
168
272
 
169
- main();
273
+ main().catch((err) => {
274
+ // A throw here would exit 0 under an unhandled rejection on some Node builds,
275
+ // which reads as success to whatever invoked the launcher.
276
+ console.error(`probe-research: ${err && err.message ? err.message : err}`);
277
+ process.exit(1);
278
+ });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "probe-research",
3
- "version": "0.10.6",
4
- "description": "Setup wizard for Probe Research in Claude Code. Runs from zero with npx.",
3
+ "version": "0.12.0",
4
+ "description": "Setup wizard for Probe Research in Claude Code and Codex. Runs from zero with npx.",
5
5
  "bin": {
6
6
  "probe-research": "bin/probe-research.js"
7
7
  },
@@ -19,6 +19,7 @@
19
19
  "homepage": "https://research.prbe.ai/connect",
20
20
  "keywords": [
21
21
  "claude-code",
22
+ "codex",
22
23
  "mlops",
23
24
  "experiment-tracking",
24
25
  "probe-research"