humanish 0.109.1 → 0.110.1
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 +14 -11
- package/CHANGELOG.md +24 -0
- package/CONTEXT.md +8 -6
- package/README.md +97 -48
- package/dist/actors/computer-use/loop/ending.js +4 -4
- package/dist/actors/local-agent/claude-participant.d.ts +74 -0
- package/dist/actors/local-agent/claude-participant.js +243 -0
- package/dist/actors/local-agent/claude-session.d.ts +16 -3
- package/dist/actors/local-agent/claude-session.js +64 -23
- package/dist/actors/local-agent/cli.d.ts +11 -0
- package/dist/actors/local-agent/cli.js +103 -24
- package/dist/actors/local-agent/readiness.d.ts +2 -2
- package/dist/actors/local-agent/readiness.js +18 -11
- package/dist/cli/commands/analyze.js +21 -5
- package/dist/cli/commands/comms.js +25 -7
- package/dist/cli/commands/observe.js +4 -2
- package/dist/cli/commands/run-command.js +4 -3
- package/dist/cli/commands/run-signals.d.ts +2 -0
- package/dist/cli/commands/run-signals.js +96 -44
- package/dist/cli/commands/runs.d.ts +5 -0
- package/dist/cli/commands/runs.js +89 -67
- package/dist/cli/commands/setup.js +19 -5
- package/dist/cli/commands/study-format.js +29 -2
- package/dist/cli/commands/study-route-computer-use.js +2 -0
- package/dist/cli/commands/study-route-scripted.js +2 -0
- package/dist/cli/commands/study-route-shared-world.js +2 -0
- package/dist/cli/commands/study-route-terminal.js +2 -0
- package/dist/cli/commands/study.js +4 -3
- package/dist/cli/commands/tui.js +4 -3
- package/dist/cli/commands/watch.js +2 -2
- package/dist/cli/doctor.d.ts +7 -2
- package/dist/cli/doctor.js +47 -8
- package/dist/cli/findings.d.ts +103 -0
- package/dist/cli/findings.js +316 -0
- package/dist/cli/first-run-path.d.ts +5 -4
- package/dist/cli/first-run-path.js +14 -7
- package/dist/cli/io.d.ts +17 -3
- package/dist/cli/io.js +22 -4
- package/dist/cli/product-sentence.d.ts +3 -3
- package/dist/cli/product-sentence.js +3 -3
- package/dist/comms/receiving-runtime.js +1 -1
- package/dist/comms/setup.js +1 -1
- package/dist/evidence/redaction.d.ts +7 -3
- package/dist/evidence/redaction.js +68 -24
- package/dist/feedback/export-bundle.js +6 -1
- package/dist/feedback/export.js +3 -0
- package/dist/keys/env-file.d.ts +4 -0
- package/dist/keys/env-file.js +17 -0
- package/dist/keys/key-resolution.d.ts +7 -0
- package/dist/keys/key-resolution.js +10 -8
- package/dist/observer/data.d.ts +14 -1
- package/dist/observer/data.js +19 -9
- package/dist/observer/library.d.ts +4 -0
- package/dist/observer/library.js +18 -15
- package/dist/observer/render.js +8 -2
- package/dist/observer/run-routes.d.ts +2 -0
- package/dist/observer/run-routes.js +43 -34
- package/dist/observer-app.html +3 -3
- package/dist/routes/computer-use/bundle.js +3 -1
- package/dist/routes/computer-use/fanout-records.js +1 -1
- package/dist/routes/computer-use/live-phase.d.ts +1 -0
- package/dist/routes/computer-use/participant-execution.js +3 -1
- package/dist/routes/computer-use/participant-model.js +5 -2
- package/dist/routes/computer-use/plan.js +4 -4
- package/dist/routes/computer-use/preflight.js +2 -2
- package/dist/routes/computer-use/result.js +17 -28
- package/dist/routes/computer-use/single-bundle.js +7 -1
- package/dist/routes/computer-use/types.d.ts +6 -0
- package/dist/routes/scripted/bundle.js +4 -2
- package/dist/routes/scripted/plan.js +2 -2
- package/dist/routes/scripted/result.d.ts +3 -5
- package/dist/routes/scripted/result.js +16 -22
- package/dist/routes/scripted/scenario.js +4 -4
- package/dist/routes/scripted/types.d.ts +1 -0
- package/dist/routes/shared-world/bundle-records.js +6 -1
- package/dist/routes/shared-world/bundle.js +2 -0
- package/dist/routes/shared-world/plan.js +1 -1
- package/dist/routes/shared-world/result.js +22 -22
- package/dist/routes/shared-world/route.js +1 -1
- package/dist/routes/shared-world/types.d.ts +2 -0
- package/dist/routes/terminal/bundle.js +4 -2
- package/dist/routes/terminal/credentials.js +1 -1
- package/dist/routes/terminal/dry-run.js +8 -12
- package/dist/routes/terminal/ledger.d.ts +3 -3
- package/dist/routes/terminal/ledger.js +6 -6
- package/dist/routes/terminal/live-finish.js +23 -17
- package/dist/routes/terminal/plan.js +4 -4
- package/dist/routes/terminal/result.d.ts +6 -7
- package/dist/routes/terminal/result.js +6 -22
- package/dist/routes/terminal/session.js +1 -1
- package/dist/routes/terminal/types.d.ts +3 -0
- package/dist/run/bundle-shape.d.ts +3 -1
- package/dist/run/bundle-shape.js +26 -1
- package/dist/run/bundle.d.ts +30 -0
- package/dist/run/display.d.ts +59 -0
- package/dist/run/display.js +106 -0
- package/dist/run/dry-run.js +11 -7
- package/dist/run/judge.d.ts +3 -2
- package/dist/run/judge.js +10 -0
- package/dist/run/pricing.js +1 -1
- package/dist/run/projection.d.ts +0 -2
- package/dist/run/projection.js +0 -11
- package/dist/run/reclaim-outcomes.d.ts +89 -0
- package/dist/run/reclaim-outcomes.js +213 -0
- package/dist/run/reclaim.d.ts +18 -14
- package/dist/run/reclaim.js +179 -105
- package/dist/run/results.d.ts +11 -18
- package/dist/run/results.js +2 -2
- package/dist/run/run-index.d.ts +11 -1
- package/dist/run/run-index.js +11 -0
- package/dist/run/run.d.ts +34 -18
- package/dist/run/run.js +177 -72
- package/dist/run/sandbox-creates.d.ts +59 -0
- package/dist/run/sandbox-creates.js +138 -0
- package/dist/run/sandbox-ids.d.ts +53 -0
- package/dist/run/sandbox-ids.js +198 -0
- package/dist/run/sandbox-receipts.d.ts +16 -0
- package/dist/run/sandbox-receipts.js +70 -0
- package/dist/run/stats.d.ts +5 -0
- package/dist/run/stats.js +8 -4
- package/dist/run/status.d.ts +6 -9
- package/dist/run/status.js +7 -20
- package/dist/run/stored-runs.d.ts +5 -14
- package/dist/run/stored-runs.js +31 -137
- package/dist/run/streams.d.ts +3 -2
- package/dist/run-study.js +6 -1
- package/dist/study/doctor.d.ts +1 -1
- package/dist/study/doctor.js +7 -4
- package/dist/study/init-templates.js +2 -2
- package/dist/study/init.js +14 -3
- package/dist/study/keys.d.ts +12 -0
- package/dist/study/keys.js +32 -0
- package/dist/study/persona.d.ts +4 -0
- package/dist/study/persona.js +4 -2
- package/dist/study/types.d.ts +1 -1
- package/dist/substrates/e2b/peer-install.d.ts +51 -0
- package/dist/substrates/e2b/peer-install.js +161 -0
- package/dist/substrates/e2b/sandbox.d.ts +34 -6
- package/dist/substrates/e2b/sandbox.js +128 -35
- package/dist/substrates/e2b/sdk.d.ts +33 -0
- package/dist/substrates/e2b/sdk.js +21 -21
- package/dist/substrates/shell.d.ts +2 -0
- package/dist/substrates/shell.js +4 -0
- package/dist/tui-app.js +133 -133
- package/dist/verify/artifacts.d.ts +4 -0
- package/dist/verify/artifacts.js +8 -0
- package/dist/verify/liveness.d.ts +19 -3
- package/dist/verify/liveness.js +53 -28
- package/dist/verify/verify.d.ts +4 -1
- package/dist/verify/verify.js +27 -3
- package/docs/architecture/actor-contract.md +2 -1
- package/docs/architecture/observer.md +22 -7
- package/docs/architecture/restricted-codex-analysis.md +1 -1
- package/docs/architecture/task-protocol-support.md +13 -13
- package/docs/architecture/terminal-product-route.md +1 -1
- package/docs/contracts/run-bundle.md +90 -7
- package/docs/contracts/schemas.md +39 -22
- package/docs/contracts/study-analysis.md +45 -0
- package/docs/contracts/study-costs.md +8 -0
- package/docs/decisions/0001-run-bundle-is-the-source-of-truth.md +3 -0
- package/docs/decisions/0003-contained-paths-and-cleanup-authority.md +9 -3
- package/docs/principles/invariants-and-defaults.md +11 -8
- package/package.json +5 -3
- package/skills/humanish/SKILL.md +18 -4
package/dist/run/stored-runs.js
CHANGED
|
@@ -1,148 +1,17 @@
|
|
|
1
1
|
import { lstat, readdir } from "node:fs/promises";
|
|
2
|
-
import { runNotFoundMessage } from "./run-not-found.js";
|
|
3
2
|
import path from "node:path";
|
|
4
3
|
import { redactText } from "../evidence/redaction.js";
|
|
5
|
-
import { bindExistingRunArtifactPaths, RUNS_RELATIVE_ROOT, isSafeRunIdSegment, resolveRunsRoot,
|
|
6
|
-
import { assertPreparedSelectedOutputDirectory, bindExistingManagedHumanishOutputDirectory,
|
|
7
|
-
import { RUN_BUNDLE_FILE
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
4
|
+
import { bindExistingRunArtifactPaths, RUNS_RELATIVE_ROOT, isSafeRunIdSegment, resolveRunsRoot, } from "./paths.js";
|
|
5
|
+
import { assertPreparedSelectedOutputDirectory, bindExistingManagedHumanishOutputDirectory, } from "./contained-output.js";
|
|
6
|
+
import { RUN_BUNDLE_FILE } from "./bundle.js";
|
|
7
|
+
import { isReviewSummary, isRunOutcome } from "./bundle-shape.js";
|
|
8
|
+
import { bundleDisplayFacts, runDisplay } from "./display.js";
|
|
9
|
+
import { RUN_STATUS_FILE } from "./status.js";
|
|
10
10
|
import { readLatest, readRunJsonIfExists, resolveRunPath } from "./locate.js";
|
|
11
11
|
import { withCuaReviewProvenance } from "./outcomes.js";
|
|
12
12
|
import { isNodeError, isRecord } from "./type-guards.js";
|
|
13
13
|
import { invalidRunStorageVerifyResult, verifyResolvedRun, } from "../verify/verify.js";
|
|
14
14
|
const RUNS_SCHEMA = "humanish.runs-result.v1";
|
|
15
|
-
/** A cleanup result that refused before inspecting any resource. */
|
|
16
|
-
function cleanupRefusal(cwd, runInput, checkedAt, error, bundlePath) {
|
|
17
|
-
return {
|
|
18
|
-
schema: CLEANUP_SCHEMA,
|
|
19
|
-
ok: false,
|
|
20
|
-
cwd,
|
|
21
|
-
run: runInput,
|
|
22
|
-
...(bundlePath === undefined ? {} : { bundlePath }),
|
|
23
|
-
checkedAt,
|
|
24
|
-
summary: { resources: 0, killed: 0, alreadyClean: 0, failed: 0, skipped: 0 },
|
|
25
|
-
resources: [],
|
|
26
|
-
adapterResults: [],
|
|
27
|
-
warnings: [],
|
|
28
|
-
error,
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
/** What the recorded evidence says about one provider resource; nothing is killed from here. */
|
|
32
|
-
function recordedResourceResult(resource) {
|
|
33
|
-
const base = { provider: resource.provider, kind: resource.kind, id: resource.id };
|
|
34
|
-
if (resource.provider !== "e2b-desktop" || resource.kind !== "sandbox") {
|
|
35
|
-
return {
|
|
36
|
-
...base,
|
|
37
|
-
status: "skipped",
|
|
38
|
-
message: "cleanup only supports e2b-desktop sandbox resources",
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
if (resource.status === "killed" || resource.cleanup?.killed === true) {
|
|
42
|
-
return {
|
|
43
|
-
...base,
|
|
44
|
-
status: "already_clean",
|
|
45
|
-
message: "resource was already recorded as killed",
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
return {
|
|
49
|
-
...base,
|
|
50
|
-
status: "failed",
|
|
51
|
-
message: "automatic provider cleanup requires a verified resource lease",
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
export async function cleanupRun(cwdInput, runInput, hooks = {}) {
|
|
55
|
-
const cwd = path.resolve(cwdInput);
|
|
56
|
-
const checkedAt = (hooks.now ?? (() => new Date()))().toISOString();
|
|
57
|
-
let resolved;
|
|
58
|
-
try {
|
|
59
|
-
resolved = await resolveRunPath(cwd, runInput);
|
|
60
|
-
}
|
|
61
|
-
catch {
|
|
62
|
-
return cleanupRefusal(cwd, runInput, checkedAt, {
|
|
63
|
-
code: "HUMANISH_INVALID_RUN_BUNDLE",
|
|
64
|
-
message: "Run storage failed containment validation.",
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
if (!resolved) {
|
|
68
|
-
return cleanupRefusal(cwd, runInput, checkedAt, {
|
|
69
|
-
code: "HUMANISH_RUN_NOT_FOUND",
|
|
70
|
-
message: await runNotFoundMessage(cwd, runInput),
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
const runPaths = resolved;
|
|
74
|
-
const bundlePath = path.join(runPaths.absoluteRunRoot, RUN_BUNDLE_FILE);
|
|
75
|
-
const cleanupPath = path.join(runPaths.absoluteRunRoot, "cleanup.json");
|
|
76
|
-
await prepareContainedOutputFile(runPaths, "cleanup.json");
|
|
77
|
-
const bundleBytes = await readContainedRegularFile(runPaths, RUN_BUNDLE_FILE);
|
|
78
|
-
let bundle = null;
|
|
79
|
-
if (bundleBytes) {
|
|
80
|
-
try {
|
|
81
|
-
bundle = JSON.parse(bundleBytes.toString("utf8"));
|
|
82
|
-
}
|
|
83
|
-
catch {
|
|
84
|
-
bundle = null;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
if (!isRunBundle(bundle)) {
|
|
88
|
-
return cleanupRefusal(cwd, runInput, checkedAt, {
|
|
89
|
-
code: "HUMANISH_INVALID_RUN_BUNDLE",
|
|
90
|
-
message: "Run bundle failed cleanup shape validation.",
|
|
91
|
-
}, path.relative(cwd, bundlePath));
|
|
92
|
-
}
|
|
93
|
-
const warnings = [];
|
|
94
|
-
const providerResources = bundle.providerResources ?? [];
|
|
95
|
-
const resources = providerResources.map(recordedResourceResult);
|
|
96
|
-
let adapterResults = [];
|
|
97
|
-
if (hooks.cleanupAdapterResources) {
|
|
98
|
-
try {
|
|
99
|
-
adapterResults = await hooks.cleanupAdapterResources({
|
|
100
|
-
cwd,
|
|
101
|
-
runDir: runPaths.physicalRunRoot,
|
|
102
|
-
bundle,
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
catch (error) {
|
|
106
|
-
adapterResults = [
|
|
107
|
-
{
|
|
108
|
-
id: "adapter-cleanup",
|
|
109
|
-
ok: false,
|
|
110
|
-
message: error instanceof Error ? error.message : String(error),
|
|
111
|
-
},
|
|
112
|
-
];
|
|
113
|
-
}
|
|
114
|
-
await validatePreparedRunArtifactPaths(runPaths);
|
|
115
|
-
}
|
|
116
|
-
if (providerResources.length === 0 && adapterResults.length === 0) {
|
|
117
|
-
warnings.push("Run bundle recorded no provider resource evidence; nothing to inspect.");
|
|
118
|
-
}
|
|
119
|
-
const summary = {
|
|
120
|
-
resources: resources.length,
|
|
121
|
-
killed: 0,
|
|
122
|
-
alreadyClean: resources.filter((resource) => resource.status === "already_clean").length,
|
|
123
|
-
failed: resources.filter((resource) => resource.status === "failed").length +
|
|
124
|
-
adapterResults.filter((result) => !result.ok).length,
|
|
125
|
-
skipped: resources.filter((resource) => resource.status === "skipped").length,
|
|
126
|
-
};
|
|
127
|
-
const ok = summary.failed === 0;
|
|
128
|
-
const result = {
|
|
129
|
-
schema: CLEANUP_SCHEMA,
|
|
130
|
-
ok,
|
|
131
|
-
cwd: PUBLIC_TARGET_CWD,
|
|
132
|
-
run: runInput,
|
|
133
|
-
runId: bundle.runId,
|
|
134
|
-
bundlePath: path.relative(cwd, bundlePath),
|
|
135
|
-
cleanupPath: path.relative(cwd, cleanupPath),
|
|
136
|
-
checkedAt,
|
|
137
|
-
summary,
|
|
138
|
-
resources,
|
|
139
|
-
adapterResults,
|
|
140
|
-
warnings,
|
|
141
|
-
};
|
|
142
|
-
await validatePreparedRunArtifactPaths(runPaths);
|
|
143
|
-
await writeContainedOutputFile(runPaths, "cleanup.json", `${JSON.stringify(result, null, 2)}\n`, "utf8");
|
|
144
|
-
return result;
|
|
145
|
-
}
|
|
146
15
|
export async function listRuns(cwdInput) {
|
|
147
16
|
const cwd = path.resolve(cwdInput);
|
|
148
17
|
const runsRootPath = resolveRunsRoot(cwd);
|
|
@@ -207,6 +76,7 @@ export async function listRuns(cwdInput) {
|
|
|
207
76
|
createdAt: isRecord(bundle) && typeof bundle.createdAt === "string" ? bundle.createdAt : null,
|
|
208
77
|
mode: isRecord(bundle) && typeof bundle.mode === "string" ? bundle.mode : null,
|
|
209
78
|
path: path.join(RUNS_RELATIVE_ROOT, entryName),
|
|
79
|
+
...(await listedRunDisplay(entryRunPaths, bundle)),
|
|
210
80
|
});
|
|
211
81
|
}
|
|
212
82
|
if (runsRoot) {
|
|
@@ -225,6 +95,29 @@ export async function listRuns(cwdInput) {
|
|
|
225
95
|
latest: latest?.runId ?? null,
|
|
226
96
|
};
|
|
227
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* A listed run's display, from its run.json and, for a bundle without an outcome, its status
|
|
100
|
+
* record. Like the listing's other fields it reads the file without verifying it; a run.json
|
|
101
|
+
* that is not an object naming a run gets none.
|
|
102
|
+
*/
|
|
103
|
+
async function listedRunDisplay(runPaths, bundle) {
|
|
104
|
+
if (!isRecord(bundle) || typeof bundle.runId !== "string")
|
|
105
|
+
return {};
|
|
106
|
+
const outcome = isRunOutcome(bundle.outcome) ? bundle.outcome : undefined;
|
|
107
|
+
const status = outcome === undefined ? await readRunJsonIfExists(runPaths, RUN_STATUS_FILE) : undefined;
|
|
108
|
+
const facts = bundleDisplayFacts({
|
|
109
|
+
runId: bundle.runId,
|
|
110
|
+
...(typeof bundle.mode === "string" ? { mode: bundle.mode } : {}),
|
|
111
|
+
...(isRecord(bundle.review) && typeof bundle.review.verdict === "string"
|
|
112
|
+
? { review: { verdict: bundle.review.verdict } }
|
|
113
|
+
: {}),
|
|
114
|
+
...(Array.isArray(bundle.simulations)
|
|
115
|
+
? { simulations: bundle.simulations.filter(isRecord) }
|
|
116
|
+
: {}),
|
|
117
|
+
...(outcome === undefined ? {} : { outcome }),
|
|
118
|
+
}, status);
|
|
119
|
+
return { display: runDisplay(facts) };
|
|
120
|
+
}
|
|
228
121
|
function runsUnavailableResult(cwd, error) {
|
|
229
122
|
return {
|
|
230
123
|
schema: RUNS_SCHEMA,
|
|
@@ -270,5 +163,6 @@ export async function readReview(cwdInput, runInput) {
|
|
|
270
163
|
...projected,
|
|
271
164
|
path: path.relative(cwd, path.join(runPaths.absoluteRunRoot, "review.json")),
|
|
272
165
|
runId: path.basename(runPaths.absoluteRunRoot),
|
|
166
|
+
...(runPaths ? await listedRunDisplay(runPaths, bundle) : {}),
|
|
273
167
|
};
|
|
274
168
|
}
|
package/dist/run/streams.d.ts
CHANGED
|
@@ -171,8 +171,9 @@ export interface RunStream {
|
|
|
171
171
|
/**
|
|
172
172
|
* The judge's status for this participant (`judgedStatus` in src/run/judge.ts). It differs from
|
|
173
173
|
* the trace status when a goal_satisfied session reported a blocker or never engaged. Computer-use
|
|
174
|
-
*
|
|
175
|
-
* and
|
|
174
|
+
* and shared-world runs write it for a participant whose session finished; rerun selection and
|
|
175
|
+
* the Observer read it. Absent on the terminal and scripted routes, whose judge has no blocker
|
|
176
|
+
* rule, and in bundles written before it existed.
|
|
176
177
|
*/
|
|
177
178
|
judgedStatus?: ActorStatus;
|
|
178
179
|
transport: "snapshot" | "polling" | "sse" | "pty" | "app-server";
|
package/dist/run-study.js
CHANGED
|
@@ -19,6 +19,7 @@ import {} from "./routes/shared-world/types.js";
|
|
|
19
19
|
import { admitTerminalPlan, terminalStudyRefusal } from "./routes/terminal/route.js";
|
|
20
20
|
import {} from "./routes/terminal/types.js";
|
|
21
21
|
import {} from "./run/bundle.js";
|
|
22
|
+
import { publicRunResult } from "./run/sandbox-ids.js";
|
|
22
23
|
import { isLocalBrowserStudy, localBrowserDefaults } from "./substrates/local/runtime-config.js";
|
|
23
24
|
/**
|
|
24
25
|
* Runs a study on its route. The options are checked against the route and mapped into the route's
|
|
@@ -100,7 +101,11 @@ export async function prepareStudy(config, options, deps = {}) {
|
|
|
100
101
|
// study, so a rebuilt one would leave the participants they start for no finally to close.
|
|
101
102
|
const outcome = await admitted.run(scorer);
|
|
102
103
|
outcome.result.warnings.push(...normalized.warnings);
|
|
103
|
-
|
|
104
|
+
// The result names each sandbox by digest; the raw ids stay in the run's receipts.
|
|
105
|
+
return {
|
|
106
|
+
...outcome,
|
|
107
|
+
result: await publicRunResult(outcome.result, options.cwd),
|
|
108
|
+
};
|
|
104
109
|
}
|
|
105
110
|
finally {
|
|
106
111
|
await vm?.close();
|
package/dist/study/doctor.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type LocalRuntimeStatus } from "../substrates/local/runtime.js";
|
|
2
2
|
import type { StudyConfig } from "./types.js";
|
|
3
3
|
import type { PlanResult } from "./plan-types.js";
|
|
4
|
-
import type
|
|
4
|
+
import { type DetectedLocalAgent } from "../actors/local-agent/cli.js";
|
|
5
5
|
import type { ReasoningEffort } from "../actors/reasoning-effort.js";
|
|
6
6
|
import type { DoctorCheckDraft } from "../cli/doctor.js";
|
|
7
7
|
import { type CodexInstallation } from "../actors/codex/codex-admission.js";
|
package/dist/study/doctor.js
CHANGED
|
@@ -2,6 +2,7 @@ import path from "node:path";
|
|
|
2
2
|
import { isLocalBrowserStudy } from "../substrates/local/runtime-config.js";
|
|
3
3
|
import { localRuntimeStatus } from "../substrates/local/runtime.js";
|
|
4
4
|
import { keyNamesOf, requiredKeys, requiredSubjectEnv } from "./requirements.js";
|
|
5
|
+
import { localAgentVersionRefusal } from "../actors/local-agent/cli.js";
|
|
5
6
|
import { protocolAdditionsWarning, protocolIncompatibilityMessage, } from "../actors/codex/protocol-compat.js";
|
|
6
7
|
import { automaticAnalysisBudget } from "../analysis/automatic-config.js";
|
|
7
8
|
import { externalCatchHealthy } from "../comms/sandbox-catch.js";
|
|
@@ -227,7 +228,7 @@ async function realEmailCheck(connection, keys, args) {
|
|
|
227
228
|
message: name === null
|
|
228
229
|
? "The selected email connection is missing or invalid. Open Connections in the TUI."
|
|
229
230
|
: !args.keyPresent(name)
|
|
230
|
-
? `Missing ${name} for the selected email connection. Provide it through process env or --
|
|
231
|
+
? `Missing ${name} for the selected email connection. Provide it through process env or --dotenv. Authentication has not been checked.`
|
|
231
232
|
: "Fresh hosted inbox per participant. Local presence only; run humanish comms check --online to authenticate. Provider permissions/capacity and delivery remain untested.",
|
|
232
233
|
};
|
|
233
234
|
}
|
|
@@ -254,12 +255,14 @@ async function participantChecks(config, route, keys, local, args, checkAccount)
|
|
|
254
255
|
// that the operator's config, release and model admit a launch.
|
|
255
256
|
if (choice === "codex" && agent?.authStatus === "authenticated")
|
|
256
257
|
return [await hostedCodexParticipantCheck(args, config.actors[0])];
|
|
258
|
+
const outdated = agent === undefined ? undefined : localAgentVersionRefusal(agent);
|
|
257
259
|
return [
|
|
258
260
|
{
|
|
259
261
|
name: "local participant authentication",
|
|
260
|
-
ok: agent?.authStatus === "authenticated",
|
|
262
|
+
ok: agent?.authStatus === "authenticated" && outdated === undefined,
|
|
261
263
|
message: agent?.authStatus === "authenticated"
|
|
262
|
-
?
|
|
264
|
+
? (outdated ??
|
|
265
|
+
`${agent.label}${agent.version === undefined ? "" : ` ${agent.version}`} reports authenticated on the host. E2B supplies the desktop; no OpenAI API key is required for this participant.`)
|
|
263
266
|
: agent
|
|
264
267
|
? `${agent.label} ${agent.authStatus === "unauthenticated" ? "reports not signed in" : "authentication could not be checked"}. Run \`${choice === "codex" ? "codex login status" : "claude auth status"}\`; sign in or update the CLI before running.`
|
|
265
268
|
: `${choice} is not on this process's PATH. Install and sign in to that CLI, or choose openai-computer-use with OPENAI_API_KEY.`,
|
|
@@ -300,7 +303,7 @@ function subjectEnvChecks(names, args) {
|
|
|
300
303
|
ok: !!args.env[name]?.trim() || args.keyPresent(name),
|
|
301
304
|
message: args.env[name]?.trim() || args.keyPresent(name)
|
|
302
305
|
? "present; value not shown"
|
|
303
|
-
: "missing declared subject environment variable; provide it with --
|
|
306
|
+
: "missing declared subject environment variable; provide it with --dotenv",
|
|
304
307
|
}));
|
|
305
308
|
}
|
|
306
309
|
/** The post-run analysis: the Codex account, or the OpenAI key and the analysis cost limit. */
|
|
@@ -245,9 +245,9 @@ title: Computer-use browser study
|
|
|
245
245
|
description: >-
|
|
246
246
|
A computer-use participant drives your app in a hosted desktop browser and writes its evidence
|
|
247
247
|
to gitignored .humanish/. Starts as a dry run. For a live session, set mode to live
|
|
248
|
-
and pass OPENAI_API_KEY and E2B_API_KEY with --
|
|
248
|
+
and pass OPENAI_API_KEY and E2B_API_KEY with --dotenv. The clone subject below serves your
|
|
249
249
|
repo inside the sandbox, and the env names declared under subject get their values from
|
|
250
|
-
--
|
|
250
|
+
--dotenv without the values being saved. Screenshots keep full fidelity by default; set
|
|
251
251
|
policies.redactScreenshots: true to blur them at capture for a bundle you can share as is.
|
|
252
252
|
Typed text is recorded as its length only.
|
|
253
253
|
route: computer-use
|
package/dist/study/init.js
CHANGED
|
@@ -5,6 +5,7 @@ import { detectLocalAgents } from "../actors/local-agent/cli.js";
|
|
|
5
5
|
import { probeKeySources } from "../keys/key-resolution.js";
|
|
6
6
|
import { DEFAULT_LOCAL_BROWSER_STARTER, runtimeDirectories, starterFilesFor, } from "./init-templates.js";
|
|
7
7
|
import { assertPreparedSelectedOutputDirectory, prepareContainedOutputDirectory, prepareSelectedOutputDirectory, writeContainedOutputFile, } from "../run/contained-output.js";
|
|
8
|
+
import { desktopPeerAdvice, humanishInstall, } from "../substrates/e2b/peer-install.js";
|
|
8
9
|
import { validateCwd } from "../run/project.js";
|
|
9
10
|
import { pathExists, readTextIfExists, validateInitProjectPaths } from "./init-paths.js";
|
|
10
11
|
import { planGitignore, planPackageJson } from "./init-plan.js";
|
|
@@ -270,6 +271,18 @@ function validateLocalBrowserStarter(input) {
|
|
|
270
271
|
return { ok: false, message: "--local-mission must be 4,000 characters or fewer." };
|
|
271
272
|
return { ok: true, value };
|
|
272
273
|
}
|
|
274
|
+
/**
|
|
275
|
+
* The first-run hint's peer install, ahead of `npx humanish`: beside humanish in its project, else
|
|
276
|
+
* humanish and the peer together here, where `npx humanish` then finds them. None where `npm i`
|
|
277
|
+
* would prune a node_modules that no package.json declares.
|
|
278
|
+
*/
|
|
279
|
+
function initDesktopPeerCommand(install) {
|
|
280
|
+
if (install.kind === "project")
|
|
281
|
+
return desktopPeerAdvice(install).command;
|
|
282
|
+
if ((install.kind === "one-shot" || install.kind === "other") && install.unmanagedCwd)
|
|
283
|
+
return undefined;
|
|
284
|
+
return "npm i -D humanish @e2b/desktop";
|
|
285
|
+
}
|
|
273
286
|
/**
|
|
274
287
|
* What this machine can run, for the starter study and the next steps. Local CLI status is
|
|
275
288
|
* classified without returning its output or reading its credential file. Provider keys go
|
|
@@ -297,10 +310,8 @@ async function firstRunEnvironment(env, cwd) {
|
|
|
297
310
|
catch {
|
|
298
311
|
hasDesktopSdk = false;
|
|
299
312
|
}
|
|
300
|
-
const { fileURLToPath } = await import("node:url");
|
|
301
|
-
const here = fileURLToPath(import.meta.url);
|
|
302
313
|
return {
|
|
303
|
-
|
|
314
|
+
desktopPeerCommand: initDesktopPeerCommand(humanishInstall()),
|
|
304
315
|
hasDesktopSdk,
|
|
305
316
|
hasE2bKey: present("E2B_API_KEY"),
|
|
306
317
|
hasProviderKey: present("OPENAI_API_KEY"),
|
package/dist/study/keys.d.ts
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a dotted key path, such as `caps.maxUsd` or `participants[0].persona`, names a key a
|
|
3
|
+
* humanish.study.v3 file can set. A path below a leaf is not one: `scenario.ref`, where `scenario`
|
|
4
|
+
* is a string, and `review.analysis.question`, whose keys resolveAutomaticAnalysis checks.
|
|
5
|
+
*/
|
|
6
|
+
export declare function isStudyKeyPath(path: string): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* The dotted path of every key a humanish.study.v3 file can set, down to `depth` levels:
|
|
9
|
+
* `route`, `caps.maxUsd`, `participants.persona`. scripts/check-doc-study-fields.ts holds the
|
|
10
|
+
* study file reference to these paths.
|
|
11
|
+
*/
|
|
12
|
+
export declare function studyKeyPaths(depth: number): string[];
|
|
1
13
|
/** The first key in `raw` that V2_KEYS does not list, as an error message; undefined if none. */
|
|
2
14
|
export declare function findUnknownV2Key(raw: unknown): string | undefined;
|
|
3
15
|
/** The first key in a humanish.study.v3 document that the format does not have; undefined if none. */
|
package/dist/study/keys.js
CHANGED
|
@@ -230,6 +230,38 @@ const STUDY_KEYS = {
|
|
|
230
230
|
defaults: V2_KEYS.defaults,
|
|
231
231
|
comms: V2_KEYS.comms,
|
|
232
232
|
};
|
|
233
|
+
/**
|
|
234
|
+
* Whether a dotted key path, such as `caps.maxUsd` or `participants[0].persona`, names a key a
|
|
235
|
+
* humanish.study.v3 file can set. A path below a leaf is not one: `scenario.ref`, where `scenario`
|
|
236
|
+
* is a string, and `review.analysis.question`, whose keys resolveAutomaticAnalysis checks.
|
|
237
|
+
*/
|
|
238
|
+
export function isStudyKeyPath(path) {
|
|
239
|
+
let shape = STUDY_KEYS;
|
|
240
|
+
for (const segment of path.replaceAll(/\[\d*\]/g, "").split(".")) {
|
|
241
|
+
if (shape === true || !Object.hasOwn(shape, segment))
|
|
242
|
+
return false;
|
|
243
|
+
shape = shape[segment];
|
|
244
|
+
}
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* The dotted path of every key a humanish.study.v3 file can set, down to `depth` levels:
|
|
249
|
+
* `route`, `caps.maxUsd`, `participants.persona`. scripts/check-doc-study-fields.ts holds the
|
|
250
|
+
* study file reference to these paths.
|
|
251
|
+
*/
|
|
252
|
+
export function studyKeyPaths(depth) {
|
|
253
|
+
const paths = [];
|
|
254
|
+
const visit = (shape, prefix, level) => {
|
|
255
|
+
for (const [key, child] of Object.entries(shape)) {
|
|
256
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
257
|
+
paths.push(path);
|
|
258
|
+
if (child !== true && level < depth)
|
|
259
|
+
visit(child, path, level + 1);
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
visit(STUDY_KEYS, "", 1);
|
|
263
|
+
return paths;
|
|
264
|
+
}
|
|
233
265
|
/** The first key in `raw` that V2_KEYS does not list, as an error message; undefined if none. */
|
|
234
266
|
export function findUnknownV2Key(raw) {
|
|
235
267
|
return walk(raw, V2_KEYS, "", "study");
|
package/dist/study/persona.d.ts
CHANGED
|
@@ -26,6 +26,10 @@ export interface PersonaDirectives {
|
|
|
26
26
|
/** Directives included in the prompt, not proof of behavioral adherence. */
|
|
27
27
|
traitsApplied: string[];
|
|
28
28
|
}
|
|
29
|
+
/** The fields a persona file can set; any other warns and is not sent. The study file reference lists each. */
|
|
30
|
+
export declare const PERSONA_FIELDS: Set<string>;
|
|
31
|
+
/** The traits under `traits:` that become directives; any other warns and is not sent. */
|
|
32
|
+
export declare const TRAIT_FIELDS: Set<string>;
|
|
29
33
|
/** Legacy short fields keep their bounds, but every discarded value is diagnosed. */
|
|
30
34
|
export declare function parseResolvedPersona(raw: unknown, fallback: {
|
|
31
35
|
id: string;
|
package/dist/study/persona.js
CHANGED
|
@@ -9,7 +9,8 @@ export class PersonaConfigError extends Error {
|
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
11
|
const LEVELS = ["low", "medium", "high"];
|
|
12
|
-
|
|
12
|
+
/** The fields a persona file can set; any other warns and is not sent. The study file reference lists each. */
|
|
13
|
+
export const PERSONA_FIELDS = new Set([
|
|
13
14
|
"schema",
|
|
14
15
|
"id",
|
|
15
16
|
"name",
|
|
@@ -18,7 +19,8 @@ const PERSONA_FIELDS = new Set([
|
|
|
18
19
|
"traits",
|
|
19
20
|
"constraints",
|
|
20
21
|
]);
|
|
21
|
-
|
|
22
|
+
/** The traits under `traits:` that become directives; any other warns and is not sent. */
|
|
23
|
+
export const TRAIT_FIELDS = new Set(["patience", "technical_confidence", "accessibility_needs"]);
|
|
22
24
|
function cleanText(value) {
|
|
23
25
|
return value
|
|
24
26
|
.replace(/\r\n?/g, "\n")
|
package/dist/study/types.d.ts
CHANGED
|
@@ -239,7 +239,7 @@ export interface StudySubject {
|
|
|
239
239
|
serve?: StudySubjectServe;
|
|
240
240
|
/**
|
|
241
241
|
* Env var names the subject app needs, provisioned into the sandbox from the caller's
|
|
242
|
-
* environment (--
|
|
242
|
+
* environment (--dotenv). Names are recorded in evidence; values never are. Consumed
|
|
243
243
|
* on the computer-use clone route.
|
|
244
244
|
*/
|
|
245
245
|
env?: string[];
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
type Manager = "npm" | "pnpm";
|
|
2
|
+
/** Where the running humanish is installed, read from its own file's real path. */
|
|
3
|
+
export type HumanishInstall =
|
|
4
|
+
/** The nearest package.json declaring humanish: the working directory's, or `up` levels above. */
|
|
5
|
+
{
|
|
6
|
+
kind: "project";
|
|
7
|
+
up: number;
|
|
8
|
+
manager: Manager;
|
|
9
|
+
}
|
|
10
|
+
/** A one-shot runner's cache: `npx humanish` or `pnpm dlx humanish`. */
|
|
11
|
+
| {
|
|
12
|
+
kind: "one-shot";
|
|
13
|
+
unmanagedCwd: boolean;
|
|
14
|
+
}
|
|
15
|
+
/** The global directory npm installs to from here, or pnpm's global directory. */
|
|
16
|
+
| {
|
|
17
|
+
kind: "global";
|
|
18
|
+
manager: Manager;
|
|
19
|
+
}
|
|
20
|
+
/** A source checkout of humanish, run with node or tsx. */
|
|
21
|
+
| {
|
|
22
|
+
kind: "checkout";
|
|
23
|
+
}
|
|
24
|
+
/** Anywhere else, such as another project or a global prefix npm is not configured for here. */
|
|
25
|
+
| {
|
|
26
|
+
kind: "other";
|
|
27
|
+
unmanagedCwd: boolean;
|
|
28
|
+
};
|
|
29
|
+
export interface InstallContext {
|
|
30
|
+
cwd: string;
|
|
31
|
+
/** The directory npm's global installs go to from this environment. */
|
|
32
|
+
npmGlobalRoot: string;
|
|
33
|
+
/** Whether the package.json in `directory` declares humanish as a dependency. */
|
|
34
|
+
declaresHumanish: (directory: string) => boolean;
|
|
35
|
+
/** Whether `npm i` from `directory` installs into a node_modules no package.json declares. */
|
|
36
|
+
unmanaged: (directory: string) => boolean;
|
|
37
|
+
}
|
|
38
|
+
/** The install `modulePath` belongs to. */
|
|
39
|
+
export declare function classifyHumanishInstall(modulePath: string, context: InstallContext): HumanishInstall;
|
|
40
|
+
/** The running humanish's install, from this file's real path. */
|
|
41
|
+
export declare function humanishInstall(cwd?: string): HumanishInstall;
|
|
42
|
+
/**
|
|
43
|
+
* Where this humanish is installed, the command that puts `spec` (`@e2b/desktop`, or a version of
|
|
44
|
+
* it) where it will resolve, and that command as one or two sentences of advice.
|
|
45
|
+
*/
|
|
46
|
+
export declare function desktopPeerAdvice(install: HumanishInstall, spec?: string): {
|
|
47
|
+
where: string;
|
|
48
|
+
command: string;
|
|
49
|
+
advice: string;
|
|
50
|
+
};
|
|
51
|
+
export {};
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// How to add the optional @e2b/desktop peer depends on where humanish itself is installed: Node
|
|
2
|
+
// resolves the peer from humanish's own directory, not from the project a command runs in. Advice
|
|
3
|
+
// for the wrong place installs a copy Node never reads, and `npm i` in a directory whose manifest
|
|
4
|
+
// does not declare what its node_modules holds prunes it, so a command names only a project whose
|
|
5
|
+
// package.json declares humanish, or npm's or pnpm's global directory.
|
|
6
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { dirname, join, sep } from "node:path";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
const NODE_MODULES = `${sep}node_modules${sep}`;
|
|
11
|
+
/** The install `modulePath` belongs to. */
|
|
12
|
+
export function classifyHumanishInstall(modulePath, context) {
|
|
13
|
+
const { cwd } = context;
|
|
14
|
+
const segments = modulePath.split(sep);
|
|
15
|
+
if (segments.includes("_npx") || segments.includes("dlx"))
|
|
16
|
+
return { kind: "one-shot", unmanagedCwd: context.unmanaged(cwd) };
|
|
17
|
+
if (modulePath.includes(`${sep}pnpm${sep}global${sep}`))
|
|
18
|
+
return { kind: "global", manager: "pnpm" };
|
|
19
|
+
const at = modulePath.indexOf(NODE_MODULES);
|
|
20
|
+
if (at < 0)
|
|
21
|
+
return { kind: "checkout" };
|
|
22
|
+
// The first node_modules is the project's; pnpm keeps humanish's files deeper, in .pnpm.
|
|
23
|
+
const root = modulePath.slice(0, at);
|
|
24
|
+
if (root === context.npmGlobalRoot)
|
|
25
|
+
return { kind: "global", manager: "npm" };
|
|
26
|
+
if (cwd === root || cwd.startsWith(`${root}${sep}`)) {
|
|
27
|
+
const manager = modulePath.includes(`${NODE_MODULES}.pnpm${sep}`) ? "pnpm" : "npm";
|
|
28
|
+
// A workspace member declares humanish in its own package.json, and the root may not.
|
|
29
|
+
for (let directory = cwd, up = 0;; directory = dirname(directory), up += 1) {
|
|
30
|
+
if (context.declaresHumanish(directory))
|
|
31
|
+
return { kind: "project", up, manager };
|
|
32
|
+
if (directory === root)
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return { kind: "other", unmanagedCwd: context.unmanaged(cwd) };
|
|
37
|
+
}
|
|
38
|
+
/** The `prefix` an npmrc file sets, with `~` and `$HOME` expanded; undefined when it sets none. */
|
|
39
|
+
function npmrcPrefix(file) {
|
|
40
|
+
let text;
|
|
41
|
+
try {
|
|
42
|
+
text = readFileSync(file, "utf8");
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
const value = /^\s*prefix\s*=\s*(.+?)\s*$/m.exec(text)?.[1];
|
|
48
|
+
if (value === undefined)
|
|
49
|
+
return undefined;
|
|
50
|
+
return value
|
|
51
|
+
.replace(/^~(?=$|[\\/])/, homedir())
|
|
52
|
+
.replace(/\$\{?HOME\}?/g, homedir())
|
|
53
|
+
.replace(/^"(.*)"$/, "$1");
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The global root npm installs to from this environment, by npm's own order: the prefix in the
|
|
57
|
+
* environment, then the user, global and built-in npmrc files (Homebrew sets it in the built-in
|
|
58
|
+
* one), then the prefix the Node binary implies.
|
|
59
|
+
*/
|
|
60
|
+
function npmGlobalRoot() {
|
|
61
|
+
const windows = process.platform === "win32";
|
|
62
|
+
const nodePrefix = windows ? dirname(process.execPath) : dirname(dirname(process.execPath));
|
|
63
|
+
const prefix = process.env.npm_config_prefix ||
|
|
64
|
+
process.env.NPM_CONFIG_PREFIX ||
|
|
65
|
+
npmrcPrefix(process.env.NPM_CONFIG_USERCONFIG || join(homedir(), ".npmrc")) ||
|
|
66
|
+
npmrcPrefix(process.env.NPM_CONFIG_GLOBALCONFIG || join(nodePrefix, "etc", "npmrc")) ||
|
|
67
|
+
npmrcPrefix(windows
|
|
68
|
+
? join(nodePrefix, "node_modules", "npm", "npmrc")
|
|
69
|
+
: join(nodePrefix, "lib", "node_modules", "npm", "npmrc")) ||
|
|
70
|
+
nodePrefix;
|
|
71
|
+
return windows ? prefix : join(prefix, "lib");
|
|
72
|
+
}
|
|
73
|
+
function declaresHumanish(directory) {
|
|
74
|
+
try {
|
|
75
|
+
const manifest = JSON.parse(readFileSync(join(directory, "package.json"), "utf8"));
|
|
76
|
+
return ["dependencies", "devDependencies", "optionalDependencies"].some((field) => {
|
|
77
|
+
const declared = manifest[field];
|
|
78
|
+
return typeof declared === "object" && declared !== null && "humanish" in declared;
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Whether `npm i` from `directory` would install into a node_modules no package.json declares: npm
|
|
87
|
+
* installs into the nearest directory, this one or above, that holds either.
|
|
88
|
+
*/
|
|
89
|
+
function unmanaged(directory) {
|
|
90
|
+
for (let current = directory;; current = dirname(current)) {
|
|
91
|
+
if (existsSync(join(current, "package.json")))
|
|
92
|
+
return false;
|
|
93
|
+
if (existsSync(join(current, "node_modules")))
|
|
94
|
+
return true;
|
|
95
|
+
if (dirname(current) === current)
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/** The running humanish's install, from this file's real path. */
|
|
100
|
+
export function humanishInstall(cwd = process.cwd()) {
|
|
101
|
+
return classifyHumanishInstall(fileURLToPath(import.meta.url), {
|
|
102
|
+
cwd,
|
|
103
|
+
npmGlobalRoot: npmGlobalRoot(),
|
|
104
|
+
declaresHumanish,
|
|
105
|
+
unmanaged,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Where this humanish is installed, the command that puts `spec` (`@e2b/desktop`, or a version of
|
|
110
|
+
* it) where it will resolve, and that command as one or two sentences of advice.
|
|
111
|
+
*/
|
|
112
|
+
export function desktopPeerAdvice(install, spec = "@e2b/desktop") {
|
|
113
|
+
const both = `npm i -D humanish ${spec}`;
|
|
114
|
+
const relativeTo = "Node resolves the peer relative to humanish itself, so";
|
|
115
|
+
switch (install.kind) {
|
|
116
|
+
case "project": {
|
|
117
|
+
// A directory option, not `cd`: one command, and no `&&` for Windows PowerShell 5.1.
|
|
118
|
+
const ups = Array.from({ length: install.up }, () => "..").join("/");
|
|
119
|
+
const command = install.manager === "pnpm"
|
|
120
|
+
? `pnpm add -D ${ups ? `--dir ${ups} ` : ""}${spec}`
|
|
121
|
+
: `npm i -D ${ups ? `--prefix ${ups} ` : ""}${spec}`;
|
|
122
|
+
const place = ups ? "the project above this directory" : "this project";
|
|
123
|
+
return {
|
|
124
|
+
where: "",
|
|
125
|
+
command,
|
|
126
|
+
advice: `Install it beside humanish in ${place}: \`${command}\`.`,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
case "global": {
|
|
130
|
+
const command = install.manager === "pnpm" ? `pnpm add -g ${spec}` : `npm i -g ${spec}`;
|
|
131
|
+
return {
|
|
132
|
+
where: ", and humanish is installed globally",
|
|
133
|
+
command,
|
|
134
|
+
advice: `${relativeTo} install it globally too: \`${command}\`. That reaches humanish while ` +
|
|
135
|
+
"the global directory is still the one it was installed in.",
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
case "checkout":
|
|
139
|
+
return {
|
|
140
|
+
where: ", and humanish is running from a source checkout",
|
|
141
|
+
command: "pnpm install",
|
|
142
|
+
advice: spec === "@e2b/desktop"
|
|
143
|
+
? "Run `pnpm install` in the humanish checkout, which installs it as a dev dependency."
|
|
144
|
+
: `Set ${spec} in the humanish checkout's package.json, then run \`pnpm install\` there.`,
|
|
145
|
+
};
|
|
146
|
+
case "one-shot":
|
|
147
|
+
case "other": {
|
|
148
|
+
const where = install.kind === "one-shot"
|
|
149
|
+
? ", and humanish is running from an npx or pnpm dlx cache"
|
|
150
|
+
: ", and humanish is installed outside this project";
|
|
151
|
+
const run = `\`${both}\` then \`npx humanish run <study>\``;
|
|
152
|
+
return {
|
|
153
|
+
where,
|
|
154
|
+
command: both,
|
|
155
|
+
advice: install.unmanagedCwd
|
|
156
|
+
? `${relativeTo} it has to be installed with humanish. From here npm would install into a node_modules folder that no package.json declares, and \`npm i\` would remove what it holds. In your project's directory, run ${run}.`
|
|
157
|
+
: `${relativeTo} a copy installed here is not found. Install both into this project and run that copy: ${run}.`,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|