@galaxyproject/olit 0.0.0
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/LICENSE +21 -0
- package/README.md +188 -0
- package/package.json +61 -0
- package/static/InterVariable-Italic.woff2 +0 -0
- package/static/InterVariable.woff2 +0 -0
- package/static/JetBrainsMono-Bold.woff2 +0 -0
- package/static/JetBrainsMono-BoldItalic.woff2 +0 -0
- package/static/JetBrainsMono-Italic.woff2 +0 -0
- package/static/JetBrainsMono-Regular.woff2 +0 -0
- package/static/index.css +1 -0
- package/static/index.html +13 -0
- package/static/index.js +4024 -0
- package/static/logo.svg +31 -0
- package/static/olit.xml +98 -0
- package/static/pyodide/README.md +62 -0
- package/static/pyodide/annotated_types-0.7.0-py3-none-any.whl +0 -0
- package/static/pyodide/attrs-25.2.0-py3-none-any.whl +0 -0
- package/static/pyodide/console-v2.html +393 -0
- package/static/pyodide/console.html +305 -0
- package/static/pyodide/ffi.d.ts +1130 -0
- package/static/pyodide/galaxy-charts-e7348f1e5617.js +38078 -0
- package/static/pyodide/galaxy-ops-6b6069501426.js +9476 -0
- package/static/pyodide/jsonschema-4.23.0-py3-none-any.whl +0 -0
- package/static/pyodide/jsonschema_specifications-2024.10.1-py3-none-any.whl +0 -0
- package/static/pyodide/llm-fetch.js +16 -0
- package/static/pyodide/micropip-0.11.1-py3-none-any.whl +0 -0
- package/static/pyodide/numpy-2.2.5-cp313-cp313-pyemscripten_2025_0_wasm32.whl +0 -0
- package/static/pyodide/olit-0.0.0-07b000ed515e9-py3-none-any.whl +0 -0
- package/static/pyodide/package.json +126 -0
- package/static/pyodide/pandas-2.3.3-cp313-cp313-pyemscripten_2025_0_wasm32.whl +0 -0
- package/static/pyodide/pydantic-2.12.5-py3-none-any.whl +0 -0
- package/static/pyodide/pydantic_core-2.41.5-cp313-cp313-pyemscripten_2025_0_wasm32.whl +0 -0
- package/static/pyodide/pyodide-lock.json +1 -0
- package/static/pyodide/pyodide-worker.js +120 -0
- package/static/pyodide/pyodide.asm.js +15 -0
- package/static/pyodide/pyodide.asm.wasm +0 -0
- package/static/pyodide/pyodide.d.ts +1699 -0
- package/static/pyodide/pyodide.js +6 -0
- package/static/pyodide/pyodide.js.map +7 -0
- package/static/pyodide/pyodide.mjs +4 -0
- package/static/pyodide/pyodide.mjs.map +7 -0
- package/static/pyodide/pyrsistent-0.20.0-cp313-cp313-pyemscripten_2025_0_wasm32.whl +0 -0
- package/static/pyodide/python_dateutil-2.9.0.post0-py2.py3-none-any.whl +0 -0
- package/static/pyodide/python_stdlib.zip +0 -0
- package/static/pyodide/pytz-2025.2-py2.py3-none-any.whl +0 -0
- package/static/pyodide/pyyaml-6.0.2-cp313-cp313-pyemscripten_2025_0_wasm32.whl +0 -0
- package/static/pyodide/referencing-0.36.2-py3-none-any.whl +0 -0
- package/static/pyodide/rpds_py-0.30.0-cp313-cp313-pyemscripten_2025_0_wasm32.whl +0 -0
- package/static/pyodide/six-1.17.0-py2.py3-none-any.whl +0 -0
- package/static/pyodide/typing_extensions-4.15.0-py3-none-any.whl +0 -0
- package/static/pyodide/typing_inspection-0.4.2-py3-none-any.whl +0 -0
package/static/logo.svg
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
2
|
+
<svg
|
|
3
|
+
viewBox="0 0 465 340"
|
|
4
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
+
xmlns:svg="http://www.w3.org/2000/svg">
|
|
6
|
+
<path
|
|
7
|
+
d="m 1.5575189,268.57426 c 3.738,-17.481 15.9770001,-31.997 29.6140001,-43.046 2.778,-2.251 5.633,-4.453 8.347,-6.778 1.218,-1.044 2.43,-2.431 4.103,-2.61 0.159,0.737 -0.006,1.01 -0.395,1.652 -0.962,1.386 -2.104,2.44 -3.216,3.694 -7.785,8.055 -16.384,18.015 -19.467,29.011 -2.632,9.391 0.234,19.1 9.17,23.834 3.159,1.673 6.709,2.908 10.19,3.702 15.889,3.624 32.13,3.459 48.272,2.071 16.599991,-1.426 33.102991,-3.98 49.420991,-7.342 72.135,-14.864 141.91,-42.161 205.915,-78.44 21.984,-12.46 43.219,-26.581 61.819,-43.763 9.267,-8.56 18.611,-18.353 22.876,-30.523 4.065,-11.597 -1.295,-22.218 -11.925,-27.643001 -3.845,-1.962 -8.033,-3.39 -12.161,-4.621 -1.187,-0.355 -3.606,-0.595 -3.306,-2.09 1.263,-0.563 3.126,-0.146 4.481,-0.059 3.699,0.237 7.372,0.703 11.041,1.208 9.817,1.351 19.657,3.862 27.555,10.142001 2.861,2.275 5.464,4.789 6.963,8.204 0.77,1.755 0.955,3.671 1.237,5.543 0.309,2.051 0.063,4.376 -0.345,6.41 -1.731,8.643 -6.461,16.055 -11.713,22.995 -10.277,13.577 -23.557,24.969 -37.249,34.977 -13.504,9.87 -27.738,18.56 -42.136,27.051 -28.085,16.565 -57.429,31.013 -87.527,43.534 -34.289,14.265 -69.126,26.495 -105.042,35.989 -20.327,5.373 -40.931,10.306 -61.728,13.531 -13.713991,2.126 -27.559991,3.218 -41.409991,3.643 -13.051,0.4 -25.998,0.092 -38.82,-2.576 -9.188,-1.911 -20.6920001,-6.719 -23.9020001,-16.399 -1.168,-3.522 -1.207,-7.656 -0.662,-11.301 z"
|
|
8
|
+
opacity="1"
|
|
9
|
+
fill="#e30a17"
|
|
10
|
+
fill-rule="evenodd" />
|
|
11
|
+
<path
|
|
12
|
+
d="m 46.019519,184.09026 c 0.897,-5.833 1.019,-11.713 2.287,-17.52 3.4,-15.581 9.513,-30.15 17.832,-43.73 3.601,-5.88 8.125,-11.318 12.665,-16.482 33.762991,-38.400001 88.721991,-53.111001 138.000991,-41.656001 32.036,7.447 61.237,26.543 79.801,53.935001 6.609,9.75 11.607,20.177 15.441,31.292 3.204,9.29 5.192,19.251 5.98,29.047 0.258,3.211 1.222,7.622 0.207,10.734 -1.558,1.431 -3.681,2.135 -5.514,3.147 -4.029,2.225 -8.128,4.37 -12.25,6.415 -13.896,6.891 -28.037,13.21 -42.29,19.311 -4.662,1.995 -9.362,3.947 -14.075,5.804 -1.063,0.419 -3.896,2.308 -4.918,1.786 6.828,-26.041 7.23,-58.105 -10.584,-80.097 -9.817,-12.121 -24.951,-17.623 -40.016,-19.007 -20.802,-1.912 -41.469,4.72 -54.523,21.598 -11.959,15.462 -13.433,37.786 -12.164,56.517 0.885,13.049 4.377,26.375 12.147,37.089 1.947,2.686 4.111,5.309 6.623,7.481 1.621,1.402 4.176,2.854 5.231,4.758 -0.823,0.89 -3.961,0.932 -5.158,1.165 -4.597,0.896 -9.134,2.1 -13.74,2.936 -14.548,2.639 -29.293,5.174 -44.049991,6.21 -3.73,0.261 -7.445,0.472 -11.182,0.662 -1.984,0.1 -4.478,0.371 -6.416,-0.056 -3.952,-5.263 -6.647,-11.373 -9.245,-17.388 -8.586,-19.877 -11.591,-42.407 -10.09,-63.951 z"
|
|
13
|
+
opacity="1"
|
|
14
|
+
fill="#e30a17"
|
|
15
|
+
fill-rule="evenodd" />
|
|
16
|
+
<path
|
|
17
|
+
d="m 100.74151,311.30126 c 6.182,-1.155 12.488,-1.83 18.705,-2.836 10.061,-1.626 20.096,-3.851 30.02,-6.159 56.495,-13.139 111.887,-32.716 164.046,-58.067 -0.496,3.912 -2.489,8.157 -3.978,11.798 -4.389,10.731 -9.822,20.65 -16.848,29.906 -7.873,10.371 -16.705,19.478 -27.151,27.29 -30.13,22.531 -70.484,30.682 -107.254,23.556 -15.748,-3.052 -30.885,-8.252 -44.746,-16.429 -3.198,-1.886 -6.196,-4.027 -9.266,-6.104 -1.201,-0.812 -2.868,-1.624 -3.528,-2.955 z"
|
|
18
|
+
opacity="1"
|
|
19
|
+
fill="#e30a17"
|
|
20
|
+
fill-rule="evenodd" />
|
|
21
|
+
<path
|
|
22
|
+
d="m 327.97651,6.03826 c 2.41,-5.5070001 9.182,-4.0470001 14.097,-4.0390001 12.769,0.022 25.541,-0.044 38.31,-0.084 4.384,-0.013 11.34,-1.22 13.99,3.233 1.408,2.3670001 0.507,6.6720001 0.529,9.3380001 0.061,7.501 0.029,15.004 0.027,22.506 -0.005,23.666999 -0.008,47.333999 0.057,71.002 0.019,6.998 0.057,14 -0.029,20.998 -0.031,2.533 0.63,6.358 -0.401,8.688 -3.22,3.572 -7.726,6.375 -11.451,9.421 -11.509,9.412 -23.923,17.799 -36.51,25.686 -4.129,2.586 -8.377,4.948 -12.586,7.402 -1.751,1.022 -3.791,2.887 -5.814,3.179 -0.537,-2.01 -0.213,-4.321 -0.209,-6.389 0.008,-3.997 -0.036,-7.996 -0.073,-11.993 -0.123,-13.665 -0.087,-27.333 -0.092,-40.998 -0.01,-26.667 -0.009,-53.334001 -0.022,-80.001001 -0.004,-8.333999 0.005,-16.666999 -0.014,-24.999999 -0.009,-4.122 -0.663,-8.911 0.191,-12.949 z"
|
|
23
|
+
opacity="1"
|
|
24
|
+
fill="#e30a17"
|
|
25
|
+
fill-rule="evenodd" />
|
|
26
|
+
<path
|
|
27
|
+
d="m 327.88051,237.38326 c 8.248,-4.362 16.717,-8.307 24.9,-12.812 14.585,-8.028 28.67,-16.761 42.469,-26.065 0.326,4.787 0.083,9.675 0.091,14.477 0.016,9.333 -0.009,18.666 0.013,27.999 0.02,8.043 -0.387,16.245 5.553,22.539 6.659,7.057 15.927,7.047 24.883,6.986 6.594,-0.044 13.19,-0.078 19.784,-0.11 2.819,-0.013 5.637,0.008 8.456,0.017 1.328,0.004 2.912,-0.233 4.161,0.296 5.278,2.236 3.755,9.178 3.758,13.776 0.005,10.334 0.012,20.668 0.023,31.002 0.003,3.165 0.006,6.331 -0.025,9.497 -0.013,1.375 0.29,3.034 -0.119,4.36 -0.254,0.823 -1.047,2.569 -1.914,2.894 -3.067,1.152 -7.616,0.409 -10.847,0.419 -13.394,0.043 -26.804,0.146 -40.198,0.022 -7.462,-0.068 -14.89,-0.789 -22.067,-2.968 -27.434,-8.327 -48.058,-29.985 -56.063,-57.319 -3.343,-11.415 -2.556,-22.869 -3.085,-34.568 0.076,-0.147 0.152,-0.295 0.227,-0.442 z"
|
|
28
|
+
opacity="1"
|
|
29
|
+
fill="#e30a17"
|
|
30
|
+
fill-rule="evenodd" />
|
|
31
|
+
</svg>
|
package/static/olit.xml
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE visualization SYSTEM "../../visualization.dtd">
|
|
3
|
+
<visualization name="AI Research Assistant" embeddable="true">
|
|
4
|
+
<description>powered by Olit</description>
|
|
5
|
+
<data_sources>
|
|
6
|
+
<data_source>
|
|
7
|
+
<model_class>HistoryDatasetAssociation</model_class>
|
|
8
|
+
<test test_attr="ext">csv</test>
|
|
9
|
+
<test test_attr="ext">tabular</test>
|
|
10
|
+
<test test_attr="ext">txt</test>
|
|
11
|
+
</data_source>
|
|
12
|
+
</data_sources>
|
|
13
|
+
<params>
|
|
14
|
+
<param required="false">dataset_id</param>
|
|
15
|
+
</params>
|
|
16
|
+
<entry_point entry_point_type="script" src="index.js" css="index.css" />
|
|
17
|
+
<specs>
|
|
18
|
+
<ai_prompt><![CDATA[
|
|
19
|
+
You are Olit, a co-scientist that orchestrates the Galaxy bioinformatics platform.
|
|
20
|
+
You run entirely in the user's browser; you already have their authenticated Galaxy
|
|
21
|
+
session, so there is no connect step. Everything that touches Galaxy or runs code
|
|
22
|
+
happens through a tool call.
|
|
23
|
+
|
|
24
|
+
Most tasks follow the same shape: pick or create a history, get data into it, run a
|
|
25
|
+
Galaxy tool on the data, then read results back from the history.
|
|
26
|
+
|
|
27
|
+
Three kinds of "tool" exist here and are easy to confuse:
|
|
28
|
+
- Your tools (the operations listed to you) are things like get_histories, run_tool,
|
|
29
|
+
invoke_workflow, upload_file_from_url.
|
|
30
|
+
- Galaxy tools (FastQC, Trimmomatic, Bowtie2, ...) are bioinformatics programs inside
|
|
31
|
+
Galaxy. They are NOT in your tool list. Find them with search_tools_by_name or
|
|
32
|
+
search_tools_by_keywords, then execute via run_tool(history_id, tool_id, inputs).
|
|
33
|
+
Use get_tool_input_template(tool_id) to see a tool's expected inputs.
|
|
34
|
+
- Galaxy visualizations (IGV, Plotly, tabulator, ...) are display plugins with their own
|
|
35
|
+
operations: list_visualizations, show_visualization and save_visualization. Showing,
|
|
36
|
+
opening or adding a track to one goes there, never to a tool search.
|
|
37
|
+
|
|
38
|
+
For curated multi-step analyses, prefer search_iwc_workflows to find a vetted
|
|
39
|
+
Intergalactic Workflow Commission (IWC) workflow, then import_workflow_from_iwc and
|
|
40
|
+
invoke_workflow. User-defined tools (created via create_user_tool) are run with
|
|
41
|
+
run_user_tool, not run_tool.
|
|
42
|
+
|
|
43
|
+
Pages are Galaxy-flavored markdown documents: a history-attached page is a "Notebook",
|
|
44
|
+
a standalone page a "Report". Manage them with list_pages / get_page / create_page /
|
|
45
|
+
update_page. Page content embeds datasets through directives that use ENCODED ids
|
|
46
|
+
(e.g. history_dataset_display(history_dataset_id=<encoded-dataset-id>)), which you get
|
|
47
|
+
from get_history_contents / get_dataset_details. Never write a literal id you have not
|
|
48
|
+
read from a tool result in this session.
|
|
49
|
+
|
|
50
|
+
Beyond Galaxy you also have:
|
|
51
|
+
- skills_fetch(repo, path): load operational know-how on demand. The "Skills
|
|
52
|
+
repositories" section below lists what exists and the exact path for each.
|
|
53
|
+
- run_python(code): run Python locally in the browser (Pyodide; numpy and pandas;
|
|
54
|
+
state persists). Use for quick local computation and inspection. It runs in the
|
|
55
|
+
browser, not on Galaxy.
|
|
56
|
+
- vega_dataset(dataset_id, spec): chart one tabular dataset inline with a Vega-Lite
|
|
57
|
+
spec you write. Leave `data` out of it: the spec is pointed at the dataset here.
|
|
58
|
+
- finish(summary): call when the task is complete.
|
|
59
|
+
|
|
60
|
+
You both talk and act. Chat carries dialogue and status — questions, a plan draft
|
|
61
|
+
awaiting approval, a parameter table, a short end-of-turn summary. Tools carry
|
|
62
|
+
everything that touches Galaxy or runs code. A turn that ends in chat is normal and
|
|
63
|
+
expected: that is how you hand a decision back to the user.
|
|
64
|
+
|
|
65
|
+
When the task is complete, call finish with a one-line summary.
|
|
66
|
+
]]></ai_prompt>
|
|
67
|
+
</specs>
|
|
68
|
+
<help format="markdown"><![CDATA[
|
|
69
|
+
# What is Olit?
|
|
70
|
+
|
|
71
|
+
Olit is a conversational research assistant that helps you plan and run analyses in Galaxy. Ask questions in plain language, explore your data, or describe an analysis you want to perform. For multi-step analyses, Olit proposes a plan for you to review before running Galaxy tools or workflows.
|
|
72
|
+
|
|
73
|
+
After selecting a dataset, a chat panel opens alongside your history. Olit works with your Galaxy data and tools while keeping you in control of what gets executed.
|
|
74
|
+
|
|
75
|
+
## Key Features
|
|
76
|
+
|
|
77
|
+
- **Plan First, Run Second**: Multi-step analyses are drafted as a plan you can approve, edit, or reject before execution.
|
|
78
|
+
- **Parameter Review**: Tool parameters are shown before execution and can be changed, so you can see exactly what will be submitted.
|
|
79
|
+
- **Runs Real Galaxy Jobs**: Finds and runs installed Galaxy tools and can import and invoke curated workflows from the Intergalactic Workflow Commission (IWC).
|
|
80
|
+
- **Keeps a Record**: Plans, executed steps, and results are recorded in a Galaxy Page attached to your history, keeping the analysis readable and shareable.
|
|
81
|
+
- **Watches Your Jobs**: Submitted jobs and workflow invocations are followed to completion and reported back in the chat.
|
|
82
|
+
- **Local Computation**: Quick calculations and data inspection can run directly in your browser without creating Galaxy jobs.
|
|
83
|
+
|
|
84
|
+
Olit runs in your browser using your existing Galaxy session. It requires no separate server or per-user container and operates with the same Galaxy access available to you.
|
|
85
|
+
|
|
86
|
+
You can use Olit with an LLM service provided by your Galaxy server or connect directly to a supported provider using your own API key. When you provide your own key, it is kept only in the current browser tab and is never sent to or stored by Galaxy.
|
|
87
|
+
|
|
88
|
+
## Olit and Orbit
|
|
89
|
+
|
|
90
|
+
Olit is closely aligned with Orbit, the original Galaxy AI co-scientist. Its agent model builds on concepts developed for Orbit, including plan-and-approve, parameter review, analysis records, and skills. Olit adapts these concepts to a lightweight, browser-native runtime integrated directly with Galaxy.
|
|
91
|
+
|
|
92
|
+
Orbit is suited to analyses requiring a shell or container environment, while Olit provides the same broader interaction model within a standard Galaxy session.
|
|
93
|
+
|
|
94
|
+
- Orbit: https://github.com/galaxyproject/loom
|
|
95
|
+
- Shared skills corpus: https://github.com/galaxyproject/galaxy-skills
|
|
96
|
+
- Galaxy visualization framework: https://github.com/galaxyproject/galaxy-charts
|
|
97
|
+
]]></help>
|
|
98
|
+
</visualization>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Pyodide JavaScript package
|
|
2
|
+
|
|
3
|
+
<a href="https://www.npmjs.com/package/pyodide"><img src="https://img.shields.io/npm/v/pyodide" alt="npm"></a>
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
Download and extract Pyodide packages from [Github
|
|
8
|
+
releases](https://github.com/pyodide/pyodide/releases)
|
|
9
|
+
(`pyodide-build-*.tar.bz2`). The version of the release needs to match exactly the version of this package.
|
|
10
|
+
|
|
11
|
+
Then you can load Pyodide in Node.js as follows,
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
// hello_python.js
|
|
15
|
+
const { loadPyodide } = require("pyodide");
|
|
16
|
+
|
|
17
|
+
async function hello_python() {
|
|
18
|
+
let pyodide = await loadPyodide({
|
|
19
|
+
indexURL: "<pyodide artifacts folder>",
|
|
20
|
+
});
|
|
21
|
+
return pyodide.runPythonAsync("1+1");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
hello_python().then((result) => {
|
|
25
|
+
console.log("Python says that 1+1 =", result);
|
|
26
|
+
});
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
$ node hello_python.js
|
|
31
|
+
Python says that 1+1= 2
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Or you can use the REPL. To start the Node.js REPL with support for top level
|
|
35
|
+
await, use `node --experimental-repl-await`:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
$ node --experimental-repl-await
|
|
39
|
+
Welcome to Node.js v18.5.0.
|
|
40
|
+
Type ".help" for more information.
|
|
41
|
+
> const { loadPyodide } = require("pyodide");
|
|
42
|
+
undefined
|
|
43
|
+
> let pyodide = await loadPyodide();
|
|
44
|
+
undefined
|
|
45
|
+
> await pyodide.runPythonAsync("1+1");
|
|
46
|
+
2
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Details
|
|
50
|
+
|
|
51
|
+
The JavaScript code in this package is responsible for the following tasks:
|
|
52
|
+
|
|
53
|
+
1. Defines the public [JavaScript API](https://pyodide.org/en/stable/usage/api/js-api.html)
|
|
54
|
+
- Package loading code to allow loading of other Python packages.
|
|
55
|
+
- Can load
|
|
56
|
+
[micropip](https://pyodide.org/en/stable/usage/api/micropip-api.html) to
|
|
57
|
+
bootstrap loading of pure Python wheels
|
|
58
|
+
2. Loads the CPython interpreter and the core/pyodide emscripten application
|
|
59
|
+
which embeds the interpreter.
|
|
60
|
+
3. Injects the `js/pyodide` JavaScript API into `sys.modules`. This is the
|
|
61
|
+
final runtime dependency for `core/pyodide` & `py/pyodide`, so after this step
|
|
62
|
+
the interpreter is fully up and running.
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<!--
|
|
3
|
+
Pyodide Console v2 (Experimental)
|
|
4
|
+
|
|
5
|
+
This is an experimental version of the Pyodide console that provides
|
|
6
|
+
an enhanced terminal experience using xterm.js. This implementation replaces
|
|
7
|
+
the jQuery Terminal used in the original console with a more feature-rich
|
|
8
|
+
terminal emulator that offers better performance and modern terminal capabilities.
|
|
9
|
+
|
|
10
|
+
Note: This console is still under development and may not have all the features
|
|
11
|
+
of the stable console.
|
|
12
|
+
-->
|
|
13
|
+
<html>
|
|
14
|
+
<head>
|
|
15
|
+
<title>Pyodide Console</title>
|
|
16
|
+
<meta charset="UTF-8" />
|
|
17
|
+
<meta
|
|
18
|
+
http-equiv="origin-trial"
|
|
19
|
+
content="Aq6vv/4syIkcyMszFgCc9LlH0kX88jdE7SXfCFnh2RQN0nhhL8o6PCQ2oE3a7n3mC7+d9n89Repw5HYBtjarDw4AAAB3eyJvcmlnaW4iOiJodHRwczovL3B5b2RpZGUub3JnOjQ0MyIsImZlYXR1cmUiOiJXZWJBc3NlbWJseUpTUHJvbWlzZUludGVncmF0aW9uIiwiZXhwaXJ5IjoxNzMwMjQ2Mzk5LCJpc1N1YmRvbWFpbiI6dHJ1ZX0="
|
|
20
|
+
/>
|
|
21
|
+
<meta
|
|
22
|
+
http-equiv="origin-trial"
|
|
23
|
+
content="Ai8IXb0XqedlM/Q2guWXFfBkKiYY9uaPZpdjHqc8y0ZvpAfK9SKzp/dIuFH+txG/HEKxt59uIkk39hhWrhNgbw4AAABieyJvcmlnaW4iOiJodHRwOi8vbG9jYWxob3N0OjgwMDAiLCJmZWF0dXJlIjoiV2ViQXNzZW1ibHlKU1Byb21pc2VJbnRlZ3JhdGlvbiIsImV4cGlyeSI6MTczMDI0NjM5OX0="
|
|
24
|
+
/>
|
|
25
|
+
<link
|
|
26
|
+
rel="stylesheet"
|
|
27
|
+
href="https://unpkg.com/@xterm/xterm@5.4.0/css/xterm.css"
|
|
28
|
+
/>
|
|
29
|
+
<link
|
|
30
|
+
href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🐍</text></svg>"
|
|
31
|
+
rel="icon"
|
|
32
|
+
/>
|
|
33
|
+
<script src="https://unpkg.com/@xterm/xterm@5.4.0/lib/xterm.js"></script>
|
|
34
|
+
<script src="https://unpkg.com/@xterm/addon-fit@0.9.0/lib/addon-fit.js"></script>
|
|
35
|
+
<style>
|
|
36
|
+
* {
|
|
37
|
+
margin: 0;
|
|
38
|
+
padding: 0;
|
|
39
|
+
box-sizing: border-box;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
html,
|
|
43
|
+
body {
|
|
44
|
+
margin: 0;
|
|
45
|
+
background-color: #000000;
|
|
46
|
+
font-family: "Monaco", "Menlo", "Courier New", monospace;
|
|
47
|
+
overflow: hidden;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
#terminal {
|
|
51
|
+
position: fixed;
|
|
52
|
+
inset: 10px;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
#loading {
|
|
56
|
+
display: inline-block;
|
|
57
|
+
width: 50px;
|
|
58
|
+
height: 50px;
|
|
59
|
+
position: fixed;
|
|
60
|
+
top: 50%;
|
|
61
|
+
left: 50%;
|
|
62
|
+
border: 3px solid rgba(172, 237, 255, 0.5);
|
|
63
|
+
border-radius: 50%;
|
|
64
|
+
border-top-color: #fff;
|
|
65
|
+
animation: spin 1s ease-in-out infinite;
|
|
66
|
+
-webkit-animation: spin 1s ease-in-out infinite;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@keyframes spin {
|
|
70
|
+
to {
|
|
71
|
+
-webkit-transform: rotate(360deg);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
@-webkit-keyframes spin {
|
|
75
|
+
to {
|
|
76
|
+
-webkit-transform: rotate(360deg);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
</style>
|
|
80
|
+
</head>
|
|
81
|
+
<body>
|
|
82
|
+
<div id="loading"></div>
|
|
83
|
+
<div id="terminal"></div>
|
|
84
|
+
<script type="module">
|
|
85
|
+
async function main() {
|
|
86
|
+
const fitAddon = new FitAddon.FitAddon();
|
|
87
|
+
const term = new Terminal({
|
|
88
|
+
cursorBlink: true,
|
|
89
|
+
cursorStyle: "block",
|
|
90
|
+
convertEol: true,
|
|
91
|
+
scrollback: 2_000,
|
|
92
|
+
fontSize: 18,
|
|
93
|
+
lineHeight: 1.4,
|
|
94
|
+
fontFamily: "monospace",
|
|
95
|
+
theme: {
|
|
96
|
+
background: "#000000",
|
|
97
|
+
foreground: "rgba(255, 255, 255, 0.8)",
|
|
98
|
+
cursor: "rgba(255, 255, 255, 0.8)",
|
|
99
|
+
selection: "#404040",
|
|
100
|
+
error: "#ff0000",
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
window.term = term;
|
|
104
|
+
|
|
105
|
+
term.open(document.getElementById("terminal"));
|
|
106
|
+
term.loadAddon(fitAddon);
|
|
107
|
+
|
|
108
|
+
fitAddon.fit();
|
|
109
|
+
term.focus();
|
|
110
|
+
|
|
111
|
+
window.addEventListener("resize", () => {
|
|
112
|
+
setTimeout(() => fitAddon.fit(), 50);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Re-fit after the page has fully loaded
|
|
116
|
+
window.addEventListener("load", () => {
|
|
117
|
+
setTimeout(() => fitAddon.fit(), 100);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Initialize Pyodide
|
|
121
|
+
let indexURL = "./";
|
|
122
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
123
|
+
const buildParam = urlParams.get("build");
|
|
124
|
+
if (buildParam && ["full", "debug", "pyc"].includes(buildParam)) {
|
|
125
|
+
indexURL = indexURL.replace("/full/", "/" + buildParam + "/");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const { loadPyodide } = await import(indexURL + "pyodide.mjs");
|
|
129
|
+
const pyodide = await loadPyodide();
|
|
130
|
+
globalThis.pyodide = pyodide;
|
|
131
|
+
|
|
132
|
+
// Hide loading spinner
|
|
133
|
+
document.getElementById("loading").style.display = "none";
|
|
134
|
+
|
|
135
|
+
const { repr_shorten, BANNER, PyodideConsole } =
|
|
136
|
+
pyodide.pyimport("pyodide.console");
|
|
137
|
+
|
|
138
|
+
term.writeln(
|
|
139
|
+
`Welcome to the Pyodide ${pyodide.version} terminal emulator 🐍\n${BANNER}`
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
const pyconsole = PyodideConsole(pyodide.globals);
|
|
143
|
+
|
|
144
|
+
const namespace = pyodide.globals.get("dict")();
|
|
145
|
+
const await_fut = pyodide.runPython(
|
|
146
|
+
`
|
|
147
|
+
import builtins
|
|
148
|
+
from pyodide.ffi import to_js
|
|
149
|
+
async def await_fut(fut):
|
|
150
|
+
res = await fut
|
|
151
|
+
if res is not None:
|
|
152
|
+
builtins._ = res
|
|
153
|
+
return to_js([res], depth=1)
|
|
154
|
+
await_fut
|
|
155
|
+
`,
|
|
156
|
+
{ globals: namespace }
|
|
157
|
+
);
|
|
158
|
+
namespace.destroy();
|
|
159
|
+
|
|
160
|
+
pyconsole.stdout_callback = (s) => term.write(s);
|
|
161
|
+
pyconsole.stderr_callback = (s) => term.write(`\x1b[31m${s}\x1b[0m`);
|
|
162
|
+
|
|
163
|
+
// Handle fatal errors
|
|
164
|
+
pyodide._api.on_fatal = async (e) => {
|
|
165
|
+
if (e.name === "Exit") {
|
|
166
|
+
term.write(`\x1b[31m${e}\x1b[0m\r\n`);
|
|
167
|
+
term.write(
|
|
168
|
+
"\x1b[31mPyodide exited and can no longer be used.\x1b[0m\r\n"
|
|
169
|
+
);
|
|
170
|
+
} else {
|
|
171
|
+
term.write(
|
|
172
|
+
"\x1b[31mPyodide has suffered a fatal error. Please report this to the Pyodide maintainers.\x1b[0m\r\n"
|
|
173
|
+
);
|
|
174
|
+
term.write("\x1b[31mThe cause of the fatal error was:\x1b[0m\r\n");
|
|
175
|
+
term.write(`\x1b[31m${e.message || e}\x1b[0m\r\n`);
|
|
176
|
+
term.write(
|
|
177
|
+
"\x1b[31mLook in the browser console for more details.\x1b[0m\r\n"
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// REPL implementation
|
|
183
|
+
const ps1 = ">>> ";
|
|
184
|
+
const ps2 = "... ";
|
|
185
|
+
let buffer = "";
|
|
186
|
+
let cursorIndex = 0; // index within buffer for in-line editing
|
|
187
|
+
let prompt = ps1;
|
|
188
|
+
const history = [];
|
|
189
|
+
let historyIndex = null; // null means not navigating history
|
|
190
|
+
|
|
191
|
+
term.write(prompt);
|
|
192
|
+
|
|
193
|
+
function addToHistory(command) {
|
|
194
|
+
const trimmed = command.trimEnd();
|
|
195
|
+
if (!trimmed) return;
|
|
196
|
+
const last = history[history.length - 1];
|
|
197
|
+
if (last !== trimmed) history.push(trimmed);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function refreshLine() {
|
|
201
|
+
// Write left part, save cursor, write right part, clear, restore cursor.
|
|
202
|
+
const clearCommand = "\x1b[0K";
|
|
203
|
+
const leftPart = prompt + buffer.slice(0, cursorIndex);
|
|
204
|
+
const rightPart = buffer.slice(cursorIndex);
|
|
205
|
+
term.write(
|
|
206
|
+
`\x1b[0G${leftPart}\x1b[s${rightPart}${clearCommand}\x1b[u`
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function setBuffer(newBuffer, newCursorIndex = null) {
|
|
211
|
+
buffer = newBuffer;
|
|
212
|
+
if (newCursorIndex === null) {
|
|
213
|
+
cursorIndex = buffer.length;
|
|
214
|
+
} else {
|
|
215
|
+
cursorIndex = Math.max(0, Math.min(newCursorIndex, buffer.length));
|
|
216
|
+
}
|
|
217
|
+
refreshLine();
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async function execLine(line) {
|
|
221
|
+
// Normalize non-breaking spaces to regular spaces
|
|
222
|
+
line = line.replace(/\u00a0/g, " ");
|
|
223
|
+
// clear the terminal
|
|
224
|
+
if (line === "clear") {
|
|
225
|
+
term.clear();
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const fut = pyconsole.push(line);
|
|
230
|
+
|
|
231
|
+
switch (fut.syntax_check) {
|
|
232
|
+
case "syntax-error":
|
|
233
|
+
term.write(`\x1b[31m${fut.formatted_error.trimEnd()}\x1b[0m`);
|
|
234
|
+
term.write("\r\n");
|
|
235
|
+
prompt = ps1;
|
|
236
|
+
addToHistory(line);
|
|
237
|
+
historyIndex = null;
|
|
238
|
+
fut.destroy();
|
|
239
|
+
break;
|
|
240
|
+
case "incomplete":
|
|
241
|
+
prompt = ps2;
|
|
242
|
+
addToHistory(line);
|
|
243
|
+
historyIndex = null;
|
|
244
|
+
return;
|
|
245
|
+
case "complete":
|
|
246
|
+
prompt = ps1;
|
|
247
|
+
try {
|
|
248
|
+
const wrapped = await_fut(fut);
|
|
249
|
+
const [value] = await wrapped;
|
|
250
|
+
if (value !== undefined) {
|
|
251
|
+
const output = repr_shorten.callKwargs(value, {
|
|
252
|
+
separator: "\n<long output truncated>\n",
|
|
253
|
+
});
|
|
254
|
+
term.write(output);
|
|
255
|
+
term.write("\r\n");
|
|
256
|
+
}
|
|
257
|
+
if (value instanceof pyodide.ffi.PyProxy) value.destroy();
|
|
258
|
+
wrapped.destroy();
|
|
259
|
+
} catch (e) {
|
|
260
|
+
const msg = fut.formatted_error || e.message;
|
|
261
|
+
term.write(`\x1b[31m${String(msg).trimEnd()}\x1b[0m`);
|
|
262
|
+
term.write("\r\n");
|
|
263
|
+
} finally {
|
|
264
|
+
fut.destroy();
|
|
265
|
+
}
|
|
266
|
+
addToHistory(line);
|
|
267
|
+
historyIndex = null;
|
|
268
|
+
break;
|
|
269
|
+
default:
|
|
270
|
+
term.write(
|
|
271
|
+
`\r\nUnexpected syntax_check value: ${fut.syntax_check}`
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
term.onData(async (data) => {
|
|
277
|
+
switch (data) {
|
|
278
|
+
case "\r": // Enter
|
|
279
|
+
term.write("\r\n");
|
|
280
|
+
await execLine(buffer);
|
|
281
|
+
buffer = "";
|
|
282
|
+
cursorIndex = 0;
|
|
283
|
+
term.write(prompt);
|
|
284
|
+
break;
|
|
285
|
+
case "\u0003": // Ctrl-C
|
|
286
|
+
pyconsole.buffer.clear();
|
|
287
|
+
buffer = "";
|
|
288
|
+
cursorIndex = 0;
|
|
289
|
+
term.write("^C\r\nKeyboardInterrupt\r\n" + ps1);
|
|
290
|
+
prompt = ps1;
|
|
291
|
+
historyIndex = null;
|
|
292
|
+
break;
|
|
293
|
+
case "\u0016": // Ctrl-V
|
|
294
|
+
// paste from clipboard
|
|
295
|
+
const clipboard = await navigator.clipboard.readText();
|
|
296
|
+
const newBuf =
|
|
297
|
+
buffer.slice(0, cursorIndex) +
|
|
298
|
+
clipboard +
|
|
299
|
+
buffer.slice(cursorIndex);
|
|
300
|
+
setBuffer(newBuf, newBuf.length);
|
|
301
|
+
break;
|
|
302
|
+
case "\u007F": // Backspace
|
|
303
|
+
if (cursorIndex > 0) {
|
|
304
|
+
const before = buffer.slice(0, cursorIndex - 1);
|
|
305
|
+
const after = buffer.slice(cursorIndex);
|
|
306
|
+
cursorIndex -= 1;
|
|
307
|
+
setBuffer(before + after, cursorIndex);
|
|
308
|
+
}
|
|
309
|
+
break;
|
|
310
|
+
case "\x1B[A": // Up arrow
|
|
311
|
+
if (prompt === ps1) {
|
|
312
|
+
if (historyIndex === null) historyIndex = history.length;
|
|
313
|
+
if (historyIndex > 0) {
|
|
314
|
+
historyIndex -= 1;
|
|
315
|
+
const newBuf = history[historyIndex] || "";
|
|
316
|
+
setBuffer(newBuf, newBuf.length);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
break;
|
|
320
|
+
case "\x1B[B": // Down arrow
|
|
321
|
+
if (prompt === ps1 && historyIndex !== null) {
|
|
322
|
+
if (historyIndex < history.length - 1) {
|
|
323
|
+
historyIndex += 1;
|
|
324
|
+
const newBuf = history[historyIndex] || "";
|
|
325
|
+
setBuffer(newBuf, newBuf.length);
|
|
326
|
+
} else {
|
|
327
|
+
historyIndex = null;
|
|
328
|
+
setBuffer("", 0);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
break;
|
|
332
|
+
case "\x1B[C": // Right arrow
|
|
333
|
+
if (cursorIndex < buffer.length) {
|
|
334
|
+
cursorIndex += 1;
|
|
335
|
+
refreshLine();
|
|
336
|
+
}
|
|
337
|
+
break;
|
|
338
|
+
case "\x1B[D": // Left arrow
|
|
339
|
+
if (cursorIndex > 0) {
|
|
340
|
+
cursorIndex -= 1;
|
|
341
|
+
refreshLine();
|
|
342
|
+
}
|
|
343
|
+
break;
|
|
344
|
+
default:
|
|
345
|
+
if (data) {
|
|
346
|
+
// Normalize non-breaking spaces to regular spaces
|
|
347
|
+
data = data.replace(/\u00a0/g, " ");
|
|
348
|
+
// Insert arbitrary string at cursor position
|
|
349
|
+
const before = buffer.slice(0, cursorIndex);
|
|
350
|
+
const after = buffer.slice(cursorIndex);
|
|
351
|
+
const newBuf = before + data + after;
|
|
352
|
+
const newCursor = cursorIndex + data.length;
|
|
353
|
+
setBuffer(newBuf, newCursor);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
// 4. Extra features
|
|
359
|
+
let idbkvPromise;
|
|
360
|
+
async function getIDBKV() {
|
|
361
|
+
if (!idbkvPromise) {
|
|
362
|
+
idbkvPromise = await import(
|
|
363
|
+
"https://unpkg.com/idb-keyval@5.0.2/dist/esm/index.js"
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
return idbkvPromise;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
async function mountDirectory(pyodideDirectory, directoryKey) {
|
|
370
|
+
if (pyodide.FS.analyzePath(pyodideDirectory).exists) {
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
const { get, set } = await getIDBKV();
|
|
374
|
+
const opts = { id: "mountdirid", mode: "readwrite" };
|
|
375
|
+
let directoryHandle = await get(directoryKey);
|
|
376
|
+
if (!directoryHandle) {
|
|
377
|
+
directoryHandle = await showDirectoryPicker(opts);
|
|
378
|
+
await set(directoryKey, directoryHandle);
|
|
379
|
+
}
|
|
380
|
+
const permissionStatus = await directoryHandle.requestPermission(
|
|
381
|
+
opts
|
|
382
|
+
);
|
|
383
|
+
if (permissionStatus !== "granted") {
|
|
384
|
+
throw new Error("readwrite access to directory not granted");
|
|
385
|
+
}
|
|
386
|
+
await pyodide.mountNativeFS(pyodideDirectory, directoryHandle);
|
|
387
|
+
}
|
|
388
|
+
globalThis.mountDirectory = mountDirectory;
|
|
389
|
+
}
|
|
390
|
+
window.console_ready = main();
|
|
391
|
+
</script>
|
|
392
|
+
</body>
|
|
393
|
+
</html>
|