beatrina 0.8.7 → 0.9.43
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/NOTICES +1 -1
- package/README.md +6 -0
- package/beatrina_V0.9.43.html +1885 -0
- package/beatrina_V0.9.43.html.inputs.json +1 -0
- package/bin/beatrina.mjs +152 -24
- package/bin/browser.mjs +31 -0
- package/bin/cli.mjs +64 -5
- package/bin/doctor-rows.mjs +31 -0
- package/bin/failsafe.mjs +8 -3
- package/bin/finder.mjs +74 -0
- package/bin/lsquery.swift +55 -0
- package/bin/pages.mjs +81 -0
- package/bin/python-setup.mjs +207 -0
- package/bin/runtime-dirs.mjs +27 -0
- package/bin/sessions.mjs +30 -10
- package/bin/shortcut.mjs +250 -42
- package/bin/update-check.mjs +2 -2
- package/build-info.json +1 -1
- package/engines/js/worker.mjs +6 -3
- package/engines/python/adapter.py +14 -13
- package/engines/python/analyze.py +4 -2
- package/engines/python/bootstrap.py +32 -31
- package/engines/python/debugger.py +10 -6
- package/engines/python/engine.json +1 -1
- package/engines/python/handoff.py +4 -2
- package/engines/python/worker.py +39 -10
- package/engines/r/engine.json +1 -1
- package/engines/r/handoff.R +4 -3
- package/failsafe/ai-policy.R +16 -16
- package/failsafe/ai-store.R +6 -6
- package/failsafe/cite.R +11 -11
- package/failsafe/journal.R +11 -11
- package/failsafe/plugins.R +27 -27
- package/failsafe/serve.R +241 -241
- package/host/ai-authority.mjs +55 -0
- package/host/ai-policy.mjs +48 -26
- package/host/bundle.mjs +194 -0
- package/host/deployment.mjs +25 -18
- package/host/engine-js.mjs +12 -17
- package/host/engine-pool.mjs +48 -6
- package/host/engine-python.mjs +80 -31
- package/host/engine-r.mjs +20 -21
- package/host/engine-stdio.mjs +35 -10
- package/host/env-names.mjs +148 -0
- package/host/gateway-token.mjs +51 -0
- package/host/journal-store.mjs +10 -9
- package/host/main.mjs +193 -77
- package/host/payload.mjs +131 -0
- package/host/planes/README.md +1 -1
- package/host/planes/ai-store.mjs +6 -5
- package/host/planes/ai.mjs +49 -29
- package/host/planes/analyze.mjs +6 -5
- package/host/planes/bundle.mjs +344 -0
- package/host/planes/choose.mjs +404 -0
- package/host/planes/cite.mjs +18 -17
- package/host/planes/files.mjs +0 -0
- package/host/planes/jobs.mjs +43 -20
- package/host/planes/latex.mjs +15 -5
- package/host/planes/mcp.mjs +80 -91
- package/host/planes/pair.mjs +24 -24
- package/host/planes/pipe-term.mjs +2 -2
- package/host/planes/plugins.mjs +1 -1
- package/host/planes/recent-documents.mjs +170 -0
- package/host/planes/sessions.mjs +226 -82
- package/host/planes/settings.mjs +4 -4
- package/host/planes/terminal.mjs +13 -11
- package/host/planes/test-file.mjs +2 -2
- package/host/planes/update.mjs +6 -13
- package/host/plugin-store.mjs +35 -52
- package/host/recent-documents.mjs +124 -0
- package/host/runtime-dir.mjs +47 -0
- package/host/server.mjs +75 -18
- package/host/session-keep.mjs +1 -1
- package/host/settings.mjs +85 -37
- package/host/update-record.mjs +3 -2
- package/host/user-dirs.mjs +60 -18
- package/host/which.mjs +39 -0
- package/host/windows-runtime.mjs +6 -3
- package/host/worker-plane.mjs +34 -6
- package/host/ws.mjs +9 -2
- package/host/zip.mjs +237 -0
- package/kernel/analyze.R +1 -1
- package/{check → kernel/check}/acceptance.mjs +1 -1
- package/kernel/check/knit-file.mjs +19639 -0
- package/{check → kernel/check}/session.mjs +49 -8
- package/kernel/deployment.R +20 -20
- package/kernel/examples/NOTICE.md +1 -1
- package/kernel/fileio.R +6 -6
- package/kernel/index.html +2 -2
- package/kernel/job-run.R +83 -22
- package/kernel/jobs.R +28 -12
- package/kernel/kernel-version +1 -1
- package/kernel/kernel.R +18 -18
- package/kernel/knitr-run.R +50 -9
- package/kernel/latex.R +128 -30
- package/kernel/mcp/{carmar-mcp.mjs → beatrina-mcp.mjs} +249 -59
- package/kernel/notebook-page.R +7 -7
- package/kernel/project.R +10 -10
- package/kernel/settings.R +105 -56
- package/kernel/sniff.R +4 -4
- package/kernel/typst.R +121 -0
- package/kernel/worker.R +522 -119
- package/kernel/workspace-keep.R +3 -3
- package/lib/agent-authoring-contract.js +28 -19
- package/lib/cell-kinds.js +3 -3
- package/lib/engine-labels.js +4 -4
- package/menu/Beatrina Menu.app/Contents/Info.plist +14 -0
- package/menu/Beatrina Menu.app/Contents/MacOS/Beatrina Menu +0 -0
- package/menu/Beatrina Menu.app/Contents/_CodeSignature/CodeResources +115 -0
- package/package.json +4 -3
- package/carmar_V0.8.7.html +0 -1522
package/host/zip.mjs
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
// zip.mjs — write and read standard ZIP archives, with Node's zlib and nothing else.
|
|
2
|
+
//
|
|
3
|
+
// The container under `.btrn` (docs/results-plan.md R2). A .btrn must open in
|
|
4
|
+
// every unzip there is — macOS Archive Utility, `unzip`, R's utils::unzip() —
|
|
5
|
+
// so this writes the plain, universal subset of the format and nothing clever:
|
|
6
|
+
// one local header + data per entry, a central directory, the end record.
|
|
7
|
+
// Method 0 (stored) or 8 (deflated); UTF-8 names (flag bit 11); no ZIP64 (a
|
|
8
|
+
// .btrn holds ≤ 100 MB of data by design — past that the data lives beside
|
|
9
|
+
// it), no encryption, no data descriptors. zlib.crc32 is Node ≥ 22.2.
|
|
10
|
+
//
|
|
11
|
+
// READING IS READING SOMETHING FROM ELSEWHERE, so the reader refuses before it
|
|
12
|
+
// trusts:
|
|
13
|
+
// · a name that is absolute, has a `..` segment, a drive letter, a
|
|
14
|
+
// backslash, a NUL, or is empty — the zip-slip family, refused by name;
|
|
15
|
+
// · the same name twice;
|
|
16
|
+
// · an encrypted entry, or a method other than 0/8;
|
|
17
|
+
// · more than `maxTotal` bytes uncompressed in all — the zip-bomb family,
|
|
18
|
+
// refused with its numbers BEFORE inflating (a per-entry ratio is
|
|
19
|
+
// optional: deflate tops out near 1032×, so it guards nothing by default);
|
|
20
|
+
// · an entry that inflates to more than it declared (zlib's
|
|
21
|
+
// maxOutputLength enforces the declared size), or whose CRC-32 differs.
|
|
22
|
+
// Every refusal is a thrown Error with `code` set, so callers can say which.
|
|
23
|
+
|
|
24
|
+
import zlib from "node:zlib";
|
|
25
|
+
|
|
26
|
+
const SIG_LOCAL = 0x04034b50;
|
|
27
|
+
const SIG_CENTRAL = 0x02014b50;
|
|
28
|
+
const SIG_END = 0x06054b50;
|
|
29
|
+
const FLAG_UTF8 = 0x0800;
|
|
30
|
+
const U32 = 0xffffffff;
|
|
31
|
+
|
|
32
|
+
/** Date → the two 16-bit MS-DOS fields (2-second resolution, 1980..2107). */
|
|
33
|
+
function dosTime(date) {
|
|
34
|
+
const d = date instanceof Date && !Number.isNaN(date.getTime()) ? date : new Date(1980, 0, 1);
|
|
35
|
+
const year = Math.min(2107, Math.max(1980, d.getFullYear()));
|
|
36
|
+
return {
|
|
37
|
+
time: (d.getHours() << 11) | (d.getMinutes() << 5) | Math.floor(d.getSeconds() / 2),
|
|
38
|
+
date: ((year - 1980) << 9) | ((d.getMonth() + 1) << 5) | d.getDate(),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const fail = (code, message) => Object.assign(new Error(message), { code });
|
|
43
|
+
|
|
44
|
+
/** The Info-ZIP Unicode Path extra field: version 1, CRC-32 of the header name, the UTF-8 name. */
|
|
45
|
+
function unicodePathExtra(nameBytes) {
|
|
46
|
+
const field = Buffer.alloc(4 + 5 + nameBytes.length);
|
|
47
|
+
field.writeUInt16LE(0x7075, 0);
|
|
48
|
+
field.writeUInt16LE(5 + nameBytes.length, 2);
|
|
49
|
+
field.writeUInt8(1, 4);
|
|
50
|
+
field.writeUInt32LE(zlib.crc32(nameBytes) >>> 0, 5);
|
|
51
|
+
nameBytes.copy(field, 9);
|
|
52
|
+
return field;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Is this an entry name a reader may use? Relative, forward slashes, no
|
|
57
|
+
* `..`/`.` segment, no drive, no NUL, not empty. A directory entry ends in `/`.
|
|
58
|
+
*/
|
|
59
|
+
export function safeEntryName(name) {
|
|
60
|
+
const n = String(name ?? "");
|
|
61
|
+
if (!n || n.length > 1024 || /[\u0000\\]/.test(n)) return false;
|
|
62
|
+
if (n.startsWith("/") || /^[A-Za-z]:/.test(n)) return false;
|
|
63
|
+
const parts = n.replace(/\/$/, "").split("/");
|
|
64
|
+
return parts.every((p) => p !== "" && p !== "." && p !== "..");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Build a ZIP in memory.
|
|
69
|
+
*
|
|
70
|
+
* @param {Array<{name:string, data:Buffer|Uint8Array|string, compress?:boolean, date?:Date}>} entries
|
|
71
|
+
* `compress` false stores the bytes (right for PNG/JPEG, already compressed);
|
|
72
|
+
* the default deflates, and keeps the stored form when deflate does not help.
|
|
73
|
+
* @returns {Buffer}
|
|
74
|
+
*/
|
|
75
|
+
export function writeZip(entries, { date = new Date() } = {}) {
|
|
76
|
+
if (!Array.isArray(entries)) throw fail("zip_input", "writeZip needs a list of entries");
|
|
77
|
+
if (entries.length > 0xfffe) throw fail("zip_too_many", `${entries.length} entries is more than a plain ZIP holds`);
|
|
78
|
+
const seen = new Set();
|
|
79
|
+
const locals = [];
|
|
80
|
+
const centrals = [];
|
|
81
|
+
let offset = 0;
|
|
82
|
+
for (const entry of entries) {
|
|
83
|
+
const name = String(entry.name ?? "");
|
|
84
|
+
if (!safeEntryName(name)) throw fail("zip_bad_name", `unsafe entry name: ${JSON.stringify(name)}`);
|
|
85
|
+
if (seen.has(name)) throw fail("zip_duplicate", `the entry ${name} appears twice`);
|
|
86
|
+
seen.add(name);
|
|
87
|
+
const raw = Buffer.isBuffer(entry.data) ? entry.data
|
|
88
|
+
: typeof entry.data === "string" ? Buffer.from(entry.data, "utf8") : Buffer.from(entry.data ?? []);
|
|
89
|
+
if (raw.length >= U32) throw fail("zip_too_large", `${name} is over 4 GB; a plain ZIP cannot hold it`);
|
|
90
|
+
const crc = zlib.crc32(raw) >>> 0;
|
|
91
|
+
let method = 0;
|
|
92
|
+
let body = raw;
|
|
93
|
+
if (entry.compress !== false && raw.length) {
|
|
94
|
+
const deflated = zlib.deflateRawSync(raw, { level: 6 });
|
|
95
|
+
if (deflated.length < raw.length) { method = 8; body = deflated; }
|
|
96
|
+
}
|
|
97
|
+
const nameBytes = Buffer.from(name, "utf8");
|
|
98
|
+
// A non-ASCII name also travels in the Info-ZIP Unicode Path field
|
|
99
|
+
// (0x7075): macOS's bundled unzip ignores the UTF-8 flag and mangles
|
|
100
|
+
// "ünïcode.csv" without it; readers that honour the flag ignore this.
|
|
101
|
+
const extra = /[^\x00-\x7f]/.test(name) ? unicodePathExtra(nameBytes) : Buffer.alloc(0);
|
|
102
|
+
const { time, date: dd } = dosTime(entry.date || date);
|
|
103
|
+
const local = Buffer.alloc(30);
|
|
104
|
+
local.writeUInt32LE(SIG_LOCAL, 0);
|
|
105
|
+
local.writeUInt16LE(20, 4);
|
|
106
|
+
local.writeUInt16LE(FLAG_UTF8, 6);
|
|
107
|
+
local.writeUInt16LE(method, 8);
|
|
108
|
+
local.writeUInt16LE(time, 10);
|
|
109
|
+
local.writeUInt16LE(dd, 12);
|
|
110
|
+
local.writeUInt32LE(crc, 14);
|
|
111
|
+
local.writeUInt32LE(body.length, 18);
|
|
112
|
+
local.writeUInt32LE(raw.length, 22);
|
|
113
|
+
local.writeUInt16LE(nameBytes.length, 26);
|
|
114
|
+
local.writeUInt16LE(extra.length, 28);
|
|
115
|
+
const central = Buffer.alloc(46);
|
|
116
|
+
central.writeUInt32LE(SIG_CENTRAL, 0);
|
|
117
|
+
// Made by UNIX (3), spec 2.0: a reader then takes the name's bytes as they
|
|
118
|
+
// are. Marked MS-DOS (0), macOS's bundled unzip (6.00, no Unicode support)
|
|
119
|
+
// translated every non-ASCII name from code page 437 and wrote garbage.
|
|
120
|
+
central.writeUInt16LE((3 << 8) | 20, 4);
|
|
121
|
+
central.writeUInt16LE(20, 6);
|
|
122
|
+
central.writeUInt16LE(FLAG_UTF8, 8);
|
|
123
|
+
central.writeUInt16LE(method, 10);
|
|
124
|
+
central.writeUInt16LE(time, 12);
|
|
125
|
+
central.writeUInt16LE(dd, 14);
|
|
126
|
+
central.writeUInt32LE(crc, 16);
|
|
127
|
+
central.writeUInt32LE(body.length, 20);
|
|
128
|
+
central.writeUInt32LE(raw.length, 24);
|
|
129
|
+
central.writeUInt16LE(nameBytes.length, 28);
|
|
130
|
+
central.writeUInt16LE(extra.length, 30);
|
|
131
|
+
central.writeUInt32LE(((name.endsWith("/") ? 0o40755 : 0o100644) << 16) >>> 0, 38); // rw-r--r-- (a dir rwxr-xr-x)
|
|
132
|
+
central.writeUInt32LE(offset, 42); // comment, disk, internal attrs: 0
|
|
133
|
+
locals.push(local, nameBytes, extra, body);
|
|
134
|
+
centrals.push(central, nameBytes, extra);
|
|
135
|
+
offset += local.length + nameBytes.length + extra.length + body.length;
|
|
136
|
+
if (offset >= U32) throw fail("zip_too_large", "the archive would pass 4 GB; a plain ZIP cannot hold it");
|
|
137
|
+
}
|
|
138
|
+
const cd = Buffer.concat(centrals);
|
|
139
|
+
const end = Buffer.alloc(22);
|
|
140
|
+
end.writeUInt32LE(SIG_END, 0);
|
|
141
|
+
end.writeUInt16LE(entries.length, 8);
|
|
142
|
+
end.writeUInt16LE(entries.length, 10);
|
|
143
|
+
end.writeUInt32LE(cd.length, 12);
|
|
144
|
+
end.writeUInt32LE(offset, 16);
|
|
145
|
+
return Buffer.concat([...locals, cd, end]);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* The central directory: every entry's name, sizes, method and where its
|
|
150
|
+
* bytes are — nothing inflated. What `bundle_list` shows, and what readZip
|
|
151
|
+
* checks the caps against before touching any data.
|
|
152
|
+
*
|
|
153
|
+
* @param {Buffer} buf
|
|
154
|
+
* @returns {Array<{name:string, method:number, crc:number, csize:number, usize:number, offset:number, dir:boolean}>}
|
|
155
|
+
*/
|
|
156
|
+
export function listZip(buf) {
|
|
157
|
+
if (!Buffer.isBuffer(buf) || buf.length < 22) throw fail("zip_not_zip", "not a ZIP archive (too short)");
|
|
158
|
+
let end = -1;
|
|
159
|
+
for (let i = buf.length - 22; i >= Math.max(0, buf.length - 22 - 0xffff); i -= 1) {
|
|
160
|
+
if (buf.readUInt32LE(i) === SIG_END) { end = i; break; }
|
|
161
|
+
}
|
|
162
|
+
if (end < 0) throw fail("zip_not_zip", "not a ZIP archive (no end record)");
|
|
163
|
+
const count = buf.readUInt16LE(end + 10);
|
|
164
|
+
const cdSize = buf.readUInt32LE(end + 12);
|
|
165
|
+
const cdOffset = buf.readUInt32LE(end + 16);
|
|
166
|
+
if (count === 0xffff || cdOffset === U32) throw fail("zip_zip64", "a ZIP64 archive — not a .btrn this version wrote");
|
|
167
|
+
if (cdOffset + cdSize > end) throw fail("zip_corrupt", "the central directory runs past the end record");
|
|
168
|
+
const out = [];
|
|
169
|
+
const names = new Set();
|
|
170
|
+
let p = cdOffset;
|
|
171
|
+
for (let k = 0; k < count; k += 1) {
|
|
172
|
+
if (p + 46 > end || buf.readUInt32LE(p) !== SIG_CENTRAL) throw fail("zip_corrupt", `central directory entry ${k + 1} is damaged`);
|
|
173
|
+
const flags = buf.readUInt16LE(p + 8);
|
|
174
|
+
const method = buf.readUInt16LE(p + 10);
|
|
175
|
+
const crc = buf.readUInt32LE(p + 16) >>> 0;
|
|
176
|
+
const csize = buf.readUInt32LE(p + 20);
|
|
177
|
+
const usize = buf.readUInt32LE(p + 24);
|
|
178
|
+
const nlen = buf.readUInt16LE(p + 28);
|
|
179
|
+
const xlen = buf.readUInt16LE(p + 30);
|
|
180
|
+
const clen = buf.readUInt16LE(p + 32);
|
|
181
|
+
const offset = buf.readUInt32LE(p + 42);
|
|
182
|
+
const name = buf.subarray(p + 46, p + 46 + nlen).toString((flags & FLAG_UTF8) ? "utf8" : "latin1");
|
|
183
|
+
if (!safeEntryName(name)) throw fail("zip_bad_name", `unsafe entry name in the archive: ${JSON.stringify(name)}`);
|
|
184
|
+
if (names.has(name)) throw fail("zip_duplicate", `the entry ${name} appears twice`);
|
|
185
|
+
names.add(name);
|
|
186
|
+
if (flags & 0x0001) throw fail("zip_encrypted", `${name} is encrypted`);
|
|
187
|
+
if (method !== 0 && method !== 8) throw fail("zip_method", `${name} uses compression method ${method}; only stored and deflated are read`);
|
|
188
|
+
out.push({ name, method, crc, csize, usize, offset, dir: name.endsWith("/") });
|
|
189
|
+
p += 46 + nlen + xlen + clen;
|
|
190
|
+
}
|
|
191
|
+
return out;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Read a ZIP: every file entry's bytes, verified.
|
|
196
|
+
*
|
|
197
|
+
* @param {Buffer} buf
|
|
198
|
+
* @param {{maxTotal?:number, maxRatio?:number, only?:(name:string)=>boolean}} [opts]
|
|
199
|
+
* the cap on the whole archive's uncompressed bytes (default 2 GB); an
|
|
200
|
+
* optional per-entry expansion cap (none by default: one layer of deflate
|
|
201
|
+
* cannot pass ~1032×, and an honest file of zeros reaches 1026×, so a ratio
|
|
202
|
+
* refuses real data while the total cap and the declared-size inflate are
|
|
203
|
+
* what actually stop a bomb); `only` reads a subset
|
|
204
|
+
* @returns {Map<string, Buffer>}
|
|
205
|
+
*/
|
|
206
|
+
export function readZip(buf, { maxTotal = 2 * 1024 ** 3, maxRatio = Infinity, only = null } = {}) {
|
|
207
|
+
const entries = listZip(buf).filter((e) => !e.dir && (!only || only(e.name)));
|
|
208
|
+
const total = entries.reduce((a, e) => a + e.usize, 0);
|
|
209
|
+
if (total > maxTotal) throw fail("zip_bomb", `the archive expands to ${total} bytes, over the ${maxTotal}-byte cap`);
|
|
210
|
+
for (const e of entries) {
|
|
211
|
+
if (e.method === 8 && e.usize > Math.max(1024, e.csize * maxRatio)) {
|
|
212
|
+
throw fail("zip_bomb", `${e.name} claims to expand ${e.csize} → ${e.usize} bytes, over ${maxRatio}×`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
const files = new Map();
|
|
216
|
+
for (const e of entries) {
|
|
217
|
+
const p = e.offset;
|
|
218
|
+
if (p + 30 > buf.length || buf.readUInt32LE(p) !== SIG_LOCAL) throw fail("zip_corrupt", `${e.name}: its local header is damaged`);
|
|
219
|
+
const start = p + 30 + buf.readUInt16LE(p + 26) + buf.readUInt16LE(p + 28);
|
|
220
|
+
if (start + e.csize > buf.length) throw fail("zip_corrupt", `${e.name}: its data runs past the end`);
|
|
221
|
+
const body = buf.subarray(start, start + e.csize);
|
|
222
|
+
let data;
|
|
223
|
+
if (e.method === 0) {
|
|
224
|
+
if (e.csize !== e.usize) throw fail("zip_corrupt", `${e.name}: stored entry with two different sizes`);
|
|
225
|
+
data = Buffer.from(body);
|
|
226
|
+
} else {
|
|
227
|
+
try { data = zlib.inflateRawSync(body, { maxOutputLength: Math.max(1, e.usize) }); }
|
|
228
|
+
catch (err) { throw fail("zip_corrupt", `${e.name}: ${err.code === "ERR_BUFFER_TOO_LARGE" ? "inflates past its declared size" : "does not inflate"}`); }
|
|
229
|
+
if (data.length !== e.usize) throw fail("zip_corrupt", `${e.name}: inflated to ${data.length} bytes, declared ${e.usize}`);
|
|
230
|
+
}
|
|
231
|
+
if ((zlib.crc32(data) >>> 0) !== e.crc) throw fail("zip_crc", `${e.name}: CRC-32 does not match — the archive is damaged`);
|
|
232
|
+
files.set(e.name, data);
|
|
233
|
+
}
|
|
234
|
+
return files;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export default { writeZip, readZip, listZip, safeEntryName };
|
package/kernel/analyze.R
CHANGED
|
@@ -44,7 +44,7 @@ suppressPackageStartupMessages(stopifnot(requireNamespace("jsonlite", quietly =
|
|
|
44
44
|
# raise it knowingly. Clamped to [0, 60] so a typo cannot remove the bound —
|
|
45
45
|
# and 0 is a real setting, meaning "scan nothing and report it truncated".
|
|
46
46
|
ANALYZE_BUDGET <- local({
|
|
47
|
-
v <- suppressWarnings(as.numeric(Sys.getenv("
|
|
47
|
+
v <- suppressWarnings(as.numeric(Sys.getenv("BEATRINA_ANALYZE_BUDGET", "3")))
|
|
48
48
|
if (!is.finite(v)) v <- 3
|
|
49
49
|
max(0, min(v, 60))
|
|
50
50
|
})
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
//
|
|
15
15
|
// Usage
|
|
16
16
|
// node tools/beatrina/acceptance.mjs # this checkout's host
|
|
17
|
-
// node tools/beatrina/acceptance.mjs --installed # the installed
|
|
17
|
+
// node tools/beatrina/acceptance.mjs --installed # the installed beatrina package's host
|
|
18
18
|
// node tools/beatrina/acceptance.mjs --host=C:\path\to\host\main.mjs
|
|
19
19
|
// node tools/beatrina/acceptance.mjs --supervisor=serve-r # the retired R supervisor, for comparison
|
|
20
20
|
// options: --port=28900 --repeat=5 --json=out.json --junit=out.xml --only=stop,readline --log=host.log
|