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.
Files changed (71) hide show
  1. package/README.md +110 -78
  2. package/lib/agentSessions.d.ts +50 -0
  3. package/lib/agentSessions.js +45 -0
  4. package/lib/fileBrowser/index.js +5 -1
  5. package/lib/git/imageDiff.js +80 -8
  6. package/lib/index.d.ts +3 -2
  7. package/lib/index.js +11 -6
  8. package/lib/launcher/agents.d.ts +1 -1
  9. package/lib/launcher/agents.js +22 -13
  10. package/lib/launcher/commands.d.ts +32 -6
  11. package/lib/launcher/commands.js +81 -57
  12. package/lib/launcher/dashboard.d.ts +15 -0
  13. package/lib/launcher/dashboard.js +119 -5
  14. package/lib/launcher/editorRegistry.d.ts +43 -0
  15. package/lib/launcher/editorRegistry.js +96 -0
  16. package/lib/launcher/editors.d.ts +78 -0
  17. package/lib/launcher/editors.js +110 -0
  18. package/lib/launcher/icons.d.ts +25 -6
  19. package/lib/launcher/icons.js +116 -19
  20. package/lib/launcher/index.d.ts +2 -22
  21. package/lib/launcher/index.js +48 -25
  22. package/lib/launcher/registry.d.ts +21 -0
  23. package/lib/launcher/registry.js +28 -0
  24. package/lib/launcher/tokens.d.ts +41 -0
  25. package/lib/launcher/tokens.js +17 -0
  26. package/lib/terminals/detection.d.ts +11 -0
  27. package/lib/terminals/detection.js +50 -0
  28. package/lib/terminals/index.d.ts +27 -0
  29. package/lib/terminals/index.js +194 -0
  30. package/lib/terminals/model.d.ts +191 -0
  31. package/lib/terminals/model.js +418 -0
  32. package/lib/terminals/widget.d.ts +72 -0
  33. package/lib/terminals/widget.js +93 -0
  34. package/lib/topBar/icons.d.ts +13 -0
  35. package/lib/topBar/icons.js +27 -0
  36. package/lib/topBar/index.d.ts +16 -0
  37. package/lib/topBar/index.js +86 -0
  38. package/package.json +36 -33
  39. package/schema/launcher.json +56 -1
  40. package/src/agentSessions.ts +94 -0
  41. package/src/fileBrowser/index.ts +5 -1
  42. package/src/git/imageDiff.tsx +127 -26
  43. package/src/index.ts +11 -6
  44. package/src/launcher/agents.ts +23 -14
  45. package/src/launcher/commands.ts +98 -60
  46. package/src/launcher/dashboard.tsx +205 -3
  47. package/src/launcher/editorRegistry.ts +149 -0
  48. package/src/launcher/editors.ts +183 -0
  49. package/src/launcher/icons.ts +120 -20
  50. package/src/launcher/index.ts +62 -27
  51. package/src/launcher/registry.ts +33 -0
  52. package/src/launcher/tokens.ts +51 -0
  53. package/src/terminals/detection.ts +65 -0
  54. package/src/terminals/index.ts +222 -0
  55. package/src/terminals/model.ts +497 -0
  56. package/src/terminals/widget.tsx +240 -0
  57. package/src/topBar/icons.ts +30 -0
  58. package/src/topBar/index.ts +121 -0
  59. package/style/git.css +109 -0
  60. package/style/index.css +2 -1
  61. package/style/index.js +2 -1
  62. package/style/launcher.css +124 -2
  63. package/style/terminals.css +235 -0
  64. package/style/topBar.css +38 -0
  65. package/lib/statusBar/index.d.ts +0 -14
  66. package/lib/statusBar/index.js +0 -89
  67. package/lib/statusBar/widget.d.ts +0 -121
  68. package/lib/statusBar/widget.js +0 -237
  69. package/src/statusBar/index.ts +0 -115
  70. package/src/statusBar/widget.tsx +0 -318
  71. package/style/statusBar.css +0 -95
