xtralab 0.5.0 → 0.6.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.
Files changed (71) hide show
  1. package/README.md +110 -78
  2. package/lib/agentSessions.d.ts +50 -0
  3. package/lib/agentSessions.js +45 -0
  4. package/lib/fileBrowser/index.js +5 -1
  5. package/lib/git/imageDiff.js +80 -8
  6. package/lib/index.d.ts +3 -2
  7. package/lib/index.js +11 -6
  8. package/lib/launcher/agents.d.ts +1 -1
  9. package/lib/launcher/agents.js +22 -13
  10. package/lib/launcher/commands.d.ts +32 -6
  11. package/lib/launcher/commands.js +81 -57
  12. package/lib/launcher/dashboard.d.ts +15 -0
  13. package/lib/launcher/dashboard.js +119 -5
  14. package/lib/launcher/editorRegistry.d.ts +43 -0
  15. package/lib/launcher/editorRegistry.js +96 -0
  16. package/lib/launcher/editors.d.ts +78 -0
  17. package/lib/launcher/editors.js +110 -0
  18. package/lib/launcher/icons.d.ts +25 -6
  19. package/lib/launcher/icons.js +116 -19
  20. package/lib/launcher/index.d.ts +2 -22
  21. package/lib/launcher/index.js +48 -25
  22. package/lib/launcher/registry.d.ts +21 -0
  23. package/lib/launcher/registry.js +28 -0
  24. package/lib/launcher/tokens.d.ts +41 -0
  25. package/lib/launcher/tokens.js +17 -0
  26. package/lib/terminals/detection.d.ts +11 -0
  27. package/lib/terminals/detection.js +50 -0
  28. package/lib/terminals/index.d.ts +27 -0
  29. package/lib/terminals/index.js +194 -0
  30. package/lib/terminals/model.d.ts +191 -0
  31. package/lib/terminals/model.js +418 -0
  32. package/lib/terminals/widget.d.ts +72 -0
  33. package/lib/terminals/widget.js +93 -0
  34. package/lib/topBar/icons.d.ts +13 -0
  35. package/lib/topBar/icons.js +27 -0
  36. package/lib/topBar/index.d.ts +16 -0
  37. package/lib/topBar/index.js +86 -0
  38. package/package.json +36 -33
  39. package/schema/launcher.json +56 -1
  40. package/src/agentSessions.ts +94 -0
  41. package/src/fileBrowser/index.ts +5 -1
  42. package/src/git/imageDiff.tsx +127 -26
  43. package/src/index.ts +11 -6
  44. package/src/launcher/agents.ts +23 -14
  45. package/src/launcher/commands.ts +98 -60
  46. package/src/launcher/dashboard.tsx +205 -3
  47. package/src/launcher/editorRegistry.ts +149 -0
  48. package/src/launcher/editors.ts +183 -0
  49. package/src/launcher/icons.ts +120 -20
  50. package/src/launcher/index.ts +62 -27
  51. package/src/launcher/registry.ts +33 -0
  52. package/src/launcher/tokens.ts +51 -0
  53. package/src/terminals/detection.ts +65 -0
  54. package/src/terminals/index.ts +222 -0
  55. package/src/terminals/model.ts +497 -0
  56. package/src/terminals/widget.tsx +240 -0
  57. package/src/topBar/icons.ts +30 -0
  58. package/src/topBar/index.ts +121 -0
  59. package/style/git.css +109 -0
  60. package/style/index.css +2 -1
  61. package/style/index.js +2 -1
  62. package/style/launcher.css +124 -2
  63. package/style/terminals.css +235 -0
  64. package/style/topBar.css +38 -0
  65. package/lib/statusBar/index.d.ts +0 -14
  66. package/lib/statusBar/index.js +0 -89
  67. package/lib/statusBar/widget.d.ts +0 -121
  68. package/lib/statusBar/widget.js +0 -237
  69. package/src/statusBar/index.ts +0 -115
  70. package/src/statusBar/widget.tsx +0 -318
  71. 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
- Bundles a curated set of extensions, a path-first file browser, the
6
- `jupyterlab-git` panel with its text, notebook and image diffs rendered by
7
- xtralab, an agent-focused launcher, and a quieter default workspace.
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
  ![xtralab screenshot](./screenshot.png)
10
10
 
@@ -18,7 +18,7 @@ pip install xtralab
18
18
 
19
19
  ### As a JupyterLab package
20
20
 
21
- Run JupyterLab the usual way:
21
+ Once installed, start JupyterLab the usual way:
22
22
 
