halfcycle 0.3.35 → 0.3.37
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/.claude-plugin/plugin.json +1 -1
- package/README.md +16 -1
- package/bin/bin.bundle.mjs +471 -61
- package/dist/banner-facts.d.ts +6 -2
- package/dist/banner-facts.d.ts.map +1 -1
- package/dist/bin.js +435 -430
- package/dist/bin.js.map +3 -3
- package/dist/claude-version-probe.d.ts +44 -0
- package/dist/claude-version-probe.d.ts.map +1 -0
- package/dist/confirm-identity.d.ts +7 -0
- package/dist/confirm-identity.d.ts.map +1 -1
- package/dist/device-signin.d.ts +93 -17
- package/dist/device-signin.d.ts.map +1 -1
- package/dist/engagement-credential.d.ts +39 -27
- package/dist/engagement-credential.d.ts.map +1 -1
- package/dist/index.js +189 -290
- package/dist/index.js.map +3 -3
- package/dist/install-manifest.d.ts +32 -4
- package/dist/install-manifest.d.ts.map +1 -1
- package/dist/install.d.ts +53 -23
- package/dist/install.d.ts.map +1 -1
- package/dist/loopback-signin.d.ts +8 -2
- package/dist/loopback-signin.d.ts.map +1 -1
- package/dist/open-phase.d.ts +2 -1
- package/dist/open-phase.d.ts.map +1 -1
- package/dist/probe-mcp.d.ts +39 -7
- package/dist/probe-mcp.d.ts.map +1 -1
- package/dist/uninstall.d.ts +1 -1
- package/dist/uninstall.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ONE PLACE THAT KNOWS HOW TO ASK THE SHELL FOR CLAUDE CODE'S VERSION.
|
|
3
|
+
*
|
|
4
|
+
* `reportClaudeCodeVersion` (`bin.ts`) and `claudeCodeVersion` (`banner-facts.ts`) each used
|
|
5
|
+
* to call `execFileSync('claude', ['--version'], …)` directly. On Windows, an npm install of
|
|
6
|
+
* Claude Code puts a `claude.cmd` shim on PATH, never a `claude.exe` — and `execFileSync`
|
|
7
|
+
* without a shell never runs a `.cmd` file: Node resolves the executable itself, and only a
|
|
8
|
+
* shell applies the `.cmd` extension rule. So on Windows both probes silently found nothing,
|
|
9
|
+
* every time, regardless of whether Claude Code was installed.
|
|
10
|
+
*
|
|
11
|
+
* The fix routes through `cmd.exe`, the same way Node's own `shell: true` already does on
|
|
12
|
+
* Windows: `/d` skips AutoRun scripts, `/s` lets the quoted command string carry its own
|
|
13
|
+
* quoting, and `windowsVerbatimArguments: true` stops Node adding a second layer of quoting
|
|
14
|
+
* on top of the one already in the argument. Elsewhere (`win32` excluded) this is exactly the
|
|
15
|
+
* call it always was.
|
|
16
|
+
*
|
|
17
|
+
* `platform` and `exec` are both injectable so a test can drive the `win32` arm from a
|
|
18
|
+
* non-Windows CI runner and assert the literal command and arguments, rather than depending
|
|
19
|
+
* on the host actually being Windows with Claude Code installed.
|
|
20
|
+
*
|
|
21
|
+
* INV-001: Node builtins only.
|
|
22
|
+
*/
|
|
23
|
+
/** The slice of `execFileSync`'s call shape this probe needs. The real function already
|
|
24
|
+
* satisfies it, so production code passes no wrapper. */
|
|
25
|
+
export type ClaudeVersionExec = (file: string, args: readonly string[], options: {
|
|
26
|
+
readonly encoding: 'utf-8';
|
|
27
|
+
readonly timeout: number;
|
|
28
|
+
readonly stdio: readonly ['ignore', 'pipe', 'ignore'];
|
|
29
|
+
readonly windowsVerbatimArguments?: boolean;
|
|
30
|
+
}) => string;
|
|
31
|
+
export interface ClaudeVersionProbeDeps {
|
|
32
|
+
/** Defaults to `process.platform`. Inject to exercise the `win32` arm elsewhere. */
|
|
33
|
+
readonly platform?: NodeJS.Platform;
|
|
34
|
+
/** Defaults to the real `execFileSync`. Inject to assert the call without a shell. */
|
|
35
|
+
readonly exec?: ClaudeVersionExec;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Runs `claude --version` for the given platform and returns the parsed `x.y.z`, or
|
|
39
|
+
* `undefined` for anything that stops that from working: `claude` not on PATH, no answer
|
|
40
|
+
* inside `timeoutMs`, or an answer with no dotted version in it. Never throws — both
|
|
41
|
+
* callers' degradation (say nothing) depends on that.
|
|
42
|
+
*/
|
|
43
|
+
export declare function probeClaudeCodeVersion(timeoutMs: number, deps?: ClaudeVersionProbeDeps): string | undefined;
|
|
44
|
+
//# sourceMappingURL=claude-version-probe.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-version-probe.d.ts","sourceRoot":"","sources":["../src/claude-version-probe.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH;0DAC0D;AAC1D,MAAM,MAAM,iBAAiB,GAAG,CAC9B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE;IACP,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;IACtD,QAAQ,CAAC,wBAAwB,CAAC,EAAE,OAAO,CAAC;CAC7C,KACE,MAAM,CAAC;AAEZ,MAAM,WAAW,sBAAsB;IACrC,oFAAoF;IACpF,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;IACpC,sFAAsF;IACtF,QAAQ,CAAC,IAAI,CAAC,EAAE,iBAAiB,CAAC;CACnC;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,GAAE,sBAA2B,GAAG,MAAM,GAAG,SAAS,CAqB/G"}
|
|
@@ -286,6 +286,13 @@ export declare function readDecision(answer: string): IdentityDecision;
|
|
|
286
286
|
* with no measurement at all — so there is no default to fall back on silently; every
|
|
287
287
|
* caller states `isInteractiveRun()`'s answer, or a value it controls directly in a
|
|
288
288
|
* test.
|
|
289
|
+
*
|
|
290
|
+
* THE KEY TO PRESS IS NOT SPELLED HERE (`windows-client-seat`). The interactive arm
|
|
291
|
+
* composes `SWITCH_ACCOUNT_ANSWER` from `@halfcycle/events`, because delivery's
|
|
292
|
+
* not-this-account 401 sentence tells the same person the same thing and cannot
|
|
293
|
+
* import this module. The output is byte-identical to the spelled form it replaced;
|
|
294
|
+
* `test/credential-wire-contract.test.ts` pins both arms by hash and refuses a
|
|
295
|
+
* spelled copy of the fragment, or of a placeholder bearer, anywhere in this package.
|
|
289
296
|
*/
|
|
290
297
|
export declare function identitySwitchRemedy(home: string | undefined, interactive: boolean): string;
|
|
291
298
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"confirm-identity.d.ts","sourceRoot":"","sources":["../src/confirm-identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkGG;AAGH,OAAO,
|
|
1
|
+
{"version":3,"file":"confirm-identity.d.ts","sourceRoot":"","sources":["../src/confirm-identity.ts"],"names":[],"mappings":"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"}
|
package/dist/device-signin.d.ts
CHANGED
|
@@ -10,8 +10,11 @@
|
|
|
10
10
|
* SINCE T-25 THERE ARE TWO WAYS THE ANSWER COMES BACK, and `signIn` below is where
|
|
11
11
|
* the choice is made:
|
|
12
12
|
*
|
|
13
|
-
* LOOPBACK (the default) — the CLI listens on `127.0.0.1
|
|
14
|
-
* browser back to it,
|
|
13
|
+
* LOOPBACK (the default) — the CLI listens on `127.0.0.1` and the page redirects the
|
|
14
|
+
* browser back to it, while this file polls control beside that wait, so a sign-in
|
|
15
|
+
* approved on any tab or device completes. The user code is printed for a plain
|
|
16
|
+
* tab to be compared against; the loopback page shows none. `loopback-signin.ts`
|
|
17
|
+
* owns the listener.
|
|
15
18
|
*
|
|
16
19
|
* DEVICE CODE (the fallback) — the code is printed, the human compares it with the
|
|
17
20
|
* page, and this file polls. Unchanged, and the right answer whenever the browser
|
|
@@ -59,6 +62,7 @@
|
|
|
59
62
|
*
|
|
60
63
|
* INV-001: Node builtins, the global `fetch` (Node 20), and `@halfcycle/events`.
|
|
61
64
|
*/
|
|
65
|
+
import { spawn } from 'node:child_process';
|
|
62
66
|
import { type DeviceAuthStartResponse, type DevicePollApproved } from '@halfcycle/events';
|
|
63
67
|
/**
|
|
64
68
|
* A sign-in that ended without a credential.
|
|
@@ -101,6 +105,8 @@ export interface SignInDeps {
|
|
|
101
105
|
sleep?: (ms: number) => Promise<void>;
|
|
102
106
|
/** The clock the local deadline is measured on. Defaults to `Date.now`. */
|
|
103
107
|
now?: () => number;
|
|
108
|
+
/** How often either wait says it is still waiting. Defaults to fifteen seconds. */
|
|
109
|
+
stillWaitingMs?: number;
|
|
104
110
|
/**
|
|
105
111
|
* What signing in is FOR, spliced into `signIn`'s opening line: "Halfcycle needs
|
|
106
112
|
* an account before it can **<reason>** — signing you in through your browser."
|
|
@@ -126,22 +132,42 @@ export interface SignInDeps {
|
|
|
126
132
|
onwardEngagementId?: string;
|
|
127
133
|
}
|
|
128
134
|
/**
|
|
129
|
-
* The platform command that opens a URL in the user's browser, as
|
|
135
|
+
* The platform command that opens a URL in the user's browser, as
|
|
136
|
+
* `{command, args, windowsVerbatimArguments?}`.
|
|
130
137
|
*
|
|
131
138
|
* A PURE FUNCTION so the argv can be asserted on every platform from any platform —
|
|
132
139
|
* the alternative is a test that only ever checks the branch it happens to be
|
|
133
140
|
* running on, which on this repo's CI would mean the Windows and Linux arms were
|
|
134
141
|
* never looked at.
|
|
135
142
|
*
|
|
136
|
-
* NO SHELL, EVER. The URL is an argv element
|
|
137
|
-
* metacharacters is an odd URL and
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
143
|
+
* macOS and Linux: NO SHELL, EVER. The URL is an argv element handed straight to
|
|
144
|
+
* `open`/`xdg-open`, so a value containing shell metacharacters is an odd URL and
|
|
145
|
+
* not a command.
|
|
146
|
+
*
|
|
147
|
+
* `win32` (CHANGED — windows-install-hygiene AC5, the 2026-10-07 walk's F10): the
|
|
148
|
+
* old form, `{ command: 'cmd', args: ['/c', 'start', '', url] }`, hands `cmd.exe` an
|
|
149
|
+
* argv array it reassembles into one command line of its own, and that reassembly
|
|
150
|
+
* quotes nothing — `cmd.exe` then treats an unescaped `&` in the URL as a command
|
|
151
|
+
* separator. The loopback address carries `port` and `state` after a literal `&`
|
|
152
|
+
* (`loopbackVerificationUrl`), so the walk saw the first launch open the address cut
|
|
153
|
+
* at its first `&`, exit 1, and the fallback open a second tab.
|
|
154
|
+
*
|
|
155
|
+
* The fix routes through `cmd.exe` the way `probeClaudeCodeVersion`
|
|
156
|
+
* (`claude-version-probe.ts`) already does for AC3: one argv element carries the
|
|
157
|
+
* ENTIRE inner command as a single quoted string, and `/d /s` plus
|
|
158
|
+
* `windowsVerbatimArguments: true` tell `cmd.exe` to strip only the outer pair of
|
|
159
|
+
* quotes and run what is left — `start "" "<url>"`, the URL still inside its own
|
|
160
|
+
* quotes, which is what keeps `&`, `|`, `^`, `!` and a space inside one pair of
|
|
161
|
+
* quotes rather than cut at the first one. `openBrowser` refuses to build this at
|
|
162
|
+
* all for a URL holding `"` or `%` (see `hasWin32UnsafeOpenerChars`) — a `"` would
|
|
163
|
+
* close the quoted command early, and `cmd.exe` expands `%` as an
|
|
164
|
+
* environment-variable reference regardless of quoting, so quoting the URL does
|
|
165
|
+
* not defend against either.
|
|
141
166
|
*/
|
|
142
167
|
export declare function browserOpenCommand(os: string, url: string): {
|
|
143
168
|
command: string;
|
|
144
169
|
args: string[];
|
|
170
|
+
windowsVerbatimArguments?: boolean;
|
|
145
171
|
};
|
|
146
172
|
/**
|
|
147
173
|
* Is this URL safe to hand to the platform opener?
|
|
@@ -153,6 +179,37 @@ export declare function browserOpenCommand(os: string, url: string): {
|
|
|
153
179
|
* document and a handler-scheme URL would launch whatever claims that scheme.
|
|
154
180
|
*/
|
|
155
181
|
export declare function isOpenableUrl(url: string): boolean;
|
|
182
|
+
/**
|
|
183
|
+
* Does this URL hold a `"`, a `%`, or a C0/DEL control character (U+0000–U+001F,
|
|
184
|
+
* U+007F) — the bytes `cmd.exe` still interprets, or that reach it unaccounted
|
|
185
|
+
* for, inside the single-quoted command `browserOpenCommand`'s `win32` arm builds
|
|
186
|
+
* (windows-install-hygiene AC5; the control-character case added at the
|
|
187
|
+
* windows-client follow-up wave's T-28 review)?
|
|
188
|
+
*
|
|
189
|
+
* A `"` closes that quoted command early, handing `cmd.exe` the remainder of the
|
|
190
|
+
* URL as a second command. A `%` is expanded by `cmd.exe`'s own command-line
|
|
191
|
+
* parser as an environment-variable reference — `%PATH%`, `%COMSPEC%` — and that
|
|
192
|
+
* expansion happens whether or not the text sits inside quotes, so quoting the URL
|
|
193
|
+
* does not defend against it.
|
|
194
|
+
*
|
|
195
|
+
* A CONTROL CHARACTER is refused for a different reason, and a line feed is the
|
|
196
|
+
* sharp case: `isOpenableUrl` parses the address with the WHATWG `URL`
|
|
197
|
+
* constructor, which — per spec — strips every ASCII tab and newline from its
|
|
198
|
+
* input before parsing. So an address carrying a raw `\n` passes `isOpenableUrl`
|
|
199
|
+
* looking exactly like the clean URL it would be without one, while the
|
|
200
|
+
* UNSTRIPPED STRING — the one `browserOpenCommand` actually quotes and `spawn`
|
|
201
|
+
* actually hands to `cmd.exe` — still carries the byte inside the quoted command
|
|
202
|
+
* line. No other C0 control character or DEL is reasoned about any more than that
|
|
203
|
+
* one is, so the whole class is refused rather than only the byte a parser
|
|
204
|
+
* happens to delete.
|
|
205
|
+
*
|
|
206
|
+
* Checked only where it matters, by `openBrowser`, and only on `win32`: `open`
|
|
207
|
+
* (macOS) and `xdg-open` (Linux) take the URL as a bare argv element with no shell
|
|
208
|
+
* in between, so none of these bytes does anything there — and percent-encoding
|
|
209
|
+
* is ordinary in an http(s) URL, so refusing `%` on every platform would refuse
|
|
210
|
+
* addresses that are perfectly safe to open outside `cmd.exe`'s quoting.
|
|
211
|
+
*/
|
|
212
|
+
export declare function hasWin32UnsafeOpenerChars(url: string): boolean;
|
|
156
213
|
/** What was MEASURED when this run tried to open a browser. */
|
|
157
214
|
export interface BrowserOpenAttempt {
|
|
158
215
|
/** A browser was launched. Nobody claims the human looked at it. */
|
|
@@ -198,8 +255,25 @@ export interface BrowserOpenAttempt {
|
|
|
198
255
|
* browser over*; honouring it is not a guess about the machine, and the reason
|
|
199
256
|
* reported says so in those words. Nothing else in this flow reads the environment
|
|
200
257
|
* to decide which path to take.
|
|
258
|
+
*
|
|
259
|
+
* ---------------------------------------------------------------------------
|
|
260
|
+
* `os` IS INJECTABLE (ADDED — AC5), DEFAULTING TO THE REAL PLATFORM, for the same
|
|
261
|
+
* reason `browserOpenCommand` takes one: a `win32`-only refusal
|
|
262
|
+
* (`hasWin32UnsafeOpenerChars`) needs to be assertable from this repo's Linux CI
|
|
263
|
+
* without actually having `cmd.exe` on `PATH`. Every existing caller passes two
|
|
264
|
+
* arguments and gets the real platform, unchanged.
|
|
265
|
+
*
|
|
266
|
+
* `spawnImpl` IS INJECTABLE TOO (ADDED — windows-client T-28 review), for a
|
|
267
|
+
* narrower reason: `node:child_process`'s `spawn` is a built-in ESM export and
|
|
268
|
+
* `node:test`'s `mock.method` cannot redefine a core module's property
|
|
269
|
+
* (`TypeError: Cannot redefine property`, measured), so there was no way for a
|
|
270
|
+
* test to observe what options actually reached `spawn` — in particular, that
|
|
271
|
+
* `windowsVerbatimArguments: true` from `browserOpenCommand`'s `win32` arm
|
|
272
|
+
* survived as far as the call rather than being computed and dropped. Every
|
|
273
|
+
* existing caller passes two or three arguments and gets the real `spawn`,
|
|
274
|
+
* unchanged.
|
|
201
275
|
*/
|
|
202
|
-
export declare function openBrowser(url: string, env?: NodeJS.ProcessEnv): Promise<BrowserOpenAttempt>;
|
|
276
|
+
export declare function openBrowser(url: string, env?: NodeJS.ProcessEnv, os?: string, spawnImpl?: typeof spawn): Promise<BrowserOpenAttempt>;
|
|
203
277
|
/** True iff this run has no human at the keyboard. */
|
|
204
278
|
export declare function isNonInteractive(env?: NodeJS.ProcessEnv): boolean;
|
|
205
279
|
/** `POST /auth/device/start` — mint a code. Throws `SignInRefused` on a refusal. */
|
|
@@ -237,10 +311,12 @@ export declare const LOOPBACK_TIMEOUT_MESSAGE: string;
|
|
|
237
311
|
* ---------------------------------------------------------------------------
|
|
238
312
|
* THE TWO PATHS, AND HOW THE CHOICE BETWEEN THEM IS MADE.
|
|
239
313
|
*
|
|
240
|
-
* LOOPBACK — bind `127.0.0.1:0`, put that port in the URL we open, and
|
|
241
|
-
* the confirmation page to send the browser back
|
|
242
|
-
* (
|
|
243
|
-
*
|
|
314
|
+
* LOOPBACK — bind `127.0.0.1:0`, put that port in the URL we open, and poll
|
|
315
|
+
* control while waiting for the confirmation page to send the browser back
|
|
316
|
+
* (`pollBesideLoopback`; the redirect only wakes the poll). The USER code is
|
|
317
|
+
* printed: the loopback page shows none, but a plain sign-in tab does, and asks
|
|
318
|
+
* whether it matches this terminal. *This replaces T-25's "no code is displayed
|
|
319
|
+
* at all", withdrawn by the walk-F3 amendment of 2026-10-08.*
|
|
244
320
|
*
|
|
245
321
|
* DEVICE CODE — print the code, open (or print) the page, and poll. Unchanged from
|
|
246
322
|
* T-08, still tested, and still correct for every machine whose browser is not the
|
|
@@ -267,10 +343,10 @@ export declare const LOOPBACK_TIMEOUT_MESSAGE: string;
|
|
|
267
343
|
* to a connection error after a successful approval — a page that looks broken for a
|
|
268
344
|
* sign-in that worked.
|
|
269
345
|
*
|
|
270
|
-
* WHAT IS PRINTED AND WHAT IS NOT, on
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
*
|
|
346
|
+
* WHAT IS PRINTED AND WHAT IS NOT, on BOTH paths: the USER code is printed — it is a
|
|
347
|
+
* selector a human compares between two screens and yields nothing to whoever reads
|
|
348
|
+
* it, since only the device code can claim the credential. The DEVICE code and the
|
|
349
|
+
* credential are printed nowhere, ever, and appear in no URL.
|
|
274
350
|
*/
|
|
275
351
|
export declare function signIn(serviceUrl: string, deps?: SignInDeps & {
|
|
276
352
|
env?: NodeJS.ProcessEnv;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"device-signin.d.ts","sourceRoot":"","sources":["../src/device-signin.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"device-signin.d.ts","sourceRoot":"","sources":["../src/device-signin.ts"],"names":[],"mappings":"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"}
|
|
@@ -23,19 +23,21 @@
|
|
|
23
23
|
* ---------------------------------------------------------------------------
|
|
24
24
|
* IT IS AN ENV FILE AND NOT JSON, AND THAT IS FORCED — not a preference.
|
|
25
25
|
*
|
|
26
|
-
*
|
|
26
|
+
* The generated hook scripts read this credential, and they are POSIX `sh`:
|
|
27
27
|
*
|
|
28
28
|
* - `.claude/hooks/guard-runner.sh` SOURCES it (`set -a; . "$f"; set +a`) so the
|
|
29
29
|
* runner, which reads only `process.env`, receives its three variables. That is
|
|
30
|
-
* the INV-001 seam: env loading lives in the hook wiring
|
|
31
|
-
* - `.
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* `
|
|
30
|
+
* the INV-001 seam for the hooks: env loading lives in the hook wiring.
|
|
31
|
+
* - `.claude/hooks/session-start-marker.sh` tests for it to word its coverage line.
|
|
32
|
+
*
|
|
33
|
+
* A shell cannot parse JSON — there is no `jq` in this environment — so the
|
|
34
|
+
* machine-level file is `KEY=value` lines, exactly as Studio's is, and the ONE field
|
|
35
|
+
* the shell must take out of the committed pin — the engagement id — is cut out of
|
|
36
|
+
* JSON with `sed`. See `engagementResolutionShell()` for that, and for why
|
|
37
|
+
* `tr -d '\n'` precedes it. (The third consumer, the editor's headers command, was a
|
|
38
|
+
* generated `.halfcycle/mcp-headers.sh` that grepped one line out of this file; since
|
|
39
|
+
* `windows-client-seat` it is the vendored runner's `mcp-headers` verb, which reads
|
|
40
|
+
* the file through core's `parseEnvText` and the pin as JSON.)
|
|
39
41
|
*
|
|
40
42
|
* ---------------------------------------------------------------------------
|
|
41
43
|
* THE SEVEN — NOW EIGHT (T-10) — KEYS ARE FOUR THINGS, AND THE COUNT DECIDES WHAT
|
|
@@ -99,14 +101,15 @@
|
|
|
99
101
|
export { ENGAGEMENT_ENV_HEADER, ENGAGEMENTS_DIR_NAME, ENGAGEMENT_ENV_FILENAME, HALFCYCLE_DIR_NAME, NOT_THIS_ACCOUNT_MARKER_FILENAME, parseEnvText, shq, unquote, } from '@halfcycle/core';
|
|
100
102
|
/**
|
|
101
103
|
* The field the committed pin (`.halfcycle/bundle.json`) carries the engagement id
|
|
102
|
-
* under.
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
104
|
+
* under. Its one home is `@halfcycle/core` (`PIN_ENGAGEMENT_ID_FIELD`), because the
|
|
105
|
+
* runner's connection-headers verb reads the same field and may not import this
|
|
106
|
+
* package. It is re-exported here so `readEngagementCredential`'s TS caller and the
|
|
107
|
+
* generated POSIX `sed` keep reading it from this module. A test installs, then runs
|
|
108
|
+
* the generated shell against the real written pin and compares its answer to
|
|
109
|
+
* `engagementEnvPath()`, so writer and readers are asserted to agree rather than
|
|
110
|
+
* left to match by eye.
|
|
108
111
|
*/
|
|
109
|
-
export
|
|
112
|
+
export { PIN_ENGAGEMENT_ID_FIELD } from '@halfcycle/core';
|
|
110
113
|
/**
|
|
111
114
|
* The refusal an engagement id that is not UUID-shaped meets before it becomes a
|
|
112
115
|
* path. A class of its own so a caller can tell "this id cannot name a folder"
|
|
@@ -244,10 +247,14 @@ export declare const GUARD_ENV_KEYS: readonly ["GUARD_SERVICE_URL", "GUARD_SERVI
|
|
|
244
247
|
* keeps the two bound: add a fourth guard variable to `GUARD_ENV_KEYS` and it
|
|
245
248
|
* appears here, required, without anybody remembering this constant exists.
|
|
246
249
|
*
|
|
247
|
-
* The consumer is the
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
250
|
+
* The consumer is the install report, which names the keys a covered repository
|
|
251
|
+
* holds. The generated session-start script used to be a second consumer, looping
|
|
252
|
+
* over this list to decide whether to print the no-coverage banner; since
|
|
253
|
+
* `session-start-connection-check` that banner is the runner's, decided by the
|
|
254
|
+
* guard hook's own `readConfig`, whose required pair this list must keep naming.
|
|
255
|
+
* Naming an address a developer cannot obtain and does not need in a list of what is
|
|
256
|
+
* missing — beside an instruction to re-run the installer — is the wall this whole
|
|
257
|
+
* change removed.
|
|
251
258
|
*/
|
|
252
259
|
export declare const GUARD_COVERAGE_REQUIRED_KEYS: ("GUARD_SERVICE_TOKEN" | "GUARD_ENGAGEMENT_ID")[];
|
|
253
260
|
/**
|
|
@@ -364,15 +371,18 @@ export declare function writeEngagementEnv(engagementId: string, values: Record<
|
|
|
364
371
|
* match independent of the file's line breaks. (This repo has paid twice for a
|
|
365
372
|
* pattern that wrapped across lines reading as absent.)
|
|
366
373
|
*
|
|
367
|
-
* **Why
|
|
368
|
-
*
|
|
369
|
-
*
|
|
374
|
+
* **Why `sh` here, though the headers command is now `node`.** This block is
|
|
375
|
+
* embedded in the generated hook scripts, which are bash; parsing the pin inside
|
|
376
|
+
* them in shell keeps each hook one process until it `exec`s the runner. The old
|
|
377
|
+
* reason given here — that a desktop-launched session may have no version-manager
|
|
378
|
+
* `node` — no longer holds the line: the guard wrapper already ends `exec node`, and
|
|
379
|
+
* `windows-client-seat` made the editor's headers command a `node` program too.
|
|
370
380
|
*
|
|
371
381
|
* **IT SETS VARIABLES AND PRINTS NOTHING, and that is not a style choice.** The
|
|
372
382
|
* obvious form — print the path and let the caller take it with `$(…)` — puts the
|
|
373
383
|
* function in a SUBSHELL, so the `HALFCYCLE_ENV_PROBLEM` it sets on the failure
|
|
374
|
-
* path never reaches the caller. Measured: under `set -u` (which
|
|
375
|
-
*
|
|
384
|
+
* path never reaches the caller. Measured: under `set -u` (which the retired
|
|
385
|
+
* `mcp-headers.sh` ran with) the caller then dies on `HALFCYCLE_ENV_PROBLEM: unbound variable`
|
|
376
386
|
* instead of printing the reason it was carrying. So the function assigns
|
|
377
387
|
* `HALFCYCLE_ENV_FILE` / `HALFCYCLE_ENV_PROBLEM` / `HALFCYCLE_ENV_ENGAGEMENT` in
|
|
378
388
|
* the CURRENT shell and returns a status, and callers use `if halfcycle_env_file …`.
|
|
@@ -388,7 +398,9 @@ export declare function writeEngagementEnv(engagementId: string, values: Record<
|
|
|
388
398
|
* installer does not rewrite a pin whose id it refuses, so "run npx halfcycle to
|
|
389
399
|
* rewrite it" would be false. The guard wrapper and the session-start marker treat
|
|
390
400
|
* every failure alike (no credential sourced), which is the right answer for this
|
|
391
|
-
* one too
|
|
401
|
+
* one too. The retired `mcp-headers.sh` was the one caller that worded each case;
|
|
402
|
+
* the runner's `mcp-headers` verb now does, from its own pin read, with a `bad-id`
|
|
403
|
+
* arm of its own.
|
|
392
404
|
*
|
|
393
405
|
* **HALFCYCLE_ENV_NOT_THIS_ACCOUNT IS SET ON THE `no-credential` FAILURE PATH TOO
|
|
394
406
|
* (T-02b, `solo-project-ownership`) — that is the state it exists to distinguish.**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engagement-credential.d.ts","sourceRoot":"","sources":["../src/engagement-credential.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"engagement-credential.d.ts","sourceRoot":"","sources":["../src/engagement-credential.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmGG;AAoBH,OAAO,EACL,qBAAqB,EACrB,oBAAoB,EACpB,uBAAuB,EACvB,kBAAkB,EAClB,gCAAgC,EAChC,YAAY,EACZ,GAAG,EACH,OAAO,GACR,MAAM,iBAAiB,CAAC;AAgBzB;;;;;;;;;GASG;AACH,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAE1D;;;;GAIG;AACH,qBAAa,wBAAyB,SAAQ,KAAK;gBACrC,YAAY,EAAE,MAAM;CAajC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAE7D;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAG9E;AAED,oFAAoF;AACpF,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAE7E;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAEpF;AAED;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAMnF;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,eAAO,MAAM,cAAc,8EAIjB,CAAC;AAEX;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,4BAA4B,mDAExC,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,mBAAmB,mMAOtB,CAAC;AAEX,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEpE;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,mBAAmB,oBAAoB,CAAC;AAuCrD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,GAAG,IAAI,EACvB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,EACjD,IAAI,GAAE,SAAS,MAAM,EAAwB,GAC5C,MAAM,CAER;AAMD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,MAAM,EACpB,IAAI,CAAC,EAAE,MAAM,GACZ,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAM/B;AAED,8DAA8D;AAC9D,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,SAAS,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,kBAAkB,CAChC,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,EACjD,IAAI,CAAC,EAAE,MAAM,EACb,IAAI,GAAE,SAAS,MAAM,EAAwB,GAC5C,eAAe,CAoBjB;AA4BD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AACH,wBAAgB,yBAAyB,IAAI,MAAM,CAsDlD"}
|