@@ -0,0 +1,110 @@
1
+ import { LabIcon, textEditorIcon } from '@jupyterlab/ui-components';
2
+ import { BUILTIN_EDITOR_ICONS } from './icons';
3
+ /**
4
+ * Built-in editor candidates in preference order. The launcher shows the first
5
+ * one whose command resolves on the server's `$PATH`, so Neovim wins over Vim
6
+ * when both are installed. Users override, disable, or extend this list
7
+ * through the `editors` setting; see {@link mergeEditors}.
8
+ */
9
+ const DEFAULTS = [
10
+ {
11
+ id: 'nvim',
12
+ label: 'Neovim',
13
+ caption: 'Open Neovim in a new terminal.',
14
+ command: 'nvim',
15
+ icon: BUILTIN_EDITOR_ICONS.nvim,
16
+ rank: 0,
17
+ requireAvailable: true
18
+ },
19
+ {
20
+ id: 'vim',
21
+ label: 'Vim',
22
+ caption: 'Open Vim in a new terminal.',
23
+ command: 'vim',
24
+ icon: BUILTIN_EDITOR_ICONS.vim,
25
+ rank: 1,
26
+ requireAvailable: true
27
+ }
28
+ ];
29
+ function resolveEditorIcon(id, iconSvg) {
30
+ var _a;
31
+ if (iconSvg) {
32
+ return new LabIcon({
33
+ name: `xtralab:editor-custom-${id}`,
34
+ svgstr: iconSvg
35
+ });
36
+ }
37
+ return (_a = BUILTIN_EDITOR_ICONS[id]) !== null && _a !== void 0 ? _a : textEditorIcon;
38
+ }
39
+ /**
40
+ * Merge xtralab's built-in editors with the user's `editors` settings. Built-in
41
+ * entries keep their fields unless explicitly overridden; user-only entries are
42
+ * appended. `enabled: false` filters an entry out of the result entirely (so
43
+ * callers don't need to re-check the flag). The result is sorted by rank, which
44
+ * is also the launcher's tile-preference order. Mirrors `mergeAgents`.
45
+ */
46
+ export function mergeEditors(overrides) {
47
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
48
+ const overrideById = new Map(overrides.map(entry => [entry.id, entry]));
49
+ const merged = [];
50
+ for (const base of DEFAULTS) {
51
+ const override = overrideById.get(base.id);
52
+ if (!override) {
53
+ merged.push({ ...base });
54
+ continue;
55
+ }
56
+ overrideById.delete(base.id);
57
+ if (override.enabled === false) {
58
+ continue;
59
+ }
60
+ merged.push({
61
+ id: base.id,
62
+ label: (_a = override.label) !== null && _a !== void 0 ? _a : base.label,
63
+ caption: (_b = override.caption) !== null && _b !== void 0 ? _b : base.caption,
64
+ command: (_c = override.command) !== null && _c !== void 0 ? _c : base.command,
65
+ icon: override.iconSvg
66
+ ? resolveEditorIcon(base.id, override.iconSvg)
67
+ : base.icon,
68
+ rank: (_d = override.rank) !== null && _d !== void 0 ? _d : base.rank,
69
+ requireAvailable: (_e = override.requireAvailable) !== null && _e !== void 0 ? _e : base.requireAvailable
70
+ });
71
+ }
72
+ // What remains in `overrideById` are ids that don't match a built-in — treat
73
+ // them as new editors the user is adding. Skip disabled ones; the settings
74
+ // schema validates the shape, so missing fields just fall back here.
75
+ let nextRank = merged.reduce((max, editor) => Math.max(max, editor.rank), -1) + 1;
76
+ for (const entry of overrideById.values()) {
77
+ if (entry.enabled === false) {
78
+ continue;
79
+ }
80
+ const label = (_f = entry.label) !== null && _f !== void 0 ? _f : entry.id;
81
+ const command = (_g = entry.command) !== null && _g !== void 0 ? _g : entry.id;
82
+ merged.push({
83
+ id: entry.id,
84
+ label,
85
+ caption: (_h = entry.caption) !== null && _h !== void 0 ? _h : `Open ${label} in a new terminal.`,
86
+ command,
87
+ icon: resolveEditorIcon(entry.id, entry.iconSvg),
88
+ rank: (_j = entry.rank) !== null && _j !== void 0 ? _j : nextRank++,
89
+ requireAvailable: (_k = entry.requireAvailable) !== null && _k !== void 0 ? _k : true
90
+ });
91
+ }
92
+ merged.sort((a, b) => a.rank - b.rank);
93
+ return merged;
94
+ }
95
+ /**
96
+ * Pick the single editor tile to show, given the merged editor list and the
97
+ * set of commands known to be on `$PATH` (from the launcher's availability
98
+ * probe). Returns the first candidate, by rank, that is either available or
99
+ * opts out of the check (`requireAvailable: false`) — Neovim before Vim by
100
+ * default — or `null` when none qualifies.
101
+ *
102
+ * When `available` is `null` (the endpoint couldn't be reached), a
103
+ * `requireAvailable` editor is not shown; an entry with
104
+ * `requireAvailable: false` is shown regardless.
105
+ */
106
+ export function resolveEditor(editors, available) {
107
+ var _a;
108
+ return ((_a = editors.find(editor => !editor.requireAvailable ||
109
+ (available !== null && available.has(editor.command)))) !== null && _a !== void 0 ? _a : null);
110
+ }
@@ -1,29 +1,41 @@
1
1
  import { LabIcon } from '@jupyterlab/ui-components';
