xtralab 0.1.2 → 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.
Files changed (75) hide show
  1. package/README.md +129 -46
  2. package/lib/fileBrowser/commands.d.ts +2 -2
  3. package/lib/fileBrowser/commands.js +2 -2
  4. package/lib/fileBrowser/fileBrowser.js +72 -2
  5. package/lib/fileBrowser/gitignore.d.ts +29 -0
  6. package/lib/fileBrowser/gitignore.js +74 -0
  7. package/lib/fileBrowser/icons.d.ts +4 -4
  8. package/lib/fileBrowser/icons.js +4 -4
  9. package/lib/fileBrowser/widget.d.ts +2 -2
  10. package/lib/fileBrowser/widget.js +2 -2
  11. package/lib/git/api.d.ts +11 -0
  12. package/lib/git/api.js +19 -0
  13. package/lib/git/commands.d.ts +3 -1
  14. package/lib/git/commands.js +10 -4
  15. package/lib/git/diffWidget.d.ts +11 -0
  16. package/lib/git/diffWidget.js +90 -9
  17. package/lib/git/icons.d.ts +1 -1
  18. package/lib/git/icons.js +1 -1
  19. package/lib/git/index.js +22 -6
  20. package/lib/git/panelComponent.d.ts +3 -1
  21. package/lib/git/panelComponent.js +6 -3
  22. package/lib/index.js +9 -1
  23. package/lib/launcher/agents.d.ts +78 -0
  24. package/lib/launcher/agents.js +168 -0
  25. package/lib/launcher/availability.d.ts +12 -0
  26. package/lib/launcher/availability.js +53 -0
  27. package/lib/launcher/commands.d.ts +22 -0
  28. package/lib/launcher/commands.js +89 -0
  29. package/lib/launcher/dashboard.d.ts +46 -0
  30. package/lib/launcher/dashboard.js +214 -0
  31. package/lib/launcher/icons.d.ts +33 -0
  32. package/lib/launcher/icons.js +113 -0
  33. package/lib/launcher/index.d.ts +23 -0
  34. package/lib/launcher/index.js +191 -0
  35. package/lib/launcher/invocation.d.ts +29 -0
  36. package/lib/launcher/invocation.js +42 -0
  37. package/lib/menus/index.d.ts +14 -0
  38. package/lib/menus/index.js +102 -0
  39. package/lib/sidebar/index.d.ts +18 -0
  40. package/lib/sidebar/index.js +155 -0
  41. package/lib/statusBar/index.d.ts +14 -0
  42. package/lib/statusBar/index.js +89 -0
  43. package/lib/statusBar/widget.d.ts +121 -0
  44. package/lib/statusBar/widget.js +237 -0
  45. package/package.json +25 -3
  46. package/schema/launcher.json +70 -0
  47. package/schema/sidebar.json +57 -0
  48. package/src/fileBrowser/commands.ts +2 -2
  49. package/src/fileBrowser/fileBrowser.tsx +76 -2
  50. package/src/fileBrowser/gitignore.ts +86 -0
  51. package/src/fileBrowser/icons.ts +4 -4
  52. package/src/fileBrowser/widget.tsx +3 -3
  53. package/src/git/api.ts +20 -0
  54. package/src/git/commands.ts +19 -5
  55. package/src/git/diffWidget.tsx +109 -13
  56. package/src/git/icons.ts +1 -1
  57. package/src/git/index.ts +31 -5
  58. package/src/git/panelComponent.tsx +17 -5
  59. package/src/index.ts +9 -1
  60. package/src/launcher/agents.ts +248 -0
  61. package/src/launcher/availability.ts +73 -0
  62. package/src/launcher/commands.ts +103 -0
  63. package/src/launcher/dashboard.tsx +462 -0
  64. package/src/launcher/icons.ts +123 -0
  65. package/src/launcher/index.ts +228 -0
  66. package/src/launcher/invocation.ts +45 -0
  67. package/src/menus/index.ts +124 -0
  68. package/src/sidebar/index.ts +195 -0
  69. package/src/statusBar/index.ts +115 -0
  70. package/src/statusBar/widget.tsx +318 -0
  71. package/style/git.css +4 -8
  72. package/style/index.css +2 -0
  73. package/style/index.js +2 -0
  74. package/style/launcher.css +323 -0
  75. package/style/statusBar.css +95 -0
