humanish 0.110.1 → 0.110.3
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/ARCHITECTURE.md +11 -16
- package/CHANGELOG.md +26 -0
- package/CONTEXT.md +1 -1
- package/README.md +8 -7
- package/dist/actors/computer-use/actor.js +2 -2
- package/dist/actors/computer-use/loop/ending.js +1 -1
- package/dist/actors/local-agent/cli.d.ts +6 -0
- package/dist/actors/local-agent/cli.js +8 -0
- package/dist/actors/local-agent/readiness.js +2 -1
- package/dist/actors/registry.js +1 -1
- package/dist/analysis/automatic-completion.js +8 -0
- package/dist/cli/commands/observe.js +18 -4
- package/dist/cli/commands/run-signals.js +13 -4
- package/dist/cli/commands/runs.js +36 -13
- package/dist/cli/commands/setup.js +23 -14
- package/dist/cli/commands/study-route-computer-use.js +7 -3
- package/dist/cli/commands/study-route-open.js +1 -0
- package/dist/cli/commands/study-route-preview.js +1 -0
- package/dist/cli/commands/study-route-shared-world.js +4 -2
- package/dist/cli/commands/study-run.js +9 -19
- package/dist/cli/commands/watch.js +14 -4
- package/dist/cli/doctor.js +9 -6
- package/dist/cli/findings.d.ts +5 -0
- package/dist/cli/findings.js +28 -6
- package/dist/cli/first-run-path.d.ts +27 -7
- package/dist/cli/first-run-path.js +112 -75
- package/dist/cli/invocation.d.ts +7 -0
- package/dist/cli/invocation.js +32 -0
- package/dist/cli/io.d.ts +5 -2
- package/dist/cli/io.js +9 -16
- package/dist/cli/observer-follow.d.ts +24 -0
- package/dist/cli/observer-follow.js +48 -3
- package/dist/cli/orientation.js +16 -14
- package/dist/cli/version.d.ts +1 -0
- package/dist/cli/version.js +11 -0
- package/dist/evidence/encoded-text.d.ts +9 -6
- package/dist/evidence/encoded-text.js +84 -19
- package/dist/evidence/redaction.d.ts +2 -0
- package/dist/evidence/redaction.js +134 -21
- package/dist/feedback/export-bundle.js +2 -2
- package/dist/keys/key-resolution.js +6 -5
- package/dist/observer/exposure.js +1 -1
- package/dist/observer/library.js +3 -2
- package/dist/observer/render.js +2 -1
- package/dist/observer/run-routes.js +8 -1
- package/dist/observer/serve.js +2 -1
- package/dist/routes/computer-use/participant-runs.d.ts +6 -9
- package/dist/routes/computer-use/participant-runs.js +16 -36
- package/dist/routes/computer-use/plan.d.ts +2 -2
- package/dist/routes/computer-use/plan.js +6 -1
- package/dist/routes/computer-use/route.d.ts +5 -18
- package/dist/routes/computer-use/route.js +17 -78
- package/dist/routes/computer-use/setup.d.ts +3 -2
- package/dist/routes/computer-use/types.d.ts +6 -10
- package/dist/routes/scripted/route.d.ts +4 -17
- package/dist/routes/scripted/route.js +10 -42
- package/dist/routes/scripted/types.d.ts +1 -6
- package/dist/routes/shared-world/plan.js +5 -1
- package/dist/routes/shared-world/route.d.ts +5 -11
- package/dist/routes/shared-world/route.js +24 -63
- package/dist/routes/shared-world/types.d.ts +2 -6
- package/dist/routes/terminal/bundle.js +2 -1
- package/dist/routes/terminal/credentials.d.ts +5 -5
- package/dist/routes/terminal/credentials.js +14 -6
- package/dist/routes/terminal/plan.js +8 -0
- package/dist/routes/terminal/route.d.ts +3 -13
- package/dist/routes/terminal/route.js +24 -63
- package/dist/routes/terminal/runtime-auth.d.ts +6 -0
- package/dist/routes/terminal/runtime-auth.js +5 -0
- package/dist/routes/terminal/session.d.ts +2 -3
- package/dist/routes/terminal/session.js +2 -3
- package/dist/routes/terminal/types.d.ts +2 -7
- package/dist/run/dry-run.js +2 -1
- package/dist/run/judge.js +2 -1
- package/dist/run/local-only-files.d.ts +13 -0
- package/dist/run/local-only-files.js +35 -0
- package/dist/run/reclaim.d.ts +5 -0
- package/dist/run/reclaim.js +34 -2
- package/dist/run/route-shell.d.ts +60 -0
- package/dist/run/route-shell.js +51 -0
- package/dist/run/run-not-found.js +3 -2
- package/dist/run/run.d.ts +6 -1
- package/dist/run/run.js +11 -2
- package/dist/run/sandbox-creates.js +5 -3
- package/dist/run/sandbox-receipts.d.ts +6 -5
- package/dist/run/sandbox-receipts.js +12 -7
- package/dist/run/status.d.ts +2 -0
- package/dist/run/status.js +5 -0
- package/dist/study/agents-section.d.ts +21 -0
- package/dist/study/agents-section.js +86 -0
- package/dist/study/composition-rules.js +1 -1
- package/dist/study/doctor.js +5 -4
- package/dist/study/init-templates.d.ts +1 -1
- package/dist/study/init-templates.js +43 -129
- package/dist/study/init.js +32 -35
- package/dist/study/local-browser-host.d.ts +11 -0
- package/dist/study/local-browser-host.js +42 -0
- package/dist/study/parse/actors.js +9 -1
- package/dist/study/parse/subject.js +13 -1
- package/dist/study/parse/url-credentials.d.ts +9 -0
- package/dist/study/parse/url-credentials.js +50 -0
- package/dist/study/preflight-probes.js +2 -1
- package/dist/study/types.d.ts +8 -6
- package/dist/study/url-credentials.d.ts +6 -0
- package/dist/study/url-credentials.js +34 -0
- package/dist/substrates/e2b/sandbox.js +33 -15
- package/dist/substrates/local/firecracker-desktop.js +3 -2
- package/dist/substrates/local/runtime-host.js +5 -4
- package/dist/substrates/local/runtime.js +5 -4
- package/dist/tui/contract.js +5 -4
- package/dist/verify/liveness.js +2 -1
- package/dist/verify/verify.js +2 -1
- package/docs/architecture/actor-contract.md +1 -1
- package/docs/architecture/external-public-shared-world.md +1 -1
- package/docs/architecture/terminal-product-route.md +26 -24
- package/docs/contracts/run-bundle.md +20 -4
- package/docs/contracts/schemas.md +6 -5
- package/docs/contracts/study-analysis.md +5 -2
- package/docs/product/automatic-analysis.md +1 -1
- package/package.json +4 -2
- package/skills/humanish/SKILL.md +22 -18
|
@@ -94,7 +94,7 @@ export async function runStudyCommand(args) {
|
|
|
94
94
|
// checks (keys, runtime auth, subject env, the local agent, caps) refuse this machine, all before
|
|
95
95
|
// the scorer loads, so none imports the scorer's host code. The scripted route's checks and the
|
|
96
96
|
// run-id claim still come after it; the scripted route takes no scorer.
|
|
97
|
-
const routeRun =
|
|
97
|
+
const routeRun = ROUTE_RUNS[route]({ ...args, config });
|
|
98
98
|
if (routeRun === undefined)
|
|
99
99
|
return;
|
|
100
100
|
// Every route's runStudyWith options carry the study's provenance, from here only.
|
|
@@ -155,24 +155,14 @@ function announcedKeyNames(config, options) {
|
|
|
155
155
|
const planned = planStudy(config, { cwd: options.cwd, dryRun: false });
|
|
156
156
|
return planned.ok ? keyNamesOf(planned.planned.plan) : undefined;
|
|
157
157
|
}
|
|
158
|
-
/**
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
return scriptedRouteRun(args);
|
|
167
|
-
case "terminal":
|
|
168
|
-
return terminalRouteRun(args);
|
|
169
|
-
case "shared-world":
|
|
170
|
-
return sharedWorldRouteRun(args);
|
|
171
|
-
default:
|
|
172
|
-
// Compile-time exhaustiveness: a future route must be handled here, not silently no-op.
|
|
173
|
-
throw new Error(`Unhandled study route: ${String(route)}`);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
158
|
+
/** Each route's CLI setup. It returns undefined when it has already written its own result. */
|
|
159
|
+
const ROUTE_RUNS = {
|
|
160
|
+
preview: previewRouteRun,
|
|
161
|
+
"computer-use": computerUseRouteRun,
|
|
162
|
+
scripted: scriptedRouteRun,
|
|
163
|
+
terminal: terminalRouteRun,
|
|
164
|
+
"shared-world": sharedWorldRouteRun,
|
|
165
|
+
};
|
|
176
166
|
function studyRerunFlagsRequested(options) {
|
|
177
167
|
return options.rerunFailedFrom !== undefined || options.participants !== undefined;
|
|
178
168
|
}
|
|
@@ -5,7 +5,8 @@ import { runDryRun } from "../../run/dry-run.js";
|
|
|
5
5
|
import { runStudyCommand } from "./study-run.js";
|
|
6
6
|
import { addRunOptions, studyOnlyFlags } from "./run-command.js";
|
|
7
7
|
import { applyEnvFileOption, collectRepeated, dotenvPathOf, formatRunHuman, parseObserverPort, parsePositiveInteger, wantsJson, writeResult, } from "../io.js";
|
|
8
|
-
import { followObserver, formatObserverHuman, watchExposeRequested } from "../observer-follow.js";
|
|
8
|
+
import { followDecision, followObserver, formatObserverHuman, unattendedObserverWarning, watchExposeRequested, } from "../observer-follow.js";
|
|
9
|
+
import { cli } from "../invocation.js";
|
|
9
10
|
export function registerWatchCommand(parent, io) {
|
|
10
11
|
// A run's flags come from addRunOptions, the helper `run` uses, so the two cannot drift.
|
|
11
12
|
addRunOptions(parent
|
|
@@ -23,6 +24,7 @@ export function registerWatchCommand(parent, io) {
|
|
|
23
24
|
.option("--allow-email <addr>", "Edge OAuth allow rule: permit this email. Repeatable. Requires --oauth.", collectRepeated)
|
|
24
25
|
.option("--allow-domain <domain>", "Edge OAuth allow rule: permit this domain. Repeatable. Requires --oauth.", collectRepeated)
|
|
25
26
|
.option("--public-url <origin>", "Bring-your-own authed edge (Cloudflare Access/Tailscale/manual). Binds loopback and trusts your edge. Requires --expose.")
|
|
27
|
+
.addOption(new Option("--serve", "Serve the Observer and stay attached until Ctrl-C even without an interactive terminal; by default an agent's shell or a pipe gets the result and an exit.").conflicts(["detach", "json"]))
|
|
26
28
|
// Hidden: watch rejects --safe with a pointer to the library filter and to edge auth, which
|
|
27
29
|
// a bare "unknown option" would lose.
|
|
28
30
|
.addOption(new Option("--safe").hideHelp())
|
|
@@ -37,7 +39,7 @@ export function registerWatchCommand(parent, io) {
|
|
|
37
39
|
"Watch a live computer-use run from your phone through an authenticated tunnel:",
|
|
38
40
|
" humanish watch my-browser-study --expose --tunnel ngrok --oauth google --allow-email you@example.com",
|
|
39
41
|
"",
|
|
40
|
-
"Agent/CI path:",
|
|
42
|
+
"Agent/CI path (without an interactive terminal, watch prints the result and exits):",
|
|
41
43
|
" humanish watch --json --no-open",
|
|
42
44
|
"",
|
|
43
45
|
"Saved runs:",
|
|
@@ -84,7 +86,7 @@ async function handleWatch(io, named, parsed, command) {
|
|
|
84
86
|
if (watchExposeRequested(options)) {
|
|
85
87
|
refuseWatch(command, io, options.cwd, {
|
|
86
88
|
code: "HUMANISH_WATCH_OPTION_CONFLICT",
|
|
87
|
-
message:
|
|
89
|
+
message: `--expose/--tunnel/--oauth apply only to a live computer-use run; to expose finished evidence use \`${cli("observe --all --expose")}\`.`,
|
|
88
90
|
});
|
|
89
91
|
return;
|
|
90
92
|
}
|
|
@@ -107,7 +109,12 @@ async function handleWatch(io, named, parsed, command) {
|
|
|
107
109
|
: options.open === true
|
|
108
110
|
? true
|
|
109
111
|
: !wantsMachine && process.stdout.isTTY === true;
|
|
110
|
-
const
|
|
112
|
+
const decision = followDecision({
|
|
113
|
+
wantsMachine,
|
|
114
|
+
detach: options.detach,
|
|
115
|
+
serve: options.serve,
|
|
116
|
+
});
|
|
117
|
+
const wantsFollow = decision.follow;
|
|
111
118
|
const staticOpen = wantsFollow ? false : shouldOpen;
|
|
112
119
|
const rendered = await renderWatchEvidence(io, command, options, target.requestedParticipantCount, staticOpen);
|
|
113
120
|
if (rendered === undefined)
|
|
@@ -116,6 +123,7 @@ async function handleWatch(io, named, parsed, command) {
|
|
|
116
123
|
follow: wantsFollow,
|
|
117
124
|
open: shouldOpen,
|
|
118
125
|
port: target.port,
|
|
126
|
+
unattended: decision.unattended,
|
|
119
127
|
});
|
|
120
128
|
}
|
|
121
129
|
function refuseWatch(command, io, cwd, refusal) {
|
|
@@ -174,6 +182,8 @@ async function renderWatchEvidence(io, command, options, requestedParticipantCou
|
|
|
174
182
|
async function reportWatch(io, command, rendered, serve) {
|
|
175
183
|
let server = null;
|
|
176
184
|
let result = rendered;
|
|
185
|
+
if (rendered.ok && serve.unattended)
|
|
186
|
+
result = { ...rendered, warnings: [...rendered.warnings, unattendedObserverWarning("watch")] };
|
|
177
187
|
if (rendered.ok && serve.follow) {
|
|
178
188
|
server = await serveObserver(rendered, { open: serve.open, port: serve.port });
|
|
179
189
|
result = {
|
package/dist/cli/doctor.js
CHANGED
|
@@ -5,11 +5,12 @@ import path from "node:path";
|
|
|
5
5
|
import { probeKeySources, } from "../keys/key-resolution.js";
|
|
6
6
|
import { desktopPeerAdvice, humanishInstall, } from "../substrates/e2b/peer-install.js";
|
|
7
7
|
import { nodeSupportsTui, terminalSurfaceMessage, TUI_BUNDLE_URL } from "../tui/contract.js";
|
|
8
|
-
import { detectLocalAgents, localAgentDoctorMessage, NO_LOCAL_AGENT_MESSAGE, } from "../actors/local-agent/cli.js";
|
|
8
|
+
import { detectLocalAgents, localAgentDoctorMessage, localAgentReady, NO_LOCAL_AGENT_MESSAGE, } from "../actors/local-agent/cli.js";
|
|
9
9
|
import { leftoverClaudeTranscripts } from "../actors/local-agent/claude-participant.js";
|
|
10
10
|
import { studySetupChecks, studiesByRequiredKey, } from "../study/doctor.js";
|
|
11
11
|
import { prepareSelectedOutputDirectory, } from "../run/contained-output.js";
|
|
12
12
|
import { implicitProjectDirectoryExists, readImplicitProjectFile, validateCwd, } from "../run/project.js";
|
|
13
|
+
import { cli } from "./invocation.js";
|
|
13
14
|
const DOCTOR_SCHEMA = "humanish.doctor-result.v1";
|
|
14
15
|
/**
|
|
15
16
|
* Version 2.3.1 stopped holding a launched background command's event stream open. Version 2.3.2
|
|
@@ -100,7 +101,7 @@ export async function doctor(cwdInput, options = {}) {
|
|
|
100
101
|
{
|
|
101
102
|
name: "setup route",
|
|
102
103
|
ok: true,
|
|
103
|
-
message:
|
|
104
|
+
message: `General capabilities only. Use ${cli("doctor --study <study>")} for the selected participant's requirements and separate analysis readiness.`,
|
|
104
105
|
},
|
|
105
106
|
]),
|
|
106
107
|
];
|
|
@@ -173,7 +174,7 @@ async function sourceCheck(projectRoot) {
|
|
|
173
174
|
ok,
|
|
174
175
|
message: ok
|
|
175
176
|
? "committed humanish/ source directory is present and safe to read"
|
|
176
|
-
:
|
|
177
|
+
: `no readable humanish/ source directory; run ${cli("init --yes")}`,
|
|
177
178
|
};
|
|
178
179
|
}
|
|
179
180
|
async function runtimeIgnoreCheck(projectRoot) {
|
|
@@ -183,7 +184,7 @@ async function runtimeIgnoreCheck(projectRoot) {
|
|
|
183
184
|
ok,
|
|
184
185
|
message: ok
|
|
185
186
|
? ".gitignore lists .humanish/"
|
|
186
|
-
:
|
|
187
|
+
: `.gitignore does not list .humanish/; run ${cli("init --yes")}`,
|
|
187
188
|
};
|
|
188
189
|
}
|
|
189
190
|
/**
|
|
@@ -271,7 +272,9 @@ async function probeDoctorKeys(cwd, env, study, keyDeps, dotenv) {
|
|
|
271
272
|
* The operator's own coding agents, one row each, reported as a capability and never a gate: a
|
|
272
273
|
* machine with none is not broken, it just needs a provider key. These rows exist because "go make
|
|
273
274
|
* an API key" is where most people trying humanish stop, and a developer very often already has one
|
|
274
|
-
* of these signed in. An agent that
|
|
275
|
+
* of these signed in. An agent that cannot run participants (not signed in under the participant's
|
|
276
|
+
* environment, sign-in unknown, or a Claude Code below the floor) is a note; `--study` fails on it
|
|
277
|
+
* when the study selects that agent.
|
|
275
278
|
*/
|
|
276
279
|
function localAgentChecks(agents) {
|
|
277
280
|
if (agents.length === 0)
|
|
@@ -279,7 +282,7 @@ function localAgentChecks(agents) {
|
|
|
279
282
|
return agents.map((agent) => ({
|
|
280
283
|
name: `local agent ${agent.bin}`,
|
|
281
284
|
ok: true,
|
|
282
|
-
...(agent
|
|
285
|
+
...(localAgentReady(agent) ? {} : { status: "note" }),
|
|
283
286
|
message: localAgentDoctorMessage(agent),
|
|
284
287
|
}));
|
|
285
288
|
}
|
package/dist/cli/findings.d.ts
CHANGED
|
@@ -75,6 +75,11 @@ interface FindingsSource {
|
|
|
75
75
|
loaded: LoadedAnalysis;
|
|
76
76
|
/** ` --cwd <dir>` when the commands this view names need it, or "". */
|
|
77
77
|
cwdFlag: string;
|
|
78
|
+
/**
|
|
79
|
+
* The project-relative path of the run's study file when that file sets `review.analysis:
|
|
80
|
+
* false`, read when the view is built. Undefined when it does not, or cannot be read.
|
|
81
|
+
*/
|
|
82
|
+
analysisOffIn?: string;
|
|
78
83
|
}
|
|
79
84
|
/** The findings view of a run's selected analysis, or why it has none. */
|
|
80
85
|
export declare function analysisFindings(source: FindingsSource): AnalysisFindings;
|
package/dist/cli/findings.js
CHANGED
|
@@ -7,9 +7,12 @@ import { loadAnalysis } from "../analysis/load.js";
|
|
|
7
7
|
import { projectShareCheckedAnalysis } from "../analysis/sharing.js";
|
|
8
8
|
import { loadRunBundlePrepared, resolveRunPath } from "../run/locate.js";
|
|
9
9
|
import { resolvePhysicalCwd, runIdOf } from "../run/paths.js";
|
|
10
|
+
import { studyProvenanceOf } from "../run/study-provenance.js";
|
|
11
|
+
import { resolveStudyManifest } from "../study/discover.js";
|
|
10
12
|
import { plural } from "../run/text.js";
|
|
11
13
|
import { shellQuote } from "../substrates/shell.js";
|
|
12
14
|
import { analysisOutcomeText, wantsJson } from "./io.js";
|
|
15
|
+
import { cli } from "./invocation.js";
|
|
13
16
|
export const ANALYSIS_FINDINGS_SCHEMA = "humanish.analysis-findings.v1";
|
|
14
17
|
/** ` --cwd <dir>`, or "" when `cwd` is the current directory. */
|
|
15
18
|
function cwdFlag(cwd) {
|
|
@@ -18,7 +21,7 @@ function cwdFlag(cwd) {
|
|
|
18
21
|
return ` --cwd ${/^[\w@%+=:,./-]+$/.test(cwd) ? cwd : shellQuote(cwd)}`;
|
|
19
22
|
}
|
|
20
23
|
function analyzeCommand(source, provider) {
|
|
21
|
-
const run = `
|
|
24
|
+
const run = cli(`analyze --run ${source.runId}${source.cwdFlag}`);
|
|
22
25
|
return provider === "codex"
|
|
23
26
|
? `${run} --provider codex`
|
|
24
27
|
: `${run} --max-cost ${DEFAULT_ANALYSIS_MAX_COST_USD}`;
|
|
@@ -132,10 +135,10 @@ export function analysisFindings(source) {
|
|
|
132
135
|
};
|
|
133
136
|
}
|
|
134
137
|
if (loaded.warnings.includes("ANALYSIS_SENSITIVE_TEXT_QUARANTINED"))
|
|
135
|
-
return withoutFindings(source, "unavailable", "ANALYSIS_SENSITIVE_TEXT_QUARANTINED", `The analysis text matched a sensitive-text pattern, so it is not printed.
|
|
138
|
+
return withoutFindings(source, "unavailable", "ANALYSIS_SENSITIVE_TEXT_QUARANTINED", `The analysis text matched a sensitive-text pattern, so it is not printed. ${cli(`analyze show --run ${source.runId}${source.cwdFlag} --json`)} prints the raw record.`, null);
|
|
136
139
|
const job = loaded.automatic;
|
|
137
140
|
if (job?.state === "queued" || job?.state === "running")
|
|
138
|
-
return withoutFindings(source, "running", null, "The automatic analysis is still running. Check again when it finishes.", `
|
|
141
|
+
return withoutFindings(source, "running", null, "The automatic analysis is still running. Check again when it finishes.", cli(`review --run ${source.runId}${source.cwdFlag}`));
|
|
139
142
|
if (analysis && analysis.result === null)
|
|
140
143
|
return withoutFindings(source, "failed", analysis.error, `Analysis attempt ${analysis.id} ${analysis.status === "cancelled" ? "was cancelled" : "failed"}${analysis.error ? ` (${analysis.error})` : ""} and left no findings.`, analyzeCommand(source, analysis.provider));
|
|
141
144
|
if (job?.state === "failed" || job?.state === "cancelled") {
|
|
@@ -149,9 +152,24 @@ export function analysisFindings(source) {
|
|
|
149
152
|
: `${analyzeCommand(source, undefined)}${refused ? " --dry-run" : ""}`);
|
|
150
153
|
}
|
|
151
154
|
if (job !== undefined || loaded.state === "invalid")
|
|
152
|
-
return withoutFindings(source, "unavailable", job?.reason ?? loaded.warnings[0] ?? null, "This run's analysis records could not be read or checked, so no findings are shown.", `
|
|
155
|
+
return withoutFindings(source, "unavailable", job?.reason ?? loaded.warnings[0] ?? null, "This run's analysis records could not be read or checked, so no findings are shown.", cli(`analyze list --run ${source.runId}${source.cwdFlag}`));
|
|
156
|
+
if (source.analysisOffIn !== undefined)
|
|
157
|
+
return withoutFindings(source, "skipped", "AUTOMATIC_ANALYSIS_DISABLED", `Analysis is turned off in this run's study (review.analysis: false in ${source.analysisOffIn}), so none ran after the run. To analyze this run anyway, run the next command.`, analyzeCommand(source, undefined));
|
|
153
158
|
return withoutFindings(source, "none", null, "No analysis has run for this run.", analyzeCommand(source, undefined));
|
|
154
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* The run's study file, project-relative, when it still names the same study and sets
|
|
162
|
+
* `review.analysis: false`. A study that turns analysis off leaves no analysis record, so the file
|
|
163
|
+
* is the only place that says why a live run has none.
|
|
164
|
+
*/
|
|
165
|
+
async function studyAnalysisOff(cwd, study) {
|
|
166
|
+
if (study?.path === undefined)
|
|
167
|
+
return undefined;
|
|
168
|
+
const resolved = await resolveStudyManifest(cwd, study.path).catch(() => null);
|
|
169
|
+
if (!resolved?.ok || resolved.config.id !== study.id)
|
|
170
|
+
return undefined;
|
|
171
|
+
return resolved.config.review?.analysis === false ? study.path : undefined;
|
|
172
|
+
}
|
|
155
173
|
/**
|
|
156
174
|
* The run's bundle mode and its selected analysis: `analysisId`, or the version `analyze show`
|
|
157
175
|
* defaults to. Null when the run is missing or its storage is unsafe.
|
|
@@ -162,12 +180,16 @@ export async function readRunAnalysis(cwd, run, analysisId) {
|
|
|
162
180
|
if (!prepared)
|
|
163
181
|
return null;
|
|
164
182
|
const bundle = await loadRunBundlePrepared(physical, prepared).catch(() => null);
|
|
183
|
+
const analysisOffIn = bundle?.bundle.mode === "live"
|
|
184
|
+
? await studyAnalysisOff(physical, studyProvenanceOf(bundle.bundle))
|
|
185
|
+
: undefined;
|
|
165
186
|
return {
|
|
166
187
|
runId: runIdOf(prepared),
|
|
167
188
|
mode: bundle?.bundle.mode,
|
|
168
189
|
runRoot: prepared.relativeRunRoot,
|
|
169
190
|
loaded: await loadAnalysis(prepared, analysisId),
|
|
170
191
|
cwdFlag: cwdFlag(cwd),
|
|
192
|
+
...(analysisOffIn === undefined ? {} : { analysisOffIn }),
|
|
171
193
|
};
|
|
172
194
|
}
|
|
173
195
|
/** The view of a run that could not be found. */
|
|
@@ -179,7 +201,7 @@ export function missingRunFindings(cwd, run) {
|
|
|
179
201
|
warnings: ["ANALYSIS_RUN_NOT_FOUND"],
|
|
180
202
|
};
|
|
181
203
|
const flag = cwdFlag(cwd);
|
|
182
|
-
return withoutFindings({ runId: run, mode: undefined, runRoot: "", loaded, cwdFlag: flag }, "unavailable", "ANALYSIS_RUN_NOT_FOUND", `Run ${run} was not found or its storage is unsafe.`, `
|
|
204
|
+
return withoutFindings({ runId: run, mode: undefined, runRoot: "", loaded, cwdFlag: flag }, "unavailable", "ANALYSIS_RUN_NOT_FOUND", `Run ${run} was not found or its storage is unsafe.`, cli(`runs${flag}`));
|
|
183
205
|
}
|
|
184
206
|
/** The findings of a run's selected analysis, or why it has none. */
|
|
185
207
|
export async function readRunFindings(cwd, run, analysisId) {
|
|
@@ -307,7 +329,7 @@ export async function writeRunFindings(command, io, cwd, result) {
|
|
|
307
329
|
return;
|
|
308
330
|
try {
|
|
309
331
|
const view = await readRunFindings(cwd, result.runId, analysisId);
|
|
310
|
-
const full = `
|
|
332
|
+
const full = cli(`review --run ${view.runId}${cwdFlag(cwd)}`);
|
|
311
333
|
io.writeOut(`${formatFindingsSummary(view, full).join("\n")}\n`);
|
|
312
334
|
}
|
|
313
335
|
catch {
|
|
@@ -25,10 +25,25 @@ export interface FirstRunEnvironment {
|
|
|
25
25
|
hasProviderKey: boolean;
|
|
26
26
|
/** The coding agents already signed in locally: Codex, Claude Code, or both. */
|
|
27
27
|
localAgents: readonly SignedInAgent[];
|
|
28
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* init's quick read of this host for a local browser study: Linux x64 with `/dev/kvm`,
|
|
30
|
+
* `/dev/net/tun` and Docker,
|
|
31
|
+
* or an M3-or-newer Mac. Undefined when it was not read. `doctor --study local-browser` owns the
|
|
32
|
+
* full check.
|
|
33
|
+
*/
|
|
34
|
+
localBrowserHost?: LocalBrowserHost;
|
|
35
|
+
/** Host shape only, for the prerequisites a local step names. */
|
|
29
36
|
platform?: NodeJS.Platform;
|
|
30
|
-
|
|
37
|
+
/** How a suggested command invokes humanish; `npx humanish` when unset. */
|
|
38
|
+
humanish?: string;
|
|
31
39
|
}
|
|
40
|
+
/** Whether this host passed init's quick checks for a local browser study, and why not. */
|
|
41
|
+
export type LocalBrowserHost = {
|
|
42
|
+
ok: true;
|
|
43
|
+
} | {
|
|
44
|
+
ok: false;
|
|
45
|
+
reason: string;
|
|
46
|
+
};
|
|
32
47
|
export type FirstRunActor = "openai-computer-use" | "local-agent";
|
|
33
48
|
/**
|
|
34
49
|
* Which brain the starter live study should be written for. A machine with a signed-in coding agent
|
|
@@ -41,7 +56,7 @@ export declare function starterActorFor(env: FirstRunEnvironment): FirstRunActor
|
|
|
41
56
|
* refuse to run on a machine where only Claude Code is signed in.
|
|
42
57
|
*/
|
|
43
58
|
export declare function starterLocalAgentFor(env: FirstRunEnvironment): LocalAgentId | undefined;
|
|
44
|
-
/**
|
|
59
|
+
/** Host shapes the local browser route is built for: Linux x64 and Apple Silicon Macs. */
|
|
45
60
|
export declare function supportsLocalBrowser(platform: NodeJS.Platform | undefined, arch: string | undefined): boolean;
|
|
46
61
|
export interface FirstRunStep {
|
|
47
62
|
/** The exact command to run. */
|
|
@@ -50,14 +65,18 @@ export interface FirstRunStep {
|
|
|
50
65
|
why: string;
|
|
51
66
|
}
|
|
52
67
|
/**
|
|
53
|
-
* The next
|
|
54
|
-
*
|
|
68
|
+
* The next two or three commands, in order: the dry run, the hosted try-live study when keys or a
|
|
69
|
+
* signed-in agent allow it, and the local browser study where this host passed its quick checks.
|
|
70
|
+
* Deliberately short: a list of twelve options is the same as no guidance, and the reader here has
|
|
71
|
+
* just been handed a directory of new files.
|
|
55
72
|
*/
|
|
56
73
|
export declare function firstRunSteps(env: FirstRunEnvironment): FirstRunStep[];
|
|
57
74
|
/** The block init prints after its changes. */
|
|
58
75
|
export declare function firstRunGuidance(env: FirstRunEnvironment): string[];
|
|
59
|
-
/**
|
|
76
|
+
/** Starts humanish's section of `AGENTS.md`, on its heading line. */
|
|
60
77
|
export declare const AGENTS_SECTION_MARKER = "<!-- humanish:agents-guide -->";
|
|
78
|
+
/** Ends humanish's section, so init can replace the section and nothing after it. */
|
|
79
|
+
export declare const AGENTS_SECTION_END_MARKER = "<!-- /humanish:agents-guide -->";
|
|
61
80
|
/**
|
|
62
81
|
* What the next coding agent needs to know about humanish in this project.
|
|
63
82
|
*
|
|
@@ -65,6 +84,7 @@ export declare const AGENTS_SECTION_MARKER = "<!-- humanish:agents-guide -->";
|
|
|
65
84
|
* read on arrival. Increasingly the thing that ran `humanish init` was itself an agent working for
|
|
66
85
|
* someone, and the agent that shows up tomorrow finds a `humanish/` directory with no idea what it
|
|
67
86
|
* is for. Deliberately short and command-first, because an agent acts on the commands it is given.
|
|
87
|
+
* Commands use `humanish`, the prefix init chose for where humanish is installed.
|
|
68
88
|
*/
|
|
69
|
-
export declare function agentsSection(): string;
|
|
89
|
+
export declare function agentsSection(humanish?: string): string;
|
|
70
90
|
export {};
|
|
@@ -33,87 +33,111 @@ function preferredAgent(env) {
|
|
|
33
33
|
export function starterLocalAgentFor(env) {
|
|
34
34
|
return starterActorFor(env) === "local-agent" ? preferredAgent(env)?.id : undefined;
|
|
35
35
|
}
|
|
36
|
-
/**
|
|
37
|
-
* How each hint invokes the CLI. \`npx humanish\` works from a project install and from an npx cache;
|
|
38
|
-
* a dev-dependency install puts no \`humanish\` on \`PATH\`, so a bare name can reach a stale global one.
|
|
39
|
-
*/
|
|
40
|
-
const HUMANISH = "npx humanish";
|
|
41
|
-
/** Hosts that run local browser studies: Linux x64 and Apple Silicon Macs. */
|
|
36
|
+
/** Host shapes the local browser route is built for: Linux x64 and Apple Silicon Macs. */
|
|
42
37
|
export function supportsLocalBrowser(platform, arch) {
|
|
43
38
|
return (platform === "linux" && arch === "x64") || (platform === "darwin" && arch === "arm64");
|
|
44
39
|
}
|
|
45
40
|
/**
|
|
46
|
-
* The next
|
|
47
|
-
*
|
|
41
|
+
* The next two or three commands, in order: the dry run, the hosted try-live study when keys or a
|
|
42
|
+
* signed-in agent allow it, and the local browser study where this host passed its quick checks.
|
|
43
|
+
* Deliberately short: a list of twelve options is the same as no guidance, and the reader here has
|
|
44
|
+
* just been handed a directory of new files.
|
|
48
45
|
*/
|
|
49
46
|
export function firstRunSteps(env) {
|
|
47
|
+
const humanish = env.humanish ?? "npx humanish";
|
|
50
48
|
const steps = [
|
|
51
49
|
{
|
|
52
|
-
command: `${
|
|
50
|
+
command: `${humanish} run first-run`,
|
|
53
51
|
why: "a dry run: no browser or model runs, no keys, no spend",
|
|
54
52
|
},
|
|
55
53
|
];
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
why: `check the local browser study (${prerequisites}); no resources or quota used. Run \`${HUMANISH} runtime setup\` to prepare it, then start your app and run \`${HUMANISH} run local-browser\`; no E2B or model API key`,
|
|
63
|
-
});
|
|
64
|
-
return steps;
|
|
54
|
+
const local = localBrowserStep(env, humanish);
|
|
55
|
+
const hostedRunnable = env.hasE2bKey && (env.hasProviderKey || env.localAgents.length > 0);
|
|
56
|
+
if (hostedRunnable) {
|
|
57
|
+
steps.push(hostedStep(env, humanish, ""));
|
|
58
|
+
if (local !== undefined)
|
|
59
|
+
steps.push(local);
|
|
65
60
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
: " Local browsers are unavailable on this host; they support Linux x64 or M3-or-newer Apple Silicon Macs.";
|
|
69
|
-
if (!env.hasE2bKey) {
|
|
70
|
-
steps.push({
|
|
71
|
-
command: `${HUMANISH} keys set e2b`,
|
|
72
|
-
why: `the hosted starter needs an E2B desktop.${localUnavailable}`,
|
|
73
|
-
});
|
|
74
|
-
return steps;
|
|
61
|
+
else if (local !== undefined) {
|
|
62
|
+
steps.push(local);
|
|
75
63
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
? "your provider key"
|
|
79
|
-
: `${preferredAgent(env)?.label} (already signed in, so no API key is needed)`;
|
|
80
|
-
// Everything the step needs, in one line. Splitting it across two commands means the second
|
|
81
|
-
// one fails, which is the same dead end this guidance exists to remove.
|
|
82
|
-
if (!env.hasDesktopSdk && env.desktopPeerCommand === undefined) {
|
|
83
|
-
steps.push({
|
|
84
|
-
command: `${HUMANISH} doctor`,
|
|
85
|
-
why: "the hosted starter needs the desktop SDK, and installing it from here would remove what this directory's node_modules holds; doctor names the command to run from your project's directory",
|
|
86
|
-
});
|
|
87
|
-
return steps;
|
|
88
|
-
}
|
|
89
|
-
const command = env.hasDesktopSdk
|
|
90
|
-
? `${HUMANISH} run try-live`
|
|
91
|
-
: `${env.desktopPeerCommand} && ${HUMANISH} run try-live`;
|
|
92
|
-
steps.push({
|
|
93
|
-
command,
|
|
94
|
-
why: `a live study: one participant drives a real app in a hosted desktop, using ${brain}` +
|
|
95
|
-
(env.hasDesktopSdk ? "" : " (the desktop SDK is an optional peer, so it installs first)") +
|
|
96
|
-
(!env.hasProviderKey
|
|
97
|
-
? "; automatic findings analysis is skipped without OPENAI_API_KEY"
|
|
98
|
-
: "") +
|
|
99
|
-
localUnavailable,
|
|
100
|
-
});
|
|
101
|
-
return steps;
|
|
64
|
+
else {
|
|
65
|
+
steps.push(hostedStep(env, humanish, localUnavailableNote(env)));
|
|
102
66
|
}
|
|
103
|
-
steps.push({
|
|
104
|
-
command: `${HUMANISH} keys set openai`,
|
|
105
|
-
why: "openai-computer-use needs an API key; to use a Codex or Claude Code login instead, sign in and change the study to actor.type: local-agent" +
|
|
106
|
-
localUnavailable,
|
|
107
|
-
});
|
|
108
67
|
return steps;
|
|
109
68
|
}
|
|
69
|
+
/** The local browser step, where the host passed init's checks and Codex is signed in. */
|
|
70
|
+
function localBrowserStep(env, humanish) {
|
|
71
|
+
if (env.localBrowserHost?.ok !== true)
|
|
72
|
+
return undefined;
|
|
73
|
+
if (!env.localAgents.some((agent) => agent.id === "codex"))
|
|
74
|
+
return undefined;
|
|
75
|
+
const prerequisites = env.platform === "darwin"
|
|
76
|
+
? "native ARM64 Node, Lima 2.2+, and a supported signed-in Codex CLI"
|
|
77
|
+
: "local rootful Docker, KVM, TUN, and a supported signed-in Codex CLI";
|
|
78
|
+
return {
|
|
79
|
+
command: `${humanish} doctor --study local-browser`,
|
|
80
|
+
why: `check the local browser study (${prerequisites}); no resources or quota used. Run \`${humanish} runtime setup\` to prepare it, then start your app and run \`${humanish} run local-browser\`; no E2B or model API key`,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/** Why the local route is not offered, appended to a key step so the reader knows it was considered. */
|
|
84
|
+
function localUnavailableNote(env) {
|
|
85
|
+
const host = env.localBrowserHost;
|
|
86
|
+
if (host === undefined)
|
|
87
|
+
return "";
|
|
88
|
+
if (!host.ok)
|
|
89
|
+
return ` Local browser studies are unavailable on this host: ${host.reason}.`;
|
|
90
|
+
return " A local browser study also runs on this host once Codex is signed in (`codex login`).";
|
|
91
|
+
}
|
|
92
|
+
/** The hosted try-live step, or the key or setup step it still needs. */
|
|
93
|
+
function hostedStep(env, humanish, note) {
|
|
94
|
+
if (!env.hasE2bKey) {
|
|
95
|
+
return {
|
|
96
|
+
command: `${humanish} keys set e2b`,
|
|
97
|
+
why: `the hosted starter needs an E2B desktop.${note}`,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
if (!env.hasProviderKey && env.localAgents.length === 0) {
|
|
101
|
+
return {
|
|
102
|
+
command: `${humanish} keys set openai`,
|
|
103
|
+
why: "openai-computer-use needs an API key; to use a Codex or Claude Code login instead, sign in and change the study to actor.type: local-agent." +
|
|
104
|
+
note,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
const brain = env.hasProviderKey
|
|
108
|
+
? "your provider key"
|
|
109
|
+
: `${preferredAgent(env)?.label} (already signed in, so no API key is needed)`;
|
|
110
|
+
// Everything the step needs, in one line. Splitting it across two commands means the second
|
|
111
|
+
// one fails, which is the same dead end this guidance exists to remove.
|
|
112
|
+
if (!env.hasDesktopSdk && env.desktopPeerCommand === undefined) {
|
|
113
|
+
return {
|
|
114
|
+
command: `${humanish} doctor`,
|
|
115
|
+
why: "the hosted starter needs the desktop SDK, and installing it from here would remove what this directory's node_modules holds; doctor names the command to run from your project's directory",
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
// After the peer install, humanish is a dependency of this project, where `npx humanish` finds it.
|
|
119
|
+
const command = env.hasDesktopSdk
|
|
120
|
+
? `${humanish} run try-live`
|
|
121
|
+
: `${env.desktopPeerCommand} && npx humanish run try-live`;
|
|
122
|
+
return {
|
|
123
|
+
command,
|
|
124
|
+
why: `a live study: one participant drives a real app in a hosted desktop, using ${brain}` +
|
|
125
|
+
(env.hasDesktopSdk ? "" : " (the desktop SDK is an optional peer, so it installs first)") +
|
|
126
|
+
(!env.hasProviderKey
|
|
127
|
+
? "; automatic findings analysis is skipped without OPENAI_API_KEY"
|
|
128
|
+
: "") +
|
|
129
|
+
note,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
110
132
|
/** The block init prints after its changes. */
|
|
111
133
|
export function firstRunGuidance(env) {
|
|
112
134
|
const steps = firstRunSteps(env);
|
|
113
135
|
return ["", "next:", ...steps.flatMap((step) => [` ${step.command}`, ` ${step.why}`])];
|
|
114
136
|
}
|
|
115
|
-
/**
|
|
137
|
+
/** Starts humanish's section of `AGENTS.md`, on its heading line. */
|
|
116
138
|
export const AGENTS_SECTION_MARKER = "<!-- humanish:agents-guide -->";
|
|
139
|
+
/** Ends humanish's section, so init can replace the section and nothing after it. */
|
|
140
|
+
export const AGENTS_SECTION_END_MARKER = "<!-- /humanish:agents-guide -->";
|
|
117
141
|
/**
|
|
118
142
|
* What the next coding agent needs to know about humanish in this project.
|
|
119
143
|
*
|
|
@@ -121,8 +145,23 @@ export const AGENTS_SECTION_MARKER = "<!-- humanish:agents-guide -->";
|
|
|
121
145
|
* read on arrival. Increasingly the thing that ran `humanish init` was itself an agent working for
|
|
122
146
|
* someone, and the agent that shows up tomorrow finds a `humanish/` directory with no idea what it
|
|
123
147
|
* is for. Deliberately short and command-first, because an agent acts on the commands it is given.
|
|
148
|
+
* Commands use `humanish`, the prefix init chose for where humanish is installed.
|
|
124
149
|
*/
|
|
125
|
-
export function agentsSection() {
|
|
150
|
+
export function agentsSection(humanish = "npx humanish") {
|
|
151
|
+
const commands = [
|
|
152
|
+
["study list --json", "the studies in this project"],
|
|
153
|
+
["run first-run --no-open", "dry run: no browser, model, keys, or spend"],
|
|
154
|
+
["doctor --study try-live", "what the hosted starter study still needs"],
|
|
155
|
+
[
|
|
156
|
+
"run try-live --no-open",
|
|
157
|
+
"demo app study: E2B plus the selected participant's authentication",
|
|
158
|
+
],
|
|
159
|
+
["doctor --study local-browser", "local Docker/Firecracker + Codex-account readiness"],
|
|
160
|
+
["run local-browser --no-open", "your loopback app; no E2B or model API key"],
|
|
161
|
+
["verify --run <id> --json", "is the evidence share-safe"],
|
|
162
|
+
["review --run <id>", "the run's outcome and its analysis findings"],
|
|
163
|
+
];
|
|
164
|
+
const width = Math.max(...commands.map(([command]) => command.length));
|
|
126
165
|
return [
|
|
127
166
|
"",
|
|
128
167
|
`## humanish ${AGENTS_SECTION_MARKER}`,
|
|
@@ -130,30 +169,28 @@ export function agentsSection() {
|
|
|
130
169
|
"This project uses humanish: synthetic participants use the product and leave evidence.",
|
|
131
170
|
"",
|
|
132
171
|
"```bash",
|
|
133
|
-
|
|
134
|
-
"humanish study list --json # the studies in this project",
|
|
135
|
-
"humanish run first-run # dry run: no browser, model, keys, or spend",
|
|
136
|
-
"humanish doctor --study local-browser # local Docker/Firecracker + Codex-account readiness",
|
|
137
|
-
"humanish run local-browser # your loopback app; no E2B or model API key",
|
|
138
|
-
"humanish run try-live # demo app study: E2B plus the selected participant's authentication",
|
|
139
|
-
"humanish verify --run latest --json # is the evidence share-safe",
|
|
140
|
-
"humanish review --run <id> # the run's outcome and its analysis findings",
|
|
172
|
+
...commands.map(([command, why]) => `${humanish} ${command.padEnd(width)} # ${why}`),
|
|
141
173
|
"```",
|
|
142
174
|
"",
|
|
143
|
-
|
|
144
|
-
" person you are working for: each one's title, impact, confidence, recovery and cited
|
|
145
|
-
" When it shows no findings, report its message and the command it names.",
|
|
175
|
+
`- After a live run, run \`${humanish} review --run <id>\` (or \`--json\`) and report its findings`,
|
|
176
|
+
" to the person you are working for: each one's title, impact, confidence, recovery and cited",
|
|
177
|
+
" captures. When it shows no findings, report its message and the command it names. Every run",
|
|
178
|
+
" prints its id; `latest` moves with each run, dry runs included.",
|
|
146
179
|
"- Studies are declared in `humanish/studies/*.yaml`. Edit `try-live.yaml`'s `subject` to point",
|
|
147
180
|
" at this project's own app once you have seen a run work.",
|
|
148
|
-
|
|
181
|
+
`- Configure the local study without editing YAML: \`${humanish} init --yes --local-browser`,
|
|
149
182
|
' http://127.0.0.1:3000 --local-mission "Complete the primary flow"` on first setup.',
|
|
150
183
|
"- Evidence lands in gitignored `.humanish/runs/`. Never commit it, and never paste raw run",
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
"
|
|
184
|
+
` bundles into an issue; \`${humanish} feedback issue\` writes a redacted, share-safe draft.`,
|
|
185
|
+
`- \`${humanish} tui\` is for people and refuses to run in an agent session. Use the commands`,
|
|
186
|
+
" above instead, and tell the person you are working for that the TUI exists.",
|
|
154
187
|
"- A live run spends money. `caps.maxUsd` in each study caps estimated model spend: the run",
|
|
155
188
|
" stops before its next request once the estimate passes it, so the last request can go over, and",
|
|
156
189
|
" hosted desktop time is billed on top. Do not raise it without asking the person you are working for.",
|
|
190
|
+
`- \`${humanish} init --yes\` rewrites this section, between its two markers, to match the installed`,
|
|
191
|
+
" humanish. Write your own notes outside it.",
|
|
192
|
+
"",
|
|
193
|
+
AGENTS_SECTION_END_MARKER,
|
|
157
194
|
"",
|
|
158
195
|
].join("\n");
|
|
159
196
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type HumanishInstall } from "../substrates/e2b/peer-install.js";
|
|
2
|
+
/** The prefix for a suggested command, by install. */
|
|
3
|
+
export declare function invocationFor(install: HumanishInstall, version: string): string;
|
|
4
|
+
/** How this process's suggested commands invoke humanish, read once per process. */
|
|
5
|
+
export declare function humanishCommand(): string;
|
|
6
|
+
/** A suggested command: `cli("review --run x")` is `npx humanish review --run x` in a project. */
|
|
7
|
+
export declare function cli(rest: string): string;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// How the commands humanish suggests should invoke humanish, chosen from where the running copy is
|
|
2
|
+
// installed. A dev-dependency install puts no `humanish` on `PATH`, so a bare name there reaches a
|
|
3
|
+
// stale global install when one exists, or nothing. `npx humanish` with no version, run where no
|
|
4
|
+
// project declares humanish, also resolves a global install before it fetches one. A one-shot run
|
|
5
|
+
// therefore names its own version, so the next command runs the same CLI.
|
|
6
|
+
import { humanishInstall } from "../substrates/e2b/peer-install.js";
|
|
7
|
+
import { CLI_VERSION } from "./version.js";
|
|
8
|
+
/** The prefix for a suggested command, by install. */
|
|
9
|
+
export function invocationFor(install, version) {
|
|
10
|
+
switch (install.kind) {
|
|
11
|
+
case "project":
|
|
12
|
+
case "other":
|
|
13
|
+
return "npx humanish";
|
|
14
|
+
case "one-shot":
|
|
15
|
+
return `npx humanish@${version}`;
|
|
16
|
+
// A global install is on `PATH`. A source checkout runs through a link or `pnpm humanish`, and
|
|
17
|
+
// keeping the bare name there keeps test output the same on every machine.
|
|
18
|
+
case "global":
|
|
19
|
+
case "checkout":
|
|
20
|
+
return "humanish";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
let cached;
|
|
24
|
+
/** How this process's suggested commands invoke humanish, read once per process. */
|
|
25
|
+
export function humanishCommand() {
|
|
26
|
+
cached ??= invocationFor(humanishInstall(), CLI_VERSION);
|
|
27
|
+
return cached;
|
|
28
|
+
}
|
|
29
|
+
/** A suggested command: `cli("review --run x")` is `npx humanish review --run x` in a project. */
|
|
30
|
+
export function cli(rest) {
|
|
31
|
+
return `${humanishCommand()} ${rest}`;
|
|
32
|
+
}
|