beatrina 0.8.6

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.
Files changed (114) hide show
  1. package/LICENSE +92 -0
  2. package/NOTICES +72 -0
  3. package/README.md +124 -0
  4. package/bin/beatrina.mjs +223 -0
  5. package/bin/cli.mjs +80 -0
  6. package/bin/failsafe.mjs +74 -0
  7. package/bin/identity.mjs +45 -0
  8. package/bin/prova-post.mjs +51 -0
  9. package/bin/sessions.mjs +95 -0
  10. package/bin/shortcut.mjs +151 -0
  11. package/bin/update-check.mjs +55 -0
  12. package/bin/upgrade.mjs +76 -0
  13. package/build-info.json +1 -0
  14. package/carmar_V0.8.6.html +1310 -0
  15. package/check/acceptance.mjs +278 -0
  16. package/check/session.mjs +215 -0
  17. package/engines/js/document-exec.mjs +82 -0
  18. package/engines/js/persist.mjs +214 -0
  19. package/engines/js/worker.mjs +424 -0
  20. package/engines/python/adapter.py +577 -0
  21. package/engines/python/analyze.py +814 -0
  22. package/engines/python/bootstrap.py +309 -0
  23. package/engines/python/dataview.py +735 -0
  24. package/engines/python/debugger.py +346 -0
  25. package/engines/python/document_exec.py +158 -0
  26. package/engines/python/engine.json +28 -0
  27. package/engines/python/handoff.py +118 -0
  28. package/engines/python/worker.py +564 -0
  29. package/engines/r/engine.json +25 -0
  30. package/engines/r/handoff.R +92 -0
  31. package/failsafe/ai-policy.R +255 -0
  32. package/failsafe/ai-store.R +373 -0
  33. package/failsafe/cite.R +418 -0
  34. package/failsafe/journal.R +684 -0
  35. package/failsafe/plugins.R +809 -0
  36. package/failsafe/serve.R +5500 -0
  37. package/host/ai-policy.mjs +218 -0
  38. package/host/deployment.mjs +160 -0
  39. package/host/engine-js.mjs +98 -0
  40. package/host/engine-pool.mjs +383 -0
  41. package/host/engine-python.mjs +228 -0
  42. package/host/engine-r.mjs +206 -0
  43. package/host/engine-stdio.mjs +401 -0
  44. package/host/journal-store.mjs +749 -0
  45. package/host/main.mjs +503 -0
  46. package/host/planes/README.md +41 -0
  47. package/host/planes/ai-store.mjs +327 -0
  48. package/host/planes/ai.mjs +467 -0
  49. package/host/planes/analyze.mjs +397 -0
  50. package/host/planes/cite.mjs +517 -0
  51. package/host/planes/files.mjs +0 -0
  52. package/host/planes/jobs.mjs +704 -0
  53. package/host/planes/journal.mjs +53 -0
  54. package/host/planes/latex.mjs +201 -0
  55. package/host/planes/mcp.mjs +493 -0
  56. package/host/planes/pair.mjs +325 -0
  57. package/host/planes/pipe-term.mjs +122 -0
  58. package/host/planes/plugins.mjs +112 -0
  59. package/host/planes/proc-tree.mjs +76 -0
  60. package/host/planes/sessions.mjs +434 -0
  61. package/host/planes/settings.mjs +164 -0
  62. package/host/planes/terminal.mjs +286 -0
  63. package/host/planes/test-file.mjs +80 -0
  64. package/host/planes/update.mjs +214 -0
  65. package/host/plugin-store.mjs +838 -0
  66. package/host/server.mjs +441 -0
  67. package/host/settings.mjs +379 -0
  68. package/host/update-record.mjs +59 -0
  69. package/host/user-dirs.mjs +117 -0
  70. package/host/windows-runtime.mjs +145 -0
  71. package/host/worker-plane.mjs +713 -0
  72. package/host/ws.mjs +190 -0
  73. package/kernel/analyze.R +668 -0
  74. package/kernel/deployment.R +165 -0
  75. package/kernel/examples/NOTICE.md +38 -0
  76. package/kernel/examples/tna-complete-tutorial.Rmd +210 -0
  77. package/kernel/fileio.R +656 -0
  78. package/kernel/index.html +96 -0
  79. package/kernel/job-run.R +391 -0
  80. package/kernel/jobs.R +276 -0
  81. package/kernel/kernel-protocol +1 -0
  82. package/kernel/kernel-version +1 -0
  83. package/kernel/kernel.R +671 -0
  84. package/kernel/knitr-run.R +245 -0
  85. package/kernel/latex.R +609 -0
  86. package/kernel/mcp/carmar-mcp.mjs +516 -0
  87. package/kernel/notebook-page.R +67 -0
  88. package/kernel/plugins/csl/apa/apa.csl +2273 -0
  89. package/kernel/plugins/csl/apa/plugin.json +19 -0
  90. package/kernel/plugins/csl/chicago-author-date/chicago-author-date.csl +4216 -0
  91. package/kernel/plugins/csl/chicago-author-date/plugin.json +19 -0
  92. package/kernel/plugins/csl/harvard-cite-them-right/harvard-cite-them-right.csl +316 -0
  93. package/kernel/plugins/csl/harvard-cite-them-right/plugin.json +19 -0
  94. package/kernel/plugins/csl/ieee/ieee.csl +519 -0
  95. package/kernel/plugins/csl/ieee/plugin.json +19 -0
  96. package/kernel/plugins/csl/modern-language-association/modern-language-association.csl +1184 -0
  97. package/kernel/plugins/csl/modern-language-association/plugin.json +19 -0
  98. package/kernel/plugins/csl/nature/nature.csl +189 -0
  99. package/kernel/plugins/csl/nature/plugin.json +19 -0
  100. package/kernel/plugins/latex/apa7/apa7.json +14 -0
  101. package/kernel/plugins/latex/apa7/plugin.json +19 -0
  102. package/kernel/plugins/latex/elsarticle/elsarticle.json +14 -0
  103. package/kernel/plugins/latex/elsarticle/plugin.json +19 -0
  104. package/kernel/plugins/latex/ieeetran/ieeetran.json +10 -0
  105. package/kernel/plugins/latex/ieeetran/plugin.json +19 -0
  106. package/kernel/project.R +131 -0
  107. package/kernel/settings.R +410 -0
  108. package/kernel/sniff.R +769 -0
  109. package/kernel/worker-boot.R +22 -0
  110. package/kernel/worker.R +3496 -0
  111. package/lib/agent-authoring-contract.js +547 -0
  112. package/lib/cell-kinds.js +108 -0
  113. package/lib/engine-labels.js +324 -0
  114. package/package.json +32 -0
