xtralab 0.6.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 CHANGED
@@ -82,6 +82,36 @@ See the [`jupyter-server-mcp` README][mcp] for other MCP clients.
82
82
  [mcp]: https://github.com/jupyter-ai-contrib/jupyter-server-mcp
83
83
  [mcp-spec]: https://modelcontextprotocol.io
84
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.
92
+
93
+ For Claude Code, this repository doubles as a one-plugin marketplace. Inside a
94
+ Claude Code session:
95
+
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.
112
+
113
+ [skills]: https://agentskills.io
114
+
85
115
  ## Language servers
86
116
 
87
117
  xtralab ships [`jupyterlab-lsp`][lsp] with two language servers pre-registered:
@@ -91,13 +91,28 @@ function LauncherDashboardComponent(props) {
91
91
  }
92
92
  }, [commands, cwd, onAgentLaunch]);
93
93
  const launchNotebook = React.useCallback(async () => {
94
- // No `kernelName` — let the notebook extension fall back to the default
95
- // kernel or prompt the user, matching the stock launcher's notebook
96
- // tile behavior.
97
- const result = (await commands.execute('notebook:create-new', {
98
- cwd
94
+ // Open a fresh notebook with no kernel attached — equivalent to clicking
95
+ // "No Kernel" in the Select Kernel dialog. `notebook:create-new` always
96
+ // routes through that dialog when no kernelName is given, so drive the
97
+ // underlying docmanager steps directly and pass `shouldStart: false` to
98
+ // suppress kernel selection. The user picks a kernel from the notebook
99
+ // toolbar when they're ready.
100
+ const file = (await commands.execute('docmanager:new-untitled', {
101
+ path: cwd,
102
+ type: 'notebook'
103
+ }));
104
+ if (!file) {
105
+ return;
106
+ }
107
+ const result = (await commands.execute('docmanager:open', {
108
+ path: file.path,
109
+ factory: 'Notebook',
110
+ kernelPreference: { shouldStart: false, canStart: true }
99
111
  }));
100
112
  if (result) {
113
+ // Match `notebook:create-new`'s post-open marker so the first manual
114
+ // save offers a rename instead of overwriting the auto-generated name.
115
+ result.isUntitled = true;
101
116
  onAgentLaunch(result);
102
117
  }
103
118
  }, [commands, cwd, onAgentLaunch]);
@@ -52,6 +52,18 @@ const plugin = {
52
52
  activate: (app, labShell, translator) => {
53
53
  const { commands } = app;
54
54
  const trans = (translator !== null && translator !== void 0 ? translator : nullTranslator).load('jupyterlab');
55
+ // The upstream toggle-{left,right}-area commands derive `isEnabled` from
56
+ // `!labShell.isEmpty(side)` but don't fire `commandChanged` when widgets
57
+ // move in or out of a side area. `CommandToolbarButton` only re-renders on
58
+ // `commandChanged` for its own id, so the buttons need an explicit notify
59
+ // whenever the shell layout shifts — `layoutModified` covers each side
60
+ // handler's `_updated` (add/remove, expand/collapse), keeping both
61
+ // `isEnabled` and `isToggled` in sync.
62
+ labShell.layoutModified.connect(() => {
63
+ for (const spec of BUTTONS) {
64
+ commands.notifyCommandChanged(spec.command);
65
+ }
66
+ });
55
67
  // Defer to `app.restored` so the upstream toggle commands are already in
56
68
  // the registry: `CommandToolbarButton` renders nothing until its command
57
69
  // exists and only re-renders on `commandChanged` of type `changed` /
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xtralab",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "An opinionated JupyterLab meta-package that bundles a curated set of extensions, ships a path-first file browser, renders jupyterlab-git's text, notebook and image diffs with its own viewers, and applies a quieter default workspace configuration.",
5
5
  "keywords": [
6
6
  "jupyter",
@@ -3,6 +3,7 @@ import * as React from 'react';
3
3
  import type { CommandRegistry } from '@lumino/commands';
4
4
  import type { ReadonlyPartialJSONObject } from '@lumino/coreutils';
5
5
  import { Poll } from '@lumino/polling';
6
+ import type { IDocumentWidget } from '@jupyterlab/docregistry';
6
7
  import {
7
8
  LabIcon,
8
9
  ReactWidget,
@@ -193,13 +194,28 @@ function LauncherDashboardComponent(
193
194
  }, [commands, cwd, onAgentLaunch]);
194
195
 
195
196
  const launchNotebook = React.useCallback(async () => {
196
- // No `kernelName` — let the notebook extension fall back to the default
197
- // kernel or prompt the user, matching the stock launcher's notebook
198
- // tile behavior.
199
- const result = (await commands.execute('notebook:create-new', {
200
- cwd
201
- })) as Widget | undefined;
197
+ // Open a fresh notebook with no kernel attached — equivalent to clicking
198
+ // "No Kernel" in the Select Kernel dialog. `notebook:create-new` always
199
+ // routes through that dialog when no kernelName is given, so drive the
200
+ // underlying docmanager steps directly and pass `shouldStart: false` to
201
+ // suppress kernel selection. The user picks a kernel from the notebook
202
+ // toolbar when they're ready.
203
+ const file = (await commands.execute('docmanager:new-untitled', {
204
+ path: cwd,
205
+ type: 'notebook'
206
+ })) as { path: string } | undefined;
207
+ if (!file) {
208
+ return;
209
+ }
210
+ const result = (await commands.execute('docmanager:open', {
211
+ path: file.path,
212
+ factory: 'Notebook',
213
+ kernelPreference: { shouldStart: false, canStart: true }
214
+ })) as IDocumentWidget | undefined;
202
215
  if (result) {
216
+ // Match `notebook:create-new`'s post-open marker so the first manual
217
+ // save offers a rename instead of overwriting the auto-generated name.
218
+ result.isUntitled = true;
203
219
  onAgentLaunch(result);
204
220
  }
205
221
  }, [commands, cwd, onAgentLaunch]);
@@ -86,6 +86,19 @@ const plugin: JupyterFrontEndPlugin<void> = {
86
86
  const { commands } = app;
87
87
  const trans = (translator ?? nullTranslator).load('jupyterlab');
88
88
 
89
+ // The upstream toggle-{left,right}-area commands derive `isEnabled` from
90
+ // `!labShell.isEmpty(side)` but don't fire `commandChanged` when widgets
91
+ // move in or out of a side area. `CommandToolbarButton` only re-renders on
92
+ // `commandChanged` for its own id, so the buttons need an explicit notify
93
+ // whenever the shell layout shifts — `layoutModified` covers each side
94
+ // handler's `_updated` (add/remove, expand/collapse), keeping both
95
+ // `isEnabled` and `isToggled` in sync.
96
+ labShell.layoutModified.connect(() => {
97
+ for (const spec of BUTTONS) {
98
+ commands.notifyCommandChanged(spec.command);
99
+ }
100
+ });
101
+
89
102
  // Defer to `app.restored` so the upstream toggle commands are already in
90
103
  // the registry: `CommandToolbarButton` renders nothing until its command
91
104
  // exists and only re-renders on `commandChanged` of type `changed` /