xtralab 0.4.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 -73
- package/lib/agentSessions.d.ts +50 -0
- package/lib/agentSessions.js +45 -0
- package/lib/fileBrowser/index.js +5 -1
- package/lib/git/commands.d.ts +12 -13
- package/lib/git/commands.js +7 -28
- package/lib/git/diffProvider.d.ts +8 -0
- package/lib/git/diffProvider.js +314 -0
- package/lib/git/diffSurface.d.ts +115 -0
- package/lib/git/diffSurface.js +342 -0
- package/lib/git/diffWidget.d.ts +8 -13
- package/lib/git/diffWidget.js +65 -311
- package/lib/git/imageDiff.d.ts +23 -0
- package/lib/git/imageDiff.js +256 -0
- package/lib/git/index.d.ts +10 -8
- package/lib/git/index.js +37 -52
- package/lib/index.d.ts +4 -2
- package/lib/index.js +14 -8
- 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 +52 -35
- package/schema/launcher.json +56 -1
- package/src/agentSessions.ts +94 -0
- package/src/fileBrowser/index.ts +5 -1
- package/src/git/commands.ts +12 -38
- package/src/git/diffProvider.tsx +462 -0
- package/src/git/diffSurface.tsx +588 -0
- package/src/git/diffWidget.tsx +105 -465
- package/src/git/imageDiff.tsx +469 -0
- package/src/git/index.ts +38 -68
- package/src/index.ts +14 -8
- 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 +267 -266
- 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/git/icons.d.ts +0 -14
- package/lib/git/icons.js +0 -19
- package/lib/git/panel.d.ts +0 -29
- package/lib/git/panel.js +0 -42
- package/lib/git/panelComponent.d.ts +0 -28
- package/lib/git/panelComponent.js +0 -320
- 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/git/icons.ts +0 -20
- package/src/git/panel.tsx +0 -56
- package/src/git/panelComponent.tsx +0 -575
- package/src/statusBar/index.ts +0 -115
- package/src/statusBar/widget.tsx +0 -318
- package/style/statusBar.css +0 -95
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { LabIcon, textEditorIcon } from '@jupyterlab/ui-components';
|
|
2
|
+
|
|
3
|
+
import { BUILTIN_EDITOR_ICONS } from './icons';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A terminal text editor surfaced on the launcher's "Open" section. An editor
|
|
7
|
+
* takes no initial prompt and is launched by typing its command into a fresh
|
|
8
|
+
* terminal (e.g. `nvim`). The launcher shows a single editor tile (the first
|
|
9
|
+
* available candidate; see {@link resolveEditor}); the full list is shared with
|
|
10
|
+
* the terminals panel to badge running editors.
|
|
11
|
+
*/
|
|
12
|
+
export interface IEditor {
|
|
13
|
+
/** Stable id; also the default `$PATH` command probed for availability. */
|
|
14
|
+
id: string;
|
|
15
|
+
/** Tile label, e.g. "Neovim". */
|
|
16
|
+
label: string;
|
|
17
|
+
/** Tile tooltip. */
|
|
18
|
+
caption: string;
|
|
19
|
+
/** The literal command typed into the new terminal, e.g. `nvim`. */
|
|
20
|
+
command: string;
|
|
21
|
+
/** Brand icon for the tile. */
|
|
22
|
+
icon: LabIcon;
|
|
23
|
+
/**
|
|
24
|
+
* Preference order for the single launcher tile: the first candidate, by
|
|
25
|
+
* ascending rank, that qualifies wins (Neovim before Vim by default).
|
|
26
|
+
*/
|
|
27
|
+
rank: number;
|
|
28
|
+
/**
|
|
29
|
+
* When false, the launcher skips the `which`-based availability check for
|
|
30
|
+
* this entry — useful for a shell alias or function that isn't on PATH but
|
|
31
|
+
* still resolves when typed in a real terminal. Defaults to true.
|
|
32
|
+
*/
|
|
33
|
+
requireAvailable: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The settings-side shape: every field except `id` is optional, so a user can
|
|
38
|
+
* override a single field on a built-in editor (e.g. swap `vim`'s command for
|
|
39
|
+
* a wrapper) without restating the whole entry. New ids define brand-new
|
|
40
|
+
* editors. Mirrors `IAgentSettings` minus `promptArgs`.
|
|
41
|
+
*/
|
|
42
|
+
export interface IEditorSettings {
|
|
43
|
+
id: string;
|
|
44
|
+
label?: string;
|
|
45
|
+
caption?: string;
|
|
46
|
+
command?: string;
|
|
47
|
+
/**
|
|
48
|
+
* Inline SVG for a custom editor's icon. Required for new ids whose icon
|
|
49
|
+
* isn't shipped with xtralab; ignored for built-in ids unless explicitly
|
|
50
|
+
* set (in which case it overrides the built-in).
|
|
51
|
+
*/
|
|
52
|
+
iconSvg?: string;
|
|
53
|
+
rank?: number;
|
|
54
|
+
/**
|
|
55
|
+
* When false, the editor is hidden from the launcher's Open section (and no
|
|
56
|
+
* longer recognised by the terminals panel). Disable both built-ins to drop
|
|
57
|
+
* the editor tile entirely. Defaults to true.
|
|
58
|
+
*/
|
|
59
|
+
enabled?: boolean;
|
|
60
|
+
/** See {@link IEditor.requireAvailable}. */
|
|
61
|
+
requireAvailable?: boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Built-in editor candidates in preference order. The launcher shows the first
|
|
66
|
+
* one whose command resolves on the server's `$PATH`, so Neovim wins over Vim
|
|
67
|
+
* when both are installed. Users override, disable, or extend this list
|
|
68
|
+
* through the `editors` setting; see {@link mergeEditors}.
|
|
69
|
+
*/
|
|
70
|
+
const DEFAULTS: IEditor[] = [
|
|
71
|
+
{
|
|
72
|
+
id: 'nvim',
|
|
73
|
+
label: 'Neovim',
|
|
74
|
+
caption: 'Open Neovim in a new terminal.',
|
|
75
|
+
command: 'nvim',
|
|
76
|
+
icon: BUILTIN_EDITOR_ICONS.nvim,
|
|
77
|
+
rank: 0,
|
|
78
|
+
requireAvailable: true
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: 'vim',
|
|
82
|
+
label: 'Vim',
|
|
83
|
+
caption: 'Open Vim in a new terminal.',
|
|
84
|
+
command: 'vim',
|
|
85
|
+
icon: BUILTIN_EDITOR_ICONS.vim,
|
|
86
|
+
rank: 1,
|
|
87
|
+
requireAvailable: true
|
|
88
|
+
}
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
function resolveEditorIcon(id: string, iconSvg: string | undefined): LabIcon {
|
|
92
|
+
if (iconSvg) {
|
|
93
|
+
return new LabIcon({
|
|
94
|
+
name: `xtralab:editor-custom-${id}`,
|
|
95
|
+
svgstr: iconSvg
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return BUILTIN_EDITOR_ICONS[id] ?? textEditorIcon;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Merge xtralab's built-in editors with the user's `editors` settings. Built-in
|
|
103
|
+
* entries keep their fields unless explicitly overridden; user-only entries are
|
|
104
|
+
* appended. `enabled: false` filters an entry out of the result entirely (so
|
|
105
|
+
* callers don't need to re-check the flag). The result is sorted by rank, which
|
|
106
|
+
* is also the launcher's tile-preference order. Mirrors `mergeAgents`.
|
|
107
|
+
*/
|
|
108
|
+
export function mergeEditors(overrides: IEditorSettings[]): IEditor[] {
|
|
109
|
+
const overrideById = new Map(overrides.map(entry => [entry.id, entry]));
|
|
110
|
+
const merged: IEditor[] = [];
|
|
111
|
+
|
|
112
|
+
for (const base of DEFAULTS) {
|
|
113
|
+
const override = overrideById.get(base.id);
|
|
114
|
+
if (!override) {
|
|
115
|
+
merged.push({ ...base });
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
overrideById.delete(base.id);
|
|
119
|
+
if (override.enabled === false) {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
merged.push({
|
|
123
|
+
id: base.id,
|
|
124
|
+
label: override.label ?? base.label,
|
|
125
|
+
caption: override.caption ?? base.caption,
|
|
126
|
+
command: override.command ?? base.command,
|
|
127
|
+
icon: override.iconSvg
|
|
128
|
+
? resolveEditorIcon(base.id, override.iconSvg)
|
|
129
|
+
: base.icon,
|
|
130
|
+
rank: override.rank ?? base.rank,
|
|
131
|
+
requireAvailable: override.requireAvailable ?? base.requireAvailable
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// What remains in `overrideById` are ids that don't match a built-in — treat
|
|
136
|
+
// them as new editors the user is adding. Skip disabled ones; the settings
|
|
137
|
+
// schema validates the shape, so missing fields just fall back here.
|
|
138
|
+
let nextRank =
|
|
139
|
+
merged.reduce((max, editor) => Math.max(max, editor.rank), -1) + 1;
|
|
140
|
+
for (const entry of overrideById.values()) {
|
|
141
|
+
if (entry.enabled === false) {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
const label = entry.label ?? entry.id;
|
|
145
|
+
const command = entry.command ?? entry.id;
|
|
146
|
+
merged.push({
|
|
147
|
+
id: entry.id,
|
|
148
|
+
label,
|
|
149
|
+
caption: entry.caption ?? `Open ${label} in a new terminal.`,
|
|
150
|
+
command,
|
|
151
|
+
icon: resolveEditorIcon(entry.id, entry.iconSvg),
|
|
152
|
+
rank: entry.rank ?? nextRank++,
|
|
153
|
+
requireAvailable: entry.requireAvailable ?? true
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
merged.sort((a, b) => a.rank - b.rank);
|
|
158
|
+
return merged;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Pick the single editor tile to show, given the merged editor list and the
|
|
163
|
+
* set of commands known to be on `$PATH` (from the launcher's availability
|
|
164
|
+
* probe). Returns the first candidate, by rank, that is either available or
|
|
165
|
+
* opts out of the check (`requireAvailable: false`) — Neovim before Vim by
|
|
166
|
+
* default — or `null` when none qualifies.
|
|
167
|
+
*
|
|
168
|
+
* When `available` is `null` (the endpoint couldn't be reached), a
|
|
169
|
+
* `requireAvailable` editor is not shown; an entry with
|
|
170
|
+
* `requireAvailable: false` is shown regardless.
|
|
171
|
+
*/
|
|
172
|
+
export function resolveEditor(
|
|
173
|
+
editors: IEditor[],
|
|
174
|
+
available: Set<string> | null
|
|
175
|
+
): IEditor | null {
|
|
176
|
+
return (
|
|
177
|
+
editors.find(
|
|
178
|
+
editor =>
|
|
179
|
+
!editor.requireAvailable ||
|
|
180
|
+
(available !== null && available.has(editor.command))
|
|
181
|
+
) ?? null
|
|
182
|
+
);
|
|
183
|
+
}
|
package/src/launcher/icons.ts
CHANGED
|
@@ -1,23 +1,112 @@
|
|
|
1
1
|
import { LabIcon } from '@jupyterlab/ui-components';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Brand icons for the agent launcher cards.
|
|
4
|
+
* Brand icons for the agent launcher cards. Most marks are taken verbatim
|
|
5
5
|
* from `jupyter-ai-contrib/jupyter-ai-acp-client`
|
|
6
|
-
* (`jupyter_ai_acp_client/static/*.svg`, BSD-3-Clause)
|
|
7
|
-
*
|
|
6
|
+
* (`jupyter_ai_acp_client/static/*.svg`, BSD-3-Clause); Antigravity post-dates
|
|
7
|
+
* that set, so its mark is the official glyph from `@lobehub/icons-static-svg`
|
|
8
|
+
* (MIT) — we ship the brand-colored `antigravity-color` variant so Antigravity
|
|
9
|
+
* reads as a multi-color Google mark, occupying the slot the Gemini icon used
|
|
10
|
+
* to fill before the upstream Gemini CLI sunset. Small adjustments are applied
|
|
11
|
+
* to play nicely with JupyterLab themes:
|
|
8
12
|
* - Monochrome marks (Codex, Copilot, Goose) use `currentColor` so they
|
|
9
13
|
* inherit the surrounding launcher card's text color rather than
|
|
10
14
|
* hard-coding black, which disappears on the dark theme.
|
|
11
15
|
* - The Goose mark upstream is rendered on a white rounded rect; we drop
|
|
12
16
|
* the rect so the silhouette can sit on the launcher card directly.
|
|
13
17
|
* - SVG ids that would collide if two copies of the same artwork ended up
|
|
14
|
-
* on the page (gradient/mask defs) are namespaced under
|
|
18
|
+
* on the page (gradient/mask/filter defs) are namespaced under
|
|
19
|
+
* `xtralab-…`.
|
|
15
20
|
*
|
|
16
|
-
* Brand-colored icons (
|
|
21
|
+
* Brand-colored icons (Antigravity, Claude, Kiro, Mistral Vibe, OpenCode) keep
|
|
17
22
|
* their upstream colors; the dark/light contrast of the launcher card behind
|
|
18
23
|
* them is the same as on `jupyter-ai-acp-client`.
|
|
24
|
+
*
|
|
25
|
+
* The Vim and Neovim marks belong to the launcher's "Open" editor tile rather
|
|
26
|
+
* than the agent grid. They are the brand silhouettes from Simple Icons
|
|
27
|
+
* (https://simpleicons.org, CC0), each filled with its brand green so the mark
|
|
28
|
+
* reads on both the light and dark themes.
|
|
19
29
|
*/
|
|
20
30
|
|
|
31
|
+
export const antigravityIcon = new LabIcon({
|
|
32
|
+
name: 'xtralab:agent-antigravity',
|
|
33
|
+
svgstr: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
|
|
34
|
+
<mask id="xtralab-antigravity-mask" height="23" maskUnits="userSpaceOnUse" width="24" x="0" y="1">
|
|
35
|
+
<path d="M21.751 22.607c1.34 1.005 3.35.335 1.508-1.508C17.73 15.74 18.904 1 12.037 1 5.17 1 6.342 15.74.815 21.1c-2.01 2.009.167 2.511 1.507 1.506 5.192-3.517 4.857-9.714 9.715-9.714 4.857 0 4.522 6.197 9.714 9.715z" fill="#fff"/>
|
|
36
|
+
</mask>
|
|
37
|
+
<g mask="url(#xtralab-antigravity-mask)">
|
|
38
|
+
<g filter="url(#xtralab-antigravity-blur-1)">
|
|
39
|
+
<path d="M-1.018-3.992c-.408 3.591 2.686 6.89 6.91 7.37 4.225.48 7.98-2.043 8.387-5.633.408-3.59-2.686-6.89-6.91-7.37-4.225-.479-7.98 2.043-8.387 5.633z" fill="#FFE432"/>
|
|
40
|
+
</g>
|
|
41
|
+
<g filter="url(#xtralab-antigravity-blur-2)">
|
|
42
|
+
<path d="M15.269 7.747c1.058 4.557 5.691 7.374 10.348 6.293 4.657-1.082 7.575-5.653 6.516-10.21-1.058-4.556-5.691-7.374-10.348-6.292-4.657 1.082-7.575 5.653-6.516 10.21z" fill="#FC413D"/>
|
|
43
|
+
</g>
|
|
44
|
+
<g filter="url(#xtralab-antigravity-blur-3)">
|
|
45
|
+
<path d="M-12.443 10.804c1.338 4.703 7.36 7.11 13.453 5.378 6.092-1.733 9.947-6.95 8.61-11.652C8.282-.173 2.26-2.58-3.833-.848-9.925.884-13.78 6.1-12.443 10.804z" fill="#00B95C"/>
|
|
46
|
+
</g>
|
|
47
|
+
<g filter="url(#xtralab-antigravity-blur-4)">
|
|
48
|
+
<path d="M-12.443 10.804c1.338 4.703 7.36 7.11 13.453 5.378 6.092-1.733 9.947-6.95 8.61-11.652C8.282-.173 2.26-2.58-3.833-.848-9.925.884-13.78 6.1-12.443 10.804z" fill="#00B95C"/>
|
|
49
|
+
</g>
|
|
50
|
+
<g filter="url(#xtralab-antigravity-blur-5)">
|
|
51
|
+
<path d="M-7.608 14.703c3.352 3.424 9.126 3.208 12.896-.483 3.77-3.69 4.108-9.459.756-12.883C2.69-2.087-3.083-1.871-6.853 1.82c-3.77 3.69-4.108 9.458-.755 12.883z" fill="#00B95C"/>
|
|
52
|
+
</g>
|
|
53
|
+
<g filter="url(#xtralab-antigravity-blur-6)">
|
|
54
|
+
<path d="M9.932 27.617c1.04 4.482 5.384 7.303 9.7 6.3 4.316-1.002 6.971-5.448 5.93-9.93-1.04-4.483-5.384-7.304-9.7-6.301-4.316 1.002-6.971 5.448-5.93 9.93z" fill="#3186FF"/>
|
|
55
|
+
</g>
|
|
56
|
+
<g filter="url(#xtralab-antigravity-blur-7)">
|
|
57
|
+
<path d="M2.572-8.185C.392-3.329 2.778 2.472 7.9 4.771c5.122 2.3 11.042.227 13.222-4.63 2.18-4.855-.205-10.656-5.327-12.955-5.122-2.3-11.042-.227-13.222 4.63z" fill="#FBBC04"/>
|
|
58
|
+
</g>
|
|
59
|
+
<g filter="url(#xtralab-antigravity-blur-8)">
|
|
60
|
+
<path d="M-3.267 38.686c-5.277-2.072 3.742-19.117 5.984-24.83 2.243-5.712 8.34-8.664 13.616-6.592 5.278 2.071 11.533 13.482 9.29 19.195-2.242 5.713-23.613 14.298-28.89 12.227z" fill="#3186FF"/>
|
|
61
|
+
</g>
|
|
62
|
+
<g filter="url(#xtralab-antigravity-blur-9)">
|
|
63
|
+
<path d="M28.71 17.471c-1.413 1.649-5.1.808-8.236-1.878-3.135-2.687-4.531-6.201-3.118-7.85 1.412-1.649 5.1-.808 8.235 1.878s4.532 6.2 3.119 7.85z" fill="#749BFF"/>
|
|
64
|
+
</g>
|
|
65
|
+
<g filter="url(#xtralab-antigravity-blur-10)">
|
|
66
|
+
<path d="M18.163 9.077c5.81 3.93 12.502 4.19 14.946.577 2.443-3.612-.287-9.727-6.098-13.658-5.81-3.931-12.502-4.19-14.946-.577-2.443 3.612.287 9.727 6.098 13.658z" fill="#FC413D"/>
|
|
67
|
+
</g>
|
|
68
|
+
<g filter="url(#xtralab-antigravity-blur-11)">
|
|
69
|
+
<path d="M-.915 2.684c-1.44 3.473-.97 6.967 1.05 7.804 2.02.837 4.824-1.3 6.264-4.772 1.44-3.473.97-6.967-1.05-7.804-2.02-.837-4.824 1.3-6.264 4.772z" fill="#FFEE48"/>
|
|
70
|
+
</g>
|
|
71
|
+
</g>
|
|
72
|
+
<defs>
|
|
73
|
+
<filter color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse" height="17.587" id="xtralab-antigravity-blur-1" width="19.838" x="-3.288" y="-11.917">
|
|
74
|
+
<feGaussianBlur stdDeviation="1.117"/>
|
|
75
|
+
</filter>
|
|
76
|
+
<filter color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse" height="38.565" id="xtralab-antigravity-blur-2" width="38.9" x="4.251" y="-13.493">
|
|
77
|
+
<feGaussianBlur stdDeviation="5.4"/>
|
|
78
|
+
</filter>
|
|
79
|
+
<filter color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse" height="36.517" id="xtralab-antigravity-blur-3" width="40.955" x="-21.889" y="-10.592">
|
|
80
|
+
<feGaussianBlur stdDeviation="4.591"/>
|
|
81
|
+
</filter>
|
|
82
|
+
<filter color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse" height="36.517" id="xtralab-antigravity-blur-4" width="40.955" x="-21.889" y="-10.592">
|
|
83
|
+
<feGaussianBlur stdDeviation="4.591"/>
|
|
84
|
+
</filter>
|
|
85
|
+
<filter color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse" height="36.595" id="xtralab-antigravity-blur-5" width="36.632" x="-19.099" y="-10.278">
|
|
86
|
+
<feGaussianBlur stdDeviation="4.591"/>
|
|
87
|
+
</filter>
|
|
88
|
+
<filter color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse" height="34.087" id="xtralab-antigravity-blur-6" width="33.533" x=".981" y="8.758">
|
|
89
|
+
<feGaussianBlur stdDeviation="4.363"/>
|
|
90
|
+
</filter>
|
|
91
|
+
<filter color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse" height="35.276" id="xtralab-antigravity-blur-7" width="35.978" x="-6.143" y="-21.659">
|
|
92
|
+
<feGaussianBlur stdDeviation="3.954"/>
|
|
93
|
+
</filter>
|
|
94
|
+
<filter color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse" height="46.523" id="xtralab-antigravity-blur-8" width="45.114" x="-11.96" y="-.46">
|
|
95
|
+
<feGaussianBlur stdDeviation="3.531"/>
|
|
96
|
+
</filter>
|
|
97
|
+
<filter color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse" height="24.054" id="xtralab-antigravity-blur-9" width="25.094" x="10.485" y=".58">
|
|
98
|
+
<feGaussianBlur stdDeviation="3.159"/>
|
|
99
|
+
</filter>
|
|
100
|
+
<filter color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse" height="30.007" id="xtralab-antigravity-blur-10" width="33.508" x="5.833" y="-12.467">
|
|
101
|
+
<feGaussianBlur stdDeviation="2.669"/>
|
|
102
|
+
</filter>
|
|
103
|
+
<filter color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse" height="26.151" id="xtralab-antigravity-blur-11" width="22.194" x="-8.355" y="-8.876">
|
|
104
|
+
<feGaussianBlur stdDeviation="3.303"/>
|
|
105
|
+
</filter>
|
|
106
|
+
</defs>
|
|
107
|
+
</svg>`
|
|
108
|
+
});
|
|
109
|
+
|
|
21
110
|
export const claudeIcon = new LabIcon({
|
|
22
111
|
name: 'xtralab:agent-claude',
|
|
23
112
|
svgstr: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 1200">
|
|
@@ -40,20 +129,6 @@ export const copilotIcon = new LabIcon({
|
|
|
40
129
|
</svg>`
|
|
41
130
|
});
|
|
42
131
|
|
|
43
|
-
export const geminiIcon = new LabIcon({
|
|
44
|
-
name: 'xtralab:agent-gemini',
|
|
45
|
-
svgstr: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none">
|
|
46
|
-
<path d="M16 8.016A8.522 8.522 0 008.016 16h-.032A8.521 8.521 0 000 8.016v-.032A8.521 8.521 0 007.984 0h.032A8.522 8.522 0 0016 7.984v.032z" fill="url(#xtralab-gemini-radial)"/>
|
|
47
|
-
<defs>
|
|
48
|
-
<radialGradient id="xtralab-gemini-radial" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(16.1326 5.4553 -43.70045 129.2322 1.588 6.503)">
|
|
49
|
-
<stop offset=".067" stop-color="#9168C0"/>
|
|
50
|
-
<stop offset=".343" stop-color="#5684D1"/>
|
|
51
|
-
<stop offset=".672" stop-color="#1BA1E3"/>
|
|
52
|
-
</radialGradient>
|
|
53
|
-
</defs>
|
|
54
|
-
</svg>`
|
|
55
|
-
});
|
|
56
|
-
|
|
57
132
|
export const gooseIcon = new LabIcon({
|
|
58
133
|
name: 'xtralab:agent-goose',
|
|
59
134
|
svgstr: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048">
|
|
@@ -105,6 +180,20 @@ export const opencodeIcon = new LabIcon({
|
|
|
105
180
|
</svg>`
|
|
106
181
|
});
|
|
107
182
|
|
|
183
|
+
export const neovimIcon = new LabIcon({
|
|
184
|
+
name: 'xtralab:editor-neovim',
|
|
185
|
+
svgstr: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#57A143">
|
|
186
|
+
<path d="M2.214 4.954v13.615L7.655 24V10.314L3.312 3.845 2.214 4.954zm4.999 17.98l-4.557-4.548V5.136l.59-.596 3.967 5.908v12.485zm14.573-4.457l-.862.937-4.24-6.376V0l5.068 5.092.034 13.385zM7.431.001l12.998 19.835-3.637 3.637L3.787 3.683 7.43 0z"/>
|
|
187
|
+
</svg>`
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
export const vimIcon = new LabIcon({
|
|
191
|
+
name: 'xtralab:editor-vim',
|
|
192
|
+
svgstr: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#019733">
|
|
193
|
+
<path d="M24 11.986h-.027l-4.318-4.318 4.303-4.414V1.461l-.649-.648h-8.198l-.66.605v1.045L12.015.027V0L12 .014 11.986 0v.027l-1.29 1.291-.538-.539H2.035l-.638.692v1.885l.616.616h.72v5.31L.027 11.987H0L.014 12 0 12.014h.027l2.706 2.706v6.467l.907.523h2.322l1.857-1.904 4.166 4.166V24l.015-.014.014.014v-.028l2.51-2.509h.485c.111 0 .211-.07.25-.179l.146-.426c.028-.084.012-.172-.037-.239l1.462-1.462-.612 1.962c-.043.141.036.289.177.332.025.008.052.012.078.012h1.824c.106-.001.201-.064.243-.163l.165-.394c.025-.065.024-.138-.004-.203-.027-.065-.08-.116-.146-.142-.029-.012-.062-.019-.097-.02h-.075l.84-2.644h1.232l-1.016 3.221c-.043.141.036.289.176.332.025.008.052.012.079.012h2.002c.11 0 .207-.066.248-.17l.164-.428c.051-.138-.021-.29-.158-.341-.029-.011-.06-.017-.091-.017h-.145l1.131-3.673c.027-.082.012-.173-.039-.24l-.375-.504-.003-.005c-.051-.064-.127-.102-.209-.102h-1.436c-.071 0-.141.03-.19.081l-.4.439h-.624l-.042-.046 4.445-4.445H24L23.986 12l.014-.014zM9.838 21.139l1.579-4.509h-.501l.297-.304h1.659l-1.563 4.555h.623l-.079.258H9.838zm3.695-7.516l.15.151-.269.922-.225.226h-.969l-.181-.181.311-.871.288-.247h.895zM5.59 20.829H3.877l-.262-.15V3.091H2.379l-.1-.1V1.815l.143-.154h7.371l.213.214v1.108l-.142.173H8.785v8.688l8.807-8.688h-2.086l-.175-.188V1.805l.121-.111h7.49l.132.133v1.07L12.979 13.25h-.373c-.015-.001-.028 0-.042.001l-.02.003c-.045.01-.086.03-.119.06l-.343.295-.004.003c-.033.031-.059.069-.073.111l-.296.83-6.119 6.276zm14.768-3.952l.474-.519h1.334l.309.415-1.265 4.107h.493l-.08.209H19.84l1.124-3.564h-2.015l-1.077 3.391h.424l-.073.174h-1.605l1.107-3.548h-2.096l-1.062 3.339h.436l-.072.209H13.27l1.514-4.46H14.198l.091-.271h1.65l.519.537h.906l.491-.554h1.061l.489.535h.953z"/>
|
|
194
|
+
</svg>`
|
|
195
|
+
});
|
|
196
|
+
|
|
108
197
|
/**
|
|
109
198
|
* Built-in icons keyed by the agent's id, used to resolve the icon for an
|
|
110
199
|
* agent declared without an inline `iconSvg` override (i.e. one of xtralab's
|
|
@@ -112,12 +201,23 @@ export const opencodeIcon = new LabIcon({
|
|
|
112
201
|
* `iconSvg` or fall back to the generic terminal icon.
|
|
113
202
|
*/
|
|
114
203
|
export const BUILTIN_AGENT_ICONS: Record<string, LabIcon> = {
|
|
204
|
+
antigravity: antigravityIcon,
|
|
115
205
|
claude: claudeIcon,
|
|
116
206
|
codex: codexIcon,
|
|
117
207
|
copilot: copilotIcon,
|
|
118
|
-
gemini: geminiIcon,
|
|
119
208
|
goose: gooseIcon,
|
|
120
209
|
kiro: kiroIcon,
|
|
121
210
|
'mistral-vibe': mistralVibeIcon,
|
|
122
211
|
opencode: opencodeIcon
|
|
123
212
|
};
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Built-in icons keyed by the editor's id, used to resolve the icon for a
|
|
216
|
+
* built-in editor (Neovim, Vim) declared without an inline `iconSvg`. Custom
|
|
217
|
+
* editors added via settings can supply their own `iconSvg` or fall back to
|
|
218
|
+
* the generic text-editor icon.
|
|
219
|
+
*/
|
|
220
|
+
export const BUILTIN_EDITOR_ICONS: Record<string, LabIcon> = {
|
|
221
|
+
nvim: neovimIcon,
|
|
222
|
+
vim: vimIcon
|
|
223
|
+
};
|
package/src/launcher/index.ts
CHANGED
|
@@ -12,10 +12,14 @@ import type { ReadonlyPartialJSONObject } from '@lumino/coreutils';
|
|
|
12
12
|
import type { IDisposable } from '@lumino/disposable';
|
|
13
13
|
import type { Widget } from '@lumino/widgets';
|
|
14
14
|
|
|
15
|
+
import { IAgentSessions } from '../agentSessions';
|
|
15
16
|
import { mergeAgents, type IAgent, type IAgentSettings } from './agents';
|
|
16
17
|
import { fetchAvailableCommands } from './availability';
|
|
17
18
|
import { CREATE_LAUNCHER_COMMAND, registerAgentCommands } from './commands';
|
|
18
19
|
import { LauncherDashboard } from './dashboard';
|
|
20
|
+
import { editorRegistryPlugin, IEditorRegistry } from './editorRegistry';
|
|
21
|
+
import { AgentRegistry } from './registry';
|
|
22
|
+
import { IAgentRegistry } from './tokens';
|
|
19
23
|
|
|
20
24
|
const PLUGIN_ID = 'xtralab:launcher';
|
|
21
25
|
|
|
@@ -23,7 +27,7 @@ const PLUGIN_ID = 'xtralab:launcher';
|
|
|
23
27
|
* The xtralab launcher plugin. Replaces the stock JupyterLab launcher
|
|
24
28
|
* (which is disabled via `package.json`'s `jupyterlab.disabledExtensions`)
|
|
25
29
|
* with an agent-focused dashboard: an optional initial prompt, a row of
|
|
26
|
-
* agent buttons (Claude, Codex,
|
|
30
|
+
* agent buttons (Claude, Codex, Antigravity, …), and a collapsible list of
|
|
27
31
|
* changed files (clickable into the diff viewer) below them. Clicking an
|
|
28
32
|
* agent opens a fresh terminal and pipes the agent's command into it; if
|
|
29
33
|
* the prompt textarea is non-empty, the prompt is shell-quoted and
|
|
@@ -39,25 +43,37 @@ const PLUGIN_ID = 'xtralab:launcher';
|
|
|
39
43
|
* effect, which would defeat the point of the agent-only launcher. If an
|
|
40
44
|
* extension needs to surface itself, we'll add it here explicitly.
|
|
41
45
|
*/
|
|
42
|
-
const plugin: JupyterFrontEndPlugin<
|
|
46
|
+
const plugin: JupyterFrontEndPlugin<IAgentRegistry> = {
|
|
43
47
|
id: PLUGIN_ID,
|
|
44
48
|
description:
|
|
45
49
|
'An agent-focused launcher that replaces the default JupyterLab launcher.',
|
|
46
50
|
autoStart: true,
|
|
47
|
-
|
|
51
|
+
provides: IAgentRegistry,
|
|
52
|
+
optional: [
|
|
53
|
+
ILabShell,
|
|
54
|
+
ICommandPalette,
|
|
55
|
+
ISettingRegistry,
|
|
56
|
+
ITranslator,
|
|
57
|
+
IAgentSessions,
|
|
58
|
+
IEditorRegistry
|
|
59
|
+
],
|
|
48
60
|
activate: async (
|
|
49
61
|
app: JupyterFrontEnd,
|
|
50
62
|
labShell: ILabShell | null,
|
|
51
63
|
palette: ICommandPalette | null,
|
|
52
64
|
settingRegistry: ISettingRegistry | null,
|
|
53
|
-
translator: ITranslator | null
|
|
54
|
-
|
|
65
|
+
translator: ITranslator | null,
|
|
66
|
+
agentSessions: IAgentSessions | null,
|
|
67
|
+
editorRegistry: IEditorRegistry | null
|
|
68
|
+
): Promise<IAgentRegistry> => {
|
|
55
69
|
const { commands, shell } = app;
|
|
56
70
|
const trans = (translator ?? nullTranslator).load('jupyterlab');
|
|
57
71
|
|
|
58
|
-
// The
|
|
59
|
-
//
|
|
60
|
-
|
|
72
|
+
// The shared agent registry this plugin provides on `IAgentRegistry`.
|
|
73
|
+
// It holds the active agent list (updated whenever settings change), so
|
|
74
|
+
// every freshly-created launcher widget — and any other plugin that
|
|
75
|
+
// consumes the token, e.g. the terminals panel — renders the latest list.
|
|
76
|
+
const registry = new AgentRegistry();
|
|
61
77
|
|
|
62
78
|
// Track command registrations so a settings change can wipe them
|
|
63
79
|
// before re-registering — without this the command palette would
|
|
@@ -66,11 +82,24 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
66
82
|
|
|
67
83
|
const applyAgents = async (overrides: IAgentSettings[]): Promise<void> => {
|
|
68
84
|
const agents = mergeAgents(overrides);
|
|
69
|
-
|
|
85
|
+
|
|
86
|
+
// Probe the server's `$PATH` so we only surface agents that are actually
|
|
87
|
+
// installed. Agents with `requireAvailable: false` opt out (their command
|
|
88
|
+
// may be a shell alias that `which` can't see) and are kept regardless.
|
|
89
|
+
const probe = Array.from(
|
|
90
|
+
new Set(
|
|
91
|
+
agents
|
|
92
|
+
.filter(agent => agent.requireAvailable)
|
|
93
|
+
.map(agent => agent.command)
|
|
94
|
+
)
|
|
95
|
+
);
|
|
96
|
+
const available = await fetchAvailableCommands(probe);
|
|
97
|
+
|
|
98
|
+
const filtered = filterAgents(agents, available);
|
|
70
99
|
|
|
71
100
|
registered?.dispose();
|
|
72
|
-
registered = registerAgentCommands(app, filtered);
|
|
73
|
-
|
|
101
|
+
registered = registerAgentCommands(app, filtered, agentSessions);
|
|
102
|
+
registry.setAgents(filtered);
|
|
74
103
|
};
|
|
75
104
|
|
|
76
105
|
const readOverrides = (
|
|
@@ -121,7 +150,9 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
121
150
|
};
|
|
122
151
|
const launcher = new LauncherDashboard({
|
|
123
152
|
commands,
|
|
124
|
-
agents:
|
|
153
|
+
agents: registry.agents,
|
|
154
|
+
editor: editorRegistry?.current ?? null,
|
|
155
|
+
agentSessions,
|
|
125
156
|
onAgentLaunch,
|
|
126
157
|
// Empty repoPath/cwd matches the JupyterLab convention used by
|
|
127
158
|
// the git panel and the stock launcher: let the server resolve
|
|
@@ -186,26 +217,25 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
186
217
|
return commands.execute(CREATE_LAUNCHER_COMMAND, { ref });
|
|
187
218
|
});
|
|
188
219
|
}
|
|
220
|
+
|
|
221
|
+
return registry;
|
|
189
222
|
}
|
|
190
223
|
};
|
|
191
224
|
|
|
192
225
|
/**
|
|
193
|
-
* Drop agents whose command isn't on `$PATH`, except
|
|
194
|
-
*
|
|
195
|
-
*
|
|
226
|
+
* Drop agents whose command isn't on `$PATH`, except entries that opt out via
|
|
227
|
+
* `requireAvailable: false` (e.g. shell aliases the user wants surfaced
|
|
228
|
+
* regardless). `available` is the resolved set from
|
|
229
|
+
* {@link fetchAvailableCommands}.
|
|
196
230
|
*
|
|
197
|
-
*
|
|
198
|
-
* the input unchanged — better to show an unreachable agent than
|
|
199
|
-
* the entire launcher because the server extension didn't load.
|
|
231
|
+
* When `available` is `null` (the endpoint couldn't be reached) we fail open
|
|
232
|
+
* and return the input unchanged — better to show an unreachable agent than
|
|
233
|
+
* to hide the entire launcher because the server extension didn't load.
|
|
200
234
|
*/
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
if (commands.length === 0) {
|
|
206
|
-
return agents;
|
|
207
|
-
}
|
|
208
|
-
const available = await fetchAvailableCommands(commands);
|
|
235
|
+
function filterAgents(
|
|
236
|
+
agents: IAgent[],
|
|
237
|
+
available: Set<string> | null
|
|
238
|
+
): IAgent[] {
|
|
209
239
|
if (!available) {
|
|
210
240
|
return agents;
|
|
211
241
|
}
|
|
@@ -225,4 +255,9 @@ namespace Private {
|
|
|
225
255
|
}
|
|
226
256
|
}
|
|
227
257
|
|
|
228
|
-
|
|
258
|
+
const plugins: JupyterFrontEndPlugin<unknown>[] = [
|
|
259
|
+
plugin,
|
|
260
|
+
editorRegistryPlugin
|
|
261
|
+
];
|
|
262
|
+
|
|
263
|
+
export default plugins;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ISignal, Signal } from '@lumino/signaling';
|
|
2
|
+
|
|
3
|
+
import type { IAgent } from './agents';
|
|
4
|
+
import type { IAgentRegistry } from './tokens';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The concrete {@link IAgentRegistry} the launcher plugin provides on the
|
|
8
|
+
* `IAgentRegistry` token. Holds the active agent list and re-emits `changed`
|
|
9
|
+
* whenever the launcher recomputes it (on a settings change). The launcher
|
|
10
|
+
* is the only writer, so the write side ({@link setAgents}) is kept off the
|
|
11
|
+
* shared token.
|
|
12
|
+
*/
|
|
13
|
+
export class AgentRegistry implements IAgentRegistry {
|
|
14
|
+
get agents(): IAgent[] {
|
|
15
|
+
return this._agents;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
get changed(): ISignal<IAgentRegistry, void> {
|
|
19
|
+
return this._changed;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Replace the agent list and notify observers. Called by the launcher
|
|
24
|
+
* after merging the user's settings and filtering by availability.
|
|
25
|
+
*/
|
|
26
|
+
setAgents(agents: IAgent[]): void {
|
|
27
|
+
this._agents = agents;
|
|
28
|
+
this._changed.emit();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private _agents: IAgent[] = [];
|
|
32
|
+
private _changed = new Signal<IAgentRegistry, void>(this);
|
|
33
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Token } from '@lumino/coreutils';
|
|
2
|
+
import type { ISignal } from '@lumino/signaling';
|
|
3
|
+
|
|
4
|
+
import type { IAgent } from './agents';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A read-only, observable view of the launcher's available agents.
|
|
8
|
+
*
|
|
9
|
+
* The launcher plugin owns the agent list — xtralab's defaults merged with
|
|
10
|
+
* the user's `xtralab:launcher` settings, then filtered by a server-side
|
|
11
|
+
* `which` check — and registers an `xtralab:start-agent:<id>` command for
|
|
12
|
+
* each entry. This token shares that list, and (via {@link agentCommandId})
|
|
13
|
+
* the command id that launches each agent, so other plugins can surface the
|
|
14
|
+
* same agents without re-deriving the list or duplicating the icons. The
|
|
15
|
+
* terminals panel uses it to build its "new terminal" dropdown.
|
|
16
|
+
*/
|
|
17
|
+
export interface IAgentRegistry {
|
|
18
|
+
/**
|
|
19
|
+
* The current agents, already filtered by availability and sorted by
|
|
20
|
+
* rank — the same array the launcher renders.
|
|
21
|
+
*/
|
|
22
|
+
readonly agents: IAgent[];
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Emitted whenever {@link agents} changes (e.g. the user edits the
|
|
26
|
+
* launcher settings). Consumers that cache the list should re-read it
|
|
27
|
+
* here; consumers that read it on demand can ignore the signal.
|
|
28
|
+
*/
|
|
29
|
+
readonly changed: ISignal<IAgentRegistry, void>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* DI token for {@link IAgentRegistry}. Provided by `xtralab:launcher` and
|
|
34
|
+
* consumed — optionally, so the panel still works when the launcher is
|
|
35
|
+
* disabled — by `xtralab:terminals`.
|
|
36
|
+
*/
|
|
37
|
+
export const IAgentRegistry = new Token<IAgentRegistry>(
|
|
38
|
+
'xtralab:IAgentRegistry',
|
|
39
|
+
'A read-only, observable view of the launcher agents, shared so other plugins can offer the same agent list and icons.'
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The JupyterLab command id that launches a given agent in a new terminal.
|
|
44
|
+
* Defined here — in the shared contract module rather than the launcher's
|
|
45
|
+
* command-registration internals — so the launcher (which registers the
|
|
46
|
+
* commands) and any consumer (which references them, e.g. in a menu) agree
|
|
47
|
+
* on the id.
|
|
48
|
+
*/
|
|
49
|
+
export function agentCommandId(agentId: string): string {
|
|
50
|
+
return `xtralab:start-agent:${agentId}`;
|
|
51
|
+
}
|