2
2
  /**
3
- * Brand icons for the agent launcher cards. The artwork is taken verbatim
3
+ * Brand icons for the agent launcher cards. Most marks are taken verbatim
4
4
  * from `jupyter-ai-contrib/jupyter-ai-acp-client`
5
- * (`jupyter_ai_acp_client/static/*.svg`, BSD-3-Clause), with small
6
- * adjustments to play nicely with JupyterLab themes:
5
+ * (`jupyter_ai_acp_client/static/*.svg`, BSD-3-Clause); Antigravity post-dates
6
+ * that set, so its mark is the official glyph from `@lobehub/icons-static-svg`
7
+ * (MIT) — we ship the brand-colored `antigravity-color` variant so Antigravity
8
+ * reads as a multi-color Google mark, occupying the slot the Gemini icon used
9
+ * to fill before the upstream Gemini CLI sunset. Small adjustments are applied
10
+ * to play nicely with JupyterLab themes:
7
11
  * - Monochrome marks (Codex, Copilot, Goose) use `currentColor` so they
8
12
  * inherit the surrounding launcher card's text color rather than
9
13
  * hard-coding black, which disappears on the dark theme.
10
14
  * - The Goose mark upstream is rendered on a white rounded rect; we drop
11
15
  * the rect so the silhouette can sit on the launcher card directly.
12
16
  * - SVG ids that would collide if two copies of the same artwork ended up
13
- * on the page (gradient/mask defs) are namespaced under `xtralab-…`.
17
+ * on the page (gradient/mask/filter defs) are namespaced under
18
+ * `xtralab-…`.
14
19
  *
15
- * Brand-colored icons (Claude, Gemini, Kiro, Mistral Vibe, OpenCode) keep
20
+ * Brand-colored icons (Antigravity, Claude, Kiro, Mistral Vibe, OpenCode) keep
16
21
  * their upstream colors; the dark/light contrast of the launcher card behind
17
22
  * them is the same as on `jupyter-ai-acp-client`.
23
+ *
24
+ * The Vim and Neovim marks belong to the launcher's "Open" editor tile rather
25
+ * than the agent grid. They are the brand silhouettes from Simple Icons
26
+ * (https://simpleicons.org, CC0), each filled with its brand green so the mark
27
+ * reads on both the light and dark themes.
18
28
  */
29
+ export declare const antigravityIcon: LabIcon;
19
30
  export declare const claudeIcon: LabIcon;
20
31
  export declare const codexIcon: LabIcon;
21
32
  export declare const copilotIcon: LabIcon;
22
- export declare const geminiIcon: LabIcon;
23
33
  export declare const gooseIcon: LabIcon;
24
34
  export declare const kiroIcon: LabIcon;
25
35
  export declare const mistralVibeIcon: LabIcon;
26
36
  export declare const opencodeIcon: LabIcon;
37
+ export declare const neovimIcon: LabIcon;
38
+ export declare const vimIcon: LabIcon;
27
39
  /**
28
40
  * Built-in icons keyed by the agent's id, used to resolve the icon for an
29
41
  * agent declared without an inline `iconSvg` override (i.e. one of xtralab's
@@ -31,3 +43,10 @@ export declare const opencodeIcon: LabIcon;
31
43
  * `iconSvg` or fall back to the generic terminal icon.
32
44
  */
33
45
  export declare const BUILTIN_AGENT_ICONS: Record<string, LabIcon>;
46
+ /**
47
+ * Built-in icons keyed by the editor's id, used to resolve the icon for a
48
+ * built-in editor (Neovim, Vim) declared without an inline `iconSvg`. Custom
49
+ * editors added via settings can supply their own `iconSvg` or fall back to
50
+ * the generic text-editor icon.
51
+ */
52
+ export declare const BUILTIN_EDITOR_ICONS: Record<string, LabIcon>;
@@ -1,21 +1,109 @@
1
1
  import { LabIcon } from '@jupyterlab/ui-components';
