xtralab 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +110 -78
- package/lib/agentSessions.d.ts +50 -0
- package/lib/agentSessions.js +45 -0
- package/lib/fileBrowser/index.js +5 -1
- package/lib/git/imageDiff.js +80 -8
- package/lib/index.d.ts +3 -2
- package/lib/index.js +11 -6
- package/lib/launcher/agents.d.ts +1 -1
- package/lib/launcher/agents.js +22 -13
- package/lib/launcher/commands.d.ts +32 -6
- package/lib/launcher/commands.js +81 -57
- package/lib/launcher/dashboard.d.ts +15 -0
- package/lib/launcher/dashboard.js +119 -5
- package/lib/launcher/editorRegistry.d.ts +43 -0
- package/lib/launcher/editorRegistry.js +96 -0
- package/lib/launcher/editors.d.ts +78 -0
- package/lib/launcher/editors.js +110 -0
- package/lib/launcher/icons.d.ts +25 -6
- package/lib/launcher/icons.js +116 -19
- package/lib/launcher/index.d.ts +2 -22
- package/lib/launcher/index.js +48 -25
- package/lib/launcher/registry.d.ts +21 -0
- package/lib/launcher/registry.js +28 -0
- package/lib/launcher/tokens.d.ts +41 -0
- package/lib/launcher/tokens.js +17 -0
- package/lib/terminals/detection.d.ts +11 -0
- package/lib/terminals/detection.js +50 -0
- package/lib/terminals/index.d.ts +27 -0
- package/lib/terminals/index.js +194 -0
- package/lib/terminals/model.d.ts +191 -0
- package/lib/terminals/model.js +418 -0
- package/lib/terminals/widget.d.ts +72 -0
- package/lib/terminals/widget.js +93 -0
- package/lib/topBar/icons.d.ts +13 -0
- package/lib/topBar/icons.js +27 -0
- package/lib/topBar/index.d.ts +16 -0
- package/lib/topBar/index.js +86 -0
- package/package.json +36 -33
- package/schema/launcher.json +56 -1
- package/src/agentSessions.ts +94 -0
- package/src/fileBrowser/index.ts +5 -1
- package/src/git/imageDiff.tsx +127 -26
- package/src/index.ts +11 -6
- package/src/launcher/agents.ts +23 -14
- package/src/launcher/commands.ts +98 -60
- package/src/launcher/dashboard.tsx +205 -3
- package/src/launcher/editorRegistry.ts +149 -0
- package/src/launcher/editors.ts +183 -0
- package/src/launcher/icons.ts +120 -20
- package/src/launcher/index.ts +62 -27
- package/src/launcher/registry.ts +33 -0
- package/src/launcher/tokens.ts +51 -0
- package/src/terminals/detection.ts +65 -0
- package/src/terminals/index.ts +222 -0
- package/src/terminals/model.ts +497 -0
- package/src/terminals/widget.tsx +240 -0
- package/src/topBar/icons.ts +30 -0
- package/src/topBar/index.ts +121 -0
- package/style/git.css +109 -0
- package/style/index.css +2 -1
- package/style/index.js +2 -1
- package/style/launcher.css +124 -2
- package/style/terminals.css +235 -0
- package/style/topBar.css +38 -0
- package/lib/statusBar/index.d.ts +0 -14
- package/lib/statusBar/index.js +0 -89
- package/lib/statusBar/widget.d.ts +0 -121
- package/lib/statusBar/widget.js +0 -237
- package/src/statusBar/index.ts +0 -115
- package/src/statusBar/widget.tsx +0 -318
- package/style/statusBar.css +0 -95
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { ILabShell } from '@jupyterlab/application';
|
|
2
|
+
import { ITranslator, nullTranslator } from '@jupyterlab/translation';
|
|
3
|
+
import { CommandToolbarButton } from '@jupyterlab/ui-components';
|
|
4
|
+
import { leftSidebarIcon, rightSidebarIcon } from './icons';
|
|
5
|
+
const PLUGIN_ID = 'xtralab:top-bar';
|
|
6
|
+
/**
|
|
7
|
+
* Upstream commands from `@jupyterlab/application-extension` that collapse or
|
|
8
|
+
* expand the side areas. We bind the buttons to these rather than
|
|
9
|
+
* reimplementing the toggle, so behavior (and the `isToggled`/`isEnabled`
|
|
10
|
+
* state the buttons reflect) stays in lockstep with the View menu entries
|
|
11
|
+
* and their keyboard shortcuts.
|
|
12
|
+
*/
|
|
13
|
+
const TOGGLE_LEFT_AREA = 'application:toggle-left-area';
|
|
14
|
+
const TOGGLE_RIGHT_AREA = 'application:toggle-right-area';
|
|
15
|
+
const BUTTONS = [
|
|
16
|
+
{
|
|
17
|
+
id: 'xtralab-toggle-left-sidebar',
|
|
18
|
+
command: TOGGLE_LEFT_AREA,
|
|
19
|
+
icon: leftSidebarIcon,
|
|
20
|
+
caption: trans => trans.__('Toggle left sidebar'),
|
|
21
|
+
rank: -1,
|
|
22
|
+
sideClass: 'xtralab-TopBarButton-left'
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: 'xtralab-toggle-right-sidebar',
|
|
26
|
+
command: TOGGLE_RIGHT_AREA,
|
|
27
|
+
icon: rightSidebarIcon,
|
|
28
|
+
caption: trans => trans.__('Toggle right sidebar'),
|
|
29
|
+
rank: 1000,
|
|
30
|
+
sideClass: 'xtralab-TopBarButton-right'
|
|
31
|
+
}
|
|
32
|
+
];
|
|
33
|
+
/**
|
|
34
|
+
* Add two icon buttons to the top bar that toggle the left and right side
|
|
35
|
+
* areas, mirroring the macOS-style sidebar buttons: a left-sidebar button at
|
|
36
|
+
* the leading edge and a right-sidebar button at the far edge.
|
|
37
|
+
*
|
|
38
|
+
* Each is a `CommandToolbarButton` wrapping an existing
|
|
39
|
+
* `application:toggle-{left,right}-area` command, so clicking it runs the
|
|
40
|
+
* same toggle as the View menu and the button reflects the command's state —
|
|
41
|
+
* pressed while the matching sidebar is open, and disabled when the area is
|
|
42
|
+
* empty. Note xtralab ships nothing in the right area by default (the
|
|
43
|
+
* property inspector, table of contents and debugger are all disabled), so
|
|
44
|
+
* the right button stays disabled until a widget is moved or added there.
|
|
45
|
+
*/
|
|
46
|
+
const plugin = {
|
|
47
|
+
id: PLUGIN_ID,
|
|
48
|
+
description: 'Add left and right sidebar toggle buttons to the top bar.',
|
|
49
|
+
autoStart: true,
|
|
50
|
+
requires: [ILabShell],
|
|
51
|
+
optional: [ITranslator],
|
|
52
|
+
activate: (app, labShell, translator) => {
|
|
53
|
+
const { commands } = app;
|
|
54
|
+
const trans = (translator !== null && translator !== void 0 ? translator : nullTranslator).load('jupyterlab');
|
|
55
|
+
// Defer to `app.restored` so the upstream toggle commands are already in
|
|
56
|
+
// the registry: `CommandToolbarButton` renders nothing until its command
|
|
57
|
+
// exists and only re-renders on `commandChanged` of type `changed` /
|
|
58
|
+
// `many-changed` (not `added`), so mounting earlier could leave a button
|
|
59
|
+
// blank until the next unrelated command change.
|
|
60
|
+
void app.restored.then(() => {
|
|
61
|
+
for (const spec of BUTTONS) {
|
|
62
|
+
const button = new CommandToolbarButton({
|
|
63
|
+
commands,
|
|
64
|
+
id: spec.command,
|
|
65
|
+
icon: spec.icon,
|
|
66
|
+
// Empty label keeps the button icon-only: the override is forwarded
|
|
67
|
+
// verbatim and the component skips the label span when it is falsy.
|
|
68
|
+
label: '',
|
|
69
|
+
caption: spec.caption(trans),
|
|
70
|
+
// Toggle on mousedown without focusing the button. Otherwise
|
|
71
|
+
// ToolbarButtonComponent calls `event.target.focus()` on click,
|
|
72
|
+
// leaving the stealth button showing a focus fill/outline (the
|
|
73
|
+
// stray "border") until you click elsewhere — unwanted chrome for a
|
|
74
|
+
// title-bar control. Keyboard Tab focus still works, so this stays
|
|
75
|
+
// accessible.
|
|
76
|
+
noFocusOnClick: true
|
|
77
|
+
});
|
|
78
|
+
button.id = spec.id;
|
|
79
|
+
button.addClass('xtralab-TopBarButton');
|
|
80
|
+
button.addClass(spec.sideClass);
|
|
81
|
+
labShell.add(button, 'top', { rank: spec.rank });
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
export default plugin;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xtralab",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.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",
|
|
@@ -30,27 +30,28 @@
|
|
|
30
30
|
"type": "git",
|
|
31
31
|
"url": "https://github.com/jtpio/xtralab.git"
|
|
32
32
|
},
|
|
33
|
+
"packageManager": "pnpm@10.28.0",
|
|
33
34
|
"scripts": {
|
|
34
|
-
"build": "
|
|
35
|
-
"build:prod": "
|
|
35
|
+
"build": "pnpm build:lib && pnpm build:labextension:dev",
|
|
36
|
+
"build:prod": "pnpm clean && pnpm build:lib:prod && pnpm build:labextension",
|
|
36
37
|
"build:labextension": "jupyter-builder build .",
|
|
37
38
|
"build:labextension:dev": "jupyter-builder build --development True .",
|
|
38
39
|
"build:lib": "tsc --sourceMap",
|
|
39
40
|
"build:lib:prod": "tsc",
|
|
40
|
-
"clean": "
|
|
41
|
+
"clean": "pnpm clean:lib",
|
|
41
42
|
"clean:lib": "rimraf lib tsconfig.tsbuildinfo",
|
|
42
43
|
"clean:lintcache": "rimraf .eslintcache .stylelintcache",
|
|
43
44
|
"clean:labextension": "rimraf xtralab/labextension xtralab/_version.py",
|
|
44
|
-
"clean:all": "
|
|
45
|
-
"eslint": "
|
|
45
|
+
"clean:all": "pnpm clean:lib && pnpm clean:labextension && pnpm clean:lintcache",
|
|
46
|
+
"eslint": "pnpm eslint:check --fix",
|
|
46
47
|
"eslint:check": "eslint . --cache --ext .ts,.tsx",
|
|
47
|
-
"install:extension": "
|
|
48
|
-
"lint": "
|
|
49
|
-
"lint:check": "
|
|
50
|
-
"prettier": "
|
|
48
|
+
"install:extension": "pnpm build",
|
|
49
|
+
"lint": "pnpm stylelint && pnpm prettier && pnpm eslint",
|
|
50
|
+
"lint:check": "pnpm stylelint:check && pnpm prettier:check && pnpm eslint:check",
|
|
51
|
+
"prettier": "pnpm prettier:base --write --list-different",
|
|
51
52
|
"prettier:base": "prettier \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}\"",
|
|
52
|
-
"prettier:check": "
|
|
53
|
-
"stylelint": "
|
|
53
|
+
"prettier:check": "pnpm prettier:base --check",
|
|
54
|
+
"stylelint": "pnpm stylelint:check --fix",
|
|
54
55
|
"stylelint:check": "stylelint --cache \"style/**/*.css\"",
|
|
55
56
|
"watch": "run-p watch:src watch:labextension",
|
|
56
57
|
"watch:src": "tsc -w --sourceMap",
|
|
@@ -60,25 +61,25 @@
|
|
|
60
61
|
"@codemirror/language": "^6.12.1",
|
|
61
62
|
"@codemirror/state": "^6.5.4",
|
|
62
63
|
"@codemirror/view": "^6.39.14",
|
|
63
|
-
"@jupyterlab/application": "^4.6.0-
|
|
64
|
-
"@jupyterlab/apputils": "^4.7.0-
|
|
65
|
-
"@jupyterlab/codemirror": "^4.6.0-
|
|
66
|
-
"@jupyterlab/coreutils": "^6.6.0-
|
|
67
|
-
"@jupyterlab/docmanager": "^4.6.0-
|
|
68
|
-
"@jupyterlab/docregistry": "^4.6.0-
|
|
69
|
-
"@jupyterlab/git": "^0.54.0-
|
|
70
|
-
"@jupyterlab/launcher": "^4.6.0-
|
|
71
|
-
"@jupyterlab/mainmenu": "^4.6.0-
|
|
72
|
-
"@jupyterlab/nbformat": "^4.6.0-
|
|
73
|
-
"@jupyterlab/outputarea": "^4.6.0-
|
|
74
|
-
"@jupyterlab/rendermime": "^4.6.0-
|
|
75
|
-
"@jupyterlab/services": "^7.6.0-
|
|
76
|
-
"@jupyterlab/settingregistry": "^4.6.0-
|
|
77
|
-
"@jupyterlab/
|
|
78
|
-
"@jupyterlab/
|
|
79
|
-
"@jupyterlab/
|
|
80
|
-
"@jupyterlab/ui-components": "^4.6.0-alpha.5",
|
|
64
|
+
"@jupyterlab/application": "^4.6.0-beta.0",
|
|
65
|
+
"@jupyterlab/apputils": "^4.7.0-beta.0",
|
|
66
|
+
"@jupyterlab/codemirror": "^4.6.0-beta.0",
|
|
67
|
+
"@jupyterlab/coreutils": "^6.6.0-beta.0",
|
|
68
|
+
"@jupyterlab/docmanager": "^4.6.0-beta.0",
|
|
69
|
+
"@jupyterlab/docregistry": "^4.6.0-beta.0",
|
|
70
|
+
"@jupyterlab/git": "^0.54.0-a1",
|
|
71
|
+
"@jupyterlab/launcher": "^4.6.0-beta.0",
|
|
72
|
+
"@jupyterlab/mainmenu": "^4.6.0-beta.0",
|
|
73
|
+
"@jupyterlab/nbformat": "^4.6.0-beta.0",
|
|
74
|
+
"@jupyterlab/outputarea": "^4.6.0-beta.0",
|
|
75
|
+
"@jupyterlab/rendermime": "^4.6.0-beta.0",
|
|
76
|
+
"@jupyterlab/services": "^7.6.0-beta.0",
|
|
77
|
+
"@jupyterlab/settingregistry": "^4.6.0-beta.0",
|
|
78
|
+
"@jupyterlab/terminal": "^4.6.0-beta.0",
|
|
79
|
+
"@jupyterlab/translation": "^4.6.0-beta.0",
|
|
80
|
+
"@jupyterlab/ui-components": "^4.6.0-beta.0",
|
|
81
81
|
"@lumino/algorithm": "^2.0.0",
|
|
82
|
+
"@lumino/commands": "^2.0.0",
|
|
82
83
|
"@lumino/coreutils": "^2.0.0",
|
|
83
84
|
"@lumino/disposable": "^2.0.0",
|
|
84
85
|
"@lumino/dragdrop": "^2.0.0",
|
|
@@ -94,7 +95,7 @@
|
|
|
94
95
|
"react-dom": "^18.2.0"
|
|
95
96
|
},
|
|
96
97
|
"devDependencies": {
|
|
97
|
-
"@jupyter/builder": "^0.0.
|
|
98
|
+
"@jupyter/builder": "^1.0.0-beta.1",
|
|
98
99
|
"@types/json-schema": "^7.0.11",
|
|
99
100
|
"@types/react": "^18.0.26",
|
|
100
101
|
"@types/react-addons-linked-state-mixin": "^0.14.22",
|
|
@@ -117,8 +118,10 @@
|
|
|
117
118
|
"typescript": "~5.5.4",
|
|
118
119
|
"yjs": "^13.5.0"
|
|
119
120
|
},
|
|
120
|
-
"
|
|
121
|
-
"
|
|
121
|
+
"pnpm": {
|
|
122
|
+
"ignoredBuiltDependencies": [
|
|
123
|
+
"@fortawesome/fontawesome-free"
|
|
124
|
+
]
|
|
122
125
|
},
|
|
123
126
|
"sideEffects": [
|
|
124
127
|
"style/*.css",
|
package/schema/launcher.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"properties": {
|
|
8
8
|
"agents": {
|
|
9
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,
|
|
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, antigravity, 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
11
|
"type": "array",
|
|
12
12
|
"default": [],
|
|
13
13
|
"items": {
|
|
@@ -65,6 +65,61 @@
|
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
|
+
},
|
|
69
|
+
"editors": {
|
|
70
|
+
"title": "Editors",
|
|
71
|
+
"description": "Override or extend the terminal editors offered in the launcher's Open section (and recognized in the Terminals panel). Each entry must include an `id`. When the id matches a built-in (nvim, vim), the fields you set override the corresponding defaults; leave a field out to keep the default. New ids define additional editors. The launcher shows a single tile — the first editor, by `rank`, whose `command` is on PATH (Neovim before Vim by default). Set `enabled: false` to hide a built-in; disable both to remove the tile entirely. Set `requireAvailable: false` for a shell alias that should show even though it isn't on PATH.",
|
|
72
|
+
"type": "array",
|
|
73
|
+
"default": [],
|
|
74
|
+
"items": {
|
|
75
|
+
"type": "object",
|
|
76
|
+
"additionalProperties": false,
|
|
77
|
+
"required": ["id"],
|
|
78
|
+
"properties": {
|
|
79
|
+
"id": {
|
|
80
|
+
"title": "ID",
|
|
81
|
+
"description": "Stable identifier. Match a built-in (nvim, vim) to override, or pick a new id to define a new editor.",
|
|
82
|
+
"type": "string"
|
|
83
|
+
},
|
|
84
|
+
"label": {
|
|
85
|
+
"title": "Label",
|
|
86
|
+
"description": "Tile title shown on the launcher.",
|
|
87
|
+
"type": "string"
|
|
88
|
+
},
|
|
89
|
+
"caption": {
|
|
90
|
+
"title": "Caption",
|
|
91
|
+
"description": "Tooltip shown for the tile.",
|
|
92
|
+
"type": "string"
|
|
93
|
+
},
|
|
94
|
+
"command": {
|
|
95
|
+
"title": "Command",
|
|
96
|
+
"description": "Literal string typed into the new terminal (e.g. `nvim`). Aliases work because shells expand them on interactive lines.",
|
|
97
|
+
"type": "string"
|
|
98
|
+
},
|
|
99
|
+
"iconSvg": {
|
|
100
|
+
"title": "Icon SVG",
|
|
101
|
+
"description": "Inline SVG for the tile icon. Required only for new editors whose icon is not already shipped by xtralab.",
|
|
102
|
+
"type": "string"
|
|
103
|
+
},
|
|
104
|
+
"rank": {
|
|
105
|
+
"title": "Rank",
|
|
106
|
+
"description": "Preference order (lower numbers come first). The launcher shows the first available editor by rank.",
|
|
107
|
+
"type": "number"
|
|
108
|
+
},
|
|
109
|
+
"enabled": {
|
|
110
|
+
"title": "Enabled",
|
|
111
|
+
"description": "When false, hides the editor from the launcher (and the Terminals panel stops badging it).",
|
|
112
|
+
"type": "boolean",
|
|
113
|
+
"default": true
|
|
114
|
+
},
|
|
115
|
+
"requireAvailable": {
|
|
116
|
+
"title": "Require available",
|
|
117
|
+
"description": "When false, the launcher skips the availability check for this entry (useful for a shell alias that isn't on PATH but resolves in an interactive shell).",
|
|
118
|
+
"type": "boolean",
|
|
119
|
+
"default": true
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
68
123
|
}
|
|
69
124
|
}
|
|
70
125
|
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { JupyterFrontEndPlugin } from '@jupyterlab/application';
|
|
2
|
+
import { Token } from '@lumino/coreutils';
|
|
3
|
+
import { ISignal, Signal } from '@lumino/signaling';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A shared map from a terminal session name to the agent command it was
|
|
7
|
+
* *launched* with (e.g. `claude`).
|
|
8
|
+
*
|
|
9
|
+
* This is the optimistic half of the running-agent badge: when the launcher
|
|
10
|
+
* (or the terminals panel's `+` menu) starts an agent via a
|
|
11
|
+
* `xtralab:start-agent:<id>` command, it records the session here so the
|
|
12
|
+
* panel can show the agent's logo immediately, before the server-side
|
|
13
|
+
* process detection has had a chance to confirm it. Server detection is the
|
|
14
|
+
* authoritative source once it reports on a session; this tag only fills the
|
|
15
|
+
* gap right after launch (and serves as a fallback if detection is
|
|
16
|
+
* unavailable).
|
|
17
|
+
*
|
|
18
|
+
* It exists as its own token, provided by a tiny dependency-free plugin,
|
|
19
|
+
* specifically so the launcher (the writer) and the terminals panel (the
|
|
20
|
+
* reader) can share it without depending on each other — a direct
|
|
21
|
+
* launcher↔terminals token pair would form an activation cycle.
|
|
22
|
+
*/
|
|
23
|
+
export interface IAgentSessions {
|
|
24
|
+
/**
|
|
25
|
+
* The agent command a session was launched with, or `null` if we never
|
|
26
|
+
* launched an agent into it (e.g. a plain terminal, or one started before
|
|
27
|
+
* this ran).
|
|
28
|
+
*/
|
|
29
|
+
get(sessionName: string): string | null;
|
|
30
|
+
|
|
31
|
+
/** Record that a session was launched as the given agent command. */
|
|
32
|
+
set(sessionName: string, command: string): void;
|
|
33
|
+
|
|
34
|
+
/** Forget a session — called when it shuts down so the map stays bounded. */
|
|
35
|
+
delete(sessionName: string): void;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Emitted with the affected session name whenever a record is added or
|
|
39
|
+
* removed, so readers can re-render.
|
|
40
|
+
*/
|
|
41
|
+
readonly changed: ISignal<IAgentSessions, string>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* DI token for {@link IAgentSessions}. Provided by `xtralab:agent-sessions`;
|
|
46
|
+
* consumed (optionally) by both `xtralab:launcher` and `xtralab:terminals`.
|
|
47
|
+
*/
|
|
48
|
+
export const IAgentSessions = new Token<IAgentSessions>(
|
|
49
|
+
'xtralab:IAgentSessions',
|
|
50
|
+
'A shared map from terminal session name to the agent command it was launched with.'
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
class AgentSessions implements IAgentSessions {
|
|
54
|
+
get(sessionName: string): string | null {
|
|
55
|
+
return this._byName.get(sessionName) ?? null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
set(sessionName: string, command: string): void {
|
|
59
|
+
if (this._byName.get(sessionName) === command) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
this._byName.set(sessionName, command);
|
|
63
|
+
this._changed.emit(sessionName);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
delete(sessionName: string): void {
|
|
67
|
+
if (this._byName.delete(sessionName)) {
|
|
68
|
+
this._changed.emit(sessionName);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
get changed(): ISignal<IAgentSessions, string> {
|
|
73
|
+
return this._changed;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private _byName = new Map<string, string>();
|
|
77
|
+
private _changed = new Signal<IAgentSessions, string>(this);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Provides {@link IAgentSessions}. Deliberately has no dependencies so it can
|
|
82
|
+
* sit underneath both the launcher and the terminals panel without creating
|
|
83
|
+
* a dependency cycle between them.
|
|
84
|
+
*/
|
|
85
|
+
const plugin: JupyterFrontEndPlugin<IAgentSessions> = {
|
|
86
|
+
id: 'xtralab:agent-sessions',
|
|
87
|
+
description:
|
|
88
|
+
'Shared map from terminal session to the agent command it was launched with, used to badge terminal rows with the running agent.',
|
|
89
|
+
autoStart: true,
|
|
90
|
+
provides: IAgentSessions,
|
|
91
|
+
activate: (): IAgentSessions => new AgentSessions()
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export default plugin;
|
package/src/fileBrowser/index.ts
CHANGED
|
@@ -37,7 +37,11 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
37
37
|
void app.commands.execute('docmanager:open', { path: serverPath });
|
|
38
38
|
}
|
|
39
39
|
});
|
|
40
|
-
|
|
40
|
+
// Ranks just under the Terminals panel in the left sidebar. The shipped
|
|
41
|
+
// `layout` setting assigns the default ranks (rank only, no area pin),
|
|
42
|
+
// so this browser stays movable via the "Move Widget" context menu; the
|
|
43
|
+
// `rank` here is the in-code fallback for when that setting is absent.
|
|
44
|
+
app.shell.add(browser, 'left', { rank: 2 });
|
|
41
45
|
if (restorer) {
|
|
42
46
|
restorer.add(browser, browser.id);
|
|
43
47
|
}
|
package/src/git/imageDiff.tsx
CHANGED
|
@@ -301,35 +301,136 @@ function RangeSlider(props: {
|
|
|
301
301
|
|
|
302
302
|
function Swipe({ reference, challenger }: ISideViewProps): React.ReactElement {
|
|
303
303
|
const [position, setPosition] = React.useState(50);
|
|
304
|
+
const [dragging, setDragging] = React.useState(false);
|
|
305
|
+
const frameRef = React.useRef<HTMLDivElement>(null);
|
|
306
|
+
|
|
307
|
+
const setFromClientX = React.useCallback((clientX: number) => {
|
|
308
|
+
const frame = frameRef.current;
|
|
309
|
+
if (frame === null) {
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
const rect = frame.getBoundingClientRect();
|
|
313
|
+
if (rect.width <= 0) {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
const next = ((clientX - rect.left) / rect.width) * 100;
|
|
317
|
+
setPosition(Math.max(0, Math.min(100, next)));
|
|
318
|
+
}, []);
|
|
319
|
+
|
|
320
|
+
const onPointerDown = React.useCallback(
|
|
321
|
+
(event: React.PointerEvent<HTMLDivElement>) => {
|
|
322
|
+
// Only react to the primary pointer (left click / touch / pen tip).
|
|
323
|
+
if (event.button !== 0) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
event.preventDefault();
|
|
327
|
+
setDragging(true);
|
|
328
|
+
setFromClientX(event.clientX);
|
|
329
|
+
},
|
|
330
|
+
[setFromClientX]
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
React.useEffect(() => {
|
|
334
|
+
if (!dragging) {
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
const onMove = (event: PointerEvent) => {
|
|
338
|
+
setFromClientX(event.clientX);
|
|
339
|
+
};
|
|
340
|
+
const onUp = () => {
|
|
341
|
+
setDragging(false);
|
|
342
|
+
};
|
|
343
|
+
window.addEventListener('pointermove', onMove);
|
|
344
|
+
window.addEventListener('pointerup', onUp);
|
|
345
|
+
window.addEventListener('pointercancel', onUp);
|
|
346
|
+
return () => {
|
|
347
|
+
window.removeEventListener('pointermove', onMove);
|
|
348
|
+
window.removeEventListener('pointerup', onUp);
|
|
349
|
+
window.removeEventListener('pointercancel', onUp);
|
|
350
|
+
};
|
|
351
|
+
}, [dragging, setFromClientX]);
|
|
352
|
+
|
|
353
|
+
const onKeyDown = React.useCallback(
|
|
354
|
+
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
|
355
|
+
const step = event.shiftKey ? 10 : 1;
|
|
356
|
+
let next: number | null = null;
|
|
357
|
+
switch (event.key) {
|
|
358
|
+
case 'ArrowLeft':
|
|
359
|
+
next = position - step;
|
|
360
|
+
break;
|
|
361
|
+
case 'ArrowRight':
|
|
362
|
+
next = position + step;
|
|
363
|
+
break;
|
|
364
|
+
case 'Home':
|
|
365
|
+
next = 0;
|
|
366
|
+
break;
|
|
367
|
+
case 'End':
|
|
368
|
+
next = 100;
|
|
369
|
+
break;
|
|
370
|
+
}
|
|
371
|
+
if (next === null) {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
event.preventDefault();
|
|
375
|
+
setPosition(Math.max(0, Math.min(100, next)));
|
|
376
|
+
},
|
|
377
|
+
[position]
|
|
378
|
+
);
|
|
379
|
+
|
|
304
380
|
return (
|
|
305
381
|
<div className="jp-xtralab-ImageDiff-interactive">
|
|
306
|
-
<div
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
382
|
+
<div
|
|
383
|
+
className="jp-xtralab-ImageDiff-swipeFrame"
|
|
384
|
+
ref={frameRef}
|
|
385
|
+
onPointerDown={onPointerDown}
|
|
386
|
+
data-dragging={dragging ? 'true' : undefined}
|
|
387
|
+
>
|
|
388
|
+
<div className="jp-xtralab-ImageDiff-stack">
|
|
389
|
+
{reference.uri !== null ? (
|
|
390
|
+
<img
|
|
391
|
+
className="jp-xtralab-ImageDiff-stackImg"
|
|
392
|
+
src={reference.uri}
|
|
393
|
+
alt="Reference revision"
|
|
394
|
+
draggable={false}
|
|
395
|
+
style={{
|
|
396
|
+
clipPath: `inset(0 ${100 - position}% 0 0)`
|
|
397
|
+
}}
|
|
398
|
+
/>
|
|
399
|
+
) : null}
|
|
400
|
+
{challenger.uri !== null ? (
|
|
401
|
+
<img
|
|
402
|
+
className="jp-xtralab-ImageDiff-stackImg"
|
|
403
|
+
src={challenger.uri}
|
|
404
|
+
alt="Challenger revision"
|
|
405
|
+
draggable={false}
|
|
406
|
+
style={{
|
|
407
|
+
clipPath: `inset(0 0 0 ${position}%)`
|
|
408
|
+
}}
|
|
409
|
+
/>
|
|
410
|
+
) : null}
|
|
411
|
+
</div>
|
|
412
|
+
<div
|
|
413
|
+
className="jp-xtralab-ImageDiff-swipeDivider"
|
|
414
|
+
style={{ left: `${position}%` }}
|
|
415
|
+
role="slider"
|
|
416
|
+
aria-label="Swipe between reference and challenger"
|
|
417
|
+
aria-valuemin={0}
|
|
418
|
+
aria-valuemax={100}
|
|
419
|
+
aria-valuenow={Math.round(position)}
|
|
420
|
+
aria-orientation="vertical"
|
|
421
|
+
tabIndex={0}
|
|
422
|
+
onKeyDown={onKeyDown}
|
|
423
|
+
>
|
|
424
|
+
<div className="jp-xtralab-ImageDiff-swipeDividerHandle jp-mod-top">
|
|
425
|
+
<div className="jp-xtralab-ImageDiff-swipeDividerArrow jp-mod-left" />
|
|
426
|
+
<div className="jp-xtralab-ImageDiff-swipeDividerArrow jp-mod-right" />
|
|
427
|
+
</div>
|
|
428
|
+
<div className="jp-xtralab-ImageDiff-swipeDividerHandle jp-mod-bottom">
|
|
429
|
+
<div className="jp-xtralab-ImageDiff-swipeDividerArrow jp-mod-left" />
|
|
430
|
+
<div className="jp-xtralab-ImageDiff-swipeDividerArrow jp-mod-right" />
|
|
431
|
+
</div>
|
|
432
|
+
</div>
|
|
327
433
|
</div>
|
|
328
|
-
<RangeSlider
|
|
329
|
-
value={position}
|
|
330
|
-
onChange={setPosition}
|
|
331
|
-
ariaLabel="Swipe between reference and challenger"
|
|
332
|
-
/>
|
|
333
434
|
</div>
|
|
334
435
|
);
|
|
335
436
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,31 +1,36 @@
|
|
|
1
1
|
import { JupyterFrontEndPlugin } from '@jupyterlab/application';
|
|
2
2
|
|
|
3
|
+
import agentSessionsPlugin from './agentSessions';
|
|
3
4
|
import fileBrowserPlugin from './fileBrowser';
|
|
4
5
|
import editorBreadcrumbsPlugin from './editorBreadcrumbs';
|
|
5
6
|
import editorIndentPlugin from './editorIndent';
|
|
6
7
|
import gitPlugins from './git';
|
|
7
|
-
import
|
|
8
|
+
import launcherPlugins from './launcher';
|
|
8
9
|
import menusPlugin from './menus';
|
|
9
10
|
import sidebarPlugin from './sidebar';
|
|
10
|
-
import
|
|
11
|
+
import terminalsPlugin from './terminals';
|
|
12
|
+
import topBarPlugin from './topBar';
|
|
11
13
|
|
|
12
14
|
/**
|
|
13
15
|
* Every plugin contributed by `xtralab`. The entry point of the
|
|
14
16
|
* labextension is an array because the package bundles several independent
|
|
15
17
|
* enhancements (file browser, git diff providers, …) — JupyterLab activates
|
|
16
18
|
* each plugin individually and only the ones whose required tokens are
|
|
17
|
-
* available end up running. `gitPlugins`
|
|
18
|
-
*
|
|
19
|
+
* available end up running. `gitPlugins` and `launcherPlugins` are themselves
|
|
20
|
+
* arrays (the git diff providers; the launcher plus its editor registry), so
|
|
21
|
+
* they are spread in.
|
|
19
22
|
*/
|
|
20
23
|
const plugins: JupyterFrontEndPlugin<unknown>[] = [
|
|
24
|
+
agentSessionsPlugin,
|
|
21
25
|
editorBreadcrumbsPlugin,
|
|
22
26
|
editorIndentPlugin,
|
|
23
27
|
fileBrowserPlugin,
|
|
24
28
|
...gitPlugins,
|
|
25
|
-
|
|
29
|
+
...launcherPlugins,
|
|
26
30
|
menusPlugin,
|
|
27
31
|
sidebarPlugin,
|
|
28
|
-
|
|
32
|
+
terminalsPlugin,
|
|
33
|
+
topBarPlugin
|
|
29
34
|
];
|
|
30
35
|
|
|
31
36
|
export default plugins;
|
package/src/launcher/agents.ts
CHANGED
|
@@ -26,7 +26,7 @@ export interface IAgent {
|
|
|
26
26
|
* the user types one into the launcher's prompt box. The semantics:
|
|
27
27
|
*
|
|
28
28
|
* - `[]` → prompt is appended as a positional argument:
|
|
29
|
-
* `<command> 'PROMPT'`. (Used by claude, codex,
|
|
29
|
+
* `<command> 'PROMPT'`. (Used by claude, codex, vibe.)
|
|
30
30
|
* - `['-i']` / `['--prompt']` → the prompt is preceded by a flag:
|
|
31
31
|
* `<command> -i 'PROMPT'`. (Used by copilot, opencode.)
|
|
32
32
|
* - `undefined` → the agent does not accept an initial prompt. The
|
|
@@ -75,12 +75,15 @@ export interface IAgentSettings {
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
/**
|
|
78
|
-
* Default agent cards shipped with xtralab.
|
|
79
|
-
* first-class personas
|
|
80
|
-
* is also where
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
78
|
+
* Default agent cards shipped with xtralab. Seven of these (Claude, Codex,
|
|
79
|
+
* Copilot, Goose, OpenCode, Kiro, Mistral Vibe) mirror first-class personas
|
|
80
|
+
* in `jupyter-ai-contrib/jupyter-ai-acp-client` (which is also where their
|
|
81
|
+
* icons come from), but use the bare CLI names a user would type in a
|
|
82
|
+
* terminal rather than the ACP wrapper binaries that project spawns.
|
|
83
|
+
* Antigravity (Google's agent-first IDE/CLI, `agy`) takes the Google slot —
|
|
84
|
+
* Gemini CLI was dropped because Google is sunsetting it for consumer
|
|
85
|
+
* accounts on 2026-06-18. Anything not on the user's `$PATH` is filtered
|
|
86
|
+
* out at activation time, so the wider list is harmless.
|
|
84
87
|
*/
|
|
85
88
|
const DEFAULTS: IAgent[] = [
|
|
86
89
|
{
|
|
@@ -104,14 +107,20 @@ const DEFAULTS: IAgent[] = [
|
|
|
104
107
|
promptArgs: []
|
|
105
108
|
},
|
|
106
109
|
{
|
|
107
|
-
id: '
|
|
108
|
-
label: '
|
|
109
|
-
caption: '
|
|
110
|
-
command: '
|
|
111
|
-
icon: BUILTIN_AGENT_ICONS.
|
|
110
|
+
id: 'antigravity',
|
|
111
|
+
label: 'Antigravity',
|
|
112
|
+
caption: 'Open Google Antigravity from a new terminal.',
|
|
113
|
+
command: 'agy',
|
|
114
|
+
icon: BUILTIN_AGENT_ICONS.antigravity,
|
|
112
115
|
rank: 2,
|
|
113
|
-
requireAvailable: true
|
|
114
|
-
|
|
116
|
+
requireAvailable: true
|
|
117
|
+
// `agy` is the Antigravity launcher binary — a VS Code-derived editor
|
|
118
|
+
// CLI that opens the Agent Manager / workspace, not a terminal REPL.
|
|
119
|
+
// Its positional args are file/folder paths, so there is no inline
|
|
120
|
+
// natural-language prompt form; like goose/kiro we omit promptArgs so a
|
|
121
|
+
// typed prompt dims the button instead of being mangled into a path.
|
|
122
|
+
// The command stays the bare binary (no `agy .`) so the server-side
|
|
123
|
+
// `shutil.which` availability check still resolves it.
|
|
115
124
|
},
|
|
116
125
|
{
|
|
117
126
|
id: 'copilot',
|