xtralab 0.10.0 → 0.11.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 +23 -9
  2. package/lib/commandBar/index.d.ts +14 -0
  3. package/lib/commandBar/index.js +114 -0
  4. package/lib/fileBrowser/fileBrowser.js +6 -1
  5. package/lib/highlight/index.d.ts +15 -0
  6. package/lib/highlight/index.js +214 -0
  7. package/lib/index.js +13 -1
  8. package/lib/launcher/agents.d.ts +7 -0
  9. package/lib/launcher/agents.js +24 -0
  10. package/lib/launcher/editorRegistry.js +5 -3
  11. package/lib/launcher/editors.d.ts +8 -0
  12. package/lib/launcher/editors.js +17 -0
  13. package/lib/launcher/index.js +4 -0
  14. package/lib/launcher/schemaDefaults.d.ts +15 -0
  15. package/lib/launcher/schemaDefaults.js +44 -0
  16. package/lib/omnibox/files.d.ts +7 -0
  17. package/lib/omnibox/files.js +50 -0
  18. package/lib/omnibox/index.d.ts +14 -0
  19. package/lib/omnibox/index.js +83 -0
  20. package/lib/omnibox/model.d.ts +45 -0
  21. package/lib/omnibox/model.js +142 -0
  22. package/lib/omnibox/tokens.d.ts +23 -0
  23. package/lib/omnibox/tokens.js +11 -0
  24. package/lib/omnibox/widget.d.ts +36 -0
  25. package/lib/omnibox/widget.js +161 -0
  26. package/lib/searchReplace/index.d.ts +25 -0
  27. package/lib/searchReplace/index.js +68 -0
  28. package/lib/showOutput/index.d.ts +17 -0
  29. package/lib/showOutput/index.js +154 -0
  30. package/lib/terminals/index.d.ts +4 -0
  31. package/lib/terminals/index.js +65 -16
  32. package/lib/terminals/model.d.ts +56 -3
  33. package/lib/terminals/model.js +261 -5
  34. package/lib/terminals/widget.d.ts +3 -1
  35. package/lib/terminals/widget.js +11 -3
  36. package/lib/walkthrough/index.d.ts +14 -0
  37. package/lib/walkthrough/index.js +162 -0
  38. package/lib/walkthrough/panel.d.ts +54 -0
  39. package/lib/walkthrough/panel.js +98 -0
  40. package/package.json +1 -1
  41. package/schema/launcher.json +3 -2
  42. package/schema/terminals.json +15 -0
  43. package/src/commandBar/index.ts +165 -0
  44. package/src/fileBrowser/fileBrowser.tsx +8 -1
  45. package/src/highlight/index.ts +251 -0
  46. package/src/index.ts +13 -1
  47. package/src/launcher/agents.ts +25 -0
  48. package/src/launcher/editorRegistry.ts +8 -4
  49. package/src/launcher/editors.ts +18 -0
  50. package/src/launcher/index.ts +4 -0
  51. package/src/launcher/schemaDefaults.ts +53 -0
  52. package/src/omnibox/files.ts +68 -0
  53. package/src/omnibox/index.ts +105 -0
  54. package/src/omnibox/model.ts +210 -0
  55. package/src/omnibox/tokens.ts +29 -0
  56. package/src/omnibox/widget.tsx +322 -0
  57. package/src/searchReplace/index.ts +86 -0
  58. package/src/showOutput/index.ts +199 -0
  59. package/src/terminals/index.ts +79 -16
  60. package/src/terminals/model.ts +304 -3
  61. package/src/terminals/widget.tsx +21 -4
  62. package/src/walkthrough/index.ts +202 -0
  63. package/src/walkthrough/panel.ts +152 -0
  64. package/style/commandBar.css +104 -0
  65. package/style/highlight.css +11 -0
  66. package/style/index.css +5 -0
  67. package/style/index.js +5 -0
  68. package/style/omnibox.css +120 -0
  69. package/style/showOutput.css +11 -0
  70. package/style/terminals.css +54 -10
  71. package/style/walkthrough.css +80 -0
package/README.md CHANGED
@@ -107,13 +107,23 @@ See the [`jupyter-server-mcp` README][mcp] for other MCP clients.
107
107
  [mcp]: https://github.com/jupyter-ai-contrib/jupyter-server-mcp
108
108
  [mcp-spec]: https://modelcontextprotocol.io
109
109
 
110
- ## Agent skill
110
+ ## Agent skills
111
111
 
