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 CHANGED
@@ -2,153 +2,152 @@
2
2
 
3
3
  An opinionated JupyterLab meta-package.
4
4
 
5
- `xtralab` bundles a curated set of JupyterLab extensions, ships its own
6
- JupyterLab extension with a path-first file browser and a VS Code-style git
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
- The goal is to keep the package easy to inspect: most behavior comes from the
12
- upstream Jupyter packages, while `xtralab` defines the default environment we
13
- want out of the box.
8
+ ## Install
14
9
 
15
- ## Approach
10
+ ```bash
11
+ pip install xtralab
12
+ ```
16
13
 
17
- The package brings together:
14
+ ## Usage
18
15
 
19
- - The `ajlab` meta-package (agent-ready JupyterLab — collaboration plumbing, MCP support, command tooling, and its own quieter defaults)
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
- // Override an agent's command — e.g. point Claude at a shell alias.
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
- // Add a brand-new agent. `iconSvg` is optional; without it the card
68
- // falls back to a generic terminal icon. `promptArgs: []` means the
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
- Field reference (every field except `id` is optional):
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
- ## Install
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
- ```bash
116
- pip install xtralab
117
- ```
35
+ ## What's included
118
36
 
119
- ## Run as a desktop app
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
- `xtralab` ships a tiny launcher that opens JupyterLab in a dedicated Chrome
122
- window (via Chrome's `--app` mode), with an isolated user-data directory so it
123
- does not share cookies, extensions, or history with your normal browser.
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
- Requires Chrome or Chromium installed. macOS only for now; other platforms will be added later.
123
+ ## Customizing the launcher
126
124
 
127
- The simplest way, no install needed:
125
+ Open `Settings → Settings Editor → xtralab launcher` and edit the `agents`
126
+ array. Entries are merged with the defaults by `id`.
128
127
 
129
- ```bash
130
- uvx xtralab
131
- ```
128
+ ```jsonc
129
+ {
130
+ "agents": [
131
+ // Hide an agent
132
+ { "id": "kiro", "enabled": false },
132
133
 
133
- Or, after `pip install xtralab` into an environment:
134
+ // Override an agent's command (e.g. point Claude at a shell alias)
135
+ { "id": "claude", "command": "cl", "requireAvailable": false },
134
136
 
135
- ```bash
136
- xtralab
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
- The launcher starts a local Jupyter server on a random port with a fresh
140
- token, waits for it to come up, opens the app window, and shuts the server
141
- down when you close the window. `Ctrl-C` in the launching terminal also
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
- ## Development
147
+ ## Contributing
145
148
 
146
- ```bash
147
- # Install dependencies and the package in editable mode
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 Xtralab browser. We deliberately namespace
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 Xtralab command on the application command registry, attach
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 Xtralab browser. We deliberately namespace
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 Xtralab command on the application command registry, attach
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 Xtralab.
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 Xtralab file browser's sidebar tab icon. Defined as a
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 Xtralab browser is visually distinguishable
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 Xtralab file browser. Keeps the default `complete`
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 Xtralab file tree would
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.
@@ -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 Xtralab file browser's sidebar tab icon. Defined as a
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 Xtralab browser is visually distinguishable
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 Xtralab file browser. Keeps the default `complete`
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 Xtralab file tree would
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 Xtralab file browser widget. The selectors that
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 Xtralab file browser used by command handlers and other
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 Xtralab file browser widget. The selectors that
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 = 'Xtralab File Browser';
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);
@@ -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 Xtralab file browser's `file-tree`
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 Xtralab file browser's `file-tree`
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.2.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": "tsconfig.json",
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 Xtralab browser. We deliberately namespace
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 Xtralab command on the application command registry, attach
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 Xtralab.
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';
@@ -7,9 +7,9 @@ import {
7
7
  import type { FileTreeIconConfig } from '@pierre/trees';
8
8
 
9
9
  /**
10
- * The glyph used as the Xtralab file browser's sidebar tab icon. Defined as a
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 Xtralab browser is visually distinguishable
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 Xtralab file browser. Keeps the default `complete`
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 Xtralab file tree would
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 Xtralab file browser widget. The selectors that
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 Xtralab file browser used by command handlers and other
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 = 'Xtralab File Browser';
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 Xtralab file browser's `file-tree`
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 { LabIcon, ReactWidget, refreshIcon } from '@jupyterlab/ui-components';
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
+ }