buninu 0.4.0 → 0.4.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.
@@ -0,0 +1,181 @@
1
+ ## curl
2
+
3
+ Transfer a URL over HTTP or HTTPS, built on Bun's own `fetch`.
4
+
5
+ This is a **fallback builtin**: a real `curl` found on `PATH` always wins, so
6
+ scripts keep the system binary where there is one and still run on a device
7
+ that has none. `builtin curl` selects this implementation explicitly.
8
+
9
+ ### Usage
10
+
11
+ ```sh
12
+ curl [OPTIONS] URL...
13
+ ```
14
+
15
+ A URL with no scheme gets one: `http://` by default, `https://` when the port
16
+ is `443`, or whatever `--proto-default` says. So `curl localhost:8080` and
17
+ `curl example.com/page` work the way they do with the real curl. Only `http`
18
+ and `https` are supported; anything else fails with exit code `1`.
19
+
20
+ ### Options and forms
21
+
22
+ #### Output and files
23
+
24
+ - `-o, --output FILE`: Write the body to `FILE` instead of stdout. `-o -`
25
+ means stdout. Repeat it to pair one target with each URL, in order.
26
+ - `-O, --remote-name`: Save under the filename from the URL path.
27
+ - `--remote-name-all`: Treat every URL as if it had its own `-O`.
28
+ - `-J, --remote-header-name`: With `-O`, prefer the name in the
29
+ `Content-Disposition` header.
30
+ - `--output-dir DIR`: Write `-o`/`-O` files under `DIR`.
31
+ - `--create-dirs`: Create missing parent directories for the output file.
32
+ - `-a, --append`: Append to the output file instead of truncating it.
33
+ - `-C, --continue-at OFFSET|-`: Resume a partial download. `-` sizes the
34
+ existing file and asks for the rest with a `Range` header; the file is
35
+ appended to when the server answers `206`, and rewritten when it answers
36
+ `200` and ignored the range. A `416` answer means the local file is already
37
+ complete: nothing is written and the exit status is `0`.
38
+ - `-D, --dump-header FILE`: Write the response headers to `FILE` (`-` for
39
+ stdout).
40
+ - `-i, --include`: Print the response headers before the body. With `-L`,
41
+ every hop's headers are printed, as the real curl does.
42
+ - `-I, --head`: Send `HEAD` and print the response headers.
43
+ - `-w, --write-out FORMAT`: Print `FORMAT` after the transfer, with
44
+ `%{variable}` substitutions and `\n`, `\t`, `\r` escapes. `@FILE` (or `@-`)
45
+ reads the format from a file. Supported variables: `url`, `url_effective`,
46
+ `method`, `scheme`, `http_code`, `response_code`, `http_version`,
47
+ `num_redirects`, `num_headers`, `redirect_url`, `size_download`,
48
+ `size_upload`, `size_header`, `size_request`, `speed_download`,
49
+ `speed_upload`, `content_type`, `filename_effective`, `remote_ip`,
50
+ `remote_port`, `ssl_verify_result`, `exitcode`, `errormsg`, `time_total`,
51
+ `time_namelookup`, `time_connect`, `time_appconnect`, `time_pretransfer`,
52
+ `time_starttransfer`, `time_redirect`, `header_json`, `json`, `stdout`,
53
+ `stderr`.
54
+
55
+ #### Request shaping
56
+
57
+ - `-X, --request METHOD`: Set the request method.
58
+ - `-H, --header 'Name: value'`: Add or replace a header. `'Name;'` sends an
59
+ empty value; `'Name:'` removes a header that would otherwise be sent.
60
+ - `-d, --data DATA`, `--data-ascii DATA`: `POST` body; `@file` reads a file
61
+ (newlines stripped) and `@-` reads stdin. Repeats join with `&`.
62
+ - `--data-raw DATA`: Same, but `@` has no special meaning.
63
+ - `--data-binary DATA`: Same, but a `@file` is sent byte for byte.
64
+ - `--data-urlencode DATA`: Percent-encode before sending. Accepts `content`,
65
+ `=content`, `name=content`, `@file`, and `name@file`.
66
+ - `--json DATA`: `--data-binary` plus `Content-Type: application/json` and
67
+ `Accept: application/json`. `@file` and `@-` work here too.
68
+ - `-G, --get`: Move the data onto the query string and send `GET`.
69
+ - `-F, --form 'name=value'`, `--form-string`: `multipart/form-data`. `@file`
70
+ attaches a file, `<file` sends its contents as the field value, and
71
+ `;type=` / `;filename=` set the part's type and name.
72
+ - `-T, --upload-file FILE`: `PUT` the file (`-` reads stdin).
73
+ - `-u, --user USER[:PASSWORD]`: HTTP basic authentication.
74
+ - `--oauth2-bearer TOKEN`: Send `Authorization: Bearer TOKEN`.
75
+ - `-A, --user-agent NAME`: Override the default `curl/8.14.1`.
76
+ - `-e, --referer URL`: Send a `Referer` header; a trailing `;auto` is ignored.
77
+ - `-b, --cookie 'k=v'`: Send a `Cookie` header.
78
+ - `-r, --range RANGE`: Send `Range: bytes=RANGE`.
79
+ - `--compressed`: Ask for and transparently decode a compressed response.
80
+ Without it, no `Accept-Encoding` is negotiated, matching curl's default.
81
+ - `-x, --proxy URL`: Send the request through a proxy.
82
+
83
+ #### Redirects, failure, and timing
84
+
85
+ - `-L, --location`: Follow redirects. `301`/`302`/`303` turn a `POST` into a
86
+ `GET`; `307`/`308` keep the method and body. `Authorization` is dropped when
87
+ the host changes, unless `--location-trusted` is given.
88
+ - `--max-redirs N`: Cap the redirect chain (default 50, `-1` for unlimited).
89
+ Exceeding it fails with exit code `47`.
90
+ - `-f, --fail`: No body output on HTTP errors; exit `22` instead.
91
+ - `--fail-with-body`: Print the body and still exit `22`.
92
+ - `-k, --insecure`: Do not verify the server certificate.
93
+ - `-m, --max-time SECONDS`, `--connect-timeout SECONDS`: Abort a slow
94
+ transfer with exit code `28`.
95
+ - `--retry N`, `--retry-delay SECONDS`, `--retry-all-errors`,
96
+ `--retry-connrefused`: Retry connection failures, timeouts, and the
97
+ transient statuses `408`, `429`, `500`, `502`, `503`, and `504`.
98
+
99
+ #### Reporting
100
+
101
+ - `-s, --silent`: No progress meter and no error messages.
102
+ - `-S, --show-error`: Keep error messages while `-s` is in effect.
103
+ - `-#, --progress-bar`: A single-bar progress display instead of the meter.
104
+ - `--no-progress-meter`: Keep errors, drop the meter.
105
+ - `-v, --verbose`: Trace the request and response headers on stderr.
106
+ - `-V, --version`: Print the version banner.
107
+
108
+ Like the real curl, the progress meter is shown only when the body is *not*
109
+ being painted on the terminal — a `-o`/`-O` download or a redirected stdout
110
+ gets a meter, `curl URL` at a prompt does not.
111
+
112
+ #### Accepted and ignored
113
+
114
+ These are parsed so a script does not die on them, and then have no effect,
115
+ because `fetch` either handles that part itself or does not expose it:
116
+ `--cacert`, `--capath`, `-E/--cert`, `--key`, `--interface`, `--limit-rate`,
117
+ `--resolve`, `--connect-to`, `--unix-socket`, `--noproxy`, `--proxy-user`,
118
+ `--proxy-header`, `-n/--netrc`, `-c/--cookie-jar`, `-z/--time-cond`,
119
+ `-R/--remote-time`, `-N/--no-buffer` (output is always streamed anyway),
120
+ `-g/--globoff` (URL globbing is not performed, so `{}` and `[]` are already
121
+ literal), `-q/--disable`, `-4`, `-6`, `--http1.0`, `--http1.1`, `--http2`,
122
+ `--http3`, `--tlsv1.x`, `--tcp-nodelay`, `--keepalive`, `--anyauth`,
123
+ `--basic`, `--digest`, `-Z/--parallel`, `--max-filesize`, `--path-as-is`.
124
+
125
+ ### Where it differs from the system curl
126
+
127
+ - `fetch` reports header names in lower case, so `-i`, `-I`, `-D`, and `-v`
128
+ rebuild the conventional capitalisation (`Content-Type`, `ETag`) and list
129
+ the headers in `fetch`'s order rather than the server's wire order.
130
+ - The negotiated HTTP version is not exposed, so status lines are written as
131
+ `HTTP/1.1`.
132
+ - Bun adds a `Connection: keep-alive` request header of its own.
133
+ - The progress meter is a look-alike: same columns, not the same arithmetic.
134
+ - URL globbing (`{a,b}`, `[1-10]`) is not implemented.
135
+ - `fetch` forbids a body on `GET` and `HEAD`, so `-X GET -d ...` sends the
136
+ request without the data; use `-G` to put it on the query string instead.
137
+
138
+ ### Exit status
139
+
140
+ `0` on success, `1` unsupported protocol, `2` bad usage, `3` malformed URL,
141
+ `6` host not resolved, `7` connection failed, `22` HTTP error with `-f`,
142
+ `23` write error, `26` local file could not be read, `28` timeout,
143
+ `47` too many redirects, `56` connection reset, `60` certificate problem.
144
+
145
+ ### Example
146
+
147
+ ```sh
148
+ curl -s -o /dev/null -w '%{http_code} %{content_type}\n' example.com
149
+ ```
150
+
151
+ Output:
152
+
153
+ ```text
154
+ 200 text/html
155
+ ```
156
+
157
+ The invocations a package script tends to use:
158
+
159
+ ```sh
160
+ curl -kLO https://example.com/pkg.tar.gz
161
+ curl -C - -kLO https://example.com/pkg.tar.gz
162
+ curl -fsSL https://example.com/install.sh | sh
163
+ curl -#k https://example.com/pkg.tar.gz.sha256
164
+ curl -kfsS https://example.com/probe >/dev/null 2>/dev/null
165
+ curl localhost:8080
166
+ ```
167
+
168
+ Calling a JSON API — enough for the OpenAI chat completions endpoint:
169
+
170
+ ```sh
171
+ curl -sS https://api.openai.com/v1/chat/completions \
172
+ -H "Authorization: Bearer $OPENAI_API_KEY" \
173
+ -H 'Content-Type: application/json' \
174
+ -d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"hi"}]}'
175
+ ```
176
+
177
+ `--json` shortens that, and `-N` streaming responses arrive chunk by chunk:
178
+
179
+ ```sh
180
+ curl -sN --json @request.json https://api.openai.com/v1/chat/completions
181
+ ```
@@ -12,6 +12,22 @@ kill [-SIGNAL] PID ...
12
12
 
