xtralab 0.5.0 → 0.7.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 +138 -76
- package/lib/agentSessions.d.ts +50 -0
- package/lib/agentSessions.js +45 -0
- package/lib/fileBrowser/index.js +5 -1
- package/lib/git/imageDiff.js +80 -8
- package/lib/index.d.ts +3 -2
- package/lib/index.js +11 -6
- package/lib/launcher/agents.d.ts +1 -1
- package/lib/launcher/agents.js +22 -13
- package/lib/launcher/commands.d.ts +32 -6
- package/lib/launcher/commands.js +81 -57
- package/lib/launcher/dashboard.d.ts +15 -0
- package/lib/launcher/dashboard.js +139 -10
- package/lib/launcher/editorRegistry.d.ts +43 -0
- package/lib/launcher/editorRegistry.js +96 -0
- package/lib/launcher/editors.d.ts +78 -0
- package/lib/launcher/editors.js +110 -0
- package/lib/launcher/icons.d.ts +25 -6
- package/lib/launcher/icons.js +116 -19
- package/lib/launcher/index.d.ts +2 -22
- package/lib/launcher/index.js +48 -25
- package/lib/launcher/registry.d.ts +21 -0
- package/lib/launcher/registry.js +28 -0
- package/lib/launcher/tokens.d.ts +41 -0
- package/lib/launcher/tokens.js +17 -0
- package/lib/terminals/detection.d.ts +11 -0
- package/lib/terminals/detection.js +50 -0
- package/lib/terminals/index.d.ts +27 -0
- package/lib/terminals/index.js +194 -0
- package/lib/terminals/model.d.ts +191 -0
- package/lib/terminals/model.js +418 -0
- package/lib/terminals/widget.d.ts +72 -0
- package/lib/terminals/widget.js +93 -0
- package/lib/topBar/icons.d.ts +13 -0
- package/lib/topBar/icons.js +27 -0
- package/lib/topBar/index.d.ts +16 -0
- package/lib/topBar/index.js +98 -0
- package/package.json +36 -33
- package/schema/launcher.json +56 -1
- package/src/agentSessions.ts +94 -0
- package/src/fileBrowser/index.ts +5 -1
- package/src/git/imageDiff.tsx +127 -26
- package/src/index.ts +11 -6
- package/src/launcher/agents.ts +23 -14
- package/src/launcher/commands.ts +98 -60
- package/src/launcher/dashboard.tsx +227 -9
- package/src/launcher/editorRegistry.ts +149 -0
- package/src/launcher/editors.ts +183 -0
- package/src/launcher/icons.ts +120 -20
- package/src/launcher/index.ts +62 -27
- package/src/launcher/registry.ts +33 -0
- package/src/launcher/tokens.ts +51 -0
- package/src/terminals/detection.ts +65 -0
- package/src/terminals/index.ts +222 -0
- package/src/terminals/model.ts +497 -0
- package/src/terminals/widget.tsx +240 -0
- package/src/topBar/icons.ts +30 -0
- package/src/topBar/index.ts +134 -0
- package/style/git.css +109 -0
- package/style/index.css +2 -1
- package/style/index.js +2 -1
- package/style/launcher.css +124 -2
- package/style/terminals.css +235 -0
- package/style/topBar.css +38 -0
- package/lib/statusBar/index.d.ts +0 -14
- package/lib/statusBar/index.js +0 -89
- package/lib/statusBar/widget.d.ts +0 -121
- package/lib/statusBar/widget.js +0 -237
- package/src/statusBar/index.ts +0 -115
- package/src/statusBar/widget.tsx +0 -318
- package/style/statusBar.css +0 -95
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# xtralab
|
|
2
2
|
|
|
3
|
-
An opinionated JupyterLab meta-package.
|
|
3
|
+
An opinionated JupyterLab meta-package for use with coding agents.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
It bundles a curated set of JupyterLab extensions, a path-first file browser,
|
|
6
|
+
git diffs rendered by xtralab, an agent launcher, and a quieter default
|
|
7
|
+
workspace.
|
|
8
8
|
|
|
9
9
|