2
2
  /**
3
- * Brand icons for the agent launcher cards. The artwork is taken verbatim
3
+ * Brand icons for the agent launcher cards. Most marks are taken verbatim
4
4
  * from `jupyter-ai-contrib/jupyter-ai-acp-client`
5
- * (`jupyter_ai_acp_client/static/*.svg`, BSD-3-Clause), with small
6
- * adjustments to play nicely with JupyterLab themes:
5
+ * (`jupyter_ai_acp_client/static/*.svg`, BSD-3-Clause); Antigravity post-dates
6
+ * that set, so its mark is the official glyph from `@lobehub/icons-static-svg`
7
+ * (MIT) — we ship the brand-colored `antigravity-color` variant so Antigravity
8
+ * reads as a multi-color Google mark, occupying the slot the Gemini icon used
9
+ * to fill before the upstream Gemini CLI sunset. Small adjustments are applied
10
+ * to play nicely with JupyterLab themes:
7
11
  * - Monochrome marks (Codex, Copilot, Goose) use `currentColor` so they
8
12
  * inherit the surrounding launcher card's text color rather than
9
13
  * hard-coding black, which disappears on the dark theme.
10
14
  * - The Goose mark upstream is rendered on a white rounded rect; we drop
11
15
  * the rect so the silhouette can sit on the launcher card directly.
12
16
  * - SVG ids that would collide if two copies of the same artwork ended up
13
- * on the page (gradient/mask defs) are namespaced under `xtralab-…`.
17
+ * on the page (gradient/mask/filter defs) are namespaced under
18
+ * `xtralab-…`.
14
19
  *
15
- * Brand-colored icons (Claude, Gemini, Kiro, Mistral Vibe, OpenCode) keep
20
+ * Brand-colored icons (Antigravity, Claude, Kiro, Mistral Vibe, OpenCode) keep
16
21
  * their upstream colors; the dark/light contrast of the launcher card behind
17
22
  * them is the same as on `jupyter-ai-acp-client`.
23
+ *
24
+ * The Vim and Neovim marks belong to the launcher's "Open" editor tile rather
25
+ * than the agent grid. They are the brand silhouettes from Simple Icons
26
+ * (https://simpleicons.org, CC0), each filled with its brand green so the mark
27
+ * reads on both the light and dark themes.
18
28
  */
29
+ export const antigravityIcon = new LabIcon({
30
+ name: 'xtralab:agent-antigravity',
31
+ svgstr: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
32
+ <mask id="xtralab-antigravity-mask" height="23" maskUnits="userSpaceOnUse" width="24" x="0" y="1">
33
+ <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"/>
34
+ </mask>
35
+ <g mask="url(#xtralab-antigravity-mask)">
36
+ <g filter="url(#xtralab-antigravity-blur-1)">
37
+ <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"/>
38
+ </g>
39
+ <g filter="url(#xtralab-antigravity-blur-2)">
40
+ <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"/>
41
+ </g>
42
+ <g filter="url(#xtralab-antigravity-blur-3)">
43
+ <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"/>
44
+ </g>
45
+ <g filter="url(#xtralab-antigravity-blur-4)">
46
+ <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"/>
47
+ </g>
48
+ <g filter="url(#xtralab-antigravity-blur-5)">
49
+ <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"/>
50
+ </g>
51
+ <g filter="url(#xtralab-antigravity-blur-6)">
52
+ <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"/>
53
+ </g>
54
+ <g filter="url(#xtralab-antigravity-blur-7)">
55
+ <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"/>
56
+ </g>
57
+ <g filter="url(#xtralab-antigravity-blur-8)">
58
+ <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"/>
59
+ </g>
60
+ <g filter="url(#xtralab-antigravity-blur-9)">
61
+ <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"/>
62
+ </g>
63
+ <g filter="url(#xtralab-antigravity-blur-10)">
64
+ <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"/>
65
+ </g>
66
+ <g filter="url(#xtralab-antigravity-blur-11)">
67
+ <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"/>
68
+ </g>
69
+ </g>
70
+ <defs>
71
+ <filter color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse" height="17.587" id="xtralab-antigravity-blur-1" width="19.838" x="-3.288" y="-11.917">
72
+ <feGaussianBlur stdDeviation="1.117"/>
73
+ </filter>
74
+ <filter color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse" height="38.565" id="xtralab-antigravity-blur-2" width="38.9" x="4.251" y="-13.493">
75
+ <feGaussianBlur stdDeviation="5.4"/>
76
+ </filter>
77
+ <filter color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse" height="36.517" id="xtralab-antigravity-blur-3" width="40.955" x="-21.889" y="-10.592">
78
+ <feGaussianBlur stdDeviation="4.591"/>
79
+ </filter>
80
+ <filter color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse" height="36.517" id="xtralab-antigravity-blur-4" width="40.955" x="-21.889" y="-10.592">
81
+ <feGaussianBlur stdDeviation="4.591"/>
82
+ </filter>
83
+ <filter color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse" height="36.595" id="xtralab-antigravity-blur-5" width="36.632" x="-19.099" y="-10.278">
84
+ <feGaussianBlur stdDeviation="4.591"/>
85
+ </filter>
86
+ <filter color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse" height="34.087" id="xtralab-antigravity-blur-6" width="33.533" x=".981" y="8.758">
87
+ <feGaussianBlur stdDeviation="4.363"/>
88
+ </filter>
89
+ <filter color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse" height="35.276" id="xtralab-antigravity-blur-7" width="35.978" x="-6.143" y="-21.659">
90
+ <feGaussianBlur stdDeviation="3.954"/>
91
+ </filter>
92
+ <filter color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse" height="46.523" id="xtralab-antigravity-blur-8" width="45.114" x="-11.96" y="-.46">
93
+ <feGaussianBlur stdDeviation="3.531"/>
94
+ </filter>
95
+ <filter color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse" height="24.054" id="xtralab-antigravity-blur-9" width="25.094" x="10.485" y=".58">
96
+ <feGaussianBlur stdDeviation="3.159"/>
97
+ </filter>
98
+ <filter color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse" height="30.007" id="xtralab-antigravity-blur-10" width="33.508" x="5.833" y="-12.467">
99
+ <feGaussianBlur stdDeviation="2.669"/>
100
+ </filter>
101
+ <filter color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse" height="26.151" id="xtralab-antigravity-blur-11" width="22.194" x="-8.355" y="-8.876">
102
+ <feGaussianBlur stdDeviation="3.303"/>
103
+ </filter>
104
+ </defs>
105
+ </svg>`
106
+ });
19
107
  export const claudeIcon = new LabIcon({
20
108
  name: 'xtralab:agent-claude',
21
109
  svgstr: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 1200">
@@ -35,19 +123,6 @@ export const copilotIcon = new LabIcon({
35
123
  <path d="M6.25 9.037a.75.75 0 0 1 .75.75v1.501a.75.75 0 0 1-1.5 0V9.787a.75.75 0 0 1 .75-.75Zm4.25.75v1.501a.75.75 0 0 1-1.5 0V9.787a.75.75 0 0 1 1.5 0Z"/>
36
124
  </svg>`
37
125
  });