13
13
  - `-SIGNAL`: Select a signal by name or number; the default is SIGTERM.
14
14
  - `-0`: Check whether a process can be signalled without sending a signal.
15
+ - `-l`: List the signal names this runtime knows.
16
+
17
+ ### Windows
18
+
19
+ Windows has no signals to deliver. The runtime turns SIGTERM, SIGINT, and
20
+ SIGKILL into an unconditional `TerminateProcess`, refuses every other name,
21
+ and in no case touches the target's children. So on Windows every terminating
22
+ signal is sent as `taskkill /PID PID /T /F` instead: `/T` reaches the children
23
+ the runtime would leave running, and `/F` is what makes it work on a console
24
+ process that has no message loop to close.
25
+
26
+ The consequence is that the signal name is accepted but not honoured there —
27
+ `kill -HUP PID` terminates the process rather than failing, and nothing gets
28
+ the chance to shut down gracefully. `-0` is the exception: it asks whether the
29
+ PID exists without touching it, which `taskkill` cannot express, so it still
30
+ goes through the runtime.
15
31
 
16
32
  ### Example
17
33
 
@@ -0,0 +1,38 @@
1
+ ## pspa
2
+
3
+ List every process as a PID and its full command line.
4
+
5
+ ### Usage
6
+
7
+ ```sh
8
+ pspa
9
+ ```
10
+
11
+ It is a PATH-fallback builtin, so an executable named `pspa` found in `PATH`
12
+ wins; `builtin pspa` selects this implementation. It takes no options — pipe
13
+ it into `grep` to narrow the listing, and into `kill` to act on what you find.
14
+
15
+ [`pspac`](#pspac) prints the same listing with the PID and the command line
16
+ coloured.
17
+
18
+ On POSIX systems this is `ps -eo pid,args`, and its output is passed through
19
+ untouched. Reading `ps` over a pipe is also what keeps the command lines
20
+ whole: procps truncates them at the terminal width when it is writing straight
21
+ to a terminal.
22
+
23
+ Windows has no `ps`, so the same two columns are queried from
24
+ `Win32_Process` through PowerShell and laid out the same way, with the image
25
+ name standing in for a system process that reports no command line.
26
+
27
+ ### Example
28
+
29
+ ```sh
30
+ pspa | grep bun
31
+ ```
32
+
33
+ Output:
34
+
35
+ ```text
36
+ 1004 bun ./src/main.js
37
+ 1220 bun run dev
38
+ ```
@@ -0,0 +1,57 @@
1
+ ## pspac
2
+
3
+ The `pspa` process listing, coloured.
4
+
5
+ ### Usage
6
+
7
+ ```sh
8
+ pspac
9
+ ```
10
+
11
+ It is a PATH-fallback builtin taking no options, and it lists exactly what
12
+ [`pspa`](#pspa) lists — same processes, same two columns, same order. Strip
13
+ the colour off its output and you have `pspa`'s byte for byte.
14
+
15
+ Like `catfancy`, it always colours rather than sniffing for a terminal, so the
16
+ plain listing is always one command away: use `pspa` when the output is going
17
+ somewhere that wants plain text.
18
+
19
+ ### What gets coloured
20
+
21
+ The PID is painted as the number it is. The COMMAND column is a shell command
22
+ line, so it is highlighted as one, using micro's `syntax/sh.yaml` rules and
23
+ its `colorschemes/monokai.micro` colours — the same palette `catfancy` uses,
24
+ so a command line reads the same here as in a previewed script:
25
+
26
+ - Command names, shell keywords, flags, numbers, variables, quoted strings,
27
+ and comments each take their own colour.
28
+ - Quoted strings and comments are regions, so a `#` inside an argument does
29
+ not turn the rest of the line into a comment.
30
+ - Later rules win, exactly as in the editor: `--cat` is a flag, not the
31
+ coreutils `cat`.
32
+
33
+ Two things a process listing needs that a script does not:
34
+
35
+ - The directory in front of the program is dimmed. It is the part an absolute
36
+ path buries the answer in, and `ps` prints a lot of it.
37
+ - The program's own name is coloured as a command whether or not sh.yaml's
38
+ word lists have heard of it — the file has to guess a command from a list,
39
+ while here the first token is known to be one.
40
+
41
+ A kernel thread such as `[kworker/0:1]` has no program, path, or arguments to
42
+ take apart, so it recedes as a whole.
43
+
44
+ ### Example
45
+
46
+ ```sh
47
+ pspac
48
+ ```
49
+
50
+ Output:
51
+
52
+ ```text
53
+ PID COMMAND
54
+ 1 /init
55
+ 8824 /data/data/com.termux/files/usr/bin/bash -l
56
+ 8955 bun run dev
57
+ ```
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bunmsh",
3
- "version": "0.2.0",
3
+ "version": "0.3.3",
4
4
  "description": "Cross-platform command shell for Bun JavaScript",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -10,7 +10,8 @@
10
10
  },
11
11
  "scripts": {
12
12
  "start": "bun ./src/main.js",
13
- "test": "bun test"
13
+ "test": "bun test",
14
+ "genallhelp": "bun ./src/main.js -c 'builtin cat --exclude help/README.md help/*.md' > help/README.md"
14
15
  },
15
16
  "keywords": [
16
17
  "bun",
@@ -21,6 +22,9 @@
21
22
  "assets": [
22
23
  "README.md",
23
24
  "CHANGELOG.md",
25
+ "LICENSE",
26
+ "LICENSE-MKSH",
27
+ "LICENSE-MICRO",
24
28
  "help"
25
29
  ]
26
30
  }
@@ -75,6 +75,21 @@ export function completionContext(line, cursor = line.length) {
75
75
  quote = ch;
76
76
  continue;
77
77
  }
78
+ // `$(` opens a nested command list, so what follows it is a command
79
+ // name again rather than a continuation of the word being typed.
80
+ if (ch === "$" && source[i + 1] === "(") {
81
+ finishWord();
82
+ command = true;
83
+ token = "";
84
+ i++;
85
+ wordStart = i + 1;
86
+ continue;
87
+ }
88
+ if (ch === ")") {
89
+ finishWord();
90
+ wordStart = i + 1;
91
+ continue;
92
+ }
78
93
  if (ch === ";" || ch === "|" || ch === "&" || ch === "\n") {
79
94
  finishWord();
80
95
  command = true;
@@ -96,9 +111,38 @@ export function completionContext(line, cursor = line.length) {
96
111
  command: command && !assignment && quote === null,
97
112
  prefix: token,
98
113
  start: wordStart,
114
+ quote,
99
115
  };
100
116
  }
101
117
 
118
+ // A `$` with a name being typed after it: `$NAME`, `${NAME`, `${#NAME`.
119
+ // Everything else a `$` can begin is left alone, because none of it is a
120
+ // name in progress: `$(` opens a command substitution, `$?`/`$1`/`$$` are
121
+ // already complete, and a backslash-escaped `\$` is a literal dollar sign.
122
+ export function variableContext(line, cursor = line.length) {
123
+ const source = line.slice(0, cursor);
124
+ const match = /\$(\{#?)?([A-Za-z_][A-Za-z0-9_]*)?$/.exec(source);
125
+ if (!match) return null;
126
+ if (source[match.index - 1] === "$") return null;
127
+ let backslashes = 0;
128
+ for (let i = match.index - 1; i >= 0 && source[i] === "\\"; i--) backslashes++;
129
+ if (backslashes % 2) return null;
130
+ const name = match[2] ?? "";
131
+ return {
132
+ start: match.index,
133
+ text: match[0],
134
+ lead: match[0].slice(0, match[0].length - name.length),
135
+ brace: Boolean(match[1]),
136
+ prefix: name,
137
+ };
138
+ }
139
+
140
+ // `${NAME` completes to `${NAME}`: the brace it opened is part of the name
141
+ // being written, so finishing the name finishes the brace too.
142
+ export function variableCompletion(context, name) {
143
+ return `${context.lead}${name}${context.brace ? "}" : ""}`;
144
+ }
145
+
102
146
  export function historyGhost(history, line) {
103
147
  if (!line) return null;
104
148
  for (let i = history.length - 1; i >= 0; i--) {
@@ -199,6 +243,78 @@ export class CommandIndex {
199
243
  }
200
244
  }
201
245
 
246
+ // A JavaScript line is not shell text, so the shell's own word-splitting says
247
+ // nothing useful about it: `Bun.file("/tm` is one long word to a tokenizer
248
+ // that only breaks on whitespace. This reads it as JavaScript instead, and
249
+ // answers the two questions worth asking there — is the cursor inside a
250
+ // string literal, where a path is being typed, or on a `$.` property, where
251
+ // a shell variable name is.
252
+ //
253
+ // Strings are tracked with a stack so that an interpolation is code again:
254
+ // inside `\`${...}\`` a `$.` completes, and the template only resumes after
255
+ // the closing brace.
256
+ export function javascriptContext(line, cursor = line.length) {
257
+ const source = line.slice(0, cursor);
258
+ const stack = [];
259
+ for (let i = 0; i < source.length; i++) {
260
+ const ch = source[i];
261
+ const top = stack.at(-1);
262
+ if (top?.kind === "string") {
263
+ if (ch === "\\") { i++; continue; }
264
+ if (top.quote === "`" && ch === "$" && source[i + 1] === "{") {
265
+ stack.push({ kind: "code" });
266
+ i++;
267
+ continue;
268
+ }
269
+ if (ch === top.quote) stack.pop();
270
+ continue;
271
+ }
272
+ if (ch === "'" || ch === '"' || ch === "`") {
273
+ stack.push({ kind: "string", quote: ch, start: i + 1 });
274
+ continue;
275
+ }
276
+ if (ch === "}" && top?.kind === "code") stack.pop();
277
+ }
278
+ const top = stack.at(-1);
279
+ // An unclosed string is a path being typed, whatever else is on the line.
280
+ if (top?.kind === "string")
281
+ return { kind: "path", prefix: source.slice(top.start), quote: top.quote, text: source.slice(top.start) };
282
+ // `$` alone is the table object itself, so only the property form
283
+ // completes: a name is being written only after the dot.
284
+ const match = /\$\.([A-Za-z_][A-Za-z0-9_]*)?$/.exec(source);
285
+ if (!match) return null;
286
+ const name = match[1] ?? "";
287
+ return { kind: "variable", prefix: name, text: match[0], lead: "$.", brace: false };
288
+ }
289
+
290
+ // Names come from the shell's own variable table, so completion offers what
291
+ // expansion would actually find — exported or not, and including anything
292
+ // JavaScript mode wrote through `$`.
293
+ export class VariableIndex {
294
+ constructor() {
295
+ this.names = [];
296
+ this.signature = null;
297
+ }
298
+
299
+ sync(state) {
300
+ const keys = Object.keys(state.env);
301
+ const signature = keys.join("\0");
302
+ if (signature === this.signature) return;
303
+ this.signature = signature;
304
+ this.names = keys.sort();
305
+ }
306
+
307
+ matches(state, prefix) {
308
+ this.sync(state);
309
+ return prefix ? prefixMatches(this.names, prefix) : [...this.names];
310
+ }
311
+
312
+ first(state, prefix) {
313
+ this.sync(state);
314
+ return prefix ? firstPrefixMatch(this.names, prefix) : null;
315
+ }
316
+ }
317
+
202
318
  export class FileIndex {
203
319
  constructor(ttl = 10_000) {
204
320
  this.ttl = ttl;