112
- xtralab ships an [Agent Skills][skills] entry at [`agent-skill/`](./agent-skill)
113
- that teaches any coding agent how to customize JupyterLab from plain-English
114
- requests like "hide the status bar", "change the theme", or "add a language
115
- server". The same SKILL.md file works with Claude Code, Codex CLI, Gemini CLI,
116
- GitHub Copilot, and other tools that read the Agent Skills format.
112
+ xtralab ships [Agent Skills][skills] at [`agent-skill/`](./agent-skill) that
113
+ teach any coding agent how to work with the app:
114
+
115
+ - **customize-jupyterlab** turns plain-English requests like "hide the status
116
+ bar", "change the theme", or "add a language server" into the right
117
+ config-file edits.
118
+ - **guided-code-walkthrough** drives the running app over the MCP command
119
+ bridge (see the section above) to open files, jump to and highlight specific
120
+ lines (`xtralab:highlight-lines`), and build a read-only Walkthrough panel of
121
+ prose, snippets, diagrams, and clickable code links beside the editor
122
+ (`xtralab:walkthrough`, no notebook or kernel needed), so the agent can _show_
123
+ you something at your own pace instead of only describing it in chat.
124
+
125
+ The same SKILL.md files work with Claude Code, Codex CLI, Gemini CLI, GitHub
126
+ Copilot, and other tools that read the Agent Skills format.
117
127
 
118
128
  For Claude Code, this repository doubles as a one-plugin marketplace. Inside a
119
129
  Claude Code session:
@@ -124,12 +134,12 @@ Claude Code session:
124
134
  ```
125
135
 
126
136
  For agents that read from `~/.agents/skills/`, such as Codex CLI or Gemini CLI,
127
- clone and copy the skill directory:
137
+ clone and copy the skill directories:
128
138
 
129
139
  ```bash
130
140
  git clone --depth=1 https://github.com/jtpio/xtralab.git /tmp/xtralab
131
141
  mkdir -p ~/.agents/skills