38
- export const geminiIcon = new LabIcon({
39
- name: 'xtralab:agent-gemini',
40
- svgstr: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none">
41
- <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)"/>
42
- <defs>
43
- <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)">
44
- <stop offset=".067" stop-color="#9168C0"/>
45
- <stop offset=".343" stop-color="#5684D1"/>
46
- <stop offset=".672" stop-color="#1BA1E3"/>
47
- </radialGradient>
48
- </defs>
49
- </svg>`
50
- });
51
126
  export const gooseIcon = new LabIcon({
52
127
  name: 'xtralab:agent-goose',
53
128
  svgstr: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048">
@@ -95,6 +170,18 @@ export const opencodeIcon = new LabIcon({
95
170
  <path fill-rule="evenodd" clip-rule="evenodd" d="M384 416H128V96H384V416ZM320 160H192V352H320V160Z" fill="white"/>
96
171
  </svg>`
97
172
  });
173
+ export const neovimIcon = new LabIcon({
174
+ name: 'xtralab:editor-neovim',
175
+ svgstr: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#57A143">
176
+ <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"/>
177
+ </svg>`
178
+ });
179
+ export const vimIcon = new LabIcon({
180
+ name: 'xtralab:editor-vim',
181
+ svgstr: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#019733">
182
+ <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"/>
183
+ </svg>`
184
+ });
98
185
  /**
99
186
  * Built-in icons keyed by the agent's id, used to resolve the icon for an
100
187
  * agent declared without an inline `iconSvg` override (i.e. one of xtralab's
@@ -102,12 +189,22 @@ export const opencodeIcon = new LabIcon({
102
189
  * `iconSvg` or fall back to the generic terminal icon.
103
190
  */
104
191
  export const BUILTIN_AGENT_ICONS = {
192
+ antigravity: antigravityIcon,
105
193
  claude: claudeIcon,
106
194
  codex: codexIcon,
107
195
  copilot: copilotIcon,
108
- gemini: geminiIcon,
109
196
  goose: gooseIcon,
110
197
  kiro: kiroIcon,
111
198
  'mistral-vibe': mistralVibeIcon,
112
199
  opencode: opencodeIcon
113
200
  };
201
+ /**
202
+ * Built-in icons keyed by the editor's id, used to resolve the icon for a
203
+ * built-in editor (Neovim, Vim) declared without an inline `iconSvg`. Custom
204
+ * editors added via settings can supply their own `iconSvg` or fall back to
205
+ * the generic text-editor icon.
206
+ */
207
+ export const BUILTIN_EDITOR_ICONS = {
208
+ nvim: neovimIcon,
209
+ vim: vimIcon
210
+ };
@@ -1,23 +1,3 @@
1
1
  import { JupyterFrontEndPlugin } from '@jupyterlab/application';