23
23
  ```bash
24
24
  jupyter lab
@@ -26,85 +26,84 @@ jupyter lab
26
26
 
27
27
  ### As a desktop app
28
28
 
29
- A standalone Electron build (DMG on macOS, AppImage on Linux) ships with each
30
- tagged release — grab the installer for your platform from the
31
- [Releases page](https://github.com/jtpio/xtralab/releases/latest). Builds from
32
- the current `main` branch are also produced on every push as workflow
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 build
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
- - [`ajlab`](https://github.com/jtpio/ajlab) — agent-ready JupyterLab base
41
- - JupyterLab 4.6+
42
- - `jupyterlab-git` — provides the git panel; xtralab swaps its text,
43
- notebook and image diff rendering for its own
44
- - `jupyterlab-lsp` + `ty` — Python LSP via Astral's `ty` (bundled); also
45
- detects `typescript-language-server` on `PATH` for JS/TS
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
- - xtralab-rendered diffs registered as `jupyterlab-git`'s diff providers
54
- (its own notebook/plain-text/image diff plugins are disabled) so the
55
- upstream git panel shows xtralab's diffs: `@pierre/diffs` for text and
56
- notebooks, and an `<img>`-based 2-up/swipe/onion-skin view for images.
57
- - An agent launcher with a prompt textarea, a row of agent buttons (Claude,
58
- Codex, Gemini, Copilot, Goose, OpenCode, Kiro, Mistral Vibe), and a
59
- collapsible list of changed files. Buttons are filtered to agents installed
60
- on the machine; a typed prompt is shell-quoted and spliced into the launch
61
- command for agents that accept one.
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
- ## Language servers
64
-
65
- xtralab ships with
66
- [`jupyterlab-lsp`](https://github.com/jupyter-lsp/jupyterlab-lsp) and
67
- pre-registers two language servers through
68
- `jupyter_server_config.d/xtralab-lsp.json`:
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
+ ```
69
73
 