132
- cp -r /tmp/xtralab/agent-skill/skills/customize-jupyterlab ~/.agents/skills/
142
+ cp -r /tmp/xtralab/agent-skill/skills/* ~/.agents/skills/
133
143
  ```
134
144
 
135
145
  See [`agent-skill/README.md`](./agent-skill/README.md) for additional install
@@ -184,7 +194,11 @@ See the [`jupyterlab-lsp` documentation][lsp-config] for the full spec.
184
194
  ## Customizing the launcher
185
195
 
186
196
  Open `Settings → Settings Editor → xtralab launcher` to override, hide, or add
187
- launcher entries. Both lists below merge with xtralab's defaults by `id`.
197
+ launcher entries. The editor shows xtralab's full built-in list as the default
198
+ for both the agents and editors settings, so you can read every shipped entry
199
+ and copy one into your user preferences to tweak in place. Both lists merge with
200
+ xtralab's defaults by `id`, so an override only needs the `id` plus the fields
201
+ you want to change.
188
202
 
189
203
  ### Agents
190
204
 
@@ -0,0 +1,14 @@
1
+ import { JupyterFrontEndPlugin } from '@jupyterlab/application';
2
+ /**
3
+ * Add a centered, search-bar-styled command bar to the top area. Clicking it
4
+ * opens the omnibox — a launcher overlay that fuzzy-searches files and
5
+ * commands and routes a typed prompt to an agent (the leading sidebar toggle
6
+ * and the menu bar sit to its left, the trailing sidebar toggle to its right;
7
+ * the bar is centered over the whole panel as an overlay, so it stays put
8
+ * regardless of their widths).
9
+ *
10
+ * The bar is only added when the omnibox is available — the `IOmnibox` token
11
+ * is provided by `xtralab:omnibox` — so it never appears with nothing to open.
12
+ */
13
+ declare const plugin: JupyterFrontEndPlugin<void>;
14
+ export default plugin;
@@ -0,0 +1,114 @@
1
+ import { ILabShell } from '@jupyterlab/application';
2
+ import { ITranslator, nullTranslator } from '@jupyterlab/translation';
3
+ import { searchIcon } from '@jupyterlab/ui-components';
4
+ import { CommandRegistry } from '@lumino/commands';
5
+ import { Widget } from '@lumino/widgets';
6
+ import { IOmnibox, OMNIBOX_OPEN_COMMAND } from '../omnibox/tokens';
7
+ const PLUGIN_ID = 'xtralab:command-bar';
8
+ /**
9
+ * A search-bar-styled launcher for the `top` area. It holds no text input of
10
+ * its own: clicking the pill (or pressing Enter/Space while it is focused)
11
+ * runs `onActivate`, which opens the omnibox.
12
+ *
13
+ * The widget's root is a transparent, full-height wrapper centered as an
14
+ * overlay over the whole top panel (style/commandBar.css). The wrapper
15
+ * ignores pointer events so only the inner pill is clickable and the empty
16
+ * strips above and below it let clicks fall through to whatever sits beneath
17
+ * the overlay, such as the menu bar.
18
+ */
19
+ class CommandBar extends Widget {
20
+ constructor(options) {
21
+ super({
22
+ node: CommandBar.createNode(options.label, options.caption, options.shortcut)
23
+ });
24
+ this.addClass('jp-xtralab-CommandBar');
25
+ this._onActivate = options.onActivate;
26
+ }
27
+ handleEvent(event) {
28
+ if (event.type === 'click') {
29
+ this._onActivate();
30
+ }
31
+ }
32
+ onAfterAttach() {
33
+ // The pill restores `pointer-events: auto`, so the click bubbles from it
34
+ // up to the root node listened on here; keyboard Enter/Space on the
35
+ // focused button raises the same synthetic click.
36
+ this.node.addEventListener('click', this);
37
+ }
38
+ onBeforeDetach() {
39
+ this.node.removeEventListener('click', this);
40
+ }
41
+ }
42
+ (function (CommandBar) {
43
+ function createNode(label, caption, shortcut) {
44
+ const wrapper = document.createElement('div');
45
+ const button = document.createElement('button');
46
+ button.type = 'button';
47
+ button.className = 'jp-xtralab-CommandBar-button';
48
+ button.title = caption;
49
+ button.setAttribute('aria-label', caption);
50
+ const icon = searchIcon.element({
51
+ tag: 'span',
52
+ className: 'jp-xtralab-CommandBar-icon'
53
+ });
54
+ const text = document.createElement('span');
55
+ text.className = 'jp-xtralab-CommandBar-label';
56
+ text.textContent = label;
57
+ button.appendChild(icon);
58
+ button.appendChild(text);
59
+ if (shortcut) {
60
+ const hint = document.createElement('span');
61
+ hint.className = 'jp-xtralab-CommandBar-shortcut';
62
+ hint.textContent = shortcut;
63
+ // Decorative: the button's aria-label already names the action.
64
+ hint.setAttribute('aria-hidden', 'true');
65
+ button.appendChild(hint);
66
+ }
67
+ wrapper.appendChild(button);
68
+ return wrapper;
69
+ }
70
+ CommandBar.createNode = createNode;
71
+ })(CommandBar || (CommandBar = {}));
72
+ /**
73
+ * Add a centered, search-bar-styled command bar to the top area. Clicking it
74
+ * opens the omnibox — a launcher overlay that fuzzy-searches files and
75
+ * commands and routes a typed prompt to an agent (the leading sidebar toggle
76
+ * and the menu bar sit to its left, the trailing sidebar toggle to its right;
77
+ * the bar is centered over the whole panel as an overlay, so it stays put
78
+ * regardless of their widths).
79
+ *
80
+ * The bar is only added when the omnibox is available — the `IOmnibox` token
81
+ * is provided by `xtralab:omnibox` — so it never appears with nothing to open.
82
+ */
83
+ const plugin = {
84
+ id: PLUGIN_ID,
85
+ description: 'Add a centered command bar to the top area that opens the omnibox.',
86
+ autoStart: true,
87
+ requires: [ILabShell],
88
+ optional: [IOmnibox, ITranslator],
89
+ activate: (app, labShell, omnibox, translator) => {
90
+ if (!omnibox) {
91
+ return;
92
+ }
93
+ const trans = (translator !== null && translator !== void 0 ? translator : nullTranslator).load('jupyterlab');
94
+ // Show the omnibox's keyboard shortcut as a hint in the pill, derived from
95
+ // the live binding (registered by xtralab:omnibox, which activates first as
96
+ // the IOmnibox provider) so it stays correct and uses the platform's
97
+ // modifier symbols. Empty when no binding is registered.
98
+ const binding = app.commands.keyBindings.find(keyBinding => keyBinding.command === OMNIBOX_OPEN_COMMAND);
99
+ const shortcut = binding
100
+ ? CommandRegistry.formatKeystroke(binding.keys)
101
+ : '';
102
+ const widget = new CommandBar({
103
+ label: trans.__('Search…'),
104
+ caption: trans.__('Search files and commands, or ask an agent'),
105
+ shortcut,
106
+ onActivate: () => {
107
+ omnibox.open();
108
+ }
109
+ });
110
+ widget.id = 'xtralab-command-bar';
111
+ labShell.add(widget, 'top', { rank: 500 });
112
+ }
113
+ };
114
+ export default plugin;
@@ -64,6 +64,10 @@ const GIT_REPO_PATH = '';
64
64
  * resolution cost just for one string.