2
- /**
3
- * The xtralab launcher plugin. Replaces the stock JupyterLab launcher
4
- * (which is disabled via `package.json`'s `jupyterlab.disabledExtensions`)
5
- * with an agent-focused dashboard: an optional initial prompt, a row of
6
- * agent buttons (Claude, Codex, Gemini, …), and a collapsible list of
7
- * changed files (clickable into the diff viewer) below them. Clicking an
8
- * agent opens a fresh terminal and pipes the agent's command into it; if
9
- * the prompt textarea is non-empty, the prompt is shell-quoted and
10
- * spliced into the launch line per the agent's `promptArgs` recipe.
11
- *
12
- * The agent list is the merge of xtralab's defaults with the user's
13
- * `xtralab:launcher` settings, then filtered by a server-side `which`
14
- * check so users only see agents that are actually installed. Agents with
15
- * `requireAvailable: false` (e.g. shell aliases) skip the filter.
16
- *
17
- * The plugin deliberately does NOT provide the `ILauncher` token: other
18
- * extensions register notebook/console/terminal cards on it as a side
19
- * effect, which would defeat the point of the agent-only launcher. If an
20
- * extension needs to surface itself, we'll add it here explicitly.
21
- */
22
- declare const plugin: JupyterFrontEndPlugin<void>;
23
- export default plugin;
2
+ declare const plugins: JupyterFrontEndPlugin<unknown>[];
3
+ export default plugins;
@@ -4,16 +4,20 @@ import { ISettingRegistry } from '@jupyterlab/settingregistry';
4
4
  import { ITranslator, nullTranslator } from '@jupyterlab/translation';
5
5
  import { launcherIcon } from '@jupyterlab/ui-components';
6
6
  import { find } from '@lumino/algorithm';
7
+ import { IAgentSessions } from '../agentSessions';
7
8
  import { mergeAgents } from './agents';
8
9
  import { fetchAvailableCommands } from './availability';
9
10
  import { CREATE_LAUNCHER_COMMAND, registerAgentCommands } from './commands';
10
11
  import { LauncherDashboard } from './dashboard';
12
+ import { editorRegistryPlugin, IEditorRegistry } from './editorRegistry';
13
+ import { AgentRegistry } from './registry';
14
+ import { IAgentRegistry } from './tokens';
11
15
  const PLUGIN_ID = 'xtralab:launcher';
