opencode-harness-kit 0.3.8 → 0.4.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/CHANGELOG.md +59 -1
- package/package.json +1 -1
- package/src/trust.js +38 -16
package/CHANGELOG.md
CHANGED
|
@@ -14,6 +14,62 @@ is not happening.
|
|
|
14
14
|
|
|
15
15
|
## [Unreleased]
|
|
16
16
|
|
|
17
|
+
## [0.4.0] — 2026-10-20
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- **The create could not be completed at all.** npm prints the two-factor
|
|
22
|
+
challenge as `auth/cli/***` - masked - in text mode, and only puts the URL
|
|
23
|
+
that can be opened in the JSON body. The list asked for JSON and worked; the
|
|
24
|
+
create did not, and failed with a message that said nothing about why. Every
|
|
25
|
+
npm trust call asks for JSON now.
|
|
26
|
+
- **Each npm command gets its own two-factor challenge.** The code a browser
|
|
27
|
+
flow returns is valid for the challenge that asked for it and for nothing
|
|
28
|
+
else, so carrying one from one command to the next made the second command
|
|
29
|
+
send a code that was already spent - and the registry answered that with a
|
|
30
|
+
different challenge, one that wants an authenticator code rather than a
|
|
31
|
+
browser. The failure looked like "npm wants something else now" and nothing
|
|
32
|
+
like the cause.
|
|
33
|
+
- **The URLs come from the response body, not from a guess.** The done URL
|
|
34
|
+
was rebuilt from the id, which sent a real request to the registry with a
|
|
35
|
+
made-up id; the registry answered "forbidden", which is not a token, so the
|
|
36
|
+
wait ran its full length and then reported that nobody had answered.
|
|
37
|
+
- **`OC_TRUST_NO_BROWSER` turns the browser off.** A test that opens a
|
|
38
|
+
browser on the machine it runs on is a test that breaks whatever the
|
|
39
|
+
person at the keyboard was doing.
|
|
40
|
+
|
|
41
|
+
### The release path, end to end
|
|
42
|
+
|
|
43
|
+
The publish is now automatic and holds no secret:
|
|
44
|
+
|
|
45
|
+
- The tag triggers the workflow; the tests run; the publish job exchanges the
|
|
46
|
+
runner's OIDC identity for a short-lived publish token, so there is no npm
|
|
47
|
+
token in the repository to leak, rotate or expire.
|
|
48
|
+
- A token still works when `NPM_TOKEN` is set, for the case where OIDC is not
|
|
49
|
+
configured.
|
|
50
|
+
- `oc-harness trust` sets the trusted publisher up, and completes the
|
|
51
|
+
two-factor challenge in a browser - which works with an authenticator app, a
|
|
52
|
+
security key, or a code sent to the email.
|
|
53
|
+
|
|
54
|
+
## [0.3.9] — 2026-10-20
|
|
55
|
+
|
|
56
|
+
### Fixed
|
|
57
|
+
|
|
58
|
+
- **Each npm command gets its own two-factor challenge.** The code a browser
|
|
59
|
+
flow returns is valid for the challenge that asked for it and for nothing
|
|
60
|
+
else, so carrying one from one command to the next made the second command
|
|
61
|
+
send a code that was already spent - and the registry answered that with a
|
|
62
|
+
different challenge, one that wants an authenticator code rather than a
|
|
63
|
+
browser. The failure looked like "npm wants something else now" and nothing
|
|
64
|
+
like the cause.
|
|
65
|
+
- **The URLs come from the response body, not from a guess.** The done URL
|
|
66
|
+
was rebuilt from the id, which sent a real request to the registry with a
|
|
67
|
+
made-up id; the registry answered "forbidden", which is not a token, so the
|
|
68
|
+
wait ran its full length and then reported that nobody had answered.
|
|
69
|
+
- **`OC_TRUST_NO_BROWSER` turns the browser off.** A test that opens a
|
|
70
|
+
browser on the machine it runs on is a test that breaks whatever the
|
|
71
|
+
person at the keyboard was doing.
|
|
72
|
+
|
|
17
73
|
## [0.3.8] — 2026-10-20
|
|
18
74
|
|
|
19
75
|
### Fixed
|
|
@@ -358,7 +414,9 @@ silent in the worst way:
|
|
|
358
414
|
|
|
359
415
|
---
|
|
360
416
|
|
|
361
|
-
[Unreleased]: https://github.com/pham-canh/space-harness/compare/v0.
|
|
417
|
+
[Unreleased]: https://github.com/pham-canh/space-harness/compare/v0.4.0...HEAD
|
|
418
|
+
[0.4.0]: https://github.com/pham-canh/space-harness/compare/v0.3.9...v0.4.0
|
|
419
|
+
[0.3.9]: https://github.com/pham-canh/space-harness/compare/v0.3.8...v0.3.9
|
|
362
420
|
[0.3.8]: https://github.com/pham-canh/space-harness/compare/v0.3.7...v0.3.8
|
|
363
421
|
[0.3.7]: https://github.com/pham-canh/space-harness/compare/v0.3.6...v0.3.7
|
|
364
422
|
[0.3.6]: https://github.com/pham-canh/space-harness/compare/v0.3.5...v0.3.6
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-harness-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Harness kit for OpenCode: capability profiles, a fleet of job lanes with one free model each, role-based reasoning effort, context pruning, and measured baselines.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/pham-canh/space-harness#readme",
|
package/src/trust.js
CHANGED
|
@@ -87,11 +87,10 @@ function npm(args, opts = {}) {
|
|
|
87
87
|
* Run npm with its output captured.
|
|
88
88
|
*
|
|
89
89
|
* @param {string[]} args
|
|
90
|
-
* @param {
|
|
90
|
+
* @param {string[]} [extra] arguments to add, such as `--otp=<code>`
|
|
91
91
|
* @returns {{ out: string, code: number }}
|
|
92
92
|
*/
|
|
93
|
-
function npmCaptured(args,
|
|
94
|
-
const extra = state.otp ? [`--otp=${state.otp}`] : []
|
|
93
|
+
function npmCaptured(args, extra = []) {
|
|
95
94
|
const r = spawnSync("npm", [...args, ...extra], {
|
|
96
95
|
encoding: "utf8",
|
|
97
96
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -117,26 +116,49 @@ function npmCaptured(args, state = {}) {
|
|
|
117
116
|
* it, and npm then gives up and prints a URL nobody can open. So the flow is
|
|
118
117
|
* done here, where the URL can be recovered.
|
|
119
118
|
*
|
|
119
|
+
* EACH CALL GETS ITS OWN CHALLENGE. The code a browser flow returns is valid
|
|
120
|
+
* for the challenge that asked for it and for nothing else, so carrying one
|
|
121
|
+
* from one npm command to the next makes the second command send a code that
|
|
122
|
+
* is already spent, and the registry answers that with a different challenge
|
|
123
|
+
* - one that wants an authenticator code rather than a browser.
|
|
124
|
+
*
|
|
120
125
|
* @param {string[]} args
|
|
121
|
-
* @param {{ otp?: string }} [state]
|
|
126
|
+
* @param {{ otp?: string }} [state] the user's own code, if they gave one
|
|
122
127
|
* @returns {Promise<{ out: string, code: number }>}
|
|
123
128
|
*/
|
|
124
129
|
async function npmWithOtp(args, state = {}) {
|
|
125
|
-
|
|
130
|
+
// JSON, always. In text mode npm prints the challenge as
|
|
131
|
+
// `auth/cli/***` - masked - and the URL that can be opened is only in the
|
|
132
|
+
// JSON body. Without this flag the create cannot be completed at all, and
|
|
133
|
+
// the failure says nothing about why.
|
|
134
|
+
const withJson = args.includes("--json") ? [] : ["--json"]
|
|
135
|
+
const userOtp = state.otp ? [`--otp=${state.otp}`] : []
|
|
136
|
+
let result = npmCaptured([...args, ...withJson], userOtp)
|
|
126
137
|
if (result.code === 0) return result
|
|
127
138
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const
|
|
139
|
+
// The URLs come FROM the body, not from a guess. Guessing the done URL
|
|
140
|
+
// sends a real request to the registry with a made-up id, and the registry
|
|
141
|
+
// answers "forbidden" - which is not a token, so the wait runs its full
|
|
142
|
+
// length and then reports that nobody answered.
|
|
143
|
+
const done = /"doneUrl"\s*:\s*"([^"]+)"/.exec(result.out)
|
|
144
|
+
if (!done) return result
|
|
145
|
+
const doneUrl = done[1]
|
|
146
|
+
const auth = /"authUrl"\s*:\s*"([^"]+)"/.exec(result.out)
|
|
147
|
+
const authId = /authId=([a-f0-9-]+)/.exec(doneUrl)
|
|
148
|
+
const authUrl = auth ? auth[1] : `https://www.npmjs.com/auth/cli/${authId?.[1] ?? ""}`
|
|
133
149
|
|
|
134
150
|
// Open it if this machine can. The URL is printed either way, because a
|
|
135
151
|
// browser that cannot be opened here can be opened anywhere.
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
152
|
+
//
|
|
153
|
+
// OC_TRUST_NO_BROWSER turns the opening off, which is what a test needs:
|
|
154
|
+
// a test that opens a browser on the machine it runs on is a test that
|
|
155
|
+
// breaks whatever the person at the keyboard was doing.
|
|
156
|
+
if (!process.env.OC_TRUST_NO_BROWSER) {
|
|
157
|
+
try {
|
|
158
|
+
execFileSync("xdg-open", [authUrl], { stdio: "ignore", timeout: 10_000 })
|
|
159
|
+
} catch {
|
|
160
|
+
// No browser here. The URL below is the way out.
|
|
161
|
+
}
|
|
140
162
|
}
|
|
141
163
|
console.error("")
|
|
142
164
|
console.error(bold(" npm needs to confirm it is you."))
|
|
@@ -151,8 +173,8 @@ async function npmWithOtp(args, state = {}) {
|
|
|
151
173
|
console.error(red(" no answer came back, so nothing was changed."))
|
|
152
174
|
return result
|
|
153
175
|
}
|
|
154
|
-
|
|
155
|
-
result = npmCaptured(args,
|
|
176
|
+
// The code is used for THIS call only.
|
|
177
|
+
result = npmCaptured([...args, ...withJson], [`--otp=${token}`])
|
|
156
178
|
return result
|
|
157
179
|
}
|
|
158
180
|
|