70
- - **Python — [`ty`](https://github.com/astral-sh/ty)** — installed as a
71
- Python dependency. Works out of the box.
72
- - **TypeScript / JavaScript — `typescript-language-server`** — install
73
- yourself:
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.
74
79
 
75
- ```bash
76
- npm install -g typescript-language-server typescript
77
- ```
80
+ See the [`jupyter-server-mcp` README][mcp] for other MCP clients.
78
81
 
79
- Restart JupyterLab (or the desktop app) after installing.
82
+ [mcp]: https://github.com/jupyter-ai-contrib/jupyter-server-mcp
83
+ [mcp-spec]: https://modelcontextprotocol.io
80
84
 
81
- Specs use bare command names (`["ty", "server"]`), so binaries are resolved
82
- from `PATH` at spawn time.
83
-
84
- ### Where binaries are discovered
85
+ ## Language servers
85
86
 
86
- - **`pip install xtralab`** — anything on the JupyterLab process's `PATH`.
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.
87
+ xtralab ships [`jupyterlab-lsp`][lsp] with two language servers pre-registered:
92
88
 
93
- ### Adding more servers
89
+ - Python, using [`ty`](https://github.com/astral-sh/ty), which is bundled and
90
+ works out of the box.
91
+ - TypeScript and JavaScript, using `typescript-language-server`, which you
92
+ install yourself:
94
93
 
95
- To enable another server (bash, yaml, json, dockerfile, pyright, …),
96
- install the binary then drop a JSON file into a `jupyter_server_config.d/`
97
- directory:
94
+ ```bash
95
+ npm install -g typescript-language-server typescript
96
+ ```
98
97
 
99
- - **`pip install xtralab`** — run `jupyter --paths` and pick a config dir
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/`.
98
+ Restart JupyterLab afterwards to pick it up.
106
99
 
107
- Example (`bash-lsp.json`):
100
+ To enable another server (bash, yaml, json, pyright, and more), install its
101
+ binary and drop a JSON spec into a `jupyter_server_config.d/` directory. For
102
+ `pip install xtralab`, run `jupyter --paths` to find one (typically
103
+ `~/.jupyter/jupyter_server_config.d/`). For the desktop app, use
104
+ `~/Library/Application Support/xtralab/jupyter/config/jupyter_server_config.d/`
105
+ on macOS or `~/.config/xtralab/jupyter/config/jupyter_server_config.d/` on
106
+ Linux.
108
107
 
109
108
  ```json
110
109
  {
@@ -122,15 +121,19 @@ Example (`bash-lsp.json`):
122
121
  }
123
122
  ```
124
123
 
125
- Pair with `npm install -g bash-language-server`. Reuse a bundled `key` to
126
- override it. See
127
- [jupyterlab-lsp's docs](https://jupyterlab-lsp.readthedocs.io/en/latest/Configuring.html)
128
- for the full spec schema.
124
+ See the [`jupyterlab-lsp` documentation][lsp-config] for the full spec.
125
+
126
+ [lsp]: https://github.com/jupyter-lsp/jupyterlab-lsp
127
+ [lsp-config]: https://jupyterlab-lsp.readthedocs.io/en/latest/Configuring.html
129
128
 
130
129
  ## Customizing the launcher
131
130
 
132
- Open `Settings → Settings Editor → xtralab launcher` and edit the `agents`
133
- array. Entries are merged with the defaults by `id`.
131
+ Open `Settings → Settings Editor → xtralab launcher` to override, hide, or add
132
+ launcher entries. Both lists below merge with xtralab's defaults by `id`.
133
+
134
+ ### Agents
135
+
136
+ Edit the `agents` array:
134
137
 
135
138
  ```jsonc
136
139
  {
@@ -147,14 +150,43 @@ array. Entries are merged with the defaults by `id`.
147
150
  }
148
151
  ```
149
152
 
150
- Fields: `id` (required), `label`, `caption`, `command`, `promptArgs` (how to
151
- splice the prompt — `[]` for positional, `["--flag"]` for flagged, `null` to
152
- opt out), `iconSvg`, `rank`, `enabled`, `requireAvailable`.
153
+ Fields: `id` (required), `label`, `caption`, `command`, `promptArgs` (how the
154
+ prompt is spliced: `[]` for positional, `["--flag"]` for flagged, `null` to opt
155
+ out), `iconSvg`, `rank`, `enabled`, `requireAvailable`.
156
+
157
+ ### Editors
158
+
159
+ The **Open** section's terminal-editor tile (Neovim, falling back to Vim) is
160
+ configured the same way through an `editors` array:
161
+
162
+ ```jsonc
163
+ {
164
+ "editors": [
165
+ // Hide Neovim so the tile falls back to Vim (or disappears if Vim
166
+ // isn't installed either)
167
+ { "id": "nvim", "enabled": false },
168
+
169
+ // Add Helix, preferred over the built-ins
170
+ {
171
+ "id": "helix",
172
+ "label": "Helix",
173
+ "command": "hx",
174
+ "rank": -1,
175
+ "iconSvg": "<svg>…</svg>"
176
+ }
177
+ ]
178
+ }
179
+ ```
180
+
181
+ The launcher shows a single tile: the first editor, by `rank`, whose `command`
182
+ is on `PATH`. Disable both built-ins (`nvim`, `vim`) to remove the tile
183
+ entirely. Fields: `id` (required), `label`, `caption`, `command`, `iconSvg`,
184
+ `rank`, `enabled`, `requireAvailable`.
153
185
 
154
186
  ## Contributing
155
187
 
156
- See [CONTRIBUTING.md](./CONTRIBUTING.md) for the development setup, the
157
- Electron desktop app architecture, and the build pipeline.
188
+ See [CONTRIBUTING.md](./CONTRIBUTING.md) for the development setup, the Electron
189
+ desktop app architecture, and the build pipeline.
158
190
 
159
191
  ## License
160
192
 
@@ -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;
@@ -22,7 +22,11 @@ const plugin = {
22
22
  void app.commands.execute('docmanager:open', { path: serverPath });
23
23
  }
24
24
  });
25
- app.shell.add(browser, 'left', { rank: 50 });
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
  }
@@ -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-stack" },
169
- reference.uri !== null ? (React.createElement("img", { className: "jp-xtralab-ImageDiff-stackImg", src: reference.uri, alt: "Reference revision", style: {
170
- clipPath: `inset(0 ${100 - position}% 0 0)`
171
- } })) : null,
172
- challenger.uri !== null ? (React.createElement("img", { className: "jp-xtralab-ImageDiff-stackImg", src: challenger.uri, alt: "Challenger revision", style: {
173
- clipPath: `inset(0 0 0 ${position}%)`
174
- } })) : null),
175
- React.createElement(RangeSlider, { value: position, onChange: setPosition, ariaLabel: "Swipe between reference and challenger" })));
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` is itself an array (the launcher
8
- * diff command + the jupyterlab-git diff providers), so it is spread in.
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 launcherPlugin from './launcher';
6
+ import launcherPlugins from './launcher';
6
7
  import menusPlugin from './menus';
7
8
  import sidebarPlugin from './sidebar';
8
- import statusBarPlugin from './statusBar';
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` is itself an array (the launcher
15
- * diff command + the jupyterlab-git diff providers), so it is spread in.
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
- launcherPlugin,
26
+ ...launcherPlugins,
23
27
  menusPlugin,
24
28
  sidebarPlugin,
25
- statusBarPlugin
29
+ terminalsPlugin,
30
+ topBarPlugin
26
31
  ];
27
32
  export default plugins;
@@ -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, gemini, vibe.)
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
@@ -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. The list mirrors the eight
5
- * first-class personas in `jupyter-ai-contrib/jupyter-ai-acp-client` (which
6
- * is also where the icons come from), but uses the bare CLI names a user
7
- * would type in a terminal rather than the ACP wrapper binaries that
8
- * project spawns. Anything not on the user's `$PATH` is filtered out at
9
- * activation time, so the wider list is harmless.
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: 'gemini',
34
- label: 'Gemini',
35
- caption: 'Start the Gemini CLI in a new terminal.',
36
- command: 'gemini',
37
- icon: BUILTIN_AGENT_ICONS.gemini,
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
- promptArgs: []
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 registerAgentCommands(app: JupyterFrontEnd, agents: IAgent[]): IDisposable;
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>>;