12
16
  /**
13
17
  * The xtralab launcher plugin. Replaces the stock JupyterLab launcher
14
18
  * (which is disabled via `package.json`'s `jupyterlab.disabledExtensions`)
15
19
  * with an agent-focused dashboard: an optional initial prompt, a row of
16
- * agent buttons (Claude, Codex, Gemini, …), and a collapsible list of
20
+ * agent buttons (Claude, Codex, Antigravity, …), and a collapsible list of
17
21
  * changed files (clickable into the diff viewer) below them. Clicking an
18
22
  * agent opens a fresh terminal and pipes the agent's command into it; if
19
23
  * the prompt textarea is non-empty, the prompt is shell-quoted and
@@ -33,23 +37,40 @@ const plugin = {
33
37
  id: PLUGIN_ID,
34
38
  description: 'An agent-focused launcher that replaces the default JupyterLab launcher.',
35
39
  autoStart: true,
36
- optional: [ILabShell, ICommandPalette, ISettingRegistry, ITranslator],
37
- activate: async (app, labShell, palette, settingRegistry, translator) => {
40
+ provides: IAgentRegistry,
41
+ optional: [
42
+ ILabShell,
43
+ ICommandPalette,
44
+ ISettingRegistry,
45
+ ITranslator,
46
+ IAgentSessions,
47
+ IEditorRegistry
48
+ ],
49
+ activate: async (app, labShell, palette, settingRegistry, translator, agentSessions, editorRegistry) => {
38
50
  const { commands, shell } = app;
39
51
  const trans = (translator !== null && translator !== void 0 ? translator : nullTranslator).load('jupyterlab');
40
- // The currently active agent list. Updated whenever settings change so
41
- // every freshly-created launcher widget renders the latest list.
42
- let currentAgents = [];
52
+ // The shared agent registry this plugin provides on `IAgentRegistry`.
53
+ // It holds the active agent list (updated whenever settings change), so
54
+ // every freshly-created launcher widget — and any other plugin that
55
+ // consumes the token, e.g. the terminals panel — renders the latest list.
56
+ const registry = new AgentRegistry();
43
57
  // Track command registrations so a settings change can wipe them
44
58
  // before re-registering — without this the command palette would
45
59
  // accumulate stale entries when the agent list shrinks.
46
60
  let registered = null;
47
61
  const applyAgents = async (overrides) => {
48
62
  const agents = mergeAgents(overrides);
49
- const filtered = await filterByAvailability(agents);
63
+ // Probe the server's `$PATH` so we only surface agents that are actually
64
+ // installed. Agents with `requireAvailable: false` opt out (their command
65
+ // may be a shell alias that `which` can't see) and are kept regardless.
66
+ const probe = Array.from(new Set(agents
67
+ .filter(agent => agent.requireAvailable)
68
+ .map(agent => agent.command)));
69
+ const available = await fetchAvailableCommands(probe);
70
+ const filtered = filterAgents(agents, available);
50
71
  registered === null || registered === void 0 ? void 0 : registered.dispose();
51
- registered = registerAgentCommands(app, filtered);
52
- currentAgents = filtered;
72
+ registered = registerAgentCommands(app, filtered, agentSessions);
73
+ registry.setAgents(filtered);
53
74
  };
54
75
  const readOverrides = (settings) => {
55
76
  const raw = settings.composite.agents;
@@ -81,6 +102,7 @@ const plugin = {
81
102
  commands.addCommand(CREATE_LAUNCHER_COMMAND, {
82
103
  label: trans.__('New Launcher'),
83
104
  execute: (args) => {
105
+ var _a;
84
106
  const id = Private.nextId();
85
107
  const onAgentLaunch = (item) => {
86
108
  // When an agent command returns a Widget that ends up in the main
@@ -95,7 +117,9 @@ const plugin = {
95
117
  };
96
118
  const launcher = new LauncherDashboard({
97
119
  commands,
98
- agents: currentAgents,
120
+ agents: registry.agents,
121
+ editor: (_a = editorRegistry === null || editorRegistry === void 0 ? void 0 : editorRegistry.current) !== null && _a !== void 0 ? _a : null,
122
+ agentSessions,
99
123
  onAgentLaunch,
100
124
  // Empty repoPath/cwd matches the JupyterLab convention used by
101
125
  // the git panel and the stock launcher: let the server resolve
@@ -153,25 +177,20 @@ const plugin = {
153
177
  return commands.execute(CREATE_LAUNCHER_COMMAND, { ref });
154
178
  });
155
179
  }
180
+ return registry;
156
181
  }
157
182
  };
158
183
  /**
159
- * Drop agents whose command isn't on `$PATH`, except for entries that
160
- * explicitly opt out of the check via `requireAvailable: false` (typical
161
- * use case: shell aliases the user wants surfaced regardless).
184
+ * Drop agents whose command isn't on `$PATH`, except entries that opt out via
185
+ * `requireAvailable: false` (e.g. shell aliases the user wants surfaced
186
+ * regardless). `available` is the resolved set from
187
+ * {@link fetchAvailableCommands}.
162
188
  *
163
- * If the availability endpoint can't be reached we fail open and return
164
- * the input unchanged — better to show an unreachable agent than to hide
165
- * the entire launcher because the server extension didn't load.
189
+ * When `available` is `null` (the endpoint couldn't be reached) we fail open
190
+ * and return the input unchanged — better to show an unreachable agent than
191
+ * to hide the entire launcher because the server extension didn't load.
166
192
  */
167
- async function filterByAvailability(agents) {
168
- const commands = agents
169
- .filter(agent => agent.requireAvailable)
170
- .map(agent => agent.command);
171
- if (commands.length === 0) {
172
- return agents;
173
- }
174
- const available = await fetchAvailableCommands(commands);
193
+ function filterAgents(agents, available) {
175
194
  if (!available) {
176
195
  return agents;
177
196
  }
@@ -188,4 +207,8 @@ var Private;
188
207
  }
189
208
  Private.nextId = nextId;
190
209
  })(Private || (Private = {}));