65
65
  */
66
66
  const FILE_TREE_TAG = 'file-tree-container';
67
+ const FILE_TREE_UNSAFE_CSS = '[data-type="item"][data-item-selected="true"] ' +
68
+ '[data-item-section="spacing-item"] {' +
69
+ 'border-left-color: transparent;' +
70
+ '}';
67
71
  /**
68
72
  * Renders a `@pierre/trees` file tree backed by the Jupyter contents API.
69
73
  *
@@ -80,7 +84,8 @@ export function FileBrowserComponent(props) {
80
84
  initialExpansion: 'closed',
81
85
  search: true,
82
86
  icons: FILE_BROWSER_ICONS,
83
- itemHeight: 24
87
+ itemHeight: 24,
88
+ unsafeCSS: FILE_TREE_UNSAFE_CSS
84
89
  });
85
90
  React.useEffect(() => {
86
91
  const knownDirs = new Map();
@@ -0,0 +1,15 @@
1
+ import { JupyterFrontEndPlugin } from '@jupyterlab/application';
2
+ /**
3
+ * Contribute `xtralab:highlight-lines` and `xtralab:clear-highlights`.
4
+ *
5
+ * The built-in command surface can open a file (`docmanager:open`) and move
6
+ * the cursor to a line (`fileeditor:go-to-line`), but it cannot persistently
7
+ * highlight a span of lines — `documentsearch:start` only marks text matches
8
+ * and hijacks the find box. This plugin fills that gap with a CodeMirror line
9
+ * decoration, so an agent can point at "lines 31–43 of src/index.ts" while it
10
+ * narrates a walkthrough. `xtralab:highlight-lines` opens the file when needed,
11
+ * scrolls the range into view, and replaces any previous highlight in that
12
+ * editor; `xtralab:clear-highlights` removes every highlight.
13
+ */
14
+ declare const plugin: JupyterFrontEndPlugin<void>;
15
+ export default plugin;
@@ -0,0 +1,214 @@
1
+ import { ICommandPalette } from '@jupyterlab/apputils';
2
+ import { CodeMirrorEditor } from '@jupyterlab/codemirror';
3
+ import { IDocumentManager } from '@jupyterlab/docmanager';
4
+ import { ITranslator, nullTranslator } from '@jupyterlab/translation';
5
+ import { StateEffect, StateField } from '@codemirror/state';
6
+ import { Decoration, EditorView } from '@codemirror/view';
7
+ const PLUGIN_ID = 'xtralab:highlight';
8
+ /**
9
+ * Highlight a contiguous range of lines, or clear every highlight. These are
10
+ * the only two commands the plugin contributes; both are designed to be driven
11
+ * by a coding agent over the MCP command bridge to walk a user through code.
12
+ */
13
+ const HIGHLIGHT_LINES_COMMAND = 'xtralab:highlight-lines';
14
+ const CLEAR_HIGHLIGHTS_COMMAND = 'xtralab:clear-highlights';
15
+ /** CodeMirror line-decoration class; styled in style/highlight.css. */
16
+ const HIGHLIGHT_LINE_CLASS = 'jp-xtralab-highlightLine';
17
+ /**
18
+ * A CodeMirror effect carrying the 1-indexed, inclusive line range to
19
+ * highlight, or `null` to clear the editor's highlights.
20
+ */
21
+ const setHighlight = StateEffect.define();
22
+ /**
23
+ * Holds the highlight decorations for one editor. It is injected lazily (the
24
+ * first time an editor is highlighted) via `StateEffect.appendConfig`, so
25
+ * editors that are never highlighted pay nothing.
26
+ */
27
+ const highlightField = StateField.define({
28
+ create: () => Decoration.none,
29
+ update(decorations, transaction) {
30
+ // Keep decorations anchored as the user edits around them.
31
+ decorations = decorations.map(transaction.changes);
32
+ for (const effect of transaction.effects) {
33
+ if (!effect.is(setHighlight)) {
34
+ continue;
35
+ }
36
+ if (effect.value === null) {
37
+ decorations = Decoration.none;
38
+ continue;
39
+ }
40
+ const { doc } = transaction.state;
41
+ const lineDecoration = Decoration.line({ class: HIGHLIGHT_LINE_CLASS });
42
+ const start = Math.max(1, Math.min(effect.value.start, doc.lines));
43
+ const end = Math.max(start, Math.min(effect.value.end, doc.lines));
44
+ const ranges = [];
45
+ for (let line = start; line <= end; line++) {
46
+ ranges.push(lineDecoration.range(doc.line(line).from));
47
+ }
48
+ decorations = Decoration.set(ranges);
49
+ }
50
+ return decorations;
51
+ },
52
+ provide: field => EditorView.decorations.from(field)
53
+ });
54
+ /**
55
+ * Return the CodeMirror view backing a widget's editor, or `null` when the
56
+ * widget is not a CodeMirror-based text editor (a notebook, a terminal, the
57
+ * settings editor, …). Duck-typed on `content.editor` so the plugin does not
58
+ * depend on `@jupyterlab/fileeditor`.
59
+ */
60
+ function viewForWidget(widget) {
61
+ var _a;
62
+ const editor = (_a = widget === null || widget === void 0 ? void 0 : widget.content) === null || _a === void 0 ? void 0 : _a.editor;
63
+ return editor instanceof CodeMirrorEditor ? editor.editor : null;
64
+ }
65
+ /**
66
+ * Coerce a JSON command argument to a positive integer line number, falling
67
+ * back to `fallback` when it is missing or not a finite number.
68
+ */
69
+ function toLine(value, fallback) {
70
+ const n = Math.trunc(Number(value));
71
+ return Number.isFinite(n) && n >= 1 ? n : fallback;
72
+ }
73
+ /**
74
+ * Contribute `xtralab:highlight-lines` and `xtralab:clear-highlights`.
75
+ *
76
+ * The built-in command surface can open a file (`docmanager:open`) and move
77
+ * the cursor to a line (`fileeditor:go-to-line`), but it cannot persistently
78
+ * highlight a span of lines — `documentsearch:start` only marks text matches
79
+ * and hijacks the find box. This plugin fills that gap with a CodeMirror line
80
+ * decoration, so an agent can point at "lines 31–43 of src/index.ts" while it
81
+ * narrates a walkthrough. `xtralab:highlight-lines` opens the file when needed,
82
+ * scrolls the range into view, and replaces any previous highlight in that
83
+ * editor; `xtralab:clear-highlights` removes every highlight.
84
+ */
85
+ const plugin = {
86
+ id: PLUGIN_ID,
87
+ description: 'Highlight line ranges in text editors to guide walkthroughs.',
88
+ autoStart: true,
89
+ requires: [IDocumentManager],
90
+ optional: [ICommandPalette, ITranslator],
91
+ activate: (app, docManager, palette, translator) => {
92
+ const { commands, shell } = app;
93
+ const trans = (translator !== null && translator !== void 0 ? translator : nullTranslator).load('jupyterlab');
94
+ // Editors that currently carry a highlight, so a single clear can reach
95
+ // them all. Disposed editors are dropped on the next clear.
96
+ const highlighted = new Set();
97
+ const ensureField = (view) => {
98
+ if (view.state.field(highlightField, false) === undefined) {
99
+ view.dispatch({ effects: StateEffect.appendConfig.of(highlightField) });
100
+ }
101
+ };
102
+ commands.addCommand(HIGHLIGHT_LINES_COMMAND, {
103
+ label: trans.__('Highlight Lines'),
104
+ caption: trans.__('Highlight a range of lines in a text editor'),
105
+ // Advertise the argument shape so agents listing commands over the MCP
106
+ // bridge can see how to call it, the same way core commands do.
107
+ describedBy: {
108
+ args: {
109
+ type: 'object',
110
+ properties: {
111
+ path: {
112
+ type: 'string',
113
+ description: 'Path of the file to highlight in, relative to the server root. Opened if needed; defaults to the active editor when omitted.'
114
+ },
115
+ line: {
116
+ type: 'number',
117
+ description: 'First line to highlight (1-indexed). Defaults to 1.'
118
+ },
119
+ endLine: {
120
+ type: 'number',
121
+ description: 'Last line to highlight (1-indexed, inclusive). Defaults to `line` for a single line.'
122
+ },
123
+ reveal: {
124
+ type: 'boolean',
125
+ description: 'Whether to scroll the highlighted range into view. Defaults to true.'
126
+ }
127
+ }
128
+ }
129
+ },
130
+ execute: async (args) => {
131
+ const line = toLine(args['line'], 1);
132
+ const endLine = toLine(args['endLine'], line);
133
+ const path = typeof args['path'] === 'string' ? args['path'] : undefined;
134
+ const reveal = args['reveal'] !== false;
135
+ // With a path, bind strictly to that document: never fall back to the
136
+ // active widget, or an explicit path could silently highlight an
137
+ // unrelated editor that happens to be focused.
138
+ let widget;
139
+ if (path) {
140
+ // Fail fast on a missing path instead of opening a phantom widget
141
+ // behind a blocking "file not found" dialog.
142
+ try {
143
+ await docManager.services.contents.get(path, { content: false });
144
+ }
145
+ catch (_a) {
146
+ throw new Error(`xtralab: no file at "${path}"`);
147
+ }
148
+ const opened = docManager.openOrReveal(path);
149
+ if (!opened) {
150
+ throw new Error(`xtralab: could not open "${path}"`);
151
+ }
152
+ // Wait for the model and the editor view to be ready.
153
+ await opened.context.ready;
154
+ await opened.revealed;
155
+ widget = opened;
156
+ }
157
+ else {
158
+ widget = shell.currentWidget;
159
+ }
160
+ const view = viewForWidget(widget);
161
+ if (!view) {
162
+ throw new Error(path
163
+ ? `xtralab: "${path}" is not open in a text editor`
164
+ : 'xtralab: the active widget is not a text editor');
165
+ }
166
+ const start = Math.min(line, endLine);
167
+ const end = Math.max(line, endLine);
168
+ ensureField(view);
169
+ const lineCount = view.state.doc.lines;
170
+ const anchor = view.state.doc.line(Math.max(1, Math.min(start, lineCount))).from;
171
+ const effects = [
172
+ setHighlight.of({ start, end })
173
+ ];
174
+ if (reveal) {
175
+ effects.push(EditorView.scrollIntoView(anchor, { y: 'center' }));
176
+ }
177
+ view.dispatch({ effects });
178
+ // Forget editors that have since been closed before tracking this one.
179
+ for (const tracked of highlighted) {
180
+ if (!tracked.dom.isConnected) {
181
+ highlighted.delete(tracked);
182
+ }
183
+ }
184
+ highlighted.add(view);
185
+ return start === end
186
+ ? trans.__('Highlighted line %1', start)
187
+ : trans.__('Highlighted lines %1-%2', start, end);
188
+ }
189
+ });
190
+ commands.addCommand(CLEAR_HIGHLIGHTS_COMMAND, {
191
+ label: trans.__('Clear Highlights'),
192
+ caption: trans.__('Remove all xtralab line highlights'),
193
+ execute: () => {
194
+ for (const view of highlighted) {
195
+ try {
196
+ if (view.state.field(highlightField, false) !== undefined) {
197
+ view.dispatch({ effects: setHighlight.of(null) });
198
+ }
199
+ }
200
+ catch (_a) {
201
+ // The editor was disposed; nothing to clear.
202
+ }
203
+ }
204
+ highlighted.clear();
205
+ }
206
+ });
207
+ if (palette) {
208
+ const category = trans.__('Other');
209
+ palette.addItem({ command: HIGHLIGHT_LINES_COMMAND, category });
210
+ palette.addItem({ command: CLEAR_HIGHLIGHTS_COMMAND, category });
211
+ }
212
+ }
213
+ };
214
+ export default plugin;
package/lib/index.js CHANGED
@@ -1,15 +1,21 @@
1
1
  import agentSessionsPlugin from './agentSessions';
