@kitman-sh/cli 0.10.53 → 0.10.55
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/dist/{chunk-x9dhxfts.js → chunk-148mjppg.js} +5 -5
- package/dist/{chunk-pp1p3g6e.js → chunk-4kssrezn.js} +6 -1
- package/dist/{chunk-ctasxxz5.js → chunk-5j3cxsnp.js} +10 -10
- package/dist/{chunk-7thnf14y.js → chunk-5y5axze6.js} +609 -6
- package/dist/{chunk-mbctzx7e.js → chunk-61k67vxh.js} +76 -1389
- package/dist/{chunk-arvwar2c.js → chunk-7evwar6b.js} +1 -1
- package/dist/{chunk-mej2bx5n.js → chunk-99ver3za.js} +4 -4
- package/dist/{chunk-jagzz148.js → chunk-axdmbr61.js} +26 -26
- package/dist/{chunk-p6spvbwq.js → chunk-dgb6s5vq.js} +482 -100
- package/dist/{chunk-v290mf51.js → chunk-dj7pp4w4.js} +179 -35
- package/dist/{chunk-eg8f5ncv.js → chunk-dkpqwk0q.js} +7 -7
- package/dist/{chunk-r8p3nxcp.js → chunk-eerh2t20.js} +10 -10
- package/dist/{chunk-jafetf7z.js → chunk-g53wn1q8.js} +2 -2
- package/dist/chunk-g7vk5tjp.js +1109 -0
- package/dist/{chunk-vjmz23rs.js → chunk-gwcqz1bj.js} +1 -1
- package/dist/{chunk-wh1pa2aa.js → chunk-m32wb6t0.js} +245 -108
- package/dist/{chunk-3bj7sc3v.js → chunk-mmetyep8.js} +7 -7
- package/dist/{chunk-30397r95.js → chunk-mtgkvbk4.js} +4 -4
- package/dist/{chunk-dzh9e6n2.js → chunk-q0yna8sw.js} +52 -35
- package/dist/{chunk-6vns3ej0.js → chunk-rjfxnk05.js} +5 -5
- package/dist/{chunk-g5bs3f6z.js → chunk-twp7aprz.js} +4 -6
- package/dist/{chunk-x0b92bjb.js → chunk-vtn088tq.js} +6 -6
- package/dist/{chunk-b6thzgc9.js → chunk-wczs5vrq.js} +2 -2
- package/dist/{chunk-11p4wf6n.js → chunk-xgwjcq4z.js} +3 -3
- package/dist/egress-proxy.ts +138 -21
- package/dist/half-close-relay.ts +125 -0
- package/dist/index.js +4 -4
- package/dist/tcp-forwarder.ts +29 -0
- package/package.json +1 -1
- package/dist/chunk-1mc9vgcr.js +0 -39
- package/dist/chunk-jfvq7s24.js +0 -145
- package/dist/chunk-txwt6r25.js +0 -1731
- package/dist/chunk-vyv0p5eq.js +0 -3183
- package/dist/chunk-z821b2gp.js +0 -164
|
@@ -7,11 +7,11 @@ import {
|
|
|
7
7
|
decoded2,
|
|
8
8
|
readOpencode2,
|
|
9
9
|
runOpencodeReader2
|
|
10
|
-
} from "./chunk-
|
|
11
|
-
import"./chunk-
|
|
12
|
-
import"./chunk-
|
|
13
|
-
import"./chunk-
|
|
14
|
-
import"./chunk-
|
|
10
|
+
} from "./chunk-vtn088tq.js";
|
|
11
|
+
import"./chunk-mmetyep8.js";
|
|
12
|
+
import"./chunk-7evwar6b.js";
|
|
13
|
+
import"./chunk-xgwjcq4z.js";
|
|
14
|
+
import"./chunk-61k67vxh.js";
|
|
15
15
|
export {
|
|
16
16
|
decoded2 as decoded,
|
|
17
17
|
decodedOption2 as decodedOption,
|
|
@@ -24,11 +24,11 @@ import {
|
|
|
24
24
|
Record,
|
|
25
25
|
Int,
|
|
26
26
|
fromJsonString
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-61k67vxh.js";
|
|
28
28
|
import {
|
|
29
29
|
withDefault,
|
|
30
30
|
String2
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-xgwjcq4z.js";
|
|
32
32
|
import {
|
|
33
33
|
digestSha256,
|
|
34
34
|
digestSha512,
|
|
@@ -36,11 +36,9 @@ import {
|
|
|
36
36
|
schemaBodyJson,
|
|
37
37
|
filterStatusOk,
|
|
38
38
|
get2,
|
|
39
|
-
layer2
|
|
40
|
-
} from "./chunk-wh1pa2aa.js";
|
|
41
|
-
import {
|
|
39
|
+
layer2,
|
|
42
40
|
KitDigest
|
|
43
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-m32wb6t0.js";
|
|
44
42
|
|
|
45
43
|
// packages/core/src/npm-registry.ts
|
|
46
44
|
var KitManifest = Struct({
|
|
@@ -22,21 +22,21 @@ import {
|
|
|
22
22
|
NullOr,
|
|
23
23
|
Finite,
|
|
24
24
|
encodeUnknownJsonStringSync
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-61k67vxh.js";
|
|
26
26
|
import {
|
|
27
27
|
isNumber,
|
|
28
28
|
isJsonObject,
|
|
29
29
|
isString
|
|
30
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-7evwar6b.js";
|
|
31
31
|
import {
|
|
32
32
|
make11,
|
|
33
33
|
runForEach,
|
|
34
34
|
compiled2
|
|
35
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-xgwjcq4z.js";
|
|
36
36
|
import {
|
|
37
37
|
ChildProcessSpawner,
|
|
38
|
-
|
|
39
|
-
} from "./chunk-
|
|
38
|
+
make3
|
|
39
|
+
} from "./chunk-mmetyep8.js";
|
|
40
40
|
|
|
41
41
|
// packages/agent-runtime/src/opencode-store.ts
|
|
42
42
|
import { lstatSync, readFileSync, realpathSync } from "fs";
|
|
@@ -164,7 +164,7 @@ var opencodeReaderCommand = () => compiled2 ? [process.execPath, "opencode-read"
|
|
|
164
164
|
var isolatedRead = (store, name, input) => gen(function* () {
|
|
165
165
|
const spawner = yield* ChildProcessSpawner;
|
|
166
166
|
const [command, ...args] = opencodeReaderCommand();
|
|
167
|
-
const handle = yield* spawner.spawn(
|
|
167
|
+
const handle = yield* spawner.spawn(make3(command, args, {
|
|
168
168
|
stdin: make11(new TextEncoder().encode(encodeUnknownJsonStringSync({ store, name, input }))),
|
|
169
169
|
stderr: "ignore",
|
|
170
170
|
extendEnv: false,
|
|
@@ -13,8 +13,8 @@ import {
|
|
|
13
13
|
writeSrtSettings2,
|
|
14
14
|
srtStepArgv2,
|
|
15
15
|
srtArgv2
|
|
16
|
-
} from "./chunk-
|
|
17
|
-
import"./chunk-
|
|
16
|
+
} from "./chunk-xgwjcq4z.js";
|
|
17
|
+
import"./chunk-61k67vxh.js";
|
|
18
18
|
export {
|
|
19
19
|
compiled2 as compiled,
|
|
20
20
|
harnessesUnsupportedInSrt2 as harnessesUnsupportedInSrt,
|
|
@@ -195,11 +195,11 @@ import {
|
|
|
195
195
|
toEncoded,
|
|
196
196
|
decodeUnknownEffect,
|
|
197
197
|
SchemaError,
|
|
198
|
-
|
|
198
|
+
make17,
|
|
199
199
|
String4,
|
|
200
200
|
toCodecStringTree,
|
|
201
201
|
encodeUnknownJsonStringSync
|
|
202
|
-
} from "./chunk-
|
|
202
|
+
} from "./chunk-61k67vxh.js";
|
|
203
203
|
|
|
204
204
|
// node_modules/.bun/effect@4.1.2/node_modules/effect/dist/Iterable.js
|
|
205
205
|
var headUnsafe = (self) => {
|
|
@@ -3438,7 +3438,7 @@ var toConfigCursorAST = /* @__PURE__ */ memoize((root) => {
|
|
|
3438
3438
|
function schema(codec, path) {
|
|
3439
3439
|
const codecStringTree = toCodecStringTree(codec);
|
|
3440
3440
|
const encodedAst = toEncoded(codecStringTree.ast);
|
|
3441
|
-
const decodeCursor = decodeUnknownEffect(
|
|
3441
|
+
const decodeCursor = decodeUnknownEffect(make17(toConfigCursorAST(codecStringTree.ast)));
|
|
3442
3442
|
const localPath = typeof path === "string" ? [path] : path ?? [];
|
|
3443
3443
|
return make14((provider, pathPrefix) => {
|
|
3444
3444
|
const fullPath = [...pathPrefix, ...localPath];
|
package/dist/egress-proxy.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Runs alone inside the egress proxy container, so it imports nothing outside Node.
|
|
2
2
|
import { once } from 'node:events'
|
|
3
|
-
import { connect, createServer, type Server, type Socket } from 'node:net'
|
|
3
|
+
import { connect, createServer, type LookupFunction, type Server, type Socket } from 'node:net'
|
|
4
4
|
|
|
5
5
|
/** Whether the allowlist takes `host:port`: an entry `host` takes any port, `host:port` one, `*.domain` subdomains. */
|
|
6
6
|
export const egressAllowed = (hosts: readonly string[], host: string, port: number) => {
|
|
@@ -39,7 +39,93 @@ const httpTarget = (absolute: string) => {
|
|
|
39
39
|
|
|
40
40
|
const answer = (socket: Socket, status: string) => socket.end(`HTTP/1.1 ${status}\r\nContent-Length: 0\r\n\r\n`)
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
/** Takes the next bytes a client sends: how many of them belong to the request body, and whether the body has ended. */
|
|
43
|
+
type BodyFraming = (chunk: Buffer) => { readonly used: number; readonly done: boolean } | 'malformed'
|
|
44
|
+
|
|
45
|
+
const countedBody = (length: number): BodyFraming => {
|
|
46
|
+
let remaining = length
|
|
47
|
+
|
|
48
|
+
return chunk => {
|
|
49
|
+
const used = Math.min(remaining, chunk.length)
|
|
50
|
+
remaining -= used
|
|
51
|
+
|
|
52
|
+
return { used, done: remaining === 0 }
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const chunkedBody = (): BodyFraming => {
|
|
57
|
+
let state: 'size' | 'data' | 'trailer' | 'done' = 'size'
|
|
58
|
+
let line = ''
|
|
59
|
+
// The chunk's data and the CRLF after it.
|
|
60
|
+
let remaining = 0
|
|
61
|
+
|
|
62
|
+
return chunk => {
|
|
63
|
+
let index = 0
|
|
64
|
+
|
|
65
|
+
while (index < chunk.length && state !== 'done') {
|
|
66
|
+
if (state === 'data') {
|
|
67
|
+
const used = Math.min(remaining, chunk.length - index)
|
|
68
|
+
index += used
|
|
69
|
+
remaining -= used
|
|
70
|
+
|
|
71
|
+
if (remaining === 0) state = 'size'
|
|
72
|
+
|
|
73
|
+
continue
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const byte = chunk[index++]
|
|
77
|
+
|
|
78
|
+
if (byte !== 0x0a) {
|
|
79
|
+
line += String.fromCharCode(byte ?? 0)
|
|
80
|
+
|
|
81
|
+
if (line.length > 4_096) return 'malformed'
|
|
82
|
+
|
|
83
|
+
continue
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const content = line.trim()
|
|
87
|
+
line = ''
|
|
88
|
+
|
|
89
|
+
if (state === 'trailer') {
|
|
90
|
+
if (content === '') state = 'done'
|
|
91
|
+
continue
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const hex = content.split(';')[0]?.trim() ?? ''
|
|
95
|
+
|
|
96
|
+
if (!/^[0-9a-f]+$/i.test(hex)) return 'malformed'
|
|
97
|
+
|
|
98
|
+
const size = Number.parseInt(hex, 16)
|
|
99
|
+
|
|
100
|
+
if (size === 0) state = 'trailer'
|
|
101
|
+
else {
|
|
102
|
+
remaining = size + 2
|
|
103
|
+
state = 'data'
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return { used: index, done: state === 'done' }
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const requestBody = (headers: readonly string[]): BodyFraming | 'malformed' => {
|
|
112
|
+
const value = (name: string) =>
|
|
113
|
+
headers.flatMap(line => (line.toLowerCase().startsWith(`${name}:`) ? [line.slice(name.length + 1).trim()] : []))
|
|
114
|
+
|
|
115
|
+
const transferEncoding = value('transfer-encoding').join(',').toLowerCase()
|
|
116
|
+
|
|
117
|
+
if (transferEncoding !== '') return /(^|,)\s*chunked\s*$/.test(transferEncoding) ? chunkedBody() : 'malformed'
|
|
118
|
+
|
|
119
|
+
const lengths = [...new Set(value('content-length'))]
|
|
120
|
+
|
|
121
|
+
if (lengths.length === 0) return countedBody(0)
|
|
122
|
+
|
|
123
|
+
const [length = ''] = lengths
|
|
124
|
+
|
|
125
|
+
return lengths.length === 1 && /^\d+$/.test(length) ? countedBody(Number(length)) : 'malformed'
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const handle = (hosts: readonly string[], lookup: LookupFunction | undefined) => (client: Socket) => {
|
|
43
129
|
let head = Buffer.alloc(0)
|
|
44
130
|
|
|
45
131
|
const onData = (chunk: Buffer) => {
|
|
@@ -48,6 +134,8 @@ const handle = (hosts: readonly string[]) => (client: Socket) => {
|
|
|
48
134
|
|
|
49
135
|
if (end === -1) return head.length > 16_384 ? answer(client, '431 Request Header Fields Too Large') : undefined
|
|
50
136
|
client.off('data', onData)
|
|
137
|
+
// What a pipelining client sends next arrives while the proxy connects, and a flowing socket would drop it.
|
|
138
|
+
client.pause()
|
|
51
139
|
|
|
52
140
|
const [requestLine = '', ...headers] = head.subarray(0, end).toString().split('\r\n')
|
|
53
141
|
const [method = '', target = '', version = 'HTTP/1.1'] = requestLine.split(' ')
|
|
@@ -57,30 +145,57 @@ const handle = (hosts: readonly string[]) => (client: Socket) => {
|
|
|
57
145
|
|
|
58
146
|
if (!egressAllowed(hosts, destination.host, destination.port)) return answer(client, '403 Forbidden')
|
|
59
147
|
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
148
|
+
const framing = 'path' in destination ? requestBody(headers) : undefined
|
|
149
|
+
|
|
150
|
+
if (framing === 'malformed') return answer(client, '400 Bad Request')
|
|
151
|
+
|
|
152
|
+
const request = 'path' in destination && framing !== undefined ? { path: destination.path, framing } : undefined
|
|
153
|
+
|
|
154
|
+
const upstream = connect(
|
|
155
|
+
{ port: destination.port, host: destination.host, ...(lookup === undefined ? undefined : { lookup }) },
|
|
156
|
+
() => {
|
|
157
|
+
if (request === undefined) {
|
|
158
|
+
client.write('HTTP/1.1 200 Connection Established\r\n\r\n')
|
|
159
|
+
upstream.write(head.subarray(end + 4))
|
|
160
|
+
client.pipe(upstream)
|
|
161
|
+
upstream.pipe(client)
|
|
162
|
+
client.resume()
|
|
163
|
+
|
|
164
|
+
return
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// One request per connection, and the client's connection ends with its response: a reused one could
|
|
168
|
+
// carry a request for another host to this one, and a pipelined one would mismatch the answers.
|
|
66
169
|
const kept = headers.filter(line => !/^(proxy-)?connection:/i.test(line))
|
|
170
|
+
upstream.write([[method, request.path, version].join(' '), ...kept, 'Connection: close', '', ''].join('\r\n'))
|
|
67
171
|
|
|
68
|
-
|
|
69
|
-
Buffer.concat([
|
|
70
|
-
Buffer.from(
|
|
71
|
-
[[method, destination.path, version].join(' '), ...kept, 'Connection: close', '', ''].join('\r\n')
|
|
72
|
-
),
|
|
73
|
-
head.subarray(end + 4)
|
|
74
|
-
])
|
|
75
|
-
)
|
|
76
|
-
}
|
|
172
|
+
let bodyDone = false
|
|
77
173
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
174
|
+
const forward = (chunk: Buffer) => {
|
|
175
|
+
if (bodyDone) return
|
|
176
|
+
|
|
177
|
+
const framed = request.framing(chunk)
|
|
178
|
+
|
|
179
|
+
if (framed === 'malformed') return client.destroy()
|
|
180
|
+
|
|
181
|
+
bodyDone = framed.done
|
|
182
|
+
|
|
183
|
+
if (upstream.write(chunk.subarray(0, framed.used))) return
|
|
184
|
+
client.pause()
|
|
185
|
+
upstream.once('drain', () => client.resume())
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Pipelined requests after the body are dropped; the client sends them again on a new connection.
|
|
189
|
+
client.on('data', forward)
|
|
190
|
+
upstream.pipe(client)
|
|
191
|
+
client.resume()
|
|
192
|
+
forward(head.subarray(end + 4))
|
|
193
|
+
}
|
|
194
|
+
)
|
|
81
195
|
|
|
82
196
|
upstream.on('error', () => (client.writable ? answer(client, '502 Bad Gateway') : client.destroy()))
|
|
83
197
|
client.on('error', () => upstream.destroy())
|
|
198
|
+
client.on('close', () => upstream.destroy())
|
|
84
199
|
}
|
|
85
200
|
|
|
86
201
|
client.on('data', onData)
|
|
@@ -92,8 +207,10 @@ export const startEgressProxy = (input: {
|
|
|
92
207
|
readonly hosts: readonly string[]
|
|
93
208
|
readonly port: number
|
|
94
209
|
readonly hostname: string
|
|
210
|
+
/** How it resolves a host, if not as the system does. */
|
|
211
|
+
readonly lookup?: LookupFunction
|
|
95
212
|
}): Promise<Server> => {
|
|
96
|
-
const server = createServer(handle(input.hosts)).listen(input.port, input.hostname)
|
|
213
|
+
const server = createServer(handle(input.hosts, input.lookup)).listen(input.port, input.hostname)
|
|
97
214
|
|
|
98
215
|
return once(server, 'listening').then(() => server)
|
|
99
216
|
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// Runs alone inside a pipeline check's sandbox too, so it imports nothing outside Node.
|
|
2
|
+
import { once } from 'node:events'
|
|
3
|
+
import { connect, createServer, type Server, type Socket } from 'node:net'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* srt's proxy closes a connection once its client stops sending, and a Docker client attached to a job stops before
|
|
7
|
+
* the job prints anything. So the encoder inside the sandbox never stops sending: it sends what its client sends as
|
|
8
|
+
* frames, each a 4-byte big-endian length and that many bytes, and the client's end as a frame of length 0. The
|
|
9
|
+
* decoder outside the sandbox turns that frame back into the end of what it sends on.
|
|
10
|
+
*/
|
|
11
|
+
const frameHeader = 4
|
|
12
|
+
|
|
13
|
+
const frame = (chunk: Buffer) => {
|
|
14
|
+
const header = Buffer.alloc(frameHeader)
|
|
15
|
+
|
|
16
|
+
header.writeUInt32BE(chunk.length)
|
|
17
|
+
|
|
18
|
+
return Buffer.concat([header, chunk])
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const endOfInput = Buffer.alloc(frameHeader)
|
|
22
|
+
|
|
23
|
+
/** Far more than the one socket chunk the encoder frames; a longer frame is not the encoder's. */
|
|
24
|
+
export const maxFrame = 1024 * 1024
|
|
25
|
+
|
|
26
|
+
/** Holds `from` until `to` has caught up, when a write to `to` was `accepted` only into its buffer. */
|
|
27
|
+
const keepUp = (from: Socket, to: Socket, accepted: boolean) => {
|
|
28
|
+
if (accepted) return
|
|
29
|
+
from.pause()
|
|
30
|
+
to.once('drain', () => from.resume())
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Ends each of `sockets` once another has closed, after what it holds is sent, and destroys them all if one fails. */
|
|
34
|
+
const closeTogether = (...sockets: readonly Socket[]) =>
|
|
35
|
+
sockets.forEach(socket => {
|
|
36
|
+
socket.on('error', () => sockets.forEach(other => other.destroy()))
|
|
37
|
+
socket.on('close', () => sockets.forEach(other => other.end()))
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
const listening = (server: Server, port: number) =>
|
|
41
|
+
once(server.listen(port, '127.0.0.1'), 'listening').then(() => server)
|
|
42
|
+
|
|
43
|
+
/** Serves `port` outside the sandbox, sending each connection's frames on to `127.0.0.1:<targetPort>`. */
|
|
44
|
+
export const startHalfCloseDecoder = (input: { readonly port: number; readonly targetPort: number }) =>
|
|
45
|
+
listening(
|
|
46
|
+
createServer({ allowHalfOpen: true }, client => {
|
|
47
|
+
const upstream = connect({ port: input.targetPort, host: '127.0.0.1', allowHalfOpen: true })
|
|
48
|
+
let pending: Buffer = Buffer.alloc(0)
|
|
49
|
+
|
|
50
|
+
client.on('data', (chunk: Buffer) => {
|
|
51
|
+
pending = Buffer.concat([pending, chunk])
|
|
52
|
+
|
|
53
|
+
if (pending.length >= frameHeader && pending.readUInt32BE(0) > maxFrame) return void client.destroy()
|
|
54
|
+
|
|
55
|
+
while (pending.length >= frameHeader && pending.length >= frameHeader + pending.readUInt32BE(0)) {
|
|
56
|
+
const length = pending.readUInt32BE(0)
|
|
57
|
+
|
|
58
|
+
if (length === 0) upstream.end()
|
|
59
|
+
else keepUp(client, upstream, upstream.write(pending.subarray(frameHeader, frameHeader + length)))
|
|
60
|
+
pending = pending.subarray(frameHeader + length)
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
client.on('end', () => upstream.end())
|
|
64
|
+
upstream.pipe(client)
|
|
65
|
+
closeTogether(client, upstream)
|
|
66
|
+
}),
|
|
67
|
+
input.port
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
/** `host` as `net.connect` takes it: a URL writes an IPv6 address in brackets. */
|
|
71
|
+
const unbracketed = (host: string) => host.replace(/^\[(.*)\]$/, '$1')
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Serves `port` inside the sandbox, sending each connection through `proxy` to the decoder on the Runner's
|
|
75
|
+
* `127.0.0.1:<targetPort>`, as frames.
|
|
76
|
+
*/
|
|
77
|
+
export const startHalfCloseEncoder = (input: {
|
|
78
|
+
readonly port: number
|
|
79
|
+
readonly proxy: string
|
|
80
|
+
readonly targetPort: number
|
|
81
|
+
}) => {
|
|
82
|
+
const proxy = new URL(input.proxy)
|
|
83
|
+
const target = `127.0.0.1:${input.targetPort}`
|
|
84
|
+
|
|
85
|
+
const credential =
|
|
86
|
+
proxy.username === ''
|
|
87
|
+
? ''
|
|
88
|
+
: `Proxy-Authorization: Basic ${Buffer.from(`${decodeURIComponent(proxy.username)}:${decodeURIComponent(proxy.password)}`).toString('base64')}\r\n`
|
|
89
|
+
|
|
90
|
+
return listening(
|
|
91
|
+
createServer({ allowHalfOpen: true }, client => {
|
|
92
|
+
client.pause()
|
|
93
|
+
const upstream = connect({ port: Number(proxy.port === '' ? 80 : proxy.port), host: unbracketed(proxy.hostname) })
|
|
94
|
+
let head = Buffer.alloc(0)
|
|
95
|
+
|
|
96
|
+
const onHead = (chunk: Buffer) => {
|
|
97
|
+
head = Buffer.concat([head, chunk])
|
|
98
|
+
const end = head.indexOf('\r\n\r\n')
|
|
99
|
+
|
|
100
|
+
if (end === -1) return
|
|
101
|
+
upstream.off('data', onHead)
|
|
102
|
+
|
|
103
|
+
if (!/^HTTP\/1\.[01] 200/.test(head.toString('latin1', 0, end))) return void upstream.destroy()
|
|
104
|
+
client.write(head.subarray(end + 4))
|
|
105
|
+
upstream.pipe(client)
|
|
106
|
+
client.on('data', (chunk: Buffer) => keepUp(client, upstream, upstream.write(frame(chunk))))
|
|
107
|
+
client.on('end', () => upstream.write(endOfInput))
|
|
108
|
+
client.resume()
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
upstream.on('data', onHead)
|
|
112
|
+
upstream.write(`CONNECT ${target} HTTP/1.1\r\nHost: ${target}\r\n${credential}\r\n`)
|
|
113
|
+
closeTogether(client, upstream)
|
|
114
|
+
}),
|
|
115
|
+
input.port
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// `bun half-close-relay.ts <port> <proxy URL> <decoder port>`, inside srt.
|
|
120
|
+
if (import.meta.main)
|
|
121
|
+
await startHalfCloseEncoder({
|
|
122
|
+
port: Number(Bun.argv[2]),
|
|
123
|
+
proxy: Bun.argv[3] ?? '',
|
|
124
|
+
targetPort: Number(Bun.argv[4])
|
|
125
|
+
})
|
package/dist/index.js
CHANGED
|
@@ -5,15 +5,15 @@
|
|
|
5
5
|
var args = process.argv.slice(2);
|
|
6
6
|
if (args[0] === "srt") {
|
|
7
7
|
process.argv.splice(2, 1);
|
|
8
|
-
const { runSrtCli } = await import("./chunk-
|
|
8
|
+
const { runSrtCli } = await import("./chunk-wczs5vrq.js");
|
|
9
9
|
await runSrtCli();
|
|
10
10
|
} else if (args[0] === "srt-step") {
|
|
11
|
-
const { runSrtStep } = await import("./chunk-
|
|
11
|
+
const { runSrtStep } = await import("./chunk-99ver3za.js");
|
|
12
12
|
await runSrtStep(args.slice(1));
|
|
13
13
|
} else if (args[0] === "opencode-read") {
|
|
14
|
-
const { runOpencodeReader } = await import("./chunk-
|
|
14
|
+
const { runOpencodeReader } = await import("./chunk-rjfxnk05.js");
|
|
15
15
|
runOpencodeReader();
|
|
16
16
|
} else {
|
|
17
|
-
const { runCli } = await import("./chunk-
|
|
17
|
+
const { runCli } = await import("./chunk-dj7pp4w4.js");
|
|
18
18
|
runCli(args);
|
|
19
19
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Runs alone inside a check Docker daemon's API forwarder container, so it imports nothing outside Node.
|
|
2
|
+
import { once } from 'node:events'
|
|
3
|
+
import { connect, createServer, type Server } from 'node:net'
|
|
4
|
+
|
|
5
|
+
/** Forwards each connection to `port` on to `target:targetPort`, and only there. */
|
|
6
|
+
export const startTcpForwarder = (input: {
|
|
7
|
+
readonly port: number
|
|
8
|
+
readonly target: string
|
|
9
|
+
readonly targetPort: number
|
|
10
|
+
}): Promise<Server> => {
|
|
11
|
+
// A Docker client attached to a container stops sending long before the container's output ends.
|
|
12
|
+
const server = createServer({ allowHalfOpen: true }, client => {
|
|
13
|
+
const upstream = connect({ port: input.targetPort, host: input.target, allowHalfOpen: true })
|
|
14
|
+
|
|
15
|
+
client.pipe(upstream)
|
|
16
|
+
upstream.pipe(client)
|
|
17
|
+
client.on('error', () => upstream.destroy())
|
|
18
|
+
upstream.on('error', () => client.destroy())
|
|
19
|
+
}).listen(input.port, '0.0.0.0')
|
|
20
|
+
|
|
21
|
+
return once(server, 'listening').then(() => server)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (import.meta.main)
|
|
25
|
+
await startTcpForwarder({
|
|
26
|
+
port: Number(Bun.argv[2]),
|
|
27
|
+
target: Bun.argv[3] ?? '',
|
|
28
|
+
targetPort: Number(Bun.argv[4])
|
|
29
|
+
})
|
package/package.json
CHANGED
package/dist/chunk-1mc9vgcr.js
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
import {
|
|
3
|
-
isString
|
|
4
|
-
} from "./chunk-arvwar2c.js";
|
|
5
|
-
|
|
6
|
-
// packages/core/src/question-recommendation.ts
|
|
7
|
-
var optionValue = (option) => isString(option) ? option : option.value;
|
|
8
|
-
var splitQuestionOptions = (options) => {
|
|
9
|
-
const descriptions = Object.fromEntries(options.flatMap((option) => isString(option) || option.description === undefined || option.description.trim() === "" ? [] : [[option.value, option.description]]));
|
|
10
|
-
return {
|
|
11
|
-
options: options.map(optionValue),
|
|
12
|
-
...Object.keys(descriptions).length === 0 ? undefined : { optionDescriptions: descriptions }
|
|
13
|
-
};
|
|
14
|
-
};
|
|
15
|
-
var recommendationProblem = (question) => {
|
|
16
|
-
const options = question.options?.map(optionValue);
|
|
17
|
-
const reason = question.recommendationReason?.trim() ?? "";
|
|
18
|
-
const recommended = question.recommended?.trim() ?? "";
|
|
19
|
-
if (question.informationOnly === true) {
|
|
20
|
-
if (options !== undefined)
|
|
21
|
-
return "A Question with options always has one to recommend: set `recommended` to one of them, and drop `informationOnly`.";
|
|
22
|
-
if (recommended !== "")
|
|
23
|
-
return "A Question that recommends an answer is not `informationOnly`: drop `informationOnly`.";
|
|
24
|
-
return reason === "" ? "Say in `recommendationReason` why you cannot suggest an answer, such as that only the person knows the secret\u2019s name." : undefined;
|
|
25
|
-
}
|
|
26
|
-
if (recommended === "")
|
|
27
|
-
return `Add \`recommended\`: the answer you would pick${options === undefined ? ", as a suggested free-text answer" : `, one of: ${options.join(", ")}`}. Add \`recommendationReason\` too, in one or two sentences. Only a Question that purely asks for information you cannot guess, such as a secret's name, may set \`informationOnly\` instead and say why in \`recommendationReason\`.`;
|
|
28
|
-
if (options !== undefined && !options.includes(question.recommended ?? ""))
|
|
29
|
-
return `The recommended answer "${question.recommended}" is not one of the options: ${options.join(", ")}.`;
|
|
30
|
-
return reason === "" ? "Add `recommendationReason`: why you recommend that answer, in one or two sentences." : undefined;
|
|
31
|
-
};
|
|
32
|
-
var questionAdvice = (question) => ({
|
|
33
|
-
...question.optionDescriptions === undefined ? undefined : { optionDescriptions: question.optionDescriptions },
|
|
34
|
-
...question.recommended === undefined ? undefined : { recommended: question.recommended },
|
|
35
|
-
...question.recommendationReason === undefined ? undefined : { recommendationReason: question.recommendationReason },
|
|
36
|
-
...question.informationOnly === true ? { informationOnly: true } : undefined
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
export { splitQuestionOptions, recommendationProblem, questionAdvice };
|