191
- export default plugin;
210
+ const plugins = [
211
+ plugin,
212
+ editorRegistryPlugin
213
+ ];
214
+ export default plugins;
@@ -0,0 +1,21 @@
1
+ import { ISignal } from '@lumino/signaling';
2
+ import type { IAgent } from './agents';
3
+ import type { IAgentRegistry } from './tokens';
4
+ /**
5
+ * The concrete {@link IAgentRegistry} the launcher plugin provides on the
6
+ * `IAgentRegistry` token. Holds the active agent list and re-emits `changed`
7
+ * whenever the launcher recomputes it (on a settings change). The launcher
8
+ * is the only writer, so the write side ({@link setAgents}) is kept off the
9
+ * shared token.
10
+ */
11
+ export declare class AgentRegistry implements IAgentRegistry {
12
+ get agents(): IAgent[];
13
+ get changed(): ISignal<IAgentRegistry, void>;
14
+ /**
15
+ * Replace the agent list and notify observers. Called by the launcher
16
+ * after merging the user's settings and filtering by availability.
17
+ */
18
+ setAgents(agents: IAgent[]): void;
19
+ private _agents;
20
+ private _changed;
21
+ }
@@ -0,0 +1,28 @@
1
+ import { Signal } from '@lumino/signaling';
2
+ /**
3
+ * The concrete {@link IAgentRegistry} the launcher plugin provides on the
4
+ * `IAgentRegistry` token. Holds the active agent list and re-emits `changed`
5
+ * whenever the launcher recomputes it (on a settings change). The launcher
6
+ * is the only writer, so the write side ({@link setAgents}) is kept off the
7
+ * shared token.
8
+ */
9
+ export class AgentRegistry {
10
+ constructor() {
11
+ this._agents = [];
12
+ this._changed = new Signal(this);
13
+ }
14
+ get agents() {
15
+ return this._agents;
16
+ }
17
+ get changed() {
18
+ return this._changed;
19
+ }
20
+ /**
21
+ * Replace the agent list and notify observers. Called by the launcher
22
+ * after merging the user's settings and filtering by availability.
23
+ */
24
+ setAgents(agents) {
25
+ this._agents = agents;
26
+ this._changed.emit();
27
+ }
28
+ }
@@ -0,0 +1,41 @@
1
+ import { Token } from '@lumino/coreutils';
2
+ import type { ISignal } from '@lumino/signaling';
3
+ import type { IAgent } from './agents';
4
+ /**
5
+ * A read-only, observable view of the launcher's available agents.
6
+ *
7
+ * The launcher plugin owns the agent list — xtralab's defaults merged with
8
+ * the user's `xtralab:launcher` settings, then filtered by a server-side
9
+ * `which` check — and registers an `xtralab:start-agent:<id>` command for
10
+ * each entry. This token shares that list, and (via {@link agentCommandId})
11
+ * the command id that launches each agent, so other plugins can surface the
12
+ * same agents without re-deriving the list or duplicating the icons. The
13
+ * terminals panel uses it to build its "new terminal" dropdown.
14
+ */
15
+ export interface IAgentRegistry {
16
+ /**
17
+ * The current agents, already filtered by availability and sorted by
18
+ * rank — the same array the launcher renders.
19
+ */
20
+ readonly agents: IAgent[];
21
+ /**
22
+ * Emitted whenever {@link agents} changes (e.g. the user edits the
23
+ * launcher settings). Consumers that cache the list should re-read it
24
+ * here; consumers that read it on demand can ignore the signal.
25
+ */
26
+ readonly changed: ISignal<IAgentRegistry, void>;
27
+ }
28
+ /**
29
+ * DI token for {@link IAgentRegistry}. Provided by `xtralab:launcher` and
30
+ * consumed — optionally, so the panel still works when the launcher is
31
+ * disabled — by `xtralab:terminals`.
32
+ */
33
+ export declare const IAgentRegistry: Token<IAgentRegistry>;
34
+ /**
35
+ * The JupyterLab command id that launches a given agent in a new terminal.
36
+ * Defined here — in the shared contract module rather than the launcher's
37
+ * command-registration internals — so the launcher (which registers the
38
+ * commands) and any consumer (which references them, e.g. in a menu) agree
39
+ * on the id.
40
+ */
41
+ export declare function agentCommandId(agentId: string): string;
@@ -0,0 +1,17 @@
1
+ import { Token } from '@lumino/coreutils';
2
+ /**
3
+ * DI token for {@link IAgentRegistry}. Provided by `xtralab:launcher` and
4
+ * consumed — optionally, so the panel still works when the launcher is
5
+ * disabled — by `xtralab:terminals`.
6
+ */
7
+ export const IAgentRegistry = new Token('xtralab:IAgentRegistry', 'A read-only, observable view of the launcher agents, shared so other plugins can offer the same agent list and icons.');
8
+ /**
9
+ * The JupyterLab command id that launches a given agent in a new terminal.
10
+ * Defined here — in the shared contract module rather than the launcher's
11
+ * command-registration internals — so the launcher (which registers the
12
+ * commands) and any consumer (which references them, e.g. in a menu) agree
13
+ * on the id.
14
+ */
15
+ export function agentCommandId(agentId) {
16
+ return `xtralab:start-agent:${agentId}`;
17
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Ask the xtralab server which of `commands` is currently running inside each
3
+ * open terminal session. Resolves to a map from session name to the matched
4
+ * agent command, or `null` for a session sitting at its prompt.
5
+ *
6
+ * On any failure (endpoint missing on an older server, network error,
7
+ * malformed response) the whole result is `null` — callers should treat that
8
+ * as "detection unavailable" and fall back to the optimistic launch tags
9
+ * rather than clearing any badges.
10
+ */
11
+ export declare function fetchRunningAgents(commands: string[]): Promise<Record<string, string | null> | null>;