@catheadowl/ukp 0.2.1 → 0.2.2
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/package.json +1 -1
- package/src/capabilities/remote-client.ts +42 -11
- package/src/commands/serve.ts +2 -0
- package/src/server.ts +11 -0
package/package.json
CHANGED
|
@@ -343,11 +343,40 @@ function wakeDoorCommand(remotePort: number, form: WakeShellForm = "posix"): str
|
|
|
343
343
|
/** Wrong-shell failure signature for the wake ladder: the remote shell could
|
|
344
344
|
* not even parse the sent form, so the OTHER form is the retry — as opposed
|
|
345
345
|
* to a missing-ukp failure, where the shell ran fine and named 'ukp' as the
|
|
346
|
-
* thing it cannot find. cmd.exe
|
|
347
|
-
*
|
|
346
|
+
* thing it cannot find. Language-independent: cmd.exe locales vary the
|
|
347
|
+
* message body (English, zh-CN, German … all differ) but always quote the
|
|
348
|
+
* missing ASCII token; POSIX shells prefix the
|
|
349
|
+
* token ("sh: command not found"). */
|
|
348
350
|
function wrongShellSignature(form: WakeShellForm, diagnostics: string): boolean {
|
|
349
|
-
if (form === "posix") return /'sh'
|
|
350
|
-
return /'cmd'
|
|
351
|
+
if (form === "posix") return /['"]sh['"]|sh: (?:command )?not found/i.test(diagnostics);
|
|
352
|
+
return /['"]cmd['"]|cmd: (?:command )?not found/i.test(diagnostics);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/** One cheap, language-independent roundtrip to learn the host shell family
|
|
356
|
+
* BEFORE the first wake attempt (2026-09-20, liku 9.5 finding): cmd.exe
|
|
357
|
+
* expands `%OS%` to `Windows_NT`; a POSIX shell echoes the literal token.
|
|
358
|
+
* Choosing the form up front avoids the blind POSIX-first attempt costing a
|
|
359
|
+
* full ready-timeout on every Windows invocation, and picks the right tty
|
|
360
|
+
* stance — `-tt` exists for POSIX SIGHUP reaping; on Windows there is no
|
|
361
|
+
* SIGHUP (max-idle bounds the door), and at least one Win32-OpenSSH 9.5
|
|
362
|
+
* build LOSES a quoted remote command under a pty (an interactive cmd
|
|
363
|
+
* swallows it and waits on stdin forever — the no-pty path executes it
|
|
364
|
+
* correctly). Defaults to POSIX (the common server shape) when the probe
|
|
365
|
+
* errors or times out; the ladder remains as the backstop. */
|
|
366
|
+
async function probeHostShellFamily(
|
|
367
|
+
options: { sshCommand?: readonly string[] },
|
|
368
|
+
target: string,
|
|
369
|
+
): Promise<WakeShellForm> {
|
|
370
|
+
const proc = Bun.spawn(
|
|
371
|
+
[...(options.sshCommand ?? ["ssh"]), "-o", "BatchMode=yes", target, "echo %OS%"],
|
|
372
|
+
{ stdout: "pipe", stderr: "pipe", stdin: "ignore" },
|
|
373
|
+
);
|
|
374
|
+
const output = await Promise.race([
|
|
375
|
+
new Response(proc.stdout).text().catch(() => ""),
|
|
376
|
+
Bun.sleep(5_000).then(() => ""),
|
|
377
|
+
]);
|
|
378
|
+
proc.kill();
|
|
379
|
+
return /Windows_NT/i.test(output) ? "cmd" : "posix";
|
|
351
380
|
}
|
|
352
381
|
|
|
353
382
|
/** HTTP-level readiness probe through the tunnel (W9): a TCP accept on the
|
|
@@ -428,11 +457,10 @@ async function openSshTunnel(
|
|
|
428
457
|
// options for the remaining attempts so the endpoint degrades to Tier 0
|
|
429
458
|
// instead of hard-failing what worked without mux.
|
|
430
459
|
let muxDisabled = false;
|
|
431
|
-
// Shell
|
|
432
|
-
//
|
|
433
|
-
//
|
|
434
|
-
|
|
435
|
-
let form: WakeShellForm = "posix";
|
|
460
|
+
// Shell form (2026-09-20): probed up front via `echo %OS%` (language-
|
|
461
|
+
// independent — see probeHostShellFamily), POSIX by default; the wrong-
|
|
462
|
+
// shell ladder below remains the backstop for a lying or timed-out probe.
|
|
463
|
+
let form: WakeShellForm = await probeHostShellFamily(options, target);
|
|
436
464
|
const attempts = 3;
|
|
437
465
|
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
438
466
|
const localPort = await freePort();
|
|
@@ -443,8 +471,11 @@ async function openSshTunnel(
|
|
|
443
471
|
// SIGHUP on disconnect — normal disconnect included, the door only ever
|
|
444
472
|
// died to --max-idle. Forcing a pty gives the session a controlling
|
|
445
473
|
// terminal, so killing the client reaps the door in seconds (verified:
|
|
446
|
-
// kill -9 of the local ssh → door gone within 3s).
|
|
447
|
-
|
|
474
|
+
// kill -9 of the local ssh → door gone within 3s). POSIX hosts only:
|
|
475
|
+
// Windows has no SIGHUP (max-idle bounds the door there), and pty
|
|
476
|
+
// sessions lose quoted remote commands on at least one
|
|
477
|
+
// Win32-OpenSSH 9.5 build — the cmd form goes without -tt.
|
|
478
|
+
...(form === "posix" ? ["-tt"] : []),
|
|
448
479
|
"-o", "ExitOnForwardFailure=yes",
|
|
449
480
|
"-o", "BatchMode=yes",
|
|
450
481
|
...(muxDisabled ? [] : muxClientOptions()),
|
package/src/commands/serve.ts
CHANGED
|
@@ -173,6 +173,7 @@ export function renderServeBanner(info: ServeInfo): string {
|
|
|
173
173
|
` endpoints: ${info.door!.endpoints.length > 0 ? info.door!.endpoints.join(", ") : "(none — register endpoints on this host)"}`,
|
|
174
174
|
` write: ${info.door!.write.length > 0 ? info.door!.write.join(", ") + " (propose via PUT /e/<name>/v1/propose/<id>)" : "(no endpoint declares propose)"}`,
|
|
175
175
|
` auth: ${info.authRequired ? "bearer token required" : "no token (loopback bind; ssh-forwarded clients authenticate by SSH key)"}`,
|
|
176
|
+
` rg: ${info.rg === "ok" ? "ok" : "missing (rg calls skip with a warning — install ripgrep on this door's PATH)"}`,
|
|
176
177
|
...(info.maxIdleSeconds !== undefined
|
|
177
178
|
? [` idle: exits after ${info.maxIdleSeconds}s without requests (--max-idle)`]
|
|
178
179
|
: []),
|
|
@@ -187,6 +188,7 @@ export function renderServeBanner(info: ServeInfo): string {
|
|
|
187
188
|
` listening: ${info.url}`,
|
|
188
189
|
` discovery: ${info.url.startsWith("http") ? `${info.url}${DISCOVERY_PATH}` : info.url}`,
|
|
189
190
|
` auth: ${info.authRequired ? "bearer token required" : "no token (loopback only)"}`,
|
|
191
|
+
` rg: ${info.rg === "ok" ? "ok" : "missing (rg calls skip with a warning — install ripgrep on this door's PATH)"}`,
|
|
190
192
|
...(info.maxIdleSeconds !== undefined
|
|
191
193
|
? [` idle: exits after ${info.maxIdleSeconds}s without requests (--max-idle)`]
|
|
192
194
|
: []),
|
package/src/server.ts
CHANGED
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
type NavRequest,
|
|
30
30
|
} from "./capabilities/nav.ts";
|
|
31
31
|
import {
|
|
32
|
+
rgExecutableAvailable,
|
|
32
33
|
runRg,
|
|
33
34
|
projectRgEnvelope,
|
|
34
35
|
RG_DEFAULT_LIMIT,
|
|
@@ -150,6 +151,14 @@ export interface ServeInfo {
|
|
|
150
151
|
host: string;
|
|
151
152
|
port: number;
|
|
152
153
|
authRequired: boolean;
|
|
154
|
+
/** ripgrep availability probed once at startup IN THE DOOR'S OWN PROCESS
|
|
155
|
+
* (2026-09-20, liku feedback): /v1/rg serves every endpoint and a missing
|
|
156
|
+
* binary degrades per-endpoint on the wire by design — but that truth is
|
|
157
|
+
* consumer-channel only. The banner line is the operator channel, and the
|
|
158
|
+
* probe environment is by construction the one that matters (ukp diagnose
|
|
159
|
+
* measures the invoking shell instead, which can differ from the door's
|
|
160
|
+
* schtasks / wake environment). */
|
|
161
|
+
rg: "ok" | "missing";
|
|
153
162
|
/** TLS identity summary (W5' / D-079) when serving over HTTPS; the pin is
|
|
154
163
|
* what remote clients TOFU-pin at registration. `source` "re-signed" marks
|
|
155
164
|
* the one start that grew SAN coverage over the existing key (--tls-san
|
|
@@ -893,6 +902,7 @@ function startDoorServer(config: ServeConfig, host: string, tlsMaterial: ReturnT
|
|
|
893
902
|
host,
|
|
894
903
|
port: listener.port,
|
|
895
904
|
authRequired: (config.tokens?.length ?? 0) > 0,
|
|
905
|
+
rg: rgExecutableAvailable() ? "ok" : "missing",
|
|
896
906
|
...(tlsMaterial !== undefined
|
|
897
907
|
? { tls: { pin: tlsMaterial.pin, san: tlsMaterial.san, source: tlsMaterial.source } }
|
|
898
908
|
: {}),
|
|
@@ -1026,6 +1036,7 @@ export function startUkpServer(config: ServeConfig): StartedServe {
|
|
|
1026
1036
|
host,
|
|
1027
1037
|
port: listener.port,
|
|
1028
1038
|
authRequired: (config.tokens?.length ?? 0) > 0,
|
|
1039
|
+
rg: rgExecutableAvailable() ? "ok" : "missing",
|
|
1029
1040
|
...(tlsMaterial !== undefined
|
|
1030
1041
|
? { tls: { pin: tlsMaterial.pin, san: tlsMaterial.san, source: tlsMaterial.source } }
|
|
1031
1042
|
: {}),
|