@@ -0,0 +1,324 @@
1
+ // engine-labels.js — the page's ONE answer to "what is this thing called?".
2
+ //
3
+ // For as long as there was one language the answer was a constant, so nothing
4
+ // had to ask: the editor's accessible name was the literal "R code editor",
5
+ // a figure's alt text was `R plot ${n}`, the AI chip said "⚡ R ready", and the
6
+ // footer said "local R kernel". Beatrix runs engines (R today, Python next),
7
+ // so every one of those became a question — and a question answered in a dozen
8
+ // places is a dozen answers that drift. This module is the one answer.
9
+ //
10
+ // TWO HALVES, ON PURPOSE.
11
+ //
12
+ // 1. The NAME of an engine (`engineKey`, `engineLabel`, and the phrasings
13
+ // built on them). Pure, no kernel, no DOM — a chunk header's `engine`
14
+ // string in, a word out.
15
+ // 2. What the ATTACHED KERNEL declared (`kernelLanguage`, `reportedEngines`,
16
+ // `kernelDisplay`). Also pure: it takes the ready frame's `info` object
17
+ // and returns a fact, so the page's language behaviour is testable
18
+ // without a socket.
19
+ //
20
+ // THE KERNEL DECLARES; THE PAGE NEVER GUESSES. `language` is a field on the
21
+ // ready frame and on /health. A kernel built before the field existed sends
22
+ // nothing, and an absent field means "r" — that is not a guess about an
23
+ // unknown kernel, it is the only kernel that can be speaking this protocol
24
+ // without saying so.
25
+ //
26
+ // R'S WORDING IS UNCHANGED FOR R, BYTE FOR BYTE. Every phrasing below produces
27
+ // exactly the string the page produced before it existed when the engine is R.
28
+ // That is the compatibility contract the existing suites assert, and it is
29
+ // what makes this a rename of the SOURCE of a word rather than of the word.
30
+
31
+ /** The engines the page knows how to name. Order is the options sheet's. */
32
+ export const KNOWN_ENGINES = Object.freeze([
33
+ Object.freeze({ key: "r", label: "R" }),
34
+ Object.freeze({ key: "python", label: "Python" }),
35
+ Object.freeze({ key: "js", label: "JavaScript" }),
36
+ Object.freeze({ key: "bash", label: "Bash" }),
37
+ Object.freeze({ key: "sql", label: "SQL" }),
38
+ ]);
39
+
40
+ /** What a chunk with no `engine` at all is, and what a silent kernel runs. */
41
+ export const DEFAULT_ENGINE = "r";
42
+
43
+ const BY_KEY = new Map(KNOWN_ENGINES.map((e) => [e.key, e.label]));
44
+
45
+ /**
46
+ * An engine string, normalised to the key the page compares on.
47
+ *
48
+ * An ABSENT engine (null/undefined) is `r` — a hand-made cell with no header.
49
+ * An EMPTY engine is empty: ```{} names no engine and must never collapse into
50
+ * R, because "no engine" and "R" have opposite meanings at the run gate.
51
+ *
52
+ * @param {string|null|undefined} engine
53
+ * @returns {string}
54
+ */
55
+ export function engineKey(engine) {
56
+ if (engine == null) return DEFAULT_ENGINE;
57
+ return String(engine).trim().toLowerCase();
58
+ }
59
+
60
+ /**
61
+ * `r` → "R", `python` → "Python"; an unknown engine keeps its own spelling,
62
+ * capitalised. The empty engine (```{}) has no name and returns "".
63
+ *
64
+ * @param {string|null|undefined} engine
65
+ * @returns {string}
66
+ */
67
+ export function engineLabel(engine) {
68
+ const key = engineKey(engine);
69
+ if (!key) return "";
70
+ const known = BY_KEY.get(key);
71
+ if (known) return known;
72
+ return key.charAt(0).toUpperCase() + key.slice(1);
73
+ }
74
+
75
+ /** The word to put in a sentence for an engine that may be nameless. */
76
+ function word(engine) {
77
+ return engineLabel(engine) || engineLabel(DEFAULT_ENGINE);
78
+ }
79
+
80
+ // ── phrasings ───────────────────────────────────────────────────────────────
81
+ // One function per user-visible string that used to spell "R" inline. Each is
82
+ // the WHOLE string, not a fragment, so a translator or a reviewer can read the
83
+ // sentence here instead of reassembling it from a template at the call site.
84
+
85
+ /** The code surface's accessible name: "R code editor" / "Python code editor". */
86
+ export function codeEditorLabel(engine, profile = "cell") {
87
+ return `${word(engine)} ${profile === "script" ? "script" : "code"} editor`;
88
+ }
89
+
90
+ /** A figure's alt text: "R plot 1". `index` is 0-based, as the caller counts. */
91
+ export function plotAltText(engine, index) {
92
+ return `${word(engine)} plot ${Number(index) + 1}`;
93
+ }
94
+
95
+ /** The AI panel's session chip: "⚡ R ready" (the glyph is the caller's). */
96
+ export function readyLabel(engine) {
97
+ return `${word(engine)} ready`;
98
+ }
99
+
100
+ /** The footer's affiliation tail: "local R kernel". */
101
+ export function kernelFooterLabel(engine) {
102
+ return `local ${word(engine)} kernel`;
103
+ }
104
+
105
+ /** Session ▾'s runtime row: "R Version" / "Python Version". */
106
+ export function versionMenuLabel(engine) {
107
+ return `${word(engine)} Version`;
108
+ }
109
+
110
+ // ── what the attached kernel declared ───────────────────────────────────────
111
+
112
+ /**
113
+ * The language a kernel declared, from its ready frame (`kernel.info`) or a
114
+ * parsed /health body — the two carry the same field name on purpose.
115
+ *
116
+ * @param {object|null|undefined} info
117
+ * @returns {string} an engine key: "r", "python", …
118
+ */
119
+ export function kernelLanguage(info) {
120
+ const declared = info && typeof info.language === "string" ? info.language.trim() : "";
121
+ return declared ? engineKey(declared) : DEFAULT_ENGINE;
122
+ }
123
+
124
+ /**
125
+ * The language's version, as the kernel reported it.
126
+ *
127
+ * A kernel in any language other than R sends `language_version` ("3.14.4"),
128
+ * because a field called `r` holding a Python version would be a lie every
129
+ * existing reader would print.
130
+ *
131
+ * THE `r` FIELD HAS TWO SHAPES AND ONE NAME. The ready frame sends
132
+ * `R.version.string` — "R version 4.5.2 (2025-10-31)" — and /health sends
133
+ * "4.5.2". One reader has to handle both, so it strips what may be there and
134
+ * adds nothing. A patched build keeps its word ("4.5.2 Patched").
135
+ *
136
+ * @returns {string} e.g. "4.5.2", or "" when the kernel said nothing
137
+ */
138
+ export function kernelVersion(info) {
139
+ if (!info) return "";
140
+ const declared = typeof info.language_version === "string" ? info.language_version.trim() : "";
141
+ if (declared) return declared;
142
+ if (typeof info.r === "string" && info.r.trim()) {
143
+ return info.r.replace(/^R version /, "").replace(/ \(.*\)$/, "").trim();
144
+ }
145
+ return "";
146
+ }
147
+
148
+ /**
149
+ * "R 4.5.2" / "Python 3.14.4" / "R session" — the short form for a badge, a
150
+ * chip or a chooser row. One function, so the badge, the status chip and the
151
+ * attach chooser cannot name one session three ways.
152
+ *
153
+ * With no version it is "<Language> session", which is what the attach chooser
154
+ * has always shown for a listener that named no version.
155
+ */
156
+ export function kernelDisplay(info) {
157
+ const label = engineLabel(kernelLanguage(info));
158
+ const version = kernelVersion(info);
159
+ return version ? `${label} ${version}` : `${label} session`;
160
+ }
161
+
162
+ /**
163
+ * One engine row, normalised. Two wire shapes are accepted on purpose.
164
+ *
165
+ * `ready.engines` may be
166
+ * - an array of STRINGS — `["python", "bash"]`, a bare declaration of what
167
+ * this kernel will also run. This is the shape the Python kernel
168
+ * (`carmapy`) already sends and the page-patch series already reads, so it
169
+ * must keep working.
170
+ * - an array of OBJECTS — `{name, label, version, path}`, which is what a
171
+ * host that DISCOVERED runtimes can say. The engine chooser and Doctor
172
+ * need the version and the path; a bare name cannot carry them.
173
+ *
174
+ * Accepting both is not indecision: they answer different questions ("may I
175
+ * send this chunk?" and "what did you find on this machine?"), and a kernel
176
+ * that can only answer the first must not be forced to invent the second.
177
+ *
178
+ * @returns {{name:string,label:string,version:string,path:string,current:boolean}|null}
179
+ */
180
+ function engineRow(entry, language) {
181
+ if (typeof entry === "string") {
182
+ const name = engineKey(entry);
183
+ return name ? { name, label: engineLabel(name), version: "", path: "", current: name === language } : null;
184
+ }
185
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) return null;
186
+ const name = engineKey(entry.name ?? entry.engine ?? entry.key ?? entry.language);
187
+ if (!name) return null;
188
+ const text = (value) => (typeof value === "string" ? value.trim() : "");
189
+ return {
190
+ name,
191
+ label: text(entry.label) || engineLabel(name),
192
+ version: text(entry.version) || text(entry.language_version),
193
+ path: text(entry.path),
194
+ current: entry.current === true || name === language,
195
+ };
196
+ }
197
+
198
+ /**
199
+ * What the kernel SAID it found, normalised — or `[]` when it said nothing.
200
+ *
201
+ * An empty answer is the degradation signal, and it is deliberately NOT
202
+ * "assume R": a kernel that reports no engines is one built before the field
203
+ * existed, and the chooser that reads this must then show what it always
204
+ * showed rather than inventing a one-row table.
205
+ *
206
+ * @param {object|null|undefined} info
207
+ * @returns {Array<{name:string,label:string,version:string,path:string,current:boolean}>}
208
+ */
209
+ export function reportedEngines(info) {
210
+ const declared = info && Array.isArray(info.engines) ? info.engines : null;
211
+ if (!declared || !declared.length) return [];
212
+ const language = kernelLanguage(info);
213
+ const rows = [];
214
+ declared.forEach((entry) => {
215
+ const row = engineRow(entry, language);
216
+ if (row && !rows.some((seen) => seen.name === row.name)) rows.push(row);
217
+ });
218
+ if (!rows.length) return [];
219
+ // The kernel's own language is always one of its engines — a session that
220
+ // would not run its own language is not a thing, and a list that omitted it
221
+ // would strand every chunk in the notebook.
222
+ if (!rows.some((row) => row.name === language)) {
223
+ rows.unshift({ name: language, label: engineLabel(language), version: kernelVersion(info), path: "", current: true });
224
+ } else {
225
+ const own = rows.find((row) => row.name === language);
226
+ if (!own.version) own.version = kernelVersion(info);
227
+ own.current = true;
228
+ }
229
+ return rows;
230
+ }
231
+
232
+ /**
233
+ * Every engine this kernel will execute, as keys — the language always first.
234
+ *
235
+ * This is the list a run gate compares against. It is never empty: with no
236
+ * declaration at all the answer is the kernel's language alone.
237
+ *
238
+ * @returns {string[]}
239
+ */
240
+ export function declaredEngines(info) {
241
+ const language = kernelLanguage(info);
242
+ const out = [language];
243
+ reportedEngines(info).forEach((row) => { if (!out.includes(row.name)) out.push(row.name); });
244
+ return out;
245
+ }
246
+
247
+ // ── authoring: what an AI may say about a chunk's language ─────────────────
248
+ // The tool schemas (lib/ai-tools.js, lib/mcp-bridge.js, tools/mcp/carmar-mcp.mjs)
249
+ // all name engines from HERE, so the enum a model is shown, the refusal it gets
250
+ // and the header a chunk is written with cannot disagree. Pure and import-free:
251
+ // tools/mcp/carmar-mcp.mjs loads it in Node beside the authoring contract.
252
+
253
+ /** The engines a tool schema offers. The SESSION decides which of them run. */
254
+ export const AUTHORING_ENGINES = Object.freeze(KNOWN_ENGINES.map((e) => e.key));
255
+
256
+ /**
257
+ * A tool's `engine` argument, validated. Absent (undefined, null, "") is R, so
258
+ * a call that never heard of the parameter means exactly what it always meant.
259
+ *
260
+ * @returns {string} an engine key
261
+ * @throws {Error} naming the engines that exist, for anything else
262
+ */
263
+ export function engineArg(value, name = "engine") {
264
+ if (value == null || value === "") return DEFAULT_ENGINE;
265
+ if (typeof value !== "string") {
266
+ throw new Error(`${name} must be a string: one of ${AUTHORING_ENGINES.join(", ")}.`);
267
+ }
268
+ const key = engineKey(value);
269
+ if (!AUTHORING_ENGINES.includes(key)) {
270
+ throw new Error(`${name} "${value}" is not an engine CarmaR knows. Use one of ${AUTHORING_ENGINES.join(", ")}.`);
271
+ }
272
+ return key;
273
+ }
274
+
275
+ /**
276
+ * The chunk header a new code chunk carries for `engine` — the SAME shape the
277
+ * `carmar:insert-cell` door writes (src/notebook-entry.js). R gets none, so an
278
+ * R insert is byte-identical to one made before engines existed.
279
+ *
280
+ * @returns {object|null}
281
+ */
282
+ export function engineChunkMeta(engine) {
283
+ const key = engineKey(engine);
284
+ if (!key || key === DEFAULT_ENGINE) return null;
285
+ return { engine: key, label: null, options: {}, rawOptions: null, hashPipeRaw: [] };
286
+ }
287
+
288
+ /**
289
+ * The engine of a notebook cell as a manifest or adapter describes it
290
+ * (`{kind, chunk}`): null for prose, the header's engine for code, R when the
291
+ * code cell has no header. An EMPTY engine (```{}) stays "" — it names none.
292
+ */
293
+ export function cellEngine(cell) {
294
+ if (!cell) return null;
295
+ const kind = String(cell.kind || "");
296
+ if (kind === "md" || kind === "text") return null;
297
+ return engineKey(cell.chunk && cell.chunk.engine != null ? cell.chunk.engine : null);
298
+ }
299
+
300
+ /**
301
+ * Why a chunk in `engine` cannot be authored or run here, in the sentence a
302
+ * model repeats to the user — or "" when the attached session runs it.
303
+ *
304
+ * @param {string} engine
305
+ * @param {string[]} declared what the host declared (lib/chunk-meta.js chunkEngines())
306
+ */
307
+ export function engineRefusal(engine, declared = [DEFAULT_ENGINE]) {
308
+ const key = engineKey(engine);
309
+ const list = (Array.isArray(declared) && declared.length ? declared : [DEFAULT_ENGINE]).map(engineKey);
310
+ if (list.includes(key)) return "";
311
+ const runs = list.map(engineLabel);
312
+ const said = runs.length === 1 ? runs[0] : `${runs.slice(0, -1).join(", ")} and ${runs.at(-1)}`;
313
+ const name = key ? engineLabel(key) : "Engine-less";
314
+ return `This session does not run ${name} chunks — the attached session runs ${said} only. `
315
+ + `Write the step in ${runs[0]}, or tell the user that ${name} needs a session with a `
316
+ + `${name} engine (Session ▾ lists the engines this one found).`;
317
+ }
318
+
319
+ export default {
320
+ AUTHORING_ENGINES, engineArg, engineChunkMeta, cellEngine, engineRefusal,
321
+ KNOWN_ENGINES, DEFAULT_ENGINE, engineKey, engineLabel,
322
+ codeEditorLabel, plotAltText, readyLabel, kernelFooterLabel, versionMenuLabel,
323
+ kernelLanguage, kernelVersion, kernelDisplay, reportedEngines, declaredEngines,
324
+ };
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "beatrina",
3
+ "version": "0.8.6",
4
+ "description": "Beatrina is a modern, powerful, feature-rich integrated development environment (IDE) for R, Python, and JavaScript.",
5
+ "type": "module",
6
+ "bin": {
7
+ "beatrina": "bin/beatrina.mjs"
8
+ },
9
+ "engines": {
10
+ "node": ">=22"
11
+ },
12
+ "optionalDependencies": {
13
+ "node-pty": "^1.1.0"
14
+ },
15
+ "license": "SEE LICENSE IN LICENSE",
16
+ "author": "Mohammed Saqr <saqr@saqr.me> (https://saqr.me)",
17
+ "homepage": "https://saqr.me",
18
+ "files": [
19
+ "bin",
20
+ "host",
21
+ "engines",
22
+ "kernel",
23
+ "failsafe",
24
+ "lib",
25
+ "check",
26
+ "build-info.json",
27
+ "carmar_V0.8.6.html",
28
+ "README.md",
29
+ "LICENSE",
30
+ "NOTICES"
31
+ ]
32
+ }