2
+ import commandBarPlugin from './commandBar';
2
3
  import fileBrowserPlugin from './fileBrowser';
3
4
  import fileTypeIconsPlugin from './fileTypeIcons';
4
5
  import editorBreadcrumbsPlugin from './editorBreadcrumbs';
5
6
  import editorIndentPlugin from './editorIndent';
6
7
  import gitPlugins from './git';
8
+ import highlightPlugin from './highlight';
7
9
  import launcherPlugins from './launcher';
8
10
  import menusPlugin from './menus';
11
+ import omniboxPlugin from './omnibox';
12
+ import searchReplacePlugin from './searchReplace';
13
+ import showOutputPlugin from './showOutput';
9
14
  import sidebarPlugin from './sidebar';
10
15
  import terminalNotificationsPlugin from './terminalNotifications';
11
16
  import terminalsPlugin from './terminals';
12
17
  import topBarPlugin from './topBar';
18
+ import walkthroughPlugin from './walkthrough';
13
19
  /**
14
20
  * Every plugin contributed by `xtralab`. The entry point of the
15
21
  * labextension is an array because the package bundles several independent
@@ -21,16 +27,22 @@ import topBarPlugin from './topBar';
21
27
  */
22
28
  const plugins = [
23
29
  agentSessionsPlugin,
30
+ commandBarPlugin,
24
31
  editorBreadcrumbsPlugin,
25
32
  editorIndentPlugin,
26
33
  fileBrowserPlugin,
27
34
  fileTypeIconsPlugin,
28
35
  ...gitPlugins,
36
+ highlightPlugin,
29
37
  ...launcherPlugins,
30
38
  menusPlugin,
39
+ omniboxPlugin,
40
+ searchReplacePlugin,
41
+ showOutputPlugin,
31
42
  sidebarPlugin,
32
43
  terminalNotificationsPlugin,
33
44
  terminalsPlugin,
34
- topBarPlugin
45
+ topBarPlugin,
46
+ walkthroughPlugin
35
47
  ];
