xtralab 0.2.0 → 0.3.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/README.md +125 -126
- package/lib/fileBrowser/commands.d.ts +2 -2
- package/lib/fileBrowser/commands.js +2 -2
- package/lib/fileBrowser/fileBrowser.js +1 -1
- package/lib/fileBrowser/icons.d.ts +4 -4
- package/lib/fileBrowser/icons.js +4 -4
- package/lib/fileBrowser/widget.d.ts +2 -2
- package/lib/fileBrowser/widget.js +2 -2
- package/lib/git/icons.d.ts +1 -1
- package/lib/git/icons.js +1 -1
- package/lib/launcher/dashboard.js +51 -1
- package/package.json +16 -2
- package/src/fileBrowser/commands.ts +2 -2
- package/src/fileBrowser/fileBrowser.tsx +1 -1
- package/src/fileBrowser/icons.ts +4 -4
- package/src/fileBrowser/widget.tsx +3 -3
- package/src/git/icons.ts +1 -1
- package/src/launcher/dashboard.tsx +107 -1
package/README.md
CHANGED
|
@@ -2,153 +2,152 @@
|
|
|
2
2
|
|
|
3
3
|
An opinionated JupyterLab meta-package.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
changes panel, and applies a quieter default workspace configuration. The
|
|
8
|
-
defaults are shipped under `etc/jupyter/labconfig` and the bundled
|
|
9
|
-
labextension is shipped under `share/jupyter/labextensions/xtralab`.
|
|
5
|
+
Bundles a curated set of extensions, a path-first file browser, a VS Code-style
|
|
6
|
+
git changes panel, an agent-focused launcher, and a quieter default workspace.
|
|
10
7
|
|
|
11
|
-
|
|
12
|
-
upstream Jupyter packages, while `xtralab` defines the default environment we
|
|
13
|
-
want out of the box.
|
|
8
|
+
## Install
|
|
14
9
|
|
|
15
|
-
|
|
10
|
+
```bash
|
|
11
|
+
pip install xtralab
|
|
12
|
+
```
|
|
16
13
|
|
|
17
|
-
|
|
14
|
+
## Usage
|
|
18
15
|
|
|
19
|
-
|
|
20
|
-
- JupyterLab 4.6+
|
|
21
|
-
- Git server integration (`jupyterlab-git` — backs the bundled changes panel)
|
|
22
|
-
- Quick file opening (`jupyterlab-quickopen`)
|
|
23
|
-
- Cursor styling helpers (`jupyterlab-cursor-light`, `jupyterlab-cursor-theme`)
|
|
24
|
-
- Day and night themes (`jupyterlab-day`, `jupyterlab-night`)
|
|
25
|
-
|
|
26
|
-
These are installed as normal Python dependencies, so deployments can still
|
|
27
|
-
override versions and Jupyter configuration in the usual ways.
|
|
28
|
-
|
|
29
|
-
## Bundled labextension
|
|
30
|
-
|
|
31
|
-
`xtralab` ships its own prebuilt labextension that contributes:
|
|
32
|
-
|
|
33
|
-
- A path-first file browser in the left sidebar, built on `@pierre/trees`.
|
|
34
|
-
- A VS Code-style "Source Control" panel in the left sidebar, powered by the
|
|
35
|
-
`jupyterlab-git` server REST API and `@pierre/diffs`. The bundled
|
|
36
|
-
`@jupyterlab/git` frontend is disabled automatically so the two panels do
|
|
37
|
-
not coexist.
|
|
38
|
-
- An agent-focused launcher that replaces the stock JupyterLab launcher.
|
|
39
|
-
The launcher tab shows an optional initial-prompt textarea, a row of
|
|
40
|
-
agent buttons (Claude, Codex, Gemini, Copilot, Goose, OpenCode, Kiro,
|
|
41
|
-
and Mistral Vibe), and a collapsible list of changed files (each
|
|
42
|
-
clickable into the diff viewer) below them. Clicking an agent opens a
|
|
43
|
-
fresh terminal and runs the agent's CLI; if the prompt textarea is
|
|
44
|
-
non-empty, the prompt is shell-quoted and spliced into the launch line
|
|
45
|
-
for agents that accept one. Agents are filtered by a `which`-based
|
|
46
|
-
availability check at activation, so only the ones actually installed
|
|
47
|
-
on the machine show up.
|
|
48
|
-
|
|
49
|
-
### Customizing the launcher
|
|
50
|
-
|
|
51
|
-
The launcher reads `xtralab:launcher` settings from the JupyterLab settings
|
|
52
|
-
registry. Open the Settings Editor (`Settings → Settings Editor → xtralab
|
|
53
|
-
launcher`) and edit the `agents` array; xtralab merges your entries with
|
|
54
|
-
the defaults by `id`.
|
|
55
|
-
|
|
56
|
-
```jsonc
|
|
57
|
-
{
|
|
58
|
-
"agents": [
|
|
59
|
-
// Hide an agent you never want to see, even if it's installed:
|
|
60
|
-
{ "id": "kiro", "enabled": false },
|
|
16
|
+
### As a JupyterLab package
|
|
61
17
|
|
|
62
|
-
|
|
63
|
-
// `requireAvailable: false` keeps the card visible even though
|
|
64
|
-
// `which cl` returns nothing (interactive shells expand the alias).
|
|
65
|
-
{ "id": "claude", "command": "cl", "requireAvailable": false },
|
|
18
|
+
Run JupyterLab the usual way:
|
|
66
19
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
// launcher's prompt textarea is appended as a positional argument.
|
|
70
|
-
{
|
|
71
|
-
"id": "aider",
|
|
72
|
-
"label": "Aider",
|
|
73
|
-
"command": "aider",
|
|
74
|
-
"promptArgs": []
|
|
75
|
-
}
|
|
76
|
-
]
|
|
77
|
-
}
|
|
20
|
+
```bash
|
|
21
|
+
jupyter lab
|
|
78
22
|
```
|
|
79
23
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
| Field | Effect |
|
|
83
|
-
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
84
|
-
| `id` | Built-in id to override, or a new id to define a new card. |
|
|
85
|
-
| `label` | Card title. |
|
|
86
|
-
| `caption` | Subtitle shown beneath the title. |
|
|
87
|
-
| `command` | Literal text typed into the new terminal. |
|
|
88
|
-
| `iconSvg` | Inline SVG for the card icon (only needed for new agents). |
|
|
89
|
-
| `rank` | Sort order on the launcher; lower comes first. |
|
|
90
|
-
| `enabled` | `false` hides the card from the launcher and palette. |
|
|
91
|
-
| `requireAvailable` | `false` skips the `which` check (use for shell aliases that aren't on PATH but expand at runtime). |
|
|
92
|
-
| `promptArgs` | How to splice the launcher's optional prompt into the command. `[]` appends it as a positional argument (`<command> 'PROMPT'`); `["-i"]` or `["--prompt"]` puts it after a flag. Set to `null` to opt out — the agent button is dimmed when the prompt textarea is non-empty so a typed prompt is never silently dropped. Defaults vary per agent. |
|
|
93
|
-
|
|
94
|
-
## Default settings
|
|
95
|
-
|
|
96
|
-
`xtralab` ships the following JupyterLab defaults:
|
|
97
|
-
|
|
98
|
-
- The announcements plugin is disabled, so JupyterLab does not prompt to fetch
|
|
99
|
-
news from the Jupyter news feed and does not check for application updates.
|
|
100
|
-
- The Table of Contents, Debugger, and Notebook Tools panels are disabled so
|
|
101
|
-
they do not show up in the right sidebar by default.
|
|
102
|
-
- `dockPanelPadding` is off, so the main dock area is flush with the
|
|
103
|
-
surrounding chrome.
|
|
104
|
-
- The terminal `fontFamily` defaults to `MesloLGS NF, ui-monospace, monospace`
|
|
105
|
-
so Powerline / Nerd Font glyphs (used by Oh My Zsh themes such as
|
|
106
|
-
`powerlevel10k`) render correctly when the font is installed; the
|
|
107
|
-
`ui-monospace, monospace` fallback keeps things readable when it is not.
|
|
108
|
-
|
|
109
|
-
The JupyterLab frontend defaults are shipped as `labconfig/*.d/00-xtralab.json`
|
|
110
|
-
fragments so downstream meta-packages can add their own Lab configuration
|
|
111
|
-
fragments without replacing `xtralab`'s files.
|
|
24
|
+
### As a desktop app
|
|
112
25
|
|
|
113
|
-
|
|
26
|
+
A standalone Electron build (DMG on macOS, AppImage on Linux) ships with each
|
|
27
|
+
tagged release — grab the installer for your platform from the
|
|
28
|
+
[Releases page](https://github.com/jtpio/xtralab/releases/latest). Builds from
|
|
29
|
+
the current `main` branch are also produced on every push as workflow
|
|
30
|
+
artifacts under the repository's
|
|
31
|
+
[Actions tab](https://github.com/jtpio/xtralab/actions). See
|
|
32
|
+
[CONTRIBUTING.md](./CONTRIBUTING.md) for the architecture and local build
|
|
33
|
+
instructions.
|
|
114
34
|
|
|
115
|
-
|
|
116
|
-
pip install xtralab
|
|
117
|
-
```
|
|
35
|
+
## What's included
|
|
118
36
|
|
|
119
|
-
|
|
37
|
+
- [`ajlab`](https://github.com/jtpio/ajlab) — agent-ready JupyterLab base
|
|
38
|
+
- JupyterLab 4.6+
|
|
39
|
+
- `jupyterlab-git` — backs the bundled changes panel
|
|
40
|
+
- `jupyterlab-lsp` + `ty` — Python LSP via Astral's `ty` (bundled); also
|
|
41
|
+
detects `typescript-language-server` on `PATH` for JS/TS
|
|
42
|
+
- `jupyterlab-quickopen`
|
|
43
|
+
- `jupyterlab-cursor-light`, `jupyterlab-cursor-dark`
|
|
44
|
+
- `jupyterlab-day`, `jupyterlab-night` themes
|
|
45
|
+
|
|
46
|
+
The bundled labextension adds:
|
|
47
|
+
|
|
48
|
+
- A path-first file browser in the left sidebar.
|
|
49
|
+
- A "Source Control" panel powered by `jupyterlab-git` and `@pierre/diffs`.
|
|
50
|
+
- An agent launcher with a prompt textarea, a row of agent buttons (Claude,
|
|
51
|
+
Codex, Gemini, Copilot, Goose, OpenCode, Kiro, Mistral Vibe), and a
|
|
52
|
+
collapsible list of changed files. Buttons are filtered to agents installed
|
|
53
|
+
on the machine; a typed prompt is shell-quoted and spliced into the launch
|
|
54
|
+
command for agents that accept one.
|
|
55
|
+
|
|
56
|
+
## Language servers
|
|
57
|
+
|
|
58
|
+
xtralab ships with
|
|
59
|
+
[`jupyterlab-lsp`](https://github.com/jupyter-lsp/jupyterlab-lsp) and
|
|
60
|
+
pre-registers two language servers through
|
|
61
|
+
`jupyter_server_config.d/xtralab-lsp.json`:
|
|
62
|
+
|
|
63
|
+
- **Python — [`ty`](https://github.com/astral-sh/ty)** — installed as a
|
|
64
|
+
Python dependency. Works out of the box.
|
|
65
|
+
- **TypeScript / JavaScript — `typescript-language-server`** — install
|
|
66
|
+
yourself:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npm install -g typescript-language-server typescript
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Restart JupyterLab (or the desktop app) after installing.
|
|
73
|
+
|
|
74
|
+
Specs use bare command names (`["ty", "server"]`), so binaries are resolved
|
|
75
|
+
from `PATH` at spawn time.
|
|
76
|
+
|
|
77
|
+
### Where binaries are discovered
|
|
78
|
+
|
|
79
|
+
- **`pip install xtralab`** — anything on the JupyterLab process's `PATH`.
|
|
80
|
+
- **Desktop app** — the supervisor augments `PATH` with common shim
|
|
81
|
+
locations (`~/.volta/bin`, `~/.npm-global/bin`, `~/.bun/bin`,
|
|
82
|
+
`~/.asdf/shims`, `~/.mise/shims`, `/opt/homebrew/bin`, `/usr/local/bin`,
|
|
83
|
+
…). Set `XTRALAB_EXTRA_PATH` (colon-separated) before launching the app
|
|
84
|
+
to add directories the defaults miss.
|
|
85
|
+
|
|
86
|
+
### Adding more servers
|
|
87
|
+
|
|
88
|
+
To enable another server (bash, yaml, json, dockerfile, pyright, …),
|
|
89
|
+
install the binary then drop a JSON file into a `jupyter_server_config.d/`
|
|
90
|
+
directory:
|
|
91
|
+
|
|
92
|
+
- **`pip install xtralab`** — run `jupyter --paths` and pick a config dir
|
|
93
|
+
(typically `~/.jupyter/jupyter_server_config.d/`).
|
|
94
|
+
- **Desktop app (macOS)** —
|
|
95
|
+
`~/Library/Application Support/xtralab/jupyter/config/jupyter_server_config.d/`
|
|
96
|
+
(or `xtralab dev` for local dev builds).
|
|
97
|
+
- **Desktop app (Linux AppImage)** —
|
|
98
|
+
`~/.config/xtralab/jupyter/config/jupyter_server_config.d/`.
|
|
99
|
+
|
|
100
|
+
Example (`bash-lsp.json`):
|
|
101
|
+
|
|
102
|
+
```json
|
|
103
|
+
{
|
|
104
|
+
"LanguageServerManager": {
|
|
105
|
+
"language_servers": {
|
|
106
|
+
"bash-language-server": {
|
|
107
|
+
"version": 2,
|
|
108
|
+
"argv": ["bash-language-server", "start"],
|
|
109
|
+
"languages": ["bash", "sh"],
|
|
110
|
+
"mime_types": ["text/x-sh", "application/x-sh"],
|
|
111
|
+
"display_name": "bash-language-server"
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
```
|
|
120
117
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
118
|
+
Pair with `npm install -g bash-language-server`. Reuse a bundled `key` to
|
|
119
|
+
override it. See
|
|
120
|
+
[jupyterlab-lsp's docs](https://jupyterlab-lsp.readthedocs.io/en/latest/Configuring.html)
|
|
121
|
+
for the full spec schema.
|
|
124
122
|
|
|
125
|
-
|
|
123
|
+
## Customizing the launcher
|
|
126
124
|
|
|
127
|
-
|
|
125
|
+
Open `Settings → Settings Editor → xtralab launcher` and edit the `agents`
|
|
126
|
+
array. Entries are merged with the defaults by `id`.
|
|
128
127
|
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
|
|
128
|
+
```jsonc
|
|
129
|
+
{
|
|
130
|
+
"agents": [
|
|
131
|
+
// Hide an agent
|
|
132
|
+
{ "id": "kiro", "enabled": false },
|
|
132
133
|
|
|
133
|
-
|
|
134
|
+
// Override an agent's command (e.g. point Claude at a shell alias)
|
|
135
|
+
{ "id": "claude", "command": "cl", "requireAvailable": false },
|
|
134
136
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
+
// Add a new agent; promptArgs: [] appends the prompt as a positional arg
|
|
138
|
+
{ "id": "aider", "label": "Aider", "command": "aider", "promptArgs": [] }
|
|
139
|
+
]
|
|
140
|
+
}
|
|
137
141
|
```
|
|
138
142
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
shuts everything down cleanly.
|
|
143
|
+
Fields: `id` (required), `label`, `caption`, `command`, `promptArgs` (how to
|
|
144
|
+
splice the prompt — `[]` for positional, `["--flag"]` for flagged, `null` to
|
|
145
|
+
opt out), `iconSvg`, `rank`, `enabled`, `requireAvailable`.
|
|
143
146
|
|
|
144
|
-
##
|
|
147
|
+
## Contributing
|
|
145
148
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
uv pip install -e ".[dev]"
|
|
149
|
-
jlpm
|
|
150
|
-
jlpm build
|
|
151
|
-
```
|
|
149
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md) for the development setup, the
|
|
150
|
+
Electron desktop app architecture, and the build pipeline.
|
|
152
151
|
|
|
153
152
|
## License
|
|
154
153
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { JupyterFrontEnd } from '@jupyterlab/application';
|
|
2
2
|
import { IXtralabFileBrowser } from './widget';
|
|
3
3
|
/**
|
|
4
|
-
* Command identifiers exposed by the
|
|
4
|
+
* Command identifiers exposed by the xtralab browser. We deliberately namespace
|
|
5
5
|
* these under `xtralab:` rather than reusing the `filebrowser:` ids
|
|
6
6
|
* because the core `filebrowser:*` commands look up a `FileBrowser` instance
|
|
7
7
|
* via `IFileBrowserFactory.tracker` — our widget is not a `FileBrowser`, so
|
|
@@ -31,7 +31,7 @@ export interface IRegisterCommandsOptions {
|
|
|
31
31
|
browser: IXtralabFileBrowser;
|
|
32
32
|
}
|
|
33
33
|
/**
|
|
34
|
-
* Register every
|
|
34
|
+
* Register every xtralab command on the application command registry, attach
|
|
35
35
|
* the items to the application context menu, and wire up the dynamic
|
|
36
36
|
* "Open With" submenu populator.
|
|
37
37
|
*
|
|
@@ -3,7 +3,7 @@ import { PageConfig, PathExt } from '@jupyterlab/coreutils';
|
|
|
3
3
|
import { addIcon, closeIcon, CommandToolbarButton, copyIcon, downloadIcon, editIcon, fileIcon, newFolderIcon, refreshIcon } from '@jupyterlab/ui-components';
|
|
4
4
|
import { toCanonicalPath, toServerPath } from './contents';
|
|
5
5
|
/**
|
|
6
|
-
* Command identifiers exposed by the
|
|
6
|
+
* Command identifiers exposed by the xtralab browser. We deliberately namespace
|
|
7
7
|
* these under `xtralab:` rather than reusing the `filebrowser:` ids
|
|
8
8
|
* because the core `filebrowser:*` commands look up a `FileBrowser` instance
|
|
9
9
|
* via `IFileBrowserFactory.tracker` — our widget is not a `FileBrowser`, so
|
|
@@ -136,7 +136,7 @@ function makeOpenWithUpdater(app, browser) {
|
|
|
136
136
|
};
|
|
137
137
|
}
|
|
138
138
|
/**
|
|
139
|
-
* Register every
|
|
139
|
+
* Register every xtralab command on the application command registry, attach
|
|
140
140
|
* the items to the application context menu, and wire up the dynamic
|
|
141
141
|
* "Open With" submenu populator.
|
|
142
142
|
*
|
|
@@ -12,7 +12,7 @@ import { FILE_BROWSER_ICONS } from './icons';
|
|
|
12
12
|
* synchronous function that returns a Widget. The contents MIME types
|
|
13
13
|
* mirror what the default file browser sends so other drop targets that
|
|
14
14
|
* understand them (the file browser itself, custom drop zones) keep
|
|
15
|
-
* working when the user drags out of
|
|
15
|
+
* working when the user drags out of xtralab.
|
|
16
16
|
*/
|
|
17
17
|
const FACTORY_MIME = 'application/vnd.lumino.widget-factory';
|
|
18
18
|
const CONTENTS_MIME = 'application/x-jupyter-icontents';
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { LabIcon } from '@jupyterlab/ui-components';
|
|
2
2
|
import type { FileTreeIconConfig } from '@pierre/trees';
|
|
3
3
|
/**
|
|
4
|
-
* The glyph used as the
|
|
4
|
+
* The glyph used as the xtralab file browser's sidebar tab icon. Defined as a
|
|
5
5
|
* `LabIcon` (rather than the default `folderIcon` from
|
|
6
|
-
* `@jupyterlab/ui-components`) so the
|
|
6
|
+
* `@jupyterlab/ui-components`) so the xtralab browser is visually distinguishable
|
|
7
7
|
* from JupyterLab's built-in file browser, which also lives in the left
|
|
8
8
|
* sidebar.
|
|
9
9
|
*
|
|
@@ -14,7 +14,7 @@ import type { FileTreeIconConfig } from '@pierre/trees';
|
|
|
14
14
|
*/
|
|
15
15
|
export declare const xtralabFileBrowserIcon: LabIcon;
|
|
16
16
|
/**
|
|
17
|
-
* Icon configuration for the
|
|
17
|
+
* Icon configuration for the xtralab file browser. Keeps the default `complete`
|
|
18
18
|
* built-in icon set (the colored language icons) and layers a Jupyter notebook
|
|
19
19
|
* symbol on top so `.ipynb` files render with the JupyterLab notebook glyph.
|
|
20
20
|
*
|
|
@@ -24,7 +24,7 @@ export declare const xtralabFileBrowserIcon: LabIcon;
|
|
|
24
24
|
*/
|
|
25
25
|
export declare const FILE_BROWSER_ICONS: FileTreeIconConfig;
|
|
26
26
|
/**
|
|
27
|
-
* Return a JupyterLab `LabIcon` matching the icon the
|
|
27
|
+
* Return a JupyterLab `LabIcon` matching the icon the xtralab file tree would
|
|
28
28
|
* render for the given file path. Used by other plugins (e.g. the git diff
|
|
29
29
|
* viewer) that want their main-area widget tabs to display the same per-file
|
|
30
30
|
* glyph the tree shows in the sidebar.
|
package/lib/fileBrowser/icons.js
CHANGED
|
@@ -2,9 +2,9 @@ var _a;
|
|
|
2
2
|
import { LabIcon, fileIcon } from '@jupyterlab/ui-components';
|
|
3
3
|
import { createFileTreeIconResolver, getBuiltInFileIconColor, getBuiltInSpriteSheet } from '@pierre/trees';
|
|
4
4
|
/**
|
|
5
|
-
* The glyph used as the
|
|
5
|
+
* The glyph used as the xtralab file browser's sidebar tab icon. Defined as a
|
|
6
6
|
* `LabIcon` (rather than the default `folderIcon` from
|
|
7
|
-
* `@jupyterlab/ui-components`) so the
|
|
7
|
+
* `@jupyterlab/ui-components`) so the xtralab browser is visually distinguishable
|
|
8
8
|
* from JupyterLab's built-in file browser, which also lives in the left
|
|
9
9
|
* sidebar.
|
|
10
10
|
*
|
|
@@ -42,7 +42,7 @@ const JUPYTER_NOTEBOOK_SPRITE_SHEET = `<svg data-icon-sprite aria-hidden="true"
|
|
|
42
42
|
</symbol>
|
|
43
43
|
</svg>`;
|
|
44
44
|
/**
|
|
45
|
-
* Icon configuration for the
|
|
45
|
+
* Icon configuration for the xtralab file browser. Keeps the default `complete`
|
|
46
46
|
* built-in icon set (the colored language icons) and layers a Jupyter notebook
|
|
47
47
|
* symbol on top so `.ipynb` files render with the JupyterLab notebook glyph.
|
|
48
48
|
*
|
|
@@ -105,7 +105,7 @@ function extractSymbol(symbolId) {
|
|
|
105
105
|
};
|
|
106
106
|
}
|
|
107
107
|
/**
|
|
108
|
-
* Return a JupyterLab `LabIcon` matching the icon the
|
|
108
|
+
* Return a JupyterLab `LabIcon` matching the icon the xtralab file tree would
|
|
109
109
|
* render for the given file path. Used by other plugins (e.g. the git diff
|
|
110
110
|
* viewer) that want their main-area widget tabs to display the same per-file
|
|
111
111
|
* glyph the tree shows in the sidebar.
|
|
@@ -5,7 +5,7 @@ import { ISignal } from '@lumino/signaling';
|
|
|
5
5
|
import { Widget } from '@lumino/widgets';
|
|
6
6
|
export declare const FILE_BROWSER_ID = "xtralab:file-browser";
|
|
7
7
|
/**
|
|
8
|
-
* The CSS class added to the
|
|
8
|
+
* The CSS class added to the xtralab file browser widget. The selectors that
|
|
9
9
|
* bind application context menu items hang off this class, so it must remain
|
|
10
10
|
* specific enough not to clash with other browsers (such as the default
|
|
11
11
|
* `jp-FileBrowser`).
|
|
@@ -17,7 +17,7 @@ export interface IXtralabFileBrowserOptions {
|
|
|
17
17
|
onOpenFile?: (serverPath: string) => void;
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
|
-
* Public API of the
|
|
20
|
+
* Public API of the xtralab file browser used by command handlers and other
|
|
21
21
|
* collaborators. Kept narrow on purpose so the React component stays free to
|
|
22
22
|
* evolve the underlying tree integration.
|
|
23
23
|
*/
|
|
@@ -6,7 +6,7 @@ import { FileBrowserComponent } from './fileBrowser';
|
|
|
6
6
|
import { xtralabFileBrowserIcon } from './icons';
|
|
7
7
|
export const FILE_BROWSER_ID = 'xtralab:file-browser';
|
|
8
8
|
/**
|
|
9
|
-
* The CSS class added to the
|
|
9
|
+
* The CSS class added to the xtralab file browser widget. The selectors that
|
|
10
10
|
* bind application context menu items hang off this class, so it must remain
|
|
11
11
|
* specific enough not to clash with other browsers (such as the default
|
|
12
12
|
* `jp-FileBrowser`).
|
|
@@ -32,7 +32,7 @@ export class XtralabFileBrowser extends Widget {
|
|
|
32
32
|
this._onOpenFile = options.onOpenFile;
|
|
33
33
|
this.id = FILE_BROWSER_ID;
|
|
34
34
|
this.title.icon = xtralabFileBrowserIcon;
|
|
35
|
-
this.title.caption = '
|
|
35
|
+
this.title.caption = 'xtralab File Browser';
|
|
36
36
|
this.addClass(FILE_BROWSER_CSS_CLASS);
|
|
37
37
|
this._toolbar = new Toolbar();
|
|
38
38
|
this._toolbar.addClass(TOOLBAR_CSS_CLASS);
|
package/lib/git/icons.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { LabIcon } from '@jupyterlab/ui-components';
|
|
|
5
5
|
* recoloring and inverse-color sidebar painting automatically.
|
|
6
6
|
*
|
|
7
7
|
* Source: `git-branch` from SVG Repo (https://www.svgrepo.com/svg/377352/git-branch),
|
|
8
|
-
* part of the same Pixelarticons family as the
|
|
8
|
+
* part of the same Pixelarticons family as the xtralab file browser's `file-tree`
|
|
9
9
|
* icon, so the two sidebar tab glyphs read as a matched set. The original
|
|
10
10
|
* `fill="#000000"` is replaced with `fill="currentColor"` and the root element
|
|
11
11
|
* is given the `jp-icon3` class so the icon picks up JupyterLab's theme color
|
package/lib/git/icons.js
CHANGED
|
@@ -5,7 +5,7 @@ import { LabIcon } from '@jupyterlab/ui-components';
|
|
|
5
5
|
* recoloring and inverse-color sidebar painting automatically.
|
|
6
6
|
*
|
|
7
7
|
* Source: `git-branch` from SVG Repo (https://www.svgrepo.com/svg/377352/git-branch),
|
|
8
|
-
* part of the same Pixelarticons family as the
|
|
8
|
+
* part of the same Pixelarticons family as the xtralab file browser's `file-tree`
|
|
9
9
|
* icon, so the two sidebar tab glyphs read as a matched set. The original
|
|
10
10
|
* `fill="#000000"` is replaced with `fill="currentColor"` and the root element
|
|
11
11
|
* is given the `jp-icon3` class so the icon picks up JupyterLab's theme color
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { LabIcon, ReactWidget, refreshIcon } from '@jupyterlab/ui-components';
|
|
2
|
+
import { LabIcon, ReactWidget, consoleIcon, notebookIcon, refreshIcon, terminalIcon } from '@jupyterlab/ui-components';
|
|
3
3
|
import { expandStatusFiles, status } from '../git/api';
|
|
4
4
|
import { CommandIDs as GitCommandIDs } from '../git/commands';
|
|
5
5
|
import { agentCommandId } from './commands';
|
|
@@ -63,12 +63,40 @@ function LauncherDashboardComponent(props) {
|
|
|
63
63
|
onAgentLaunch(result);
|
|
64
64
|
}
|
|
65
65
|
}, [commands, cwd, prompt, onAgentLaunch]);
|
|
66
|
+
const launchTerminal = React.useCallback(async () => {
|
|
67
|
+
const result = (await commands.execute('terminal:create-new', {
|
|
68
|
+
cwd
|
|
69
|
+
}));
|
|
70
|
+
if (result) {
|
|
71
|
+
onAgentLaunch(result);
|
|
72
|
+
}
|
|
73
|
+
}, [commands, cwd, onAgentLaunch]);
|
|
74
|
+
const launchNotebook = React.useCallback(async () => {
|
|
75
|
+
// No `kernelName` — let the notebook extension fall back to the default
|
|
76
|
+
// kernel or prompt the user, matching the stock launcher's notebook
|
|
77
|
+
// tile behavior.
|
|
78
|
+
const result = (await commands.execute('notebook:create-new', {
|
|
79
|
+
cwd
|
|
80
|
+
}));
|
|
81
|
+
if (result) {
|
|
82
|
+
onAgentLaunch(result);
|
|
83
|
+
}
|
|
84
|
+
}, [commands, cwd, onAgentLaunch]);
|
|
85
|
+
const launchConsole = React.useCallback(async () => {
|
|
86
|
+
const result = (await commands.execute('console:create', {
|
|
87
|
+
cwd
|
|
88
|
+
}));
|
|
89
|
+
if (result) {
|
|
90
|
+
onAgentLaunch(result);
|
|
91
|
+
}
|
|
92
|
+
}, [commands, cwd, onAgentLaunch]);
|
|
66
93
|
const openDiff = React.useCallback((change) => {
|
|
67
94
|
const args = { repoPath, change };
|
|
68
95
|
void commands.execute(GitCommandIDs.openDiff, args);
|
|
69
96
|
}, [commands, repoPath]);
|
|
70
97
|
return (React.createElement("div", { className: "jp-xtralab-Launcher-body" },
|
|
71
98
|
React.createElement(AgentSection, { agents: agents, prompt: prompt, onPromptChange: setPrompt, onLaunch: launch }),
|
|
99
|
+
React.createElement(OpenSection, { onLaunchTerminal: launchTerminal, onLaunchNotebook: launchNotebook, onLaunchConsole: launchConsole }),
|
|
72
100
|
React.createElement(ChangesSection, { git: git, onOpen: openDiff, onRefresh: () => void refresh() })));
|
|
73
101
|
}
|
|
74
102
|
function ChangesSection(props) {
|
|
@@ -162,3 +190,25 @@ function AgentSection(props) {
|
|
|
162
190
|
React.createElement("span", { className: "jp-xtralab-Launcher-agent-label" }, agent.label)));
|
|
163
191
|
}))));
|
|
164
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* The non-agent launch tiles — a plain terminal, a new notebook, and a
|
|
195
|
+
* new console. They sit in a separate section below the agent grid so
|
|
196
|
+
* they don't compete with the AI agents visually, and they're always
|
|
197
|
+
* available regardless of the agent prompt textarea (which doesn't
|
|
198
|
+
* apply to them).
|
|
199
|
+
*/
|
|
200
|
+
function OpenSection(props) {
|
|
201
|
+
const { onLaunchTerminal, onLaunchNotebook, onLaunchConsole } = props;
|
|
202
|
+
return (React.createElement("section", { className: "jp-xtralab-Launcher-section" },
|
|
203
|
+
React.createElement("h2", { className: "jp-xtralab-Launcher-section-title" }, "Open"),
|
|
204
|
+
React.createElement("div", { className: "jp-xtralab-Launcher-agents" },
|
|
205
|
+
React.createElement("button", { type: "button", className: "jp-xtralab-Launcher-agent", title: "Open a new terminal.", "aria-label": "Open a new terminal", onClick: onLaunchTerminal },
|
|
206
|
+
React.createElement(LabIcon.resolveReact, { icon: terminalIcon, tag: "span", className: "jp-xtralab-Launcher-agent-icon" }),
|
|
207
|
+
React.createElement("span", { className: "jp-xtralab-Launcher-agent-label" }, "Terminal")),
|
|
208
|
+
React.createElement("button", { type: "button", className: "jp-xtralab-Launcher-agent", title: "Create a new notebook.", "aria-label": "Create a new notebook", onClick: onLaunchNotebook },
|
|
209
|
+
React.createElement(LabIcon.resolveReact, { icon: notebookIcon, tag: "span", className: "jp-xtralab-Launcher-agent-icon" }),
|
|
210
|
+
React.createElement("span", { className: "jp-xtralab-Launcher-agent-label" }, "Notebook")),
|
|
211
|
+
React.createElement("button", { type: "button", className: "jp-xtralab-Launcher-agent", title: "Create a new console.", "aria-label": "Create a new console", onClick: onLaunchConsole },
|
|
212
|
+
React.createElement(LabIcon.resolveReact, { icon: consoleIcon, tag: "span", className: "jp-xtralab-Launcher-agent-icon" }),
|
|
213
|
+
React.createElement("span", { className: "jp-xtralab-Launcher-agent-label" }, "Console")))));
|
|
214
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xtralab",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "An opinionated JupyterLab meta-package that bundles a curated set of extensions, ships a path-first file browser and a VS Code-style git changes panel, and applies a quieter default workspace configuration.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jupyter",
|
|
@@ -145,12 +145,26 @@
|
|
|
145
145
|
],
|
|
146
146
|
"parser": "@typescript-eslint/parser",
|
|
147
147
|
"parserOptions": {
|
|
148
|
-
"project":
|
|
148
|
+
"project": [
|
|
149
|
+
"tsconfig.json",
|
|
150
|
+
"desktop/tsconfig.json",
|
|
151
|
+
"desktop/tsconfig.forge.json"
|
|
152
|
+
],
|
|
149
153
|
"sourceType": "module"
|
|
150
154
|
},
|
|
151
155
|
"plugins": [
|
|
152
156
|
"@typescript-eslint"
|
|
153
157
|
],
|
|
158
|
+
"overrides": [
|
|
159
|
+
{
|
|
160
|
+
"files": [
|
|
161
|
+
"desktop/src/**/*.ts"
|
|
162
|
+
],
|
|
163
|
+
"rules": {
|
|
164
|
+
"@typescript-eslint/naming-convention": "off"
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
],
|
|
154
168
|
"rules": {
|
|
155
169
|
"@typescript-eslint/naming-convention": [
|
|
156
170
|
"error",
|
|
@@ -27,7 +27,7 @@ import { toCanonicalPath, toServerPath } from './contents';
|
|
|
27
27
|
import { IXtralabFileBrowser } from './widget';
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
|
-
* Command identifiers exposed by the
|
|
30
|
+
* Command identifiers exposed by the xtralab browser. We deliberately namespace
|
|
31
31
|
* these under `xtralab:` rather than reusing the `filebrowser:` ids
|
|
32
32
|
* because the core `filebrowser:*` commands look up a `FileBrowser` instance
|
|
33
33
|
* via `IFileBrowserFactory.tracker` — our widget is not a `FileBrowser`, so
|
|
@@ -198,7 +198,7 @@ function makeOpenWithUpdater(
|
|
|
198
198
|
}
|
|
199
199
|
|
|
200
200
|
/**
|
|
201
|
-
* Register every
|
|
201
|
+
* Register every xtralab command on the application command registry, attach
|
|
202
202
|
* the items to the application context menu, and wire up the dynamic
|
|
203
203
|
* "Open With" submenu populator.
|
|
204
204
|
*
|
|
@@ -32,7 +32,7 @@ type LoadState = 'unloaded' | 'loading' | 'loaded';
|
|
|
32
32
|
* synchronous function that returns a Widget. The contents MIME types
|
|
33
33
|
* mirror what the default file browser sends so other drop targets that
|
|
34
34
|
* understand them (the file browser itself, custom drop zones) keep
|
|
35
|
-
* working when the user drags out of
|
|
35
|
+
* working when the user drags out of xtralab.
|
|
36
36
|
*/
|
|
37
37
|
const FACTORY_MIME = 'application/vnd.lumino.widget-factory';
|
|
38
38
|
const CONTENTS_MIME = 'application/x-jupyter-icontents';
|
package/src/fileBrowser/icons.ts
CHANGED
|
@@ -7,9 +7,9 @@ import {
|
|
|
7
7
|
import type { FileTreeIconConfig } from '@pierre/trees';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* The glyph used as the
|
|
10
|
+
* The glyph used as the xtralab file browser's sidebar tab icon. Defined as a
|
|
11
11
|
* `LabIcon` (rather than the default `folderIcon` from
|
|
12
|
-
* `@jupyterlab/ui-components`) so the
|
|
12
|
+
* `@jupyterlab/ui-components`) so the xtralab browser is visually distinguishable
|
|
13
13
|
* from JupyterLab's built-in file browser, which also lives in the left
|
|
14
14
|
* sidebar.
|
|
15
15
|
*
|
|
@@ -49,7 +49,7 @@ const JUPYTER_NOTEBOOK_SPRITE_SHEET = `<svg data-icon-sprite aria-hidden="true"
|
|
|
49
49
|
</svg>`;
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
|
-
* Icon configuration for the
|
|
52
|
+
* Icon configuration for the xtralab file browser. Keeps the default `complete`
|
|
53
53
|
* built-in icon set (the colored language icons) and layers a Jupyter notebook
|
|
54
54
|
* symbol on top so `.ipynb` files render with the JupyterLab notebook glyph.
|
|
55
55
|
*
|
|
@@ -121,7 +121,7 @@ function extractSymbol(
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
/**
|
|
124
|
-
* Return a JupyterLab `LabIcon` matching the icon the
|
|
124
|
+
* Return a JupyterLab `LabIcon` matching the icon the xtralab file tree would
|
|
125
125
|
* render for the given file path. Used by other plugins (e.g. the git diff
|
|
126
126
|
* viewer) that want their main-area widget tabs to display the same per-file
|
|
127
127
|
* glyph the tree shows in the sidebar.
|
|
@@ -12,7 +12,7 @@ import { xtralabFileBrowserIcon } from './icons';
|
|
|
12
12
|
export const FILE_BROWSER_ID = 'xtralab:file-browser';
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* The CSS class added to the
|
|
15
|
+
* The CSS class added to the xtralab file browser widget. The selectors that
|
|
16
16
|
* bind application context menu items hang off this class, so it must remain
|
|
17
17
|
* specific enough not to clash with other browsers (such as the default
|
|
18
18
|
* `jp-FileBrowser`).
|
|
@@ -29,7 +29,7 @@ export interface IXtralabFileBrowserOptions {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
|
-
* Public API of the
|
|
32
|
+
* Public API of the xtralab file browser used by command handlers and other
|
|
33
33
|
* collaborators. Kept narrow on purpose so the React component stays free to
|
|
34
34
|
* evolve the underlying tree integration.
|
|
35
35
|
*/
|
|
@@ -100,7 +100,7 @@ export class XtralabFileBrowser extends Widget implements IXtralabFileBrowser {
|
|
|
100
100
|
this._onOpenFile = options.onOpenFile;
|
|
101
101
|
this.id = FILE_BROWSER_ID;
|
|
102
102
|
this.title.icon = xtralabFileBrowserIcon;
|
|
103
|
-
this.title.caption = '
|
|
103
|
+
this.title.caption = 'xtralab File Browser';
|
|
104
104
|
this.addClass(FILE_BROWSER_CSS_CLASS);
|
|
105
105
|
|
|
106
106
|
this._toolbar = new Toolbar();
|
package/src/git/icons.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { LabIcon } from '@jupyterlab/ui-components';
|
|
|
6
6
|
* recoloring and inverse-color sidebar painting automatically.
|
|
7
7
|
*
|
|
8
8
|
* Source: `git-branch` from SVG Repo (https://www.svgrepo.com/svg/377352/git-branch),
|
|
9
|
-
* part of the same Pixelarticons family as the
|
|
9
|
+
* part of the same Pixelarticons family as the xtralab file browser's `file-tree`
|
|
10
10
|
* icon, so the two sidebar tab glyphs read as a matched set. The original
|
|
11
11
|
* `fill="#000000"` is replaced with `fill="currentColor"` and the root element
|
|
12
12
|
* is given the `jp-icon3` class so the icon picks up JupyterLab's theme color
|
|
@@ -2,7 +2,14 @@ import * as React from 'react';
|
|
|
2
2
|
|
|
3
3
|
import type { CommandRegistry } from '@lumino/commands';
|
|
4
4
|
import type { ReadonlyPartialJSONObject } from '@lumino/coreutils';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
LabIcon,
|
|
7
|
+
ReactWidget,
|
|
8
|
+
consoleIcon,
|
|
9
|
+
notebookIcon,
|
|
10
|
+
refreshIcon,
|
|
11
|
+
terminalIcon
|
|
12
|
+
} from '@jupyterlab/ui-components';
|
|
6
13
|
import type { Widget } from '@lumino/widgets';
|
|
7
14
|
|
|
8
15
|
import { expandStatusFiles, status } from '../git/api';
|
|
@@ -133,6 +140,36 @@ function LauncherDashboardComponent(
|
|
|
133
140
|
[commands, cwd, prompt, onAgentLaunch]
|
|
134
141
|
);
|
|
135
142
|
|
|
143
|
+
const launchTerminal = React.useCallback(async () => {
|
|
144
|
+
const result = (await commands.execute('terminal:create-new', {
|
|
145
|
+
cwd
|
|
146
|
+
})) as Widget | undefined;
|
|
147
|
+
if (result) {
|
|
148
|
+
onAgentLaunch(result);
|
|
149
|
+
}
|
|
150
|
+
}, [commands, cwd, onAgentLaunch]);
|
|
151
|
+
|
|
152
|
+
const launchNotebook = React.useCallback(async () => {
|
|
153
|
+
// No `kernelName` — let the notebook extension fall back to the default
|
|
154
|
+
// kernel or prompt the user, matching the stock launcher's notebook
|
|
155
|
+
// tile behavior.
|
|
156
|
+
const result = (await commands.execute('notebook:create-new', {
|
|
157
|
+
cwd
|
|
158
|
+
})) as Widget | undefined;
|
|
159
|
+
if (result) {
|
|
160
|
+
onAgentLaunch(result);
|
|
161
|
+
}
|
|
162
|
+
}, [commands, cwd, onAgentLaunch]);
|
|
163
|
+
|
|
164
|
+
const launchConsole = React.useCallback(async () => {
|
|
165
|
+
const result = (await commands.execute('console:create', {
|
|
166
|
+
cwd
|
|
167
|
+
})) as Widget | undefined;
|
|
168
|
+
if (result) {
|
|
169
|
+
onAgentLaunch(result);
|
|
170
|
+
}
|
|
171
|
+
}, [commands, cwd, onAgentLaunch]);
|
|
172
|
+
|
|
136
173
|
const openDiff = React.useCallback(
|
|
137
174
|
(change: IFileChange) => {
|
|
138
175
|
const args: GitCommandArguments.IOpenDiff = { repoPath, change };
|
|
@@ -152,6 +189,11 @@ function LauncherDashboardComponent(
|
|
|
152
189
|
onPromptChange={setPrompt}
|
|
153
190
|
onLaunch={launch}
|
|
154
191
|
/>
|
|
192
|
+
<OpenSection
|
|
193
|
+
onLaunchTerminal={launchTerminal}
|
|
194
|
+
onLaunchNotebook={launchNotebook}
|
|
195
|
+
onLaunchConsole={launchConsole}
|
|
196
|
+
/>
|
|
155
197
|
<ChangesSection
|
|
156
198
|
git={git}
|
|
157
199
|
onOpen={openDiff}
|
|
@@ -354,3 +396,67 @@ function AgentSection(props: {
|
|
|
354
396
|
</section>
|
|
355
397
|
);
|
|
356
398
|
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* The non-agent launch tiles — a plain terminal, a new notebook, and a
|
|
402
|
+
* new console. They sit in a separate section below the agent grid so
|
|
403
|
+
* they don't compete with the AI agents visually, and they're always
|
|
404
|
+
* available regardless of the agent prompt textarea (which doesn't
|
|
405
|
+
* apply to them).
|
|
406
|
+
*/
|
|
407
|
+
function OpenSection(props: {
|
|
408
|
+
onLaunchTerminal: () => void;
|
|
409
|
+
onLaunchNotebook: () => void;
|
|
410
|
+
onLaunchConsole: () => void;
|
|
411
|
+
}): React.ReactElement {
|
|
412
|
+
const { onLaunchTerminal, onLaunchNotebook, onLaunchConsole } = props;
|
|
413
|
+
return (
|
|
414
|
+
<section className="jp-xtralab-Launcher-section">
|
|
415
|
+
<h2 className="jp-xtralab-Launcher-section-title">Open</h2>
|
|
416
|
+
<div className="jp-xtralab-Launcher-agents">
|
|
417
|
+
<button
|
|
418
|
+
type="button"
|
|
419
|
+
className="jp-xtralab-Launcher-agent"
|
|
420
|
+
title="Open a new terminal."
|
|
421
|
+
aria-label="Open a new terminal"
|
|
422
|
+
onClick={onLaunchTerminal}
|
|
423
|
+
>
|
|
424
|
+
<LabIcon.resolveReact
|
|
425
|
+
icon={terminalIcon}
|
|
426
|
+
tag="span"
|
|
427
|
+
className="jp-xtralab-Launcher-agent-icon"
|
|
428
|
+
/>
|
|
429
|
+
<span className="jp-xtralab-Launcher-agent-label">Terminal</span>
|
|
430
|
+
</button>
|
|
431
|
+
<button
|
|
432
|
+
type="button"
|
|
433
|
+
className="jp-xtralab-Launcher-agent"
|
|
434
|
+
title="Create a new notebook."
|
|
435
|
+
aria-label="Create a new notebook"
|
|
436
|
+
onClick={onLaunchNotebook}
|
|
437
|
+
>
|
|
438
|
+
<LabIcon.resolveReact
|
|
439
|
+
icon={notebookIcon}
|
|
440
|
+
tag="span"
|
|
441
|
+
className="jp-xtralab-Launcher-agent-icon"
|
|
442
|
+
/>
|
|
443
|
+
<span className="jp-xtralab-Launcher-agent-label">Notebook</span>
|
|
444
|
+
</button>
|
|
445
|
+
<button
|
|
446
|
+
type="button"
|
|
447
|
+
className="jp-xtralab-Launcher-agent"
|
|
448
|
+
title="Create a new console."
|
|
449
|
+
aria-label="Create a new console"
|
|
450
|
+
onClick={onLaunchConsole}
|
|
451
|
+
>
|
|
452
|
+
<LabIcon.resolveReact
|
|
453
|
+
icon={consoleIcon}
|
|
454
|
+
tag="span"
|
|
455
|
+
className="jp-xtralab-Launcher-agent-icon"
|
|
456
|
+
/>
|
|
457
|
+
<span className="jp-xtralab-Launcher-agent-label">Console</span>
|
|
458
|
+
</button>
|
|
459
|
+
</div>
|
|
460
|
+
</section>
|
|
461
|
+
);
|
|
462
|
+
}
|