@@ -0,0 +1,121 @@
1
+ import type { MainAreaWidget } from '@jupyterlab/apputils';
2
+ import type { ServiceManager } from '@jupyterlab/services';
3
+ import type { ITerminal, ITerminalTracker } from '@jupyterlab/terminal';
4
+ import { type TranslationBundle } from '@jupyterlab/translation';
5
+ import { VDomModel, VDomRenderer } from '@jupyterlab/ui-components';
6
+ import * as React from 'react';
7
+ /**
8
+ * The widget shape every entry in `ITerminalTracker` takes — kept here
9
+ * so the registry's call sites read cleanly.
10
+ */
11
+ export type TerminalWidget = MainAreaWidget<ITerminal.ITerminal>;
12
+ /**
13
+ * Source-of-truth model for the status bar. Each running terminal
14
+ * session known to the server is one entry; the registry caches the
15
+ * last `widget.title.label` we observed so the agent name (or any
16
+ * xterm-published title) survives the user closing the tab while the
17
+ * session continues running on the backend.
18
+ *
19
+ * Two upstream signals feed the model:
20
+ * - `serviceManager.terminals.runningChanged` is the authoritative
21
+ * list of live sessions; everything not in it has been shut down
22
+ * server-side and we must drop it.
23
+ * - `tracker.widgetAdded` plus the per-widget `title.changed` /
24
+ * `disposed` signals keep our label cache in sync with whatever
25
+ * xterm/the launcher has set on the open tabs.
26
+ *
27
+ * `stateChanged` is reused for both kinds of update so consumers
28
+ * (the per-session status item, the plugin's add/remove sync) can
29
+ * subscribe once.
30
+ */
31
+ export declare class SessionRegistry extends VDomModel {
32
+ constructor(serviceManager: ServiceManager.IManager, tracker: ITerminalTracker);
33
+ /**
34
+ * Names of all live sessions, ordered by their stable rank so the
35
+ * status bar items keep their slots as new sessions are added.
36
+ */
37
+ sessionNames(): string[];
38
+ /**
39
+ * True iff the named session is still on the server. Used by the
40
+ * per-session item to render `null` during the brief window between
41
+ * the session's shutdown and the plugin disposing the item.
42
+ */
43
+ has(name: string): boolean;
44
+ /**
45
+ * Display name for the session. The cache wins because it only
46
+ * holds "real" labels (the launcher's agent name or an xterm escape
47
+ * sequence — see `_cacheLabel` for the filter); reading it first
48
+ * prevents the transient `Terminal {name}` an XTerm widget shows
49
+ * during reconnect from flickering into the status bar. The live
50
+ * widget title is used when no cache exists, and the
51
+ * `Terminal {name}` fallback covers sessions we have never seen a
52
+ * widget for (e.g. surviving a lab reload).
53
+ */
54
+ labelFor(name: string): string;
55
+ /**
56
+ * Return the open widget for a session, if any. The status item
57
+ * uses this to switch behaviour between "activate existing tab"
58
+ * and "open a new tab connected to the live session".
59
+ */
60
+ widgetFor(name: string): TerminalWidget | null;
61
+ /**
62
+ * Stable per-session rank assigned in observation order. Reused
63
+ * across `registerStatusItem` calls so an item that is briefly
64
+ * unregistered (e.g. while a widget is being recreated) lands back
65
+ * in the same slot. Cleaned up when the session is shut down so the
66
+ * counter does not grow without bound across long sessions.
67
+ */
68
+ rankFor(name: string): number;
69
+ dispose(): void;
70
+ private _refreshLive;
71
+ private _onRunningChanged;
72
+ private _onWidgetAdded;
73
+ private _trackWidget;
74
+ private _untrackWidget;
75
+ private _onTitleChanged;
76
+ /**
77
+ * Update the cached label for a session, but only when the new
78
+ * label is "real" — non-empty and not one of the transient defaults
79
+ * the XTerm widget cycles through during reconnect (`'...'` while
80
+ * the websocket is opening, `'Terminal {name}'` once
81
+ * `_initialConnection` fires). Without the filter, briefly
82
+ * reopening a tab while an agent has yet to re-emit its xterm
83
+ * title escape sequence would clobber the cached agent name with
84
+ * `Terminal 1`, which is exactly the regression we are guarding
85
+ * against. Live widget titles still display whatever the widget
86
+ * currently holds because `labelFor` consults the widget first;
87
+ * the filter only affects what survives a tab close.
88
+ */
89
+ private _cacheLabel;
90
+ private _onWidgetDisposed;
91
+ private _terminals;
92
+ private _tracker;
93
+ private _labels;
94
+ private _ranks;
95
+ private _live;
96
+ private _nextRank;
97
+ }
98
+ /**
99
+ * One status bar item per live terminal session. Renders a terminal
100
+ * icon plus the session's label, with an inline shutdown button that
101
+ * appears on hover/focus. The item delegates click handling to the
102
+ * plugin so the lifecycle (create/dispose) and the user-facing
103
+ * behaviour (activate vs. reopen, shutdown) live in one place.
104
+ */
105
+ export declare class TerminalSessionItem extends VDomRenderer<SessionRegistry> {
106
+ constructor(options: TerminalSessionItem.IOptions);
107
+ render(): React.ReactElement | null;
108
+ private _sessionName;
109
+ private _trans;
110
+ private _onActivate;
111
+ private _onShutdown;
112
+ }
113
+ export declare namespace TerminalSessionItem {
114
+ interface IOptions {
115
+ registry: SessionRegistry;
116
+ sessionName: string;
117
+ trans?: TranslationBundle;
118
+ onActivate: (sessionName: string) => void;
119
+ onShutdown: (sessionName: string) => void;
120
+ }
121
+ }
@@ -0,0 +1,237 @@
1
+ import { nullTranslator } from '@jupyterlab/translation';
2
+ import { closeIcon, terminalIcon, VDomModel, VDomRenderer } from '@jupyterlab/ui-components';
3
+ import * as React from 'react';
4
+ /**
5
+ * Source-of-truth model for the status bar. Each running terminal
6
+ * session known to the server is one entry; the registry caches the
7
+ * last `widget.title.label` we observed so the agent name (or any
8
+ * xterm-published title) survives the user closing the tab while the
9
+ * session continues running on the backend.
10
+ *
11
+ * Two upstream signals feed the model:
12
+ * - `serviceManager.terminals.runningChanged` is the authoritative
13
+ * list of live sessions; everything not in it has been shut down
14
+ * server-side and we must drop it.
15
+ * - `tracker.widgetAdded` plus the per-widget `title.changed` /
16
+ * `disposed` signals keep our label cache in sync with whatever
17
+ * xterm/the launcher has set on the open tabs.
18
+ *
19
+ * `stateChanged` is reused for both kinds of update so consumers
20
+ * (the per-session status item, the plugin's add/remove sync) can
21
+ * subscribe once.
22
+ */
23
+ export class SessionRegistry extends VDomModel {
24
+ constructor(serviceManager, tracker) {
25
+ super();
26
+ this._labels = new Map();
27
+ this._ranks = new Map();
28
+ this._live = new Set();
29
+ this._nextRank = 100;
30
+ this._terminals = serviceManager.terminals;
31
+ this._tracker = tracker;
32
+ this._terminals.runningChanged.connect(this._onRunningChanged, this);
33
+ this._tracker.widgetAdded.connect(this._onWidgetAdded, this);
34
+ this._tracker.forEach(widget => this._trackWidget(widget));
35
+ this._refreshLive();
36
+ }
37
+ /**
38
+ * Names of all live sessions, ordered by their stable rank so the
39
+ * status bar items keep their slots as new sessions are added.
40
+ */
41
+ sessionNames() {
42
+ const names = Array.from(this._live);
43
+ names.sort((a, b) => this.rankFor(a) - this.rankFor(b));
44
+ return names;
45
+ }
46
+ /**
47
+ * True iff the named session is still on the server. Used by the
48
+ * per-session item to render `null` during the brief window between
49
+ * the session's shutdown and the plugin disposing the item.
50
+ */
51
+ has(name) {
52
+ return this._live.has(name);
53
+ }
54
+ /**
55
+ * Display name for the session. The cache wins because it only
56
+ * holds "real" labels (the launcher's agent name or an xterm escape
57
+ * sequence — see `_cacheLabel` for the filter); reading it first
58
+ * prevents the transient `Terminal {name}` an XTerm widget shows
59
+ * during reconnect from flickering into the status bar. The live
60
+ * widget title is used when no cache exists, and the
61
+ * `Terminal {name}` fallback covers sessions we have never seen a
62
+ * widget for (e.g. surviving a lab reload).
63
+ */
64
+ labelFor(name) {
65
+ const cached = this._labels.get(name);
66
+ if (cached) {
67
+ return cached;
68
+ }
69
+ const widget = this.widgetFor(name);
70
+ if (widget && widget.title.label) {
71
+ return widget.title.label;
72
+ }
73
+ return `Terminal ${name}`;
74
+ }
75
+ /**
76
+ * Return the open widget for a session, if any. The status item
77
+ * uses this to switch behaviour between "activate existing tab"
78
+ * and "open a new tab connected to the live session".
79
+ */
80
+ widgetFor(name) {
81
+ var _a;
82
+ return ((_a = this._tracker.find(widget => widget.content.session.name === name)) !== null && _a !== void 0 ? _a : null);
83
+ }
84
+ /**
85
+ * Stable per-session rank assigned in observation order. Reused
86
+ * across `registerStatusItem` calls so an item that is briefly
87
+ * unregistered (e.g. while a widget is being recreated) lands back
88
+ * in the same slot. Cleaned up when the session is shut down so the
89
+ * counter does not grow without bound across long sessions.
90
+ */
91
+ rankFor(name) {
92
+ let rank = this._ranks.get(name);
93
+ if (rank === undefined) {
94
+ rank = this._nextRank++;
95
+ this._ranks.set(name, rank);
96
+ }
97
+ return rank;
98
+ }
99
+ dispose() {
100
+ if (this.isDisposed) {
101
+ return;
102
+ }
103
+ this._terminals.runningChanged.disconnect(this._onRunningChanged, this);
104
+ this._tracker.widgetAdded.disconnect(this._onWidgetAdded, this);
105
+ this._tracker.forEach(widget => this._untrackWidget(widget));
106
+ super.dispose();
107
+ }
108
+ _refreshLive() {
109
+ const next = new Set();
110
+ for (const model of this._terminals.running()) {
111
+ next.add(model.name);
112
+ }
113
+ this._live = next;
114
+ // Drop cache + rank entries for sessions that have been shut down
115
+ // server-side. Without this prune the rank counter would keep
116
+ // climbing across long-running labs as terminals come and go.
117
+ for (const name of Array.from(this._labels.keys())) {
118
+ if (!next.has(name)) {
119
+ this._labels.delete(name);
120
+ }
121
+ }
122
+ for (const name of Array.from(this._ranks.keys())) {
123
+ if (!next.has(name)) {
124
+ this._ranks.delete(name);
125
+ }
126
+ }
127
+ }
128
+ _onRunningChanged(_, sessions) {
129
+ this._live = new Set(sessions.map(model => model.name));
130
+ for (const name of Array.from(this._labels.keys())) {
131
+ if (!this._live.has(name)) {
132
+ this._labels.delete(name);
133
+ }
134
+ }
135
+ for (const name of Array.from(this._ranks.keys())) {
136
+ if (!this._live.has(name)) {
137
+ this._ranks.delete(name);
138
+ }
139
+ }
140
+ this.stateChanged.emit();
141
+ }
142
+ _onWidgetAdded(_, widget) {
143
+ this._trackWidget(widget);
144
+ this.stateChanged.emit();
145
+ }
146
+ _trackWidget(widget) {
147
+ const name = widget.content.session.name;
148
+ this._cacheLabel(name, widget.title.label);
149
+ widget.title.changed.connect(this._onTitleChanged, this);
150
+ widget.disposed.connect(this._onWidgetDisposed, this);
151
+ }
152
+ _untrackWidget(widget) {
153
+ widget.title.changed.disconnect(this._onTitleChanged, this);
154
+ widget.disposed.disconnect(this._onWidgetDisposed, this);
155
+ }
156
+ _onTitleChanged(title) {
157
+ var _a;
158
+ const owner = title.owner;
159
+ const session = (_a = owner.content) === null || _a === void 0 ? void 0 : _a.session;
160
+ if (!session) {
161
+ return;
162
+ }
163
+ this._cacheLabel(session.name, title.label);
164
+ this.stateChanged.emit();
165
+ }
166
+ /**
167
+ * Update the cached label for a session, but only when the new
168
+ * label is "real" — non-empty and not one of the transient defaults
169
+ * the XTerm widget cycles through during reconnect (`'...'` while
170
+ * the websocket is opening, `'Terminal {name}'` once
171
+ * `_initialConnection` fires). Without the filter, briefly
172
+ * reopening a tab while an agent has yet to re-emit its xterm
173
+ * title escape sequence would clobber the cached agent name with
174
+ * `Terminal 1`, which is exactly the regression we are guarding
175
+ * against. Live widget titles still display whatever the widget
176
+ * currently holds because `labelFor` consults the widget first;
177
+ * the filter only affects what survives a tab close.
178
+ */
179
+ _cacheLabel(name, label) {
180
+ if (!label) {
181
+ return;
182
+ }
183
+ if (label === '...' || label === `Terminal ${name}`) {
184
+ return;
185
+ }
186
+ this._labels.set(name, label);
187
+ }
188
+ _onWidgetDisposed(widget) {
189
+ // The widget is gone but the session may still be running on the
190
+ // server — keep the cached label so the status item keeps the
191
+ // agent's name when the user reopens the tab. We only clean up
192
+ // our subscriptions here; the cache is purged by `runningChanged`
193
+ // once the session itself goes away.
194
+ this._untrackWidget(widget);
195
+ this.stateChanged.emit();
196
+ }
197
+ }
198
+ /**
199
+ * One status bar item per live terminal session. Renders a terminal
200
+ * icon plus the session's label, with an inline shutdown button that
201
+ * appears on hover/focus. The item delegates click handling to the
202
+ * plugin so the lifecycle (create/dispose) and the user-facing
203
+ * behaviour (activate vs. reopen, shutdown) live in one place.
204
+ */
205
+ export class TerminalSessionItem extends VDomRenderer {
206
+ constructor(options) {
207
+ var _a;
208
+ super(options.registry);
209
+ this._sessionName = options.sessionName;
210
+ this._trans = (_a = options.trans) !== null && _a !== void 0 ? _a : nullTranslator.load('jupyterlab');
211
+ this._onActivate = options.onActivate;
212
+ this._onShutdown = options.onShutdown;
213
+ this.addClass('jp-mod-highlighted');
214
+ this.addClass('jp-xtralab-StatusBar-Terminal');
215
+ }
216
+ render() {
217
+ if (!this.model || !this.model.has(this._sessionName)) {
218
+ return null;
219
+ }
220
+ const trans = this._trans;
221
+ const label = this.model.labelFor(this._sessionName);
222
+ const hasWidget = this.model.widgetFor(this._sessionName) !== null;
223
+ const tooltip = hasWidget
224
+ ? trans.__('Activate %1', label)
225
+ : trans.__('Reopen %1', label);
226
+ this.node.title = tooltip;
227
+ return (React.createElement("div", { className: "jp-xtralab-StatusBar-Terminal-Container" },
228
+ React.createElement("button", { type: "button", className: "jp-xtralab-StatusBar-Terminal-Activate", onClick: () => this._onActivate(this._sessionName), "aria-label": tooltip },
229
+ React.createElement(terminalIcon.react, { tag: "span", verticalAlign: "middle", stylesheet: "statusBar" }),
230
+ React.createElement("span", { className: "jp-xtralab-StatusBar-Terminal-Label" }, label)),
231
+ React.createElement("button", { type: "button", className: "jp-xtralab-StatusBar-Terminal-Close", onClick: event => {
232
+ event.stopPropagation();
233
+ this._onShutdown(this._sessionName);
234
+ }, title: trans.__('Shut down %1', label), "aria-label": trans.__('Shut down %1', label) },
235
+ React.createElement(closeIcon.react, { tag: "span", verticalAlign: "middle", stylesheet: "statusBar" }))));
236
+ }
237
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xtralab",
3
- "version": "0.1.2",
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",
@@ -62,12 +62,18 @@
62
62
  "@jupyterlab/coreutils": "^6.6.0-alpha.5",
63
63
  "@jupyterlab/docmanager": "^4.6.0-alpha.5",
64
64
  "@jupyterlab/docregistry": "^4.6.0-alpha.5",
65
+ "@jupyterlab/launcher": "^4.6.0-alpha.5",
66
+ "@jupyterlab/mainmenu": "^4.6.0-alpha.5",
65
67
  "@jupyterlab/nbformat": "^4.6.0-alpha.5",
66
68
  "@jupyterlab/outputarea": "^4.6.0-alpha.5",
67
69
  "@jupyterlab/rendermime": "^4.6.0-alpha.5",
68
70
  "@jupyterlab/services": "^7.6.0-alpha.5",
69
71
  "@jupyterlab/settingregistry": "^4.6.0-alpha.5",
72
+ "@jupyterlab/statusbar": "^4.6.0-alpha.5",
73
+ "@jupyterlab/terminal": "^4.6.0-alpha.5",
74
+ "@jupyterlab/translation": "^4.6.0-alpha.5",
70
75
  "@jupyterlab/ui-components": "^4.6.0-alpha.5",
76
+ "@lumino/algorithm": "^2.0.0",
71
77
  "@lumino/coreutils": "^2.0.0",
72
78
  "@lumino/disposable": "^2.0.0",
73
79
  "@lumino/dragdrop": "^2.0.0",
@@ -76,6 +82,7 @@
76
82
  "@lumino/widgets": "^2.0.0",
77
83
  "@pierre/diffs": "^1.1.20",
78
84
  "@pierre/trees": "1.0.0-beta.3",
85
+ "ignore": "^5.3.2",
79
86
  "react": "^18.2.0",
80
87
  "react-dom": "^18.2.0"
81
88
  },
