humanish 0.48.0 → 0.49.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 +13 -3
- package/dist/actor-contract.d.ts +14 -0
- package/dist/actor-contract.js.map +1 -1
- package/dist/computer-use-actor.d.ts +3 -7
- package/dist/computer-use.d.ts +4 -7
- package/dist/computer-use.js +39 -3
- package/dist/computer-use.js.map +1 -1
- package/dist/concurrent-shared-world-lab.js +2 -1
- package/dist/concurrent-shared-world-lab.js.map +1 -1
- package/dist/cua-actor-lab.js +4 -7
- package/dist/cua-actor-lab.js.map +1 -1
- package/dist/key-resolution.d.ts +67 -0
- package/dist/key-resolution.js +373 -0
- package/dist/key-resolution.js.map +1 -0
- package/dist/openai-responses-cu.d.ts +1 -1
- package/dist/openai-responses-cu.js +8 -2
- package/dist/openai-responses-cu.js.map +1 -1
- package/dist/pricing.d.ts +20 -0
- package/dist/pricing.js +109 -17
- package/dist/pricing.js.map +1 -1
- package/dist/program.d.ts +4 -1
- package/dist/program.js +164 -8
- package/dist/program.js.map +1 -1
- package/dist/run.js +24 -0
- package/dist/run.js.map +1 -1
- package/dist/scripted-browser-lab.js +2 -1
- package/dist/scripted-browser-lab.js.map +1 -1
- package/dist/shared-world-lab.js +2 -1
- package/dist/shared-world-lab.js.map +1 -1
- package/docs/contracts/schemas.md +16 -7
- package/docs/goals/current.md +14 -6
- package/docs/ramp/README.md +1 -1
- package/package.json +1 -1
package/dist/pricing.js
CHANGED
|
@@ -12,6 +12,28 @@
|
|
|
12
12
|
// so a reader can never confuse an estimate for an authoritative charge (invariant 6).
|
|
13
13
|
export const PRICING_SCHEMA = "humanish.pricing.v1";
|
|
14
14
|
export const ACTOR_ESTIMATED_COST_SCHEMA = "humanish.actor-estimated-cost.v1";
|
|
15
|
+
// The gpt-5.6 long-context tier: >272K input tokens re-prices the FULL request at 2x
|
|
16
|
+
// input-side / 1.5x output (developers.openai.com/api/docs/models/gpt-5.6-sol, 2026-08-18).
|
|
17
|
+
const GPT56_LONG_CONTEXT = {
|
|
18
|
+
thresholdInputTokens: 272_000,
|
|
19
|
+
inputMultiplier: 2,
|
|
20
|
+
outputMultiplier: 1.5
|
|
21
|
+
};
|
|
22
|
+
const GPT56_SOURCE = "developers.openai.com/api/docs/pricing (gpt-5.6 family, standard tier)";
|
|
23
|
+
// One 5.6-family entry: rates in USD-per-1M for legibility, converted once. Cache writes bill at
|
|
24
|
+
// 1.25x the uncached input rate on this family (`cache_write_tokens`, prompt-caching guide); the
|
|
25
|
+
// built-in `computer` tool has no per-call fee on the live sheet.
|
|
26
|
+
function gpt56Rate(inPer1M, cachedPer1M, writePer1M, outPer1M) {
|
|
27
|
+
return {
|
|
28
|
+
inputUsdPerToken: inPer1M * 1e-6,
|
|
29
|
+
cachedInputUsdPerToken: cachedPer1M * 1e-6,
|
|
30
|
+
cacheWriteUsdPerToken: writePer1M * 1e-6,
|
|
31
|
+
outputUsdPerToken: outPer1M * 1e-6,
|
|
32
|
+
longContext: { ...GPT56_LONG_CONTEXT },
|
|
33
|
+
asOf: "2026-08-18",
|
|
34
|
+
source: GPT56_SOURCE
|
|
35
|
+
};
|
|
36
|
+
}
|
|
15
37
|
// Per-model rates, keyed on the model id that lands in trace.ids.model (lookup is
|
|
16
38
|
// case-insensitive on a trimmed id). An id NOT present here is DECLARED ABSENT, never guessed.
|
|
17
39
|
export const MODEL_RATES = {
|
|
@@ -23,17 +45,32 @@ export const MODEL_RATES = {
|
|
|
23
45
|
asOf: "2026-08-01",
|
|
24
46
|
source: "openai.com/api/pricing (computer-use-preview)"
|
|
25
47
|
},
|
|
26
|
-
// gpt-5.5
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
// the default model is tracked in issue #334.
|
|
48
|
+
// gpt-5.5: the PREVIOUS-generation CUA default, kept so pinned labs and old bundles still
|
|
49
|
+
// price. Pre-5.6 models bill no cache-write fee (prompt-caching guide) and no long-context
|
|
50
|
+
// tier was published for it.
|
|
30
51
|
"gpt-5.5": {
|
|
31
52
|
inputUsdPerToken: 5e-6,
|
|
32
53
|
outputUsdPerToken: 30e-6,
|
|
33
54
|
cachedInputUsdPerToken: 0.5e-6,
|
|
34
55
|
asOf: "2026-08-05",
|
|
35
56
|
source: "openrouter.ai/openai/gpt-5.5 (gpt-5.5 no longer on openai.com/api/pricing; see #334)"
|
|
36
|
-
}
|
|
57
|
+
},
|
|
58
|
+
// The gpt-5.6 family (live sheet 2026-08-18, standard tier, short-context base rates;
|
|
59
|
+
// the longContext block prices the >272K re-tier when per-request turns are recorded).
|
|
60
|
+
// sol = flagship (the shipped CUA default), terra = cost-balanced, luna = high-volume,
|
|
61
|
+
// cyber = the Daybreak frontier tier.
|
|
62
|
+
"gpt-5.6-sol": gpt56Rate(5, 0.5, 6.25, 30),
|
|
63
|
+
// "gpt-5.6" is OpenAI's own alias for gpt-5.6-sol (models index); priced identically so a
|
|
64
|
+
// lab configured with the alias never reads as unpriced.
|
|
65
|
+
"gpt-5.6": gpt56Rate(5, 0.5, 6.25, 30),
|
|
66
|
+
"gpt-5.6-terra": gpt56Rate(2, 0.2, 2.5, 12),
|
|
67
|
+
"gpt-5.6-luna": gpt56Rate(0.2, 0.02, 0.25, 1.2),
|
|
68
|
+
"gpt-5.6-cyber": gpt56Rate(12.5, 1.25, 15.625, 75),
|
|
69
|
+
// Daybreak program aliases (blue -> sol, red -> cyber today). OpenAI repoints these as new
|
|
70
|
+
// frontier models ship, so prefer the explicit tier id in labs; the entries exist so a
|
|
71
|
+
// configured alias still prices at what the alias bills TODAY.
|
|
72
|
+
"daybreak-blue-latest": gpt56Rate(5, 0.5, 6.25, 30),
|
|
73
|
+
"daybreak-red-latest": gpt56Rate(12.5, 1.25, 15.625, 75)
|
|
37
74
|
};
|
|
38
75
|
// E2B desktop sandbox compute, billed per-second by vCPU+RAM. Live sheet: 2 vCPU (default)
|
|
39
76
|
// $0.000028/s + RAM $0.0000045/GiB/s => at an ASSUMED 4 GiB desktop, $0.000046/s ~= $0.00276/min
|
|
@@ -74,17 +111,70 @@ export function estimateActorCost(tokenUsage, modelId, rates = MODEL_RATES) {
|
|
|
74
111
|
}
|
|
75
112
|
const inTok = tokenUsage.input ?? 0;
|
|
76
113
|
const outTok = tokenUsage.output ?? 0;
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
114
|
+
// Long-context tiering needs to know each REQUEST's input size (the provider re-prices whole
|
|
115
|
+
// requests past the threshold), so it engages only when per-turn usage records exist AND their
|
|
116
|
+
// input sums to the reported total — a partial turn ledger must not silently price the missing
|
|
117
|
+
// remainder at the wrong tier. Otherwise totals price on the base (short-context) rate exactly
|
|
118
|
+
// as before, which is the under-estimate direction and never trips a cap early.
|
|
119
|
+
const turns = tokenUsage.turns ?? [];
|
|
120
|
+
const sumOf = (field) => turns.reduce((sum, turn) => sum + (turn[field] ?? 0), 0);
|
|
121
|
+
// The ledger is trusted only when it decomposes the totals EXACTLY — all four sums, not just
|
|
122
|
+
// input/output. A ledger that carries request sizes but not the cache splits would otherwise
|
|
123
|
+
// price every token at the full rate while the session totals sit ignored (red-team finding:
|
|
124
|
+
// 3.5-5x overstatement, the #391 false-cap-trip direction). Inconsistent evidence falls back
|
|
125
|
+
// to the totals path, which honors the declared splits on the base tier.
|
|
126
|
+
const tiered = rate.longContext !== undefined &&
|
|
127
|
+
turns.length > 0 &&
|
|
128
|
+
sumOf("input") === inTok &&
|
|
129
|
+
sumOf("output") === outTok &&
|
|
130
|
+
sumOf("cachedInput") === (tokenUsage.cachedInput ?? 0) &&
|
|
131
|
+
sumOf("cacheWriteInput") === (tokenUsage.cacheWriteInput ?? 0);
|
|
132
|
+
let inputUsd = 0;
|
|
133
|
+
let outputUsd = 0;
|
|
134
|
+
let cachedTotal = 0;
|
|
135
|
+
let writeTotal = 0;
|
|
136
|
+
let longContextTurns = 0;
|
|
137
|
+
// Price one request's usage at one tier. Cached input is billed at a fraction of the full rate,
|
|
138
|
+
// and on a session that threads provider state it is the MAJORITY of input — pricing it at the
|
|
139
|
+
// full rate overstated real spend by up to ~10x (#391). Cache WRITES bill at their own rate
|
|
140
|
+
// (1.25x on OpenAI 5.6+) as the total rate for those tokens; a sheet without a write rate
|
|
141
|
+
// prices writes as plain input (pre-5.6: writes are free-of-extra-fee, i.e. plain input).
|
|
142
|
+
// Every piece is honestly absent: no reported split means no discount and no surcharge assumed.
|
|
143
|
+
const priceRequest = (usage, tierable) => {
|
|
144
|
+
const reqIn = usage.input ?? 0;
|
|
145
|
+
const reqOut = usage.output ?? 0;
|
|
146
|
+
// Tiering applies only to a real per-REQUEST record: session totals crossing the threshold
|
|
147
|
+
// say nothing about any single request, so totals always price on the base tier.
|
|
148
|
+
const long = tierable && rate.longContext !== undefined && reqIn > rate.longContext.thresholdInputTokens;
|
|
149
|
+
const inMul = long ? rate.longContext.inputMultiplier : 1;
|
|
150
|
+
const outMul = long ? rate.longContext.outputMultiplier : 1;
|
|
151
|
+
if (long)
|
|
152
|
+
longContextTurns += 1;
|
|
153
|
+
const cached = rate.cachedInputUsdPerToken === undefined ? 0 : Math.min(reqIn, Math.max(0, usage.cachedInput ?? 0));
|
|
154
|
+
const writes = Math.min(reqIn - cached, Math.max(0, usage.cacheWriteInput ?? 0));
|
|
155
|
+
const full = reqIn - cached - writes;
|
|
156
|
+
cachedTotal += cached;
|
|
157
|
+
writeTotal += writes;
|
|
158
|
+
inputUsd +=
|
|
159
|
+
full * rate.inputUsdPerToken * inMul +
|
|
160
|
+
cached * (rate.cachedInputUsdPerToken ?? 0) * inMul +
|
|
161
|
+
writes * (rate.cacheWriteUsdPerToken ?? rate.inputUsdPerToken) * inMul;
|
|
162
|
+
outputUsd += reqOut * rate.outputUsdPerToken * outMul;
|
|
163
|
+
};
|
|
164
|
+
if (tiered) {
|
|
165
|
+
for (const turn of turns)
|
|
166
|
+
priceRequest(turn, true);
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
priceRequest({
|
|
170
|
+
input: inTok,
|
|
171
|
+
output: outTok,
|
|
172
|
+
...(tokenUsage.cachedInput === undefined ? {} : { cachedInput: tokenUsage.cachedInput }),
|
|
173
|
+
...(tokenUsage.cacheWriteInput === undefined ? {} : { cacheWriteInput: tokenUsage.cacheWriteInput })
|
|
174
|
+
}, false);
|
|
175
|
+
}
|
|
176
|
+
inputUsd = round6(inputUsd);
|
|
177
|
+
outputUsd = round6(outputUsd);
|
|
88
178
|
return {
|
|
89
179
|
schema: ACTOR_ESTIMATED_COST_SCHEMA,
|
|
90
180
|
estimatedCostUsd: round6(inputUsd + outputUsd),
|
|
@@ -97,7 +187,9 @@ export function estimateActorCost(tokenUsage, modelId, rates = MODEL_RATES) {
|
|
|
97
187
|
outputUsd,
|
|
98
188
|
inputTokens: inTok,
|
|
99
189
|
outputTokens: outTok,
|
|
100
|
-
...(
|
|
190
|
+
...(cachedTotal > 0 ? { cachedInputTokens: cachedTotal } : {}),
|
|
191
|
+
...(writeTotal > 0 ? { cacheWriteInputTokens: writeTotal } : {}),
|
|
192
|
+
...(longContextTurns > 0 ? { longContextTurns } : {})
|
|
101
193
|
}
|
|
102
194
|
};
|
|
103
195
|
}
|
package/dist/pricing.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pricing.js","sourceRoot":"","sources":["../src/pricing.ts"],"names":[],"mappings":"AAAA,2FAA2F;AAC3F,0FAA0F;AAC1F,6FAA6F;AAC7F,uFAAuF;AACvF,EAAE;AACF,yFAAyF;AACzF,4FAA4F;AAC5F,2FAA2F;AAC3F,+FAA+F;AAC/F,2FAA2F;AAC3F,4FAA4F;AAC5F,uFAAuF;AAMvF,MAAM,CAAC,MAAM,cAAc,GAAG,qBAAqB,CAAC;AACpD,MAAM,CAAC,MAAM,2BAA2B,GAAG,kCAAkC,CAAC;
|
|
1
|
+
{"version":3,"file":"pricing.js","sourceRoot":"","sources":["../src/pricing.ts"],"names":[],"mappings":"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"}
|
package/dist/program.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
+
import { discoverProviderKeys } from "./key-resolution.js";
|
|
2
3
|
import type { ObserverResult, ObserverServer } from "./observer.js";
|
|
3
4
|
import type { OssMetaLabResult } from "./oss-meta-lab.js";
|
|
4
5
|
export declare const CLI_RESPONSE_SCHEMA = "humanish.cli-response.v1";
|
|
@@ -15,7 +16,9 @@ export interface UnexpectedErrorEnvelope {
|
|
|
15
16
|
message: string;
|
|
16
17
|
};
|
|
17
18
|
}
|
|
18
|
-
export declare function createProgram(io?: Partial<CliIo>
|
|
19
|
+
export declare function createProgram(io?: Partial<CliIo> & {
|
|
20
|
+
keyDiscovery?: typeof discoverProviderKeys;
|
|
21
|
+
}): Command;
|
|
19
22
|
/**
|
|
20
23
|
* Default browser-open policy for a lab backend run. Mirrors the observe/watch gate:
|
|
21
24
|
* an explicit --open/--no-open wins; --json (machine mode) never auto-opens; otherwise a
|
package/dist/program.js
CHANGED
|
@@ -6,6 +6,7 @@ import { fileURLToPath } from "node:url";
|
|
|
6
6
|
import { Command, Option } from "commander";
|
|
7
7
|
import { startCodexAppServerUi } from "./codex-app-server-ui.js";
|
|
8
8
|
import { loadEnvFile } from "./env-file.js";
|
|
9
|
+
import { discoverProviderKeys, listUserKeys, resolveKeyName, setUserKey, unsetUserKey, userKeyStorePath } from "./key-resolution.js";
|
|
9
10
|
import { redactText } from "./redaction.js";
|
|
10
11
|
import { draftFeedback, listFeedback, renderIssueMarkdown, renderIssueUrl, verifyFeedback } from "./feedback.js";
|
|
11
12
|
import { runInit } from "./init.js";
|
|
@@ -136,6 +137,7 @@ function reportUnexpectedActionError(command, io, error) {
|
|
|
136
137
|
}
|
|
137
138
|
export function createProgram(io = {}) {
|
|
138
139
|
const cliIo = { ...defaultIo, ...io };
|
|
140
|
+
keyDiscoveryFn = io.keyDiscovery ?? discoverProviderKeys;
|
|
139
141
|
const program = new HumanishCommand(undefined, cliIo);
|
|
140
142
|
// Bare `humanish` orients instead of printing sixteen subcommands (#367). --help is untouched;
|
|
141
143
|
// this is only what happens when no command was chosen at all.
|
|
@@ -177,6 +179,7 @@ export function createProgram(io = {}) {
|
|
|
177
179
|
].join("\n"));
|
|
178
180
|
registerInitCommand(program, cliIo);
|
|
179
181
|
registerDoctorCommand(program, cliIo);
|
|
182
|
+
registerKeysCommand(program, cliIo);
|
|
180
183
|
registerRunCommand(program, cliIo);
|
|
181
184
|
registerVerifyCommand(program, cliIo);
|
|
182
185
|
registerCleanupCommand(program, cliIo);
|
|
@@ -234,6 +237,146 @@ function registerDoctorCommand(parent, io) {
|
|
|
234
237
|
io.setExitCode(result.ok ? 0 : 2);
|
|
235
238
|
});
|
|
236
239
|
}
|
|
240
|
+
// The discovery fn the env seam calls; injectable via createProgram for hermetic CLI tests.
|
|
241
|
+
let keyDiscoveryFn = discoverProviderKeys;
|
|
242
|
+
const KEYS_RESULT_SCHEMA = "humanish.keys-result.v1";
|
|
243
|
+
function formatKeysHuman(result) {
|
|
244
|
+
const lines = [`humanish keys ${result.ok ? "ok" : "failed"}`, `store: ${result.store}`, result.message];
|
|
245
|
+
if (result.action === "list" && result.names.length > 0) {
|
|
246
|
+
for (const name of result.names)
|
|
247
|
+
lines.push(`- ${name}`);
|
|
248
|
+
}
|
|
249
|
+
return `${lines.join("\n")}\n`;
|
|
250
|
+
}
|
|
251
|
+
/** Read one secret line: from a piped stdin when --stdin, else a hidden TTY prompt. */
|
|
252
|
+
async function readSecretValue(useStdin, promptLabel, io) {
|
|
253
|
+
if (useStdin || !process.stdin.isTTY) {
|
|
254
|
+
const chunks = [];
|
|
255
|
+
for await (const chunk of process.stdin)
|
|
256
|
+
chunks.push(chunk);
|
|
257
|
+
const text = Buffer.concat(chunks).toString("utf8").trim();
|
|
258
|
+
return text.length > 0 ? text : null;
|
|
259
|
+
}
|
|
260
|
+
io.writeErr(`${promptLabel} (input hidden): `);
|
|
261
|
+
return await new Promise((resolve) => {
|
|
262
|
+
const stdin = process.stdin;
|
|
263
|
+
let value = "";
|
|
264
|
+
stdin.setRawMode?.(true);
|
|
265
|
+
stdin.resume();
|
|
266
|
+
stdin.setEncoding("utf8");
|
|
267
|
+
const onData = (key) => {
|
|
268
|
+
if (key === "\u0003") {
|
|
269
|
+
cleanup();
|
|
270
|
+
resolve(null);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
if (key === "\r" || key === "\n") {
|
|
274
|
+
cleanup();
|
|
275
|
+
io.writeErr("\n");
|
|
276
|
+
resolve(value.trim().length > 0 ? value.trim() : null);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (key === "\u007f" || key === "\b") {
|
|
280
|
+
value = value.slice(0, -1);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
value += key;
|
|
284
|
+
};
|
|
285
|
+
const cleanup = () => {
|
|
286
|
+
stdin.off("data", onData);
|
|
287
|
+
stdin.setRawMode?.(false);
|
|
288
|
+
stdin.pause();
|
|
289
|
+
};
|
|
290
|
+
stdin.on("data", onData);
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
function registerKeysCommand(parent, io) {
|
|
294
|
+
const keys = parent
|
|
295
|
+
.command("keys")
|
|
296
|
+
.description("Manage the humanish user-level key store used by provider-key discovery (#436).")
|
|
297
|
+
.summary("Manage the user-level provider key store.");
|
|
298
|
+
keys
|
|
299
|
+
.command("set")
|
|
300
|
+
.argument("<vendor-or-name>", "A vendor alias (openai, e2b, anthropic, github) or a raw ENV_NAME.")
|
|
301
|
+
.description("Store one provider key in the user store (0600), prompted with hidden input.")
|
|
302
|
+
.option("--stdin", "Read the value from stdin instead of prompting (for agents/pipes).")
|
|
303
|
+
.option("--json", JSON_OPTION_DESCRIPTION)
|
|
304
|
+
.action(async (vendorOrName, options, command) => {
|
|
305
|
+
const name = resolveKeyName(vendorOrName);
|
|
306
|
+
const storePath = userKeyStorePath(process.env);
|
|
307
|
+
if (name === null) {
|
|
308
|
+
const result = {
|
|
309
|
+
schema: KEYS_RESULT_SCHEMA, ok: false, action: "set", store: storePath, names: [],
|
|
310
|
+
message: `Not a vendor alias or valid env name: ${vendorOrName}. Vendors: openai, e2b, anthropic, github.`
|
|
311
|
+
};
|
|
312
|
+
writeResult(command, io, result, formatKeysHuman);
|
|
313
|
+
io.setExitCode(2);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
const value = await readSecretValue(options.stdin === true, `Value for ${name}`, io);
|
|
317
|
+
if (value === null) {
|
|
318
|
+
const result = {
|
|
319
|
+
schema: KEYS_RESULT_SCHEMA, ok: false, action: "set", store: storePath, names: [name],
|
|
320
|
+
message: "No value provided; nothing written."
|
|
321
|
+
};
|
|
322
|
+
writeResult(command, io, result, formatKeysHuman);
|
|
323
|
+
io.setExitCode(2);
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
try {
|
|
327
|
+
const written = setUserKey(name, value, process.env);
|
|
328
|
+
const result = {
|
|
329
|
+
schema: KEYS_RESULT_SCHEMA, ok: true, action: "set", store: written.path, names: [name],
|
|
330
|
+
message: `${name} stored (0600). Live commands resolve it automatically; remove with "humanish keys unset ${name}".`
|
|
331
|
+
};
|
|
332
|
+
writeResult(command, io, result, formatKeysHuman);
|
|
333
|
+
io.setExitCode(0);
|
|
334
|
+
}
|
|
335
|
+
catch (error) {
|
|
336
|
+
const result = {
|
|
337
|
+
schema: KEYS_RESULT_SCHEMA, ok: false, action: "set", store: storePath, names: [name],
|
|
338
|
+
message: error instanceof Error ? error.message : "Failed to write the key store."
|
|
339
|
+
};
|
|
340
|
+
writeResult(command, io, result, formatKeysHuman);
|
|
341
|
+
io.setExitCode(2);
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
keys
|
|
345
|
+
.command("unset")
|
|
346
|
+
.argument("<vendor-or-name>", "A vendor alias or raw ENV_NAME to remove from the store.")
|
|
347
|
+
.description("Remove one key from the user store.")
|
|
348
|
+
.option("--json", JSON_OPTION_DESCRIPTION)
|
|
349
|
+
.action(async (vendorOrName, _options, command) => {
|
|
350
|
+
const name = resolveKeyName(vendorOrName);
|
|
351
|
+
const storePath = userKeyStorePath(process.env);
|
|
352
|
+
const had = name !== null && unsetUserKey(name, process.env);
|
|
353
|
+
const result = {
|
|
354
|
+
schema: KEYS_RESULT_SCHEMA, ok: name !== null, action: "unset", store: storePath,
|
|
355
|
+
names: name === null ? [] : [name],
|
|
356
|
+
message: name === null
|
|
357
|
+
? `Not a vendor alias or valid env name: ${vendorOrName}.`
|
|
358
|
+
: had ? `${name} removed from the store.` : `${name} was not in the store; nothing changed.`
|
|
359
|
+
};
|
|
360
|
+
writeResult(command, io, result, formatKeysHuman);
|
|
361
|
+
io.setExitCode(name === null ? 2 : 0);
|
|
362
|
+
});
|
|
363
|
+
keys
|
|
364
|
+
.command("list")
|
|
365
|
+
.description("List the NAMES stored in the user store. Values are never printed.")
|
|
366
|
+
.option("--json", JSON_OPTION_DESCRIPTION)
|
|
367
|
+
.action(async (_options, command) => {
|
|
368
|
+
const storePath = userKeyStorePath(process.env);
|
|
369
|
+
const names = listUserKeys(process.env);
|
|
370
|
+
const result = {
|
|
371
|
+
schema: KEYS_RESULT_SCHEMA, ok: true, action: "list", store: storePath, names,
|
|
372
|
+
message: names.length === 0
|
|
373
|
+
? "The store is empty. Add a key with `humanish keys set <vendor>`."
|
|
374
|
+
: `${names.length} key name(s) stored. Values are never printed.`
|
|
375
|
+
};
|
|
376
|
+
writeResult(command, io, result, formatKeysHuman);
|
|
377
|
+
io.setExitCode(0);
|
|
378
|
+
});
|
|
379
|
+
}
|
|
237
380
|
function registerRunCommand(parent, io) {
|
|
238
381
|
parent
|
|
239
382
|
.command("run")
|
|
@@ -2440,16 +2583,29 @@ async function renderAndMaybeFollowObserver(args) {
|
|
|
2440
2583
|
}
|
|
2441
2584
|
}
|
|
2442
2585
|
async function applyEnvFileOption(args) {
|
|
2443
|
-
if (
|
|
2444
|
-
|
|
2586
|
+
if (args.envFile) {
|
|
2587
|
+
const result = await loadEnvFile(args.cwd, args.envFile);
|
|
2588
|
+
if (!result.ok) {
|
|
2589
|
+
writeResult(args.command, args.io, result, formatEnvFileHuman);
|
|
2590
|
+
args.io.setExitCode(2);
|
|
2591
|
+
return false;
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
// Provider-key discovery (#436): fill still-missing keys from the documented project
|
|
2595
|
+
// overlay, the owning vendors' native stores, and the humanish user store — fill-only
|
|
2596
|
+
// (an explicit --env-file or process env always wins), each fill announced by name and
|
|
2597
|
+
// source on stderr, never by value. HUMANISH_STRICT_KEYS=1 restores env-only behavior.
|
|
2598
|
+
try {
|
|
2599
|
+
await keyDiscoveryFn({
|
|
2600
|
+
cwd: args.cwd,
|
|
2601
|
+
env: process.env,
|
|
2602
|
+
announce: (line) => args.io.writeErr(`${line}\n`)
|
|
2603
|
+
});
|
|
2445
2604
|
}
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
return true;
|
|
2605
|
+
catch {
|
|
2606
|
+
// Discovery must never break a command; a rung that fails to read is a miss, not an error.
|
|
2449
2607
|
}
|
|
2450
|
-
|
|
2451
|
-
args.io.setExitCode(2);
|
|
2452
|
-
return false;
|
|
2608
|
+
return true;
|
|
2453
2609
|
}
|
|
2454
2610
|
function labReposOverride(options) {
|
|
2455
2611
|
const override = [
|