|
|
10
10
|
|
|
@@ -18,7 +18,7 @@ pip install xtralab
|
|
|
18
18
|
|
|
19
19
|
### As a JupyterLab package
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
Once installed, start JupyterLab the usual way:
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
24
|
jupyter lab
|
|
@@ -26,85 +26,114 @@ jupyter lab
|
|
|
26
26
|
|
|
27
27
|
### As a desktop app
|
|
28
28
|
|
|
29
|
-
A standalone Electron build
|
|
30
|
-
|
|
31
|
-
[
|
|
32
|
-
|
|
33
|
-
artifacts under the repository's
|
|
29
|
+
A standalone Electron build is also available, packaged as a DMG on macOS and
|
|
30
|
+
an AppImage on Linux. Each tagged release ships installers on the
|
|
31
|
+
[releases page](https://github.com/jtpio/xtralab/releases/latest), and builds
|
|
32
|
+
from `main` are uploaded as workflow artifacts on the
|
|
34
33
|
[Actions tab](https://github.com/jtpio/xtralab/actions). See
|
|
35
|
-
[CONTRIBUTING.md](./CONTRIBUTING.md) for the architecture and local
|
|
36
|
-
instructions.
|
|
34
|
+
[CONTRIBUTING.md](./CONTRIBUTING.md) for the desktop app architecture and local
|
|
35
|
+
build instructions.
|
|
37
36
|
|
|
38
37
|
## What's included
|
|
39
38
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
- `jupyterlab-
|
|
43
|
-
|
|
44
|
-
- `jupyterlab-
|
|
45
|
-
|
|
46
|
-
- `jupyterlab-quickopen`
|
|
47
|
-
- `jupyterlab-cursor-light`, `jupyterlab-cursor-dark`
|
|
48
|
-
- `jupyterlab-day`, `jupyterlab-night` themes
|
|
39
|
+
xtralab builds on [`ajlab`](https://github.com/jtpio/ajlab), the agent-ready
|
|
40
|
+
JupyterLab base. On top of it, xtralab pulls in JupyterLab 4.6+ with
|
|
41
|
+
`jupyterlab-git`, `jupyterlab-lsp`, `jupyterlab-quickopen`, `jupyterlab-vim`,
|
|
42
|
+
and a set of light and dark themes (`jupyterlab-cursor-light`,
|
|
43
|
+
`jupyterlab-cursor-dark`, `jupyterlab-day`, `jupyterlab-night`). See
|
|
44
|
+
[`pyproject.toml`](./pyproject.toml) for the full list and pinned versions.
|
|
49
45
|
|
|
50
|
-
The bundled labextension adds:
|
|
46
|
+
The bundled xtralab labextension then adds:
|
|
51
47
|
|
|
52
48
|
- A path-first file browser in the left sidebar.
|
|
53
|
-
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
49
|
+
- Git diffs rendered by xtralab in the `jupyterlab-git` panel, using
|
|
50
|
+
`@pierre/diffs` for text and notebooks and a 2-up / swipe / onion-skin view
|
|
51
|
+
for images.
|
|
52
|
+
- An agent launcher with a prompt box, buttons for the agents installed on your
|
|
53
|
+
machine (Claude, Codex, Antigravity, Copilot, Goose, OpenCode, Kiro, Mistral
|
|
54
|
+
Vibe), a collapsible list of changed files, and an Open row for a terminal,
|
|
55
|
+
notebook, console, or your terminal editor (Neovim or Vim).
|
|
56
|
+
- A Terminals panel listing the running terminal sessions, each badged with the
|
|
57
|
+
agent or editor detected inside it. Open terminal tabs in the main area carry
|
|
58
|
+
the same icon.
|
|
59
|
+
- Sidebar toggle buttons in the top bar for the left and right areas.
|
|
60
|
+
|
|
61
|
+
## Connecting agents to Jupyter (MCP)
|
|
62
|
+
|
|
63
|
+
xtralab runs a [Model Context Protocol][mcp-spec] server inside JupyterLab,
|
|
64
|
+
provided by [`jupyter-server-mcp`][mcp]. To let a coding agent drive JupyterLab
|
|
65
|
+
through it, register the bundled `jupyter-server-mcp-proxy` console script with
|
|
66
|
+
the agent:
|
|
62
67
|
|
|
63
|
-
|
|
68
|
+
```bash
|
|
69
|
+
claude mcp add jupyter -- jupyter-server-mcp-proxy
|
|
70
|
+
codex mcp add jupyter -- jupyter-server-mcp-proxy
|
|
71
|
+
copilot mcp add jupyter -- jupyter-server-mcp-proxy
|
|
72
|
+
```
|
|
64
73
|
|
|
65
|
-
xtralab
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
74
|
+
Run this from a terminal inside xtralab, so the proxy inherits the server
|
|
75
|
+
environment and discovers the running server automatically. No port is needed,
|
|
76
|
+
and a single registration keeps working across restarts. The launcher also
|
|
77
|
+
surfaces these commands with copy buttons, filtered to the agents you have
|
|
78
|
+
installed.
|
|
69
79
|
|
|
70
|
-
|
|
71
|
-
Python dependency. Works out of the box.
|
|
72
|
-
- **TypeScript / JavaScript — `typescript-language-server`** — install
|
|
73
|
-
yourself:
|
|
80
|
+
See the [`jupyter-server-mcp` README][mcp] for other MCP clients.
|
|
74
81
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
82
|
+
[mcp]: https://github.com/jupyter-ai-contrib/jupyter-server-mcp
|
|
83
|
+
[mcp-spec]: https://modelcontextprotocol.io
|
|
84
|
+
|
|
85
|
+
## Agent skill
|
|
86
|
+
|
|
87
|
+
xtralab ships an [Agent Skills][skills] entry at [`agent-skill/`](./agent-skill)
|
|
88
|
+
that teaches any coding agent how to customize JupyterLab from plain-English
|
|
89
|
+
requests like "hide the status bar", "change the theme", or "add a language
|
|
90
|
+
server". The same SKILL.md file works with Claude Code, Codex CLI, Gemini CLI,
|
|
91
|
+
GitHub Copilot, and other tools that read the Agent Skills format.
|
|
78
92
|
|
|
79
|
-
|
|
93
|
+
For Claude Code, this repository doubles as a one-plugin marketplace. Inside a
|
|
94
|
+
Claude Code session:
|
|
80
95
|
|
|
81
|
-
|
|
82
|
-
|
|
96
|
+
```text
|
|
97
|
+
/plugin marketplace add jtpio/xtralab
|
|
98
|
+
/plugin install xtralab-skills@xtralab
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
For agents that read from `~/.agents/skills/`, such as Codex CLI or Gemini CLI,
|
|
102
|
+
clone and copy the skill directory:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
git clone --depth=1 https://github.com/jtpio/xtralab.git /tmp/xtralab
|
|
106
|
+
mkdir -p ~/.agents/skills
|
|
107
|
+
cp -r /tmp/xtralab/agent-skill/skills/customize-jupyterlab ~/.agents/skills/
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
See [`agent-skill/README.md`](./agent-skill/README.md) for additional install
|
|
111
|
+
paths, the list of supported agents, and what the skill knows.
|
|
83
112
|
|
|
84
|
-
|
|
113
|
+
[skills]: https://agentskills.io
|
|
114
|
+
|
|
115
|
+
## Language servers
|
|
85
116
|
|
|
86
|
-
|
|
87
|
-
- **Desktop app** — the supervisor augments `PATH` with common shim
|
|
88
|
-
locations (`~/.volta/bin`, `~/.npm-global/bin`, `~/.bun/bin`,
|
|
89
|
-
`~/.asdf/shims`, `~/.mise/shims`, `/opt/homebrew/bin`, `/usr/local/bin`,
|
|
90
|
-
…). Set `XTRALAB_EXTRA_PATH` (colon-separated) before launching the app
|
|
91
|
-
to add directories the defaults miss.
|
|
117
|
+
xtralab ships [`jupyterlab-lsp`][lsp] with two language servers pre-registered:
|
|
92
118
|
|
|
93
|
-
|
|
119
|
+
- Python, using [`ty`](https://github.com/astral-sh/ty), which is bundled and
|
|
120
|
+
works out of the box.
|
|
121
|
+
- TypeScript and JavaScript, using `typescript-language-server`, which you
|
|
122
|
+
install yourself:
|
|
94
123
|
|
|
95
|
-
|
|
96
|
-
install
|
|
97
|
-
|
|
124
|
+
```bash
|
|
125
|
+
npm install -g typescript-language-server typescript
|
|
126
|
+
```
|
|
98
127
|
|
|
99
|
-
|
|
100
|
-
(typically `~/.jupyter/jupyter_server_config.d/`).
|
|
101
|
-
- **Desktop app (macOS)** —
|
|
102
|
-
`~/Library/Application Support/xtralab/jupyter/config/jupyter_server_config.d/`
|
|
103
|
-
(or `xtralab dev` for local dev builds).
|
|
104
|
-
- **Desktop app (Linux AppImage)** —
|
|
105
|
-
`~/.config/xtralab/jupyter/config/jupyter_server_config.d/`.
|
|
128
|
+
Restart JupyterLab afterwards to pick it up.
|
|
106
129
|
|
|
107
|
-
|
|
130
|
+
To enable another server (bash, yaml, json, pyright, and more), install its
|
|
131
|
+
binary and drop a JSON spec into a `jupyter_server_config.d/` directory. For
|
|
132
|
+
`pip install xtralab`, run `jupyter --paths` to find one (typically
|
|
133
|
+
`~/.jupyter/jupyter_server_config.d/`). For the desktop app, use
|
|
134
|
+
`~/Library/Application Support/xtralab/jupyter/config/jupyter_server_config.d/`
|
|
135
|
+
on macOS or `~/.config/xtralab/jupyter/config/jupyter_server_config.d/` on
|
|
136
|
+
Linux.
|
|
108
137
|
|
|
109
138
|
```json
|
|
110
139
|
{
|
|
@@ -122,15 +151,19 @@ Example (`bash-lsp.json`):
|
|
|
122
151
|
}
|
|
123
152
|
```
|
|
124
153
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
[
|
|
128
|
-
|
|
154
|
+
See the [`jupyterlab-lsp` documentation][lsp-config] for the full spec.
|
|
155
|
+
|
|
156
|
+
[lsp]: https://github.com/jupyter-lsp/jupyterlab-lsp
|
|
157
|
+
[lsp-config]: https://jupyterlab-lsp.readthedocs.io/en/latest/Configuring.html
|
|
129
158
|
|
|
130
159
|
## Customizing the launcher
|
|
131
160
|
|
|
132
|
-
Open `Settings → Settings Editor → xtralab launcher`
|
|
133
|
-
|
|
161
|
+
Open `Settings → Settings Editor → xtralab launcher` to override, hide, or add
|
|
162
|
+
launcher entries. Both lists below merge with xtralab's defaults by `id`.
|
|
163
|
+
|
|
164
|
+
### Agents
|
|
165
|
+
|
|
166
|
+
Edit the `agents` array:
|
|
134
167
|
|
|
135
168
|
```jsonc
|
|
136
169
|
{
|
|
@@ -147,14 +180,43 @@ array. Entries are merged with the defaults by `id`.
|
|
|
147
180
|
}
|
|
148
181
|
```
|
|
149
182
|
|
|
150
|
-
Fields: `id` (required), `label`, `caption`, `command`, `promptArgs` (how
|
|
151
|
-
|
|
152
|
-
|
|
183
|
+
Fields: `id` (required), `label`, `caption`, `command`, `promptArgs` (how the
|
|
184
|
+
prompt is spliced: `[]` for positional, `["--flag"]` for flagged, `null` to opt
|
|
185
|
+
out), `iconSvg`, `rank`, `enabled`, `requireAvailable`.
|
|
186
|
+
|
|
187
|
+
### Editors
|
|
188
|
+
|
|
189
|
+
The **Open** section's terminal-editor tile (Neovim, falling back to Vim) is
|
|
190
|
+
configured the same way through an `editors` array:
|
|
191
|
+
|
|
192
|
+
```jsonc
|
|
193
|
+
{
|
|
194
|
+
"editors": [
|
|
195
|
+
// Hide Neovim so the tile falls back to Vim (or disappears if Vim
|
|
196
|
+
// isn't installed either)
|
|
197
|
+
{ "id": "nvim", "enabled": false },
|
|
198
|
+
|
|
199
|
+
// Add Helix, preferred over the built-ins
|
|
200
|
+
{
|
|
201
|
+
"id": "helix",
|
|
202
|
+
"label": "Helix",
|
|
203
|
+
"command": "hx",
|
|
204
|
+
"rank": -1,
|
|
205
|
+
"iconSvg": "<svg>…</svg>"
|
|
206
|
+
}
|
|
207
|
+
]
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
The launcher shows a single tile: the first editor, by `rank`, whose `command`
|
|
212
|
+
is on `PATH`. Disable both built-ins (`nvim`, `vim`) to remove the tile
|
|
213
|
+
entirely. Fields: `id` (required), `label`, `caption`, `command`, `iconSvg`,
|
|
214
|
+
`rank`, `enabled`, `requireAvailable`.
|
|
153
215
|
|
|
154
216
|
## Contributing
|
|
155
217
|
|
|
156
|
-
See [CONTRIBUTING.md](./CONTRIBUTING.md) for the development setup, the
|
|
157
|
-
|
|
218
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md) for the development setup, the Electron
|
|
219
|
+
desktop app architecture, and the build pipeline.
|
|
158
220
|
|
|
159
221
|
## License
|
|
160
222
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { JupyterFrontEndPlugin } from '@jupyterlab/application';
|
|
2
|
+
import { Token } from '@lumino/coreutils';
|
|
3
|
+
import { ISignal } from '@lumino/signaling';
|
|
4
|
+
/**
|
|
5
|
+
* A shared map from a terminal session name to the agent command it was
|
|
6
|
+
* *launched* with (e.g. `claude`).
|
|
7
|
+
*
|
|
8
|
+
* This is the optimistic half of the running-agent badge: when the launcher
|
|
9
|
+
* (or the terminals panel's `+` menu) starts an agent via a
|
|
10
|
+
* `xtralab:start-agent:<id>` command, it records the session here so the
|
|
11
|
+
* panel can show the agent's logo immediately, before the server-side
|
|
12
|
+
* process detection has had a chance to confirm it. Server detection is the
|
|
13
|
+
* authoritative source once it reports on a session; this tag only fills the
|
|
14
|
+
* gap right after launch (and serves as a fallback if detection is
|
|
15
|
+
* unavailable).
|
|
16
|
+
*
|
|
17
|
+
* It exists as its own token, provided by a tiny dependency-free plugin,
|
|
18
|
+
* specifically so the launcher (the writer) and the terminals panel (the
|
|
19
|
+
* reader) can share it without depending on each other — a direct
|
|
20
|
+
* launcher↔terminals token pair would form an activation cycle.
|
|
21
|
+
*/
|
|
22
|
+
export interface IAgentSessions {
|
|
23
|
+
/**
|
|
24
|
+
* The agent command a session was launched with, or `null` if we never
|
|
25
|
+
* launched an agent into it (e.g. a plain terminal, or one started before
|
|
26
|
+
* this ran).
|
|
27
|
+
*/
|
|
28
|
+
get(sessionName: string): string | null;
|
|
29
|
+
/** Record that a session was launched as the given agent command. */
|
|
30
|
+
set(sessionName: string, command: string): void;
|
|
31
|
+
/** Forget a session — called when it shuts down so the map stays bounded. */
|
|
32
|
+
delete(sessionName: string): void;
|
|
33
|
+
/**
|
|
34
|
+
* Emitted with the affected session name whenever a record is added or
|
|
35
|
+
* removed, so readers can re-render.
|
|
36
|
+
*/
|
|
37
|
+
readonly changed: ISignal<IAgentSessions, string>;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* DI token for {@link IAgentSessions}. Provided by `xtralab:agent-sessions`;
|
|
41
|
+
* consumed (optionally) by both `xtralab:launcher` and `xtralab:terminals`.
|
|
42
|
+
*/
|
|
43
|
+
export declare const IAgentSessions: Token<IAgentSessions>;
|
|
44
|
+
/**
|
|
45
|
+
* Provides {@link IAgentSessions}. Deliberately has no dependencies so it can
|
|
46
|
+
* sit underneath both the launcher and the terminals panel without creating
|
|
47
|
+
* a dependency cycle between them.
|
|
48
|
+
*/
|
|
49
|
+
declare const plugin: JupyterFrontEndPlugin<IAgentSessions>;
|
|
50
|
+
export default plugin;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Token } from '@lumino/coreutils';
|
|
2
|
+
import { Signal } from '@lumino/signaling';
|
|
3
|
+
/**
|
|
4
|
+
* DI token for {@link IAgentSessions}. Provided by `xtralab:agent-sessions`;
|
|
5
|
+
* consumed (optionally) by both `xtralab:launcher` and `xtralab:terminals`.
|
|
6
|
+
*/
|
|
7
|
+
export const IAgentSessions = new Token('xtralab:IAgentSessions', 'A shared map from terminal session name to the agent command it was launched with.');
|
|
8
|
+
class AgentSessions {
|
|
9
|
+
constructor() {
|
|
10
|
+
this._byName = new Map();
|
|
11
|
+
this._changed = new Signal(this);
|
|
12
|
+
}
|
|
13
|
+
get(sessionName) {
|
|
14
|
+
var _a;
|
|
15
|
+
return (_a = this._byName.get(sessionName)) !== null && _a !== void 0 ? _a : null;
|
|
16
|
+
}
|
|
17
|
+
set(sessionName, command) {
|
|
18
|
+
if (this._byName.get(sessionName) === command) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
this._byName.set(sessionName, command);
|
|
22
|
+
this._changed.emit(sessionName);
|
|
23
|
+
}
|
|
24
|
+
delete(sessionName) {
|
|
25
|
+
if (this._byName.delete(sessionName)) {
|
|
26
|
+
this._changed.emit(sessionName);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
get changed() {
|
|
30
|
+
return this._changed;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Provides {@link IAgentSessions}. Deliberately has no dependencies so it can
|
|
35
|
+
* sit underneath both the launcher and the terminals panel without creating
|
|
36
|
+
* a dependency cycle between them.
|
|
37
|
+
*/
|
|
38
|
+
const plugin = {
|
|
39
|
+
id: 'xtralab:agent-sessions',
|
|
40
|
+
description: 'Shared map from terminal session to the agent command it was launched with, used to badge terminal rows with the running agent.',
|
|
41
|
+
autoStart: true,
|
|
42
|
+
provides: IAgentSessions,
|
|
43
|
+
activate: () => new AgentSessions()
|
|
44
|
+
};
|
|
45
|
+
export default plugin;
|
package/lib/fileBrowser/index.js
CHANGED
|
@@ -22,7 +22,11 @@ const plugin = {
|
|
|
22
22
|
void app.commands.execute('docmanager:open', { path: serverPath });
|
|
23
23
|
}
|
|
24
24
|
});
|
|
25
|
-
|
|
25
|
+
// Ranks just under the Terminals panel in the left sidebar. The shipped
|
|
26
|
+
// `layout` setting assigns the default ranks (rank only, no area pin),
|
|
27
|
+
// so this browser stays movable via the "Move Widget" context menu; the
|
|
28
|
+
// `rank` here is the in-code fallback for when that setting is absent.
|
|
29
|
+
app.shell.add(browser, 'left', { rank: 2 });
|
|
26
30
|
if (restorer) {
|
|
27
31
|
restorer.add(browser, browser.id);
|
|
28
32
|
}
|
package/lib/git/imageDiff.js
CHANGED
|
@@ -164,15 +164,87 @@ function RangeSlider(props) {
|
|
|
164
164
|
}
|
|
165
165
|
function Swipe({ reference, challenger }) {
|
|
166
166
|
const [position, setPosition] = React.useState(50);
|
|
167
|
+
const [dragging, setDragging] = React.useState(false);
|
|
168
|
+
const frameRef = React.useRef(null);
|
|
169
|
+
const setFromClientX = React.useCallback((clientX) => {
|
|
170
|
+
const frame = frameRef.current;
|
|
171
|
+
if (frame === null) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const rect = frame.getBoundingClientRect();
|
|
175
|
+
if (rect.width <= 0) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const next = ((clientX - rect.left) / rect.width) * 100;
|
|
179
|
+
setPosition(Math.max(0, Math.min(100, next)));
|
|
180
|
+
}, []);
|
|
181
|
+
const onPointerDown = React.useCallback((event) => {
|
|
182
|
+
// Only react to the primary pointer (left click / touch / pen tip).
|
|
183
|
+
if (event.button !== 0) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
event.preventDefault();
|
|
187
|
+
setDragging(true);
|
|
188
|
+
setFromClientX(event.clientX);
|
|
189
|
+
}, [setFromClientX]);
|
|
190
|
+
React.useEffect(() => {
|
|
191
|
+
if (!dragging) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
const onMove = (event) => {
|
|
195
|
+
setFromClientX(event.clientX);
|
|
196
|
+
};
|
|
197
|
+
const onUp = () => {
|
|
198
|
+
setDragging(false);
|
|
199
|
+
};
|
|
200
|
+
window.addEventListener('pointermove', onMove);
|
|
201
|
+
window.addEventListener('pointerup', onUp);
|
|
202
|
+
window.addEventListener('pointercancel', onUp);
|
|
203
|
+
return () => {
|
|
204
|
+
window.removeEventListener('pointermove', onMove);
|
|
205
|
+
window.removeEventListener('pointerup', onUp);
|
|
206
|
+
window.removeEventListener('pointercancel', onUp);
|
|
207
|
+
};
|
|
208
|
+
}, [dragging, setFromClientX]);
|
|
209
|
+
const onKeyDown = React.useCallback((event) => {
|
|
210
|
+
const step = event.shiftKey ? 10 : 1;
|
|
211
|
+
let next = null;
|
|
212
|
+
switch (event.key) {
|
|
213
|
+
case 'ArrowLeft':
|
|
214
|
+
next = position - step;
|
|
215
|
+
break;
|
|
216
|
+
case 'ArrowRight':
|
|
217
|
+
next = position + step;
|
|
218
|
+
break;
|
|
219
|
+
case 'Home':
|
|
220
|
+
next = 0;
|
|
221
|
+
break;
|
|
222
|
+
case 'End':
|
|
223
|
+
next = 100;
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
if (next === null) {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
event.preventDefault();
|
|
230
|
+
setPosition(Math.max(0, Math.min(100, next)));
|
|
231
|
+
}, [position]);
|
|
167
232
|
return (React.createElement("div", { className: "jp-xtralab-ImageDiff-interactive" },
|
|
168
|
-
React.createElement("div", { className: "jp-xtralab-ImageDiff-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
233
|
+
React.createElement("div", { className: "jp-xtralab-ImageDiff-swipeFrame", ref: frameRef, onPointerDown: onPointerDown, "data-dragging": dragging ? 'true' : undefined },
|
|
234
|
+
React.createElement("div", { className: "jp-xtralab-ImageDiff-stack" },
|
|
235
|
+
reference.uri !== null ? (React.createElement("img", { className: "jp-xtralab-ImageDiff-stackImg", src: reference.uri, alt: "Reference revision", draggable: false, style: {
|
|
236
|
+
clipPath: `inset(0 ${100 - position}% 0 0)`
|
|
237
|
+
} })) : null,
|
|
238
|
+
challenger.uri !== null ? (React.createElement("img", { className: "jp-xtralab-ImageDiff-stackImg", src: challenger.uri, alt: "Challenger revision", draggable: false, style: {
|
|
239
|
+
clipPath: `inset(0 0 0 ${position}%)`
|
|
240
|
+
} })) : null),
|
|
241
|
+
React.createElement("div", { className: "jp-xtralab-ImageDiff-swipeDivider", style: { left: `${position}%` }, role: "slider", "aria-label": "Swipe between reference and challenger", "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": Math.round(position), "aria-orientation": "vertical", tabIndex: 0, onKeyDown: onKeyDown },
|
|
242
|
+
React.createElement("div", { className: "jp-xtralab-ImageDiff-swipeDividerHandle jp-mod-top" },
|
|
243
|
+
React.createElement("div", { className: "jp-xtralab-ImageDiff-swipeDividerArrow jp-mod-left" }),
|
|
244
|
+
React.createElement("div", { className: "jp-xtralab-ImageDiff-swipeDividerArrow jp-mod-right" })),
|
|
245
|
+
React.createElement("div", { className: "jp-xtralab-ImageDiff-swipeDividerHandle jp-mod-bottom" },
|
|
246
|
+
React.createElement("div", { className: "jp-xtralab-ImageDiff-swipeDividerArrow jp-mod-left" }),
|
|
247
|
+
React.createElement("div", { className: "jp-xtralab-ImageDiff-swipeDividerArrow jp-mod-right" }))))));
|
|
176
248
|
}
|
|
177
249
|
function OnionSkin({ reference, challenger }) {
|
|
178
250
|
const [opacity, setOpacity] = React.useState(50);
|
package/lib/index.d.ts
CHANGED
|
@@ -4,8 +4,9 @@ import { JupyterFrontEndPlugin } from '@jupyterlab/application';
|
|
|
4
4
|
* labextension is an array because the package bundles several independent
|
|
5
5
|
* enhancements (file browser, git diff providers, …) — JupyterLab activates
|
|
6
6
|
* each plugin individually and only the ones whose required tokens are
|
|
7
|
-
* available end up running. `gitPlugins`
|
|
8
|
-
*
|
|
7
|
+
* available end up running. `gitPlugins` and `launcherPlugins` are themselves
|
|
8
|
+
* arrays (the git diff providers; the launcher plus its editor registry), so
|
|
9
|
+
* they are spread in.
|
|
9
10
|
*/
|
|
10
11
|
declare const plugins: JupyterFrontEndPlugin<unknown>[];
|
|
11
12
|
export default plugins;
|
package/lib/index.js
CHANGED
|
@@ -1,27 +1,32 @@
|
|
|
1
|
+
import agentSessionsPlugin from './agentSessions';
|
|
1
2
|
import fileBrowserPlugin from './fileBrowser';
|
|
2
3
|
import editorBreadcrumbsPlugin from './editorBreadcrumbs';
|
|
3
4
|
import editorIndentPlugin from './editorIndent';
|
|
4
5
|
import gitPlugins from './git';
|
|
5
|
-
import
|
|
6
|
+
import launcherPlugins from './launcher';
|
|
6
7
|
import menusPlugin from './menus';
|
|
7
8
|
import sidebarPlugin from './sidebar';
|
|
8
|
-
import
|
|
9
|
+
import terminalsPlugin from './terminals';
|
|
10
|
+
import topBarPlugin from './topBar';
|
|
9
11
|
/**
|
|
10
12
|
* Every plugin contributed by `xtralab`. The entry point of the
|
|
11
13
|
* labextension is an array because the package bundles several independent
|
|
12
14
|
* enhancements (file browser, git diff providers, …) — JupyterLab activates
|
|
13
15
|
* each plugin individually and only the ones whose required tokens are
|
|
14
|
-
* available end up running. `gitPlugins`
|
|
15
|
-
*
|
|
16
|
+
* available end up running. `gitPlugins` and `launcherPlugins` are themselves
|
|
17
|
+
* arrays (the git diff providers; the launcher plus its editor registry), so
|
|
18
|
+
* they are spread in.
|
|
16
19
|
*/
|
|
17
20
|
const plugins = [
|
|
21
|
+
agentSessionsPlugin,
|
|
18
22
|
editorBreadcrumbsPlugin,
|
|
19
23
|
editorIndentPlugin,
|
|
20
24
|
fileBrowserPlugin,
|
|
21
25
|
...gitPlugins,
|
|
22
|
-
|
|
26
|
+
...launcherPlugins,
|
|
23
27
|
menusPlugin,
|
|
24
28
|
sidebarPlugin,
|
|
25
|
-
|
|
29
|
+
terminalsPlugin,
|
|
30
|
+
topBarPlugin
|
|
26
31
|
];
|
|
27
32
|
export default plugins;
|
package/lib/launcher/agents.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ export interface IAgent {
|
|
|
23
23
|
* the user types one into the launcher's prompt box. The semantics:
|
|
24
24
|
*
|
|
25
25
|
* - `[]` → prompt is appended as a positional argument:
|
|
26
|
-
* `<command> 'PROMPT'`. (Used by claude, codex,
|
|
26
|
+
* `<command> 'PROMPT'`. (Used by claude, codex, vibe.)
|
|
27
27
|
* - `['-i']` / `['--prompt']` → the prompt is preceded by a flag:
|
|
28
28
|
* `<command> -i 'PROMPT'`. (Used by copilot, opencode.)
|
|
29
29
|
* - `undefined` → the agent does not accept an initial prompt. The
|
package/lib/launcher/agents.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { LabIcon, terminalIcon } from '@jupyterlab/ui-components';
|
|
2
2
|
import { BUILTIN_AGENT_ICONS } from './icons';
|
|
3
3
|
/**
|
|
4
|
-
* Default agent cards shipped with xtralab.
|
|
5
|
-
* first-class personas
|
|
6
|
-
* is also where
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* Default agent cards shipped with xtralab. Seven of these (Claude, Codex,
|
|
5
|
+
* Copilot, Goose, OpenCode, Kiro, Mistral Vibe) mirror first-class personas
|
|
6
|
+
* in `jupyter-ai-contrib/jupyter-ai-acp-client` (which is also where their
|
|
7
|
+
* icons come from), but use the bare CLI names a user would type in a
|
|
8
|
+
* terminal rather than the ACP wrapper binaries that project spawns.
|
|
9
|
+
* Antigravity (Google's agent-first IDE/CLI, `agy`) takes the Google slot —
|
|
10
|
+
* Gemini CLI was dropped because Google is sunsetting it for consumer
|
|
11
|
+
* accounts on 2026-06-18. Anything not on the user's `$PATH` is filtered
|
|
12
|
+
* out at activation time, so the wider list is harmless.
|
|
10
13
|
*/
|
|
11
14
|
const DEFAULTS = [
|
|
12
15
|
{
|
|
@@ -30,14 +33,20 @@ const DEFAULTS = [
|
|
|
30
33
|
promptArgs: []
|
|
31
34
|
},
|
|
32
35
|
{
|
|
33
|
-
id: '
|
|
34
|
-
label: '
|
|
35
|
-
caption: '
|
|
36
|
-
command: '
|
|
37
|
-
icon: BUILTIN_AGENT_ICONS.
|
|
36
|
+
id: 'antigravity',
|
|
37
|
+
label: 'Antigravity',
|
|
38
|
+
caption: 'Open Google Antigravity from a new terminal.',
|
|
39
|
+
command: 'agy',
|
|
40
|
+
icon: BUILTIN_AGENT_ICONS.antigravity,
|
|
38
41
|
rank: 2,
|
|
39
|
-
requireAvailable: true
|
|
40
|
-
|
|
42
|
+
requireAvailable: true
|
|
43
|
+
// `agy` is the Antigravity launcher binary — a VS Code-derived editor
|
|
44
|
+
// CLI that opens the Agent Manager / workspace, not a terminal REPL.
|
|
45
|
+
// Its positional args are file/folder paths, so there is no inline
|
|
46
|
+
// natural-language prompt form; like goose/kiro we omit promptArgs so a
|
|
47
|
+
// typed prompt dims the button instead of being mangled into a path.
|
|
48
|
+
// The command stays the bare binary (no `agy .`) so the server-side
|
|
49
|
+
// `shutil.which` availability check still resolves it.
|
|
41
50
|
},
|
|
42
51
|
{
|
|
43
52
|
id: 'copilot',
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import type { JupyterFrontEnd } from '@jupyterlab/application';
|
|
2
|
+
import type { MainAreaWidget } from '@jupyterlab/apputils';
|
|
3
|
+
import type { ITerminal } from '@jupyterlab/terminal';
|
|
4
|
+
import type { CommandRegistry } from '@lumino/commands';
|
|
2
5
|
import { type IDisposable } from '@lumino/disposable';
|
|
6
|
+
import type { IAgentSessions } from '../agentSessions';
|
|
3
7
|
import type { IAgent } from './agents';
|
|
4
8
|
/**
|
|
5
9
|
* The command id for opening the launcher.
|
|
6
10
|
*/
|
|
7
11
|
export declare const CREATE_LAUNCHER_COMMAND = "launcher:create";
|
|
8
|
-
/**
|
|
9
|
-
* Build a JupyterLab command id for a given agent. Centralised so the
|
|
10
|
-
* plugin and the launcher both use the same names.
|
|
11
|
-
*/
|
|
12
|
-
export declare function agentCommandId(agentId: string): string;
|
|
13
12
|
/**
|
|
14
13
|
* Register a JupyterLab command per agent. Each command opens a new
|
|
15
14
|
* terminal via `terminal:create-new` and feeds the agent's shell command
|
|
@@ -18,5 +17,32 @@ export declare function agentCommandId(agentId: string): string;
|
|
|
18
17
|
* Returns a single disposable that tears down every registered command —
|
|
19
18
|
* the launcher disposes the previous set before re-registering on settings
|
|
20
19
|
* changes so the command palette stays in sync with the configured agents.
|
|
20
|
+
*
|
|
21
|
+
* When an `agentSessions` registry is supplied, each launch tags its terminal
|
|
22
|
+
* session with the agent's command, so the terminals panel can badge the row
|
|
23
|
+
* with the agent's logo immediately (before server-side detection confirms
|
|
24
|
+
* it).
|
|
25
|
+
*/
|
|
26
|
+
export declare function registerAgentCommands(app: JupyterFrontEnd, agents: IAgent[], agentSessions?: IAgentSessions | null): IDisposable;
|
|
27
|
+
/**
|
|
28
|
+
* Open a fresh terminal, type `invocation` into it as if the user had, and
|
|
29
|
+
* label the tab. Shared by the per-agent launch commands and the launcher's
|
|
30
|
+
* editor tile so the fiddly "wait for the websocket, then write stdin"
|
|
31
|
+
* sequence lives in one place.
|
|
32
|
+
*
|
|
33
|
+
* `onSession`, when given, is called once with the new session's name right
|
|
34
|
+
* after the terminal is revealed. Callers use it to optimistically tag the
|
|
35
|
+
* session so the terminals panel can badge it with the agent's (or editor's)
|
|
36
|
+
* logo before server-side detection confirms the running process.
|
|
37
|
+
*
|
|
38
|
+
* Resolves with the host `MainAreaWidget` so the caller can place it — the
|
|
39
|
+
* launcher swaps it into its own tab.
|
|
21
40
|
*/
|
|
22
|
-
export declare function
|
|
41
|
+
export declare function launchInTerminal(commands: CommandRegistry, options: {
|
|
42
|
+
cwd?: string;
|
|
43
|
+
/** The literal command line typed into the fresh terminal. */
|
|
44
|
+
invocation: string;
|
|
45
|
+
/** Tab/title label applied once the command is sent. */
|
|
46
|
+
label: string;
|
|
47
|
+
onSession?: (sessionName: string) => void;
|
|
48
|
+
}): Promise<MainAreaWidget<ITerminal.ITerminal>>;
|