@@ -119,7 +126,8 @@
119
126
  "outputDir": "xtralab/labextension",
120
127
  "schemaDir": "schema",
121
128
  "disabledExtensions": [
122
- "@jupyterlab/git"
129
+ "@jupyterlab/git",
130
+ "@jupyterlab/launcher-extension"
123
131
  ]
124
132
  },
125
133
  "eslintIgnore": [
@@ -137,12 +145,26 @@
137
145
  ],
138
146
  "parser": "@typescript-eslint/parser",
139
147
  "parserOptions": {
140
- "project": "tsconfig.json",
148
+ "project": [
149
+ "tsconfig.json",
150
+ "desktop/tsconfig.json",
151
+ "desktop/tsconfig.forge.json"
152
+ ],
141
153
  "sourceType": "module"
142
154
  },
143
155
  "plugins": [
144
156
  "@typescript-eslint"
145
157
  ],
158
+ "overrides": [
159
+ {
160
+ "files": [
161
+ "desktop/src/**/*.ts"
162
+ ],
163
+ "rules": {
164
+ "@typescript-eslint/naming-convention": "off"
165
+ }
166
+ }
167
+ ],
146
168
  "rules": {
147
169
  "@typescript-eslint/naming-convention": [
148
170
  "error",
@@ -0,0 +1,70 @@
1
+ {
2
+ "title": "xtralab launcher",
3
+ "description": "Settings for the xtralab agent launcher.",
4
+ "jupyter.lab.shortcuts": [],
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "properties": {
8
+ "agents": {
9
+ "title": "Agents",
10
+ "description": "Override or extend the built-in list of agents shown on the launcher. Each entry must include an `id`. When the id matches a built-in (claude, codex, gemini, copilot, goose, opencode, kiro, mistral-vibe), the fields you set override the corresponding defaults; leave a field out to keep the default. New ids define brand-new cards. Set `enabled: false` to hide a default. Set `requireAvailable: false` for shell aliases that should always show even though they aren't on PATH.",
11
+ "type": "array",
12
+ "default": [],
13
+ "items": {
14
+ "type": "object",
15
+ "additionalProperties": false,
16
+ "required": ["id"],
17
+ "properties": {
18
+ "id": {
19
+ "title": "ID",
20
+ "description": "Stable identifier. Match one of xtralab's defaults to override, or pick a new id to define a new card.",
21
+ "type": "string"
22
+ },
23
+ "label": {
24
+ "title": "Label",
25
+ "description": "Card title shown on the launcher.",
26
+ "type": "string"
27
+ },
28
+ "caption": {
29
+ "title": "Caption",
30
+ "description": "Short description shown beneath the card title.",
31
+ "type": "string"
32
+ },
33
+ "command": {
34
+ "title": "Command",
35
+ "description": "Literal string typed into the new terminal. Aliases work because shells expand them on interactive lines.",
36
+ "type": "string"
37
+ },
38
+ "iconSvg": {
39
+ "title": "Icon SVG",
40
+ "description": "Inline SVG for the card icon. Required only for new agents whose icon is not already shipped by xtralab.",
41
+ "type": "string"
42
+ },
43
+ "rank": {
44
+ "title": "Rank",
45
+ "description": "Sort order on the launcher (lower numbers come first).",
46
+ "type": "number"
47
+ },
48
+ "enabled": {
49
+ "title": "Enabled",
50
+ "description": "When false, hides the agent from the launcher and command palette.",
51
+ "type": "boolean",
52
+ "default": true
53
+ },
54
+ "requireAvailable": {
55
+ "title": "Require available",
56
+ "description": "When false, the launcher skips the availability check for this entry (useful for shell aliases that aren't on PATH but resolve in an interactive shell).",
57
+ "type": "boolean",
58
+ "default": true
59
+ },
60
+ "promptArgs": {
61
+ "title": "Prompt arguments",
62
+ "description": "How to splice an initial prompt from the launcher textarea into the agent's command line. Empty array (`[]`) appends the prompt as a positional argument. An array like `[\"-i\"]` or `[\"--prompt\"]` prefixes the prompt with that flag. Set to `null` to opt out of prompt support entirely (the agent's button is dimmed when the prompt textarea is non-empty).",
63
+ "type": ["array", "null"],
64
+ "items": { "type": "string" }
65
+ }
66
+ }
67
+ }
68
+ }
69
+ }
70
+ }
@@ -0,0 +1,57 @@
1
+ {
2
+ "title": "xtralab sidebar",
3
+ "description": "Visibility of selected JupyterLab sidebar tabs.",
4
+ "jupyter.lab.shortcuts": [],
5
+ "jupyter.lab.menus": {
6
+ "main": [
7
+ {
8
+ "id": "jp-mainmenu-view",
9
+ "items": [
10
+ {
11
+ "type": "submenu",
12
+ "rank": 1,
13
+ "submenu": {
14
+ "id": "jp-mainmenu-view-appearance",
15
+ "items": [
16
+ {
17
+ "type": "submenu",
18
+ "rank": 12.5,
19
+ "submenu": {
20
+ "id": "jp-mainmenu-view-appearance-left-sidebar",
21
+ "label": "Left Sidebar",
22
+ "items": [
23
+ {
24
+ "command": "xtralab:toggle-default-filebrowser",
25
+ "rank": 1
26
+ },
27
+ {
28
+ "command": "xtralab:toggle-running-sessions",
29
+ "rank": 2
30
+ }
31
+ ]
32
+ }
33
+ }
34
+ ]
35
+ }
36
+ }
37
+ ]
38
+ }
39
+ ]
40
+ },
41
+ "type": "object",
42
+ "properties": {
43
+ "showDefaultFileBrowser": {
44
+ "type": "boolean",
45
+ "title": "Show the default JupyterLab file browser",
46
+ "description": "When false, hides the default `filebrowser` tab from the left sidebar. The xtralab file browser is unaffected.",
47
+ "default": true
48
+ },
49
+ "showRunningSessions": {
50
+ "type": "boolean",
51
+ "title": "Show running terminals and kernels",
52
+ "description": "When false, hides the `jp-running-sessions` (Running Terminals and Kernels) tab from the left sidebar.",
53
+ "default": true
54
+ }
55
+ },
56
+ "additionalProperties": false
57
+ }
@@ -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
  *