36
48
  export default plugins;
@@ -69,6 +69,13 @@ export interface IAgentSettings {
69
69
  */
70
70
  promptArgs?: string[] | null;
71
71
  }
72
+ /**
73
+ * The built-in agents projected into the JSON settings shape
74
+ * ({@link IAgentSettings}), dropping the runtime-only {@link LabIcon}.
75
+ * {@link registerLauncherSchemaDefaults} injects this as the `agents` setting's
76
+ * schema default so the Settings Editor shows the shipped list.
77
+ */
78
+ export declare function defaultAgentSettings(): IAgentSettings[];
72
79
  /**
73
80
  * Merge xtralab's defaults with the user's settings. Default entries keep
74
81
  * their built-in fields unless explicitly overridden; user-only entries are
@@ -103,6 +103,30 @@ const DEFAULTS = [
103
103
  promptArgs: []
104
104
  }
105
105
  ];
106
+ /**
107
+ * The built-in agents projected into the JSON settings shape
108
+ * ({@link IAgentSettings}), dropping the runtime-only {@link LabIcon}.
109
+ * {@link registerLauncherSchemaDefaults} injects this as the `agents` setting's
110
+ * schema default so the Settings Editor shows the shipped list.
111
+ */
112
+ export function defaultAgentSettings() {
113
+ return DEFAULTS.map(agent => {
114
+ const entry = {
115
+ id: agent.id,
116
+ label: agent.label,
117
+ caption: agent.caption,
118
+ command: agent.command,
119
+ rank: agent.rank,
120
+ requireAvailable: agent.requireAvailable
121
+ };
122
+ // Omit `promptArgs` for agents that don't take an inline prompt, so the
123
+ // default doesn't show a prompt form they lack.
124
+ if (agent.promptArgs !== undefined) {
125
+ entry.promptArgs = agent.promptArgs;
126
+ }
127
+ return entry;
128
+ });
129
+ }
106
130
  function resolveIcon(id, iconSvg) {
107
131
  var _a;
108
132
  if (iconSvg) {
@@ -3,6 +3,7 @@ import { Token } from '@lumino/coreutils';
3
3
  import { Signal } from '@lumino/signaling';
4
4
  import { fetchAvailableCommands } from './availability';
5
5
  import { mergeEditors, resolveEditor } from './editors';
6
+ import { LAUNCHER_PLUGIN_ID, registerLauncherSchemaDefaults } from './schemaDefaults';
6
7
  /**
7
8
  * DI token for {@link IEditorRegistry}. Provided by `xtralab:editor-registry`
8
9
  * and consumed — optionally — by `xtralab:launcher` (for its tile) and
@@ -35,8 +36,6 @@ class EditorRegistry {
35
36
  this._changed.emit();
36
37
  }
37
38
  }
38
- /** Schema id the `editors` setting lives under (shared with the launcher). */
39
- const LAUNCHER_SETTINGS_ID = 'xtralab:launcher';
40
39
  /**
41
40
  * Provides {@link IEditorRegistry}: merges xtralab's built-in editors with the
42
41
  * user's `editors` setting, probes the server's `$PATH` to resolve the launcher
@@ -69,8 +68,11 @@ export const editorRegistryPlugin = {
69
68
  return Array.isArray(raw) ? raw : [];
70
69
  };
71
70
  if (settingRegistry) {
71
+ // Must precede the first `load`: the schema defers loading until a
72
+ // transform is registered.
73
+ registerLauncherSchemaDefaults(settingRegistry);
72
74
  try {
73
- const settings = await settingRegistry.load(LAUNCHER_SETTINGS_ID);
75
+ const settings = await settingRegistry.load(LAUNCHER_PLUGIN_ID);
74
76
  await apply(readOverrides(settings));
75
77
  settings.changed.connect(async () => {
76
78
  try {
@@ -56,6 +56,14 @@ export interface IEditorSettings {
56
56
  /** See {@link IEditor.requireAvailable}. */
57
57
  requireAvailable?: boolean;
58
58
  }
59
+ /**
60
+ * The built-in editors projected into the JSON settings shape
61
+ * ({@link IEditorSettings}), dropping the runtime-only {@link LabIcon}. Mirrors
62
+ * {@link defaultAgentSettings}: {@link registerLauncherSchemaDefaults} injects
63
+ * this as the `editors` setting's schema default so the Settings Editor shows
64
+ * the shipped list.
65
+ */
66
+ export declare function defaultEditorSettings(): IEditorSettings[];
59
67
  /**
60
68
  * Merge xtralab's built-in editors with the user's `editors` settings. Built-in
61
69
  * entries keep their fields unless explicitly overridden; user-only entries are
@@ -26,6 +26,23 @@ const DEFAULTS = [
26
26
  requireAvailable: true
27
27
  }
28
28
  ];
29
+ /**
30
+ * The built-in editors projected into the JSON settings shape
31
+ * ({@link IEditorSettings}), dropping the runtime-only {@link LabIcon}. Mirrors
32
+ * {@link defaultAgentSettings}: {@link registerLauncherSchemaDefaults} injects
33
+ * this as the `editors` setting's schema default so the Settings Editor shows
34
+ * the shipped list.
35
+ */
36
+ export function defaultEditorSettings() {
37
+ return DEFAULTS.map(editor => ({
38
+ id: editor.id,
39
+ label: editor.label,
40
+ caption: editor.caption,
41
+ command: editor.command,
42
+ rank: editor.rank,
43
+ requireAvailable: editor.requireAvailable
44
+ }));
45
+ }
29
46
  function resolveEditorIcon(id, iconSvg) {
30
47
  var _a;
31
48
  if (iconSvg) {
@@ -11,6 +11,7 @@ import { CREATE_LAUNCHER_COMMAND, registerAgentCommands } from './commands';
11
11
  import { LauncherDashboard } from './dashboard';
12
12
  import { editorRegistryPlugin, IEditorRegistry } from './editorRegistry';
13
13
  import { AgentRegistry } from './registry';
14
+ import { registerLauncherSchemaDefaults } from './schemaDefaults';
14
15
  import { IAgentRegistry } from './tokens';
15
16
  const PLUGIN_ID = 'xtralab:launcher';
16
17
  /**
@@ -77,6 +78,9 @@ const plugin = {
77
78
  return Array.isArray(raw) ? raw : [];
78
79
  };
79
80
  if (settingRegistry) {
81
+ // Must precede the first `load`: the schema defers loading until a
82
+ // transform is registered.
83
+ registerLauncherSchemaDefaults(settingRegistry);
80
84
  try {
81
85
  const settings = await settingRegistry.load(PLUGIN_ID);
82
86
  await applyAgents(readOverrides(settings));
@@ -0,0 +1,15 @@
1
+ import type { ISettingRegistry } from '@jupyterlab/settingregistry';
2
+ /** Schema id the launcher's `agents`/`editors` settings live under. */
3
+ export declare const LAUNCHER_PLUGIN_ID = "xtralab:launcher";
4
+ /**
5
+ * Register a `fetch` transform that injects xtralab's built-in agent and
6
+ * editor lists as the schema defaults for the `xtralab:launcher` settings, so
7
+ * the Settings Editor shows the shipped list.
8
+ *
9
+ * The schema declares `jupyter.lab.transform: true`, so the registry defers
10
+ * loading the plugin until a transform is registered: callers must invoke this
11
+ * before their first `settingRegistry.load(LAUNCHER_PLUGIN_ID)`. Idempotent, so
12
+ * the two plugins that load these settings can both call it regardless of
13
+ * activation order.
14
+ */
15
+ export declare function registerLauncherSchemaDefaults(settingRegistry: ISettingRegistry): void;