xtralab 0.15.0 → 0.15.2

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 (237) hide show
  1. package/README.md +65 -30
  2. package/lib/about/icons.d.ts +0 -5
  3. package/lib/about/icons.js +0 -5
  4. package/lib/about/index.d.ts +4 -8
  5. package/lib/about/index.js +6 -17
  6. package/lib/agentSessions.d.ts +9 -29
  7. package/lib/agentSessions.js +2 -7
  8. package/lib/askAgent/editorSelection.d.ts +20 -26
  9. package/lib/askAgent/editorSelection.js +11 -26
  10. package/lib/askAgent/icons.d.ts +2 -3
  11. package/lib/askAgent/icons.js +2 -3
  12. package/lib/askAgent/index.d.ts +4 -26
  13. package/lib/askAgent/index.js +54 -158
  14. package/lib/askAgent/popup.d.ts +12 -16
  15. package/lib/askAgent/popup.js +18 -37
  16. package/lib/askAgent/prompt.d.ts +3 -5
  17. package/lib/askAgent/prompt.js +6 -19
  18. package/lib/askAgent/queue.d.ts +11 -21
  19. package/lib/askAgent/queue.js +9 -19
  20. package/lib/askAgent/queuePanel.d.ts +13 -18
  21. package/lib/askAgent/queuePanel.js +15 -37
  22. package/lib/askAgent/targetPicker.d.ts +2 -9
  23. package/lib/askAgent/targetPicker.js +6 -11
  24. package/lib/askAgent/tokens.d.ts +32 -46
  25. package/lib/askAgent/tokens.js +4 -6
  26. package/lib/commandBar/index.d.ts +4 -13
  27. package/lib/commandBar/index.js +21 -39
  28. package/lib/customPanel/index.js +15 -0
  29. package/lib/editorBreadcrumbs/index.d.ts +3 -6
  30. package/lib/editorBreadcrumbs/index.js +10 -6
  31. package/lib/editorBreadcrumbs/widget.d.ts +17 -4
  32. package/lib/editorBreadcrumbs/widget.js +11 -17
  33. package/lib/editorIndent/index.d.ts +4 -12
  34. package/lib/editorIndent/index.js +8 -23
  35. package/lib/fileBrowser/commands.d.ts +29 -19
  36. package/lib/fileBrowser/commands.js +83 -97
  37. package/lib/fileBrowser/contents.d.ts +3 -0
  38. package/lib/fileBrowser/dragAndDrop.d.ts +17 -4
  39. package/lib/fileBrowser/dragAndDrop.js +2 -7
  40. package/lib/fileBrowser/fileBrowser.d.ts +19 -9
  41. package/lib/fileBrowser/fileBrowser.js +70 -222
  42. package/lib/fileBrowser/gitStatus.d.ts +16 -10
  43. package/lib/fileBrowser/gitStatus.js +31 -30
  44. package/lib/fileBrowser/gitignore.d.ts +4 -17
  45. package/lib/fileBrowser/gitignore.js +4 -25
  46. package/lib/fileBrowser/icons.d.ts +7 -17
  47. package/lib/fileBrowser/icons.js +25 -80
  48. package/lib/fileBrowser/index.js +2 -4
  49. package/lib/fileBrowser/widget.d.ts +141 -48
  50. package/lib/fileBrowser/widget.js +102 -4
  51. package/lib/fileTypeIcons/index.js +0 -10
  52. package/lib/git/api.d.ts +5 -13
  53. package/lib/git/api.js +10 -36
  54. package/lib/git/askRequest.d.ts +4 -8
  55. package/lib/git/askRequest.js +6 -20
  56. package/lib/git/commands.d.ts +45 -5
  57. package/lib/git/commands.js +20 -13
  58. package/lib/git/diffModel.js +4 -0
  59. package/lib/git/diffPreferences.d.ts +90 -0
  60. package/lib/git/diffPreferences.js +107 -0
  61. package/lib/git/diffProvider.js +10 -2
  62. package/lib/git/diffSurface.d.ts +64 -63
  63. package/lib/git/diffSurface.js +72 -213
  64. package/lib/git/diffTheme.d.ts +7 -0
  65. package/lib/git/diffTheme.js +23 -0
  66. package/lib/git/diffWidget.d.ts +70 -14
  67. package/lib/git/diffWidget.js +100 -94
  68. package/lib/git/imageDiff.d.ts +14 -6
  69. package/lib/git/imageDiff.js +19 -75
  70. package/lib/git/index.d.ts +0 -3
  71. package/lib/git/index.js +44 -10
  72. package/lib/git/notebookDiff.d.ts +110 -33
  73. package/lib/git/notebookDiff.js +55 -197
  74. package/lib/git/tokens.d.ts +73 -31
  75. package/lib/git/tokens.js +2 -3
  76. package/lib/highlight/index.d.ts +4 -10
  77. package/lib/highlight/index.js +10 -41
  78. package/lib/index.d.ts +0 -9
  79. package/lib/index.js +0 -9
  80. package/lib/launcher/agents.d.ts +55 -51
  81. package/lib/launcher/agents.js +15 -57
  82. package/lib/launcher/availability.d.ts +3 -9
  83. package/lib/launcher/availability.js +3 -9
  84. package/lib/launcher/commands.d.ts +7 -32
  85. package/lib/launcher/commands.js +14 -45
  86. package/lib/launcher/dashboard.d.ts +19 -24
  87. package/lib/launcher/dashboard.js +21 -67
  88. package/lib/launcher/editorRegistry.d.ts +10 -22
  89. package/lib/launcher/editorRegistry.js +14 -18
  90. package/lib/launcher/editors.d.ts +39 -45
  91. package/lib/launcher/editors.js +8 -34
  92. package/lib/launcher/icons.d.ts +0 -12
  93. package/lib/launcher/icons.js +5 -39
  94. package/lib/launcher/index.js +12 -55
  95. package/lib/launcher/invocation.d.ts +4 -10
  96. package/lib/launcher/invocation.js +7 -22
  97. package/lib/launcher/registry.d.ts +9 -7
  98. package/lib/launcher/registry.js +9 -7
  99. package/lib/launcher/tokens.d.ts +10 -22
  100. package/lib/launcher/tokens.js +5 -8
  101. package/lib/menuBar/index.d.ts +3 -10
  102. package/lib/menuBar/index.js +11 -28
  103. package/lib/menus/index.d.ts +3 -9
  104. package/lib/menus/index.js +14 -30
  105. package/lib/omnibox/files.d.ts +2 -4
  106. package/lib/omnibox/files.js +5 -11
  107. package/lib/omnibox/index.d.ts +4 -14
  108. package/lib/omnibox/index.js +9 -27
  109. package/lib/omnibox/model.d.ts +38 -10
  110. package/lib/omnibox/model.js +15 -33
  111. package/lib/omnibox/recents.d.ts +15 -17
  112. package/lib/omnibox/recents.js +12 -17
  113. package/lib/omnibox/tokens.d.ts +5 -10
  114. package/lib/omnibox/tokens.js +2 -6
  115. package/lib/omnibox/widget.d.ts +11 -7
  116. package/lib/omnibox/widget.js +8 -16
  117. package/lib/searchReplace/index.d.ts +4 -20
  118. package/lib/searchReplace/index.js +7 -27
  119. package/lib/showOutput/index.d.ts +3 -12
  120. package/lib/showOutput/index.js +6 -22
  121. package/lib/sidebar/index.d.ts +4 -7
  122. package/lib/sidebar/index.js +5 -12
  123. package/lib/terminalNotifications/index.d.ts +4 -10
  124. package/lib/terminalNotifications/index.js +6 -20
  125. package/lib/terminals/agentTerminals.d.ts +12 -5
  126. package/lib/terminals/agentTerminals.js +25 -44
  127. package/lib/terminals/detection.d.ts +4 -8
  128. package/lib/terminals/detection.js +4 -8
  129. package/lib/terminals/index.d.ts +0 -35
  130. package/lib/terminals/index.js +12 -101
  131. package/lib/terminals/model.d.ts +64 -139
  132. package/lib/terminals/model.js +80 -220
  133. package/lib/terminals/tokens.d.ts +11 -22
  134. package/lib/terminals/tokens.js +2 -2
  135. package/lib/terminals/widget.d.ts +43 -13
  136. package/lib/terminals/widget.js +33 -18
  137. package/lib/topBar/icons.d.ts +3 -8
  138. package/lib/topBar/icons.js +3 -8
  139. package/lib/topBar/index.d.ts +4 -11
  140. package/lib/topBar/index.js +10 -38
  141. package/lib/walkthrough/index.d.ts +4 -9
  142. package/lib/walkthrough/index.js +6 -20
  143. package/lib/walkthrough/panel.d.ts +40 -9
  144. package/lib/walkthrough/panel.js +4 -10
  145. package/package.json +5 -3
  146. package/schema/git-diff-preferences.json +42 -0
  147. package/src/about/icons.ts +0 -5
  148. package/src/about/index.tsx +6 -17
  149. package/src/agentSessions.ts +9 -29
  150. package/src/askAgent/editorSelection.ts +22 -29
  151. package/src/askAgent/icons.ts +2 -3
  152. package/src/askAgent/index.ts +54 -158
  153. package/src/askAgent/popup.tsx +23 -45
  154. package/src/askAgent/prompt.ts +6 -19
  155. package/src/askAgent/queue.ts +13 -24
  156. package/src/askAgent/queuePanel.tsx +21 -46
  157. package/src/askAgent/targetPicker.tsx +5 -15
  158. package/src/askAgent/tokens.ts +32 -46
  159. package/src/commandBar/index.ts +24 -39
  160. package/src/customPanel/index.ts +15 -0
  161. package/src/editorBreadcrumbs/index.ts +10 -6
  162. package/src/editorBreadcrumbs/widget.ts +23 -17
  163. package/src/editorIndent/index.ts +17 -23
  164. package/src/fileBrowser/commands.ts +119 -96
  165. package/src/fileBrowser/contents.ts +3 -0
  166. package/src/fileBrowser/dragAndDrop.ts +17 -7
  167. package/src/fileBrowser/fileBrowser.tsx +86 -230
  168. package/src/fileBrowser/gitStatus.ts +36 -33
  169. package/src/fileBrowser/gitignore.ts +4 -25
  170. package/src/fileBrowser/icons.ts +25 -80
  171. package/src/fileBrowser/index.ts +2 -4
  172. package/src/fileBrowser/widget.tsx +171 -50
  173. package/src/fileTypeIcons/index.ts +0 -10
  174. package/src/git/api.ts +10 -36
  175. package/src/git/askRequest.ts +6 -20
  176. package/src/git/commands.ts +83 -11
  177. package/src/git/diffModel.ts +25 -0
  178. package/src/git/diffPreferences.ts +192 -0
  179. package/src/git/diffProvider.tsx +11 -2
  180. package/src/git/diffSurface.tsx +165 -286
  181. package/src/git/diffTheme.ts +24 -0
  182. package/src/git/diffWidget.tsx +194 -134
  183. package/src/git/imageDiff.tsx +56 -84
  184. package/src/git/index.ts +54 -10
  185. package/src/git/notebookDiff.tsx +214 -240
  186. package/src/git/tokens.ts +73 -31
  187. package/src/highlight/index.ts +10 -41
  188. package/src/index.ts +0 -9
  189. package/src/launcher/agents.ts +64 -95
  190. package/src/launcher/availability.ts +3 -9
  191. package/src/launcher/commands.ts +14 -51
  192. package/src/launcher/dashboard.tsx +49 -88
  193. package/src/launcher/editorRegistry.ts +21 -29
  194. package/src/launcher/editors.ts +39 -58
  195. package/src/launcher/icons.ts +5 -39
  196. package/src/launcher/index.ts +12 -55
  197. package/src/launcher/invocation.ts +7 -22
  198. package/src/launcher/registry.ts +9 -7
  199. package/src/launcher/tokens.ts +10 -22
  200. package/src/menuBar/index.ts +11 -28
  201. package/src/menus/index.ts +14 -30
  202. package/src/omnibox/files.ts +10 -14
  203. package/src/omnibox/index.ts +9 -27
  204. package/src/omnibox/model.ts +50 -38
  205. package/src/omnibox/recents.ts +17 -22
  206. package/src/omnibox/tokens.ts +5 -10
  207. package/src/omnibox/widget.tsx +14 -19
  208. package/src/searchReplace/index.ts +7 -27
  209. package/src/showOutput/index.ts +6 -22
  210. package/src/sidebar/index.ts +20 -12
  211. package/src/terminalNotifications/index.ts +42 -25
  212. package/src/terminals/agentTerminals.ts +25 -49
  213. package/src/terminals/detection.ts +4 -8
  214. package/src/terminals/index.ts +12 -101
  215. package/src/terminals/model.ts +130 -246
  216. package/src/terminals/tokens.ts +11 -22
  217. package/src/terminals/widget.tsx +48 -31
  218. package/src/topBar/icons.ts +3 -8
  219. package/src/topBar/index.ts +21 -44
  220. package/src/walkthrough/index.ts +6 -20
  221. package/src/walkthrough/panel.ts +40 -10
  222. package/style/about.css +2 -7
  223. package/style/askAgent.css +8 -40
  224. package/style/base.css +9 -45
  225. package/style/chrome.css +108 -308
  226. package/style/commandBar.css +4 -20
  227. package/style/customPanel.css +1 -4
  228. package/style/git.css +26 -186
  229. package/style/highlight.css +1 -3
  230. package/style/launcher.css +6 -70
  231. package/style/omnibox.css +2 -14
  232. package/style/showOutput.css +0 -6
  233. package/style/sidebar.css +29 -9
  234. package/style/tabs.css +5 -13
  235. package/style/terminals.css +6 -53
  236. package/style/topBar.css +2 -22
  237. package/style/walkthrough.css +0 -8
@@ -3,11 +3,10 @@ import { LabIcon, textEditorIcon } from '@jupyterlab/ui-components';
3
3
  import { BUILTIN_EDITOR_ICONS } from './icons';
4
4
 
5
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.
6
+ * A terminal text editor on the launcher's "Open" section, launched by typing
7
+ * its command into a fresh terminal. The launcher shows one tile
8
+ * ({@link resolveEditor}); the full list badges running editors in the
9
+ * terminals panel.
11
10
  */
12
11
  export interface IEditor {
13
12
  /**
@@ -31,57 +30,59 @@ export interface IEditor {
31
30
  */
32
31
  icon: LabIcon;
33
32
  /**
34
- * Preference order for the single launcher tile: the first candidate, by
35
- * ascending rank, that qualifies wins (Neovim before Vim by default).
33
+ * Preference order: the first qualifying candidate by rank wins the tile.
36
34
  */
37
35
  rank: number;
38
36
  /**
39
- * When false, the launcher skips the `which`-based availability check for
40
- * this entry — useful for a shell alias or function that isn't on PATH but
41
- * still resolves when typed in a real terminal. Defaults to true.
37
+ * When false, skip the `which`-based availability check — for aliases or
38
+ * shell functions not on PATH. Defaults to true.
42
39
  */
43
40
  requireAvailable: boolean;
44
41
  }
45
42
 
46
43
  /**
47
- * The settings-side shape: every field except `id` is optional, so a user can
48
- * override a single field on a built-in editor (e.g. swap `vim`'s command for
49
- * a wrapper) without restating the whole entry. New ids define brand-new
50
- * editors. Mirrors `IAgentSettings` minus `promptArgs`.
44
+ * The settings-side shape: every field except `id` is optional so a user can
45
+ * override a single field on a built-in editor; new ids define new editors.
46
+ * Mirrors `IAgentSettings` minus `promptArgs`.
51
47
  */
52
48
  export interface IEditorSettings {
49
+ /**
50
+ * Id of the editor to override; a new id defines a new editor.
51
+ */
53
52
  id: string;
53
+ /**
54
+ * See {@link IEditor.label}.
55
+ */
54
56
  label?: string;
57
+ /**
58
+ * See {@link IEditor.caption}.
59
+ */
55
60
  caption?: string;
61
+ /**
62
+ * See {@link IEditor.command}.
63
+ */
56
64
  command?: string;
57
65
  /**
58
- * Inline SVG for a custom editor's icon. Required for new ids whose icon
59
- * isn't shipped with xtralab; ignored for built-in ids unless explicitly
60
- * set (in which case it overrides the built-in).
66
+ * Inline SVG icon. Required for new ids; overrides the built-in when set on
67
+ * a built-in id.
61
68
  */
62
69
  iconSvg?: string;
70
+ /**
71
+ * See {@link IEditor.rank}.
72
+ */
63
73
  rank?: number;
64
74
  /**
65
- * When false, the editor is hidden from the launcher's Open section (and no
66
- * longer recognised by the terminals panel). Disable both built-ins to drop
67
- * the editor tile entirely. Defaults to true.
75
+ * When false, hides the editor. Disable both built-ins to drop the editor
76
+ * tile entirely.
68
77
  */
69
78
  enabled?: boolean;
70
79
  /**
71
- * See {@link IEditor.requireAvailable}. Defaults to true for a built-in
72
- * editor that still uses its shipped command, and to false once you override
73
- * the `command` (a user-chosen command — often a shell alias — is trusted and
74
- * always shown). Set it explicitly to force the check on or off.
80
+ * See {@link IEditor.requireAvailable}. Defaults to true, but flips to false
81
+ * once `command` is overridden (a user-chosen alias is trusted).
75
82
  */
76
83
  requireAvailable?: boolean;
77
84
  }
78
85
 
79
- /**
80
- * Built-in editor candidates in preference order. The launcher shows the first
81
- * one whose command resolves on the server's `$PATH`, so Neovim wins over Vim
82
- * when both are installed. Users override, disable, or extend this list
83
- * through the `editors` setting; see {@link mergeEditors}.
84
- */
85
86
  const DEFAULTS: IEditor[] = [
86
87
  {
87
88
  id: 'nvim',
@@ -104,11 +105,8 @@ const DEFAULTS: IEditor[] = [
104
105
  ];
105
106
 
106
107
  /**
107
- * The built-in editors projected into the JSON settings shape
108
- * ({@link IEditorSettings}), dropping the runtime-only {@link LabIcon}. Mirrors
109
- * {@link defaultAgentSettings}: {@link registerLauncherSchemaDefaults} injects
110
- * this as the `editors` setting's schema default so the Settings Editor shows
111
- * the shipped list.
108
+ * The built-in editors projected into the settings shape (no runtime LabIcon),
109
+ * injected as the `editors` schema default so the Settings Editor shows them.
112
110
  */
113
111
  export function defaultEditorSettings(): IEditorSettings[] {
114
112
  return DEFAULTS.map(editor => ({
@@ -132,13 +130,9 @@ function resolveEditorIcon(id: string, iconSvg: string | undefined): LabIcon {
132
130
  }
133
131
 
134
132
  /**
135
- * Merge xtralab's built-in editors with the user's `editors` settings. Built-in
136
- * entries keep their fields unless explicitly overridden; user-only entries are
137
- * appended. `enabled: false` filters an entry out of the result entirely (so
138
- * callers don't need to re-check the flag). The result is sorted by rank, which
139
- * is also the launcher's tile-preference order. Mirrors `mergeAgents`, including
140
- * turning `requireAvailable` off when the user overrides a built-in's `command`
141
- * so an aliased editor still shows.
133
+ * Merge the built-in editors with the user's `editors` settings; the result is
134
+ * sorted by rank, the tile-preference order. Mirrors `mergeAgents`, including
135
+ * turning `requireAvailable` off when a built-in's `command` is overridden.
142
136
  */
143
137
  export function mergeEditors(overrides: IEditorSettings[]): IEditor[] {
144
138
  const overrideById = new Map(overrides.map(entry => [entry.id, entry]));
@@ -164,10 +158,6 @@ export function mergeEditors(overrides: IEditorSettings[]): IEditor[] {
164
158
  ? resolveEditorIcon(base.id, override.iconSvg)
165
159
  : base.icon,
166
160
  rank: override.rank ?? base.rank,
167
- // See `mergeAgents`: once the user points a built-in editor at their own
168
- // command (e.g. a shell alias `which` can't resolve), stop requiring
169
- // availability so the tile still shows. The check stays on only while the
170
- // command is xtralab's default.
171
161
  requireAvailable:
172
162
  command === base.command
173
163
  ? (override.requireAvailable ?? base.requireAvailable)
@@ -175,9 +165,6 @@ export function mergeEditors(overrides: IEditorSettings[]): IEditor[] {
175
165
  });
176
166
  }
177
167
 
178
- // What remains in `overrideById` are ids that don't match a built-in — treat
179
- // them as new editors the user is adding. Skip disabled ones; the settings
180
- // schema validates the shape, so missing fields just fall back here.
181
168
  let nextRank =
182
169
  merged.reduce((max, editor) => Math.max(max, editor.rank), -1) + 1;
183
170
  for (const entry of overrideById.values()) {
@@ -202,15 +189,9 @@ export function mergeEditors(overrides: IEditorSettings[]): IEditor[] {
202
189
  }
203
190
 
204
191
  /**
205
- * Pick the single editor tile to show, given the merged editor list and the
206
- * set of commands known to be on `$PATH` (from the launcher's availability
207
- * probe). Returns the first candidate, by rank, that is either available or
208
- * opts out of the check (`requireAvailable: false`) — Neovim before Vim by
209
- * default — or `null` when none qualifies.
210
- *
211
- * When `available` is `null` (the endpoint couldn't be reached), a
212
- * `requireAvailable` editor is not shown; an entry with
213
- * `requireAvailable: false` is shown regardless.
192
+ * Pick the single tile: the first editor, by rank, that is available or opts
193
+ * out of the check, else `null`. When `available` is `null` (probe failed),
194
+ * only `requireAvailable: false` entries qualify.
214
195
  */
215
196
  export function resolveEditor(
216
197
  editors: IEditor[],
@@ -1,33 +1,11 @@
1
1
  import { LabIcon } from '@jupyterlab/ui-components';
2
2
 
3
3
  /**
4
- * Brand icons for the agent launcher cards. Most marks are taken verbatim
5
- * from `jupyter-ai-contrib/jupyter-ai-acp-client`
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. Pi's mark is the official `logo-auto.svg`
10
- * from https://pi.dev (MIT, `earendil-works/pi`). Small adjustments are applied
11
- * to play nicely with JupyterLab themes:
12
- * - Monochrome marks (Codex, Copilot, Goose, Pi) use `currentColor` so they
13
- * inherit the surrounding launcher card's text color rather than
14
- * hard-coding black, which disappears on the dark theme. For Pi this
15
- * replaces the upstream `prefers-color-scheme` style block, which follows
16
- * the OS scheme rather than the active JupyterLab theme.
17
- * - The Goose mark upstream is rendered on a white rounded rect; we drop
18
- * the rect so the silhouette can sit on the launcher card directly.
19
- * - SVG ids that would collide if two copies of the same artwork ended up
20
- * on the page (gradient/mask/filter defs) are namespaced under
21
- * `xtralab-…`.
22
- *
23
- * Brand-colored icons (Antigravity, Claude, Kiro, Mistral Vibe, OpenCode) keep
24
- * their upstream colors; the dark/light contrast of the launcher card behind
25
- * them is the same as on `jupyter-ai-acp-client`.
26
- *
27
- * The Vim and Neovim marks belong to the launcher's "Open" editor tile rather
28
- * than the agent grid. They are the brand silhouettes from Simple Icons
29
- * (https://simpleicons.org, CC0), each filled with its brand green so the mark
30
- * reads on both the light and dark themes.
4
+ * Brand icons for the launcher cards: `jupyter-ai-contrib/jupyter-ai-acp-client`
5
+ * (BSD-3-Clause), except Antigravity (`@lobehub/icons-static-svg`, MIT),
6
+ * Pi (pi.dev, MIT) and Vim/Neovim (Simple Icons, CC0). Monochrome marks use
7
+ * `currentColor` to follow the active theme; svg ids are namespaced under
8
+ * `xtralab-…` so two copies of one mark on a page don't collide.
31
9
  */
32
10
 
33
11
  const antigravityIcon = new LabIcon({
@@ -204,12 +182,6 @@ const vimIcon = new LabIcon({
204
182
  </svg>`
205
183
  });
206
184
 
207
- /**
208
- * Built-in icons keyed by the agent's id, used to resolve the icon for an
209
- * agent declared without an inline `iconSvg` override (i.e. one of xtralab's
210
- * defaults). Custom agents added via settings can either supply their own
211
- * `iconSvg` or fall back to the generic terminal icon.
212
- */
213
185
  export const BUILTIN_AGENT_ICONS: Record<string, LabIcon> = {
214
186
  antigravity: antigravityIcon,
215
187
  claude: claudeIcon,
@@ -222,12 +194,6 @@ export const BUILTIN_AGENT_ICONS: Record<string, LabIcon> = {
222
194
  pi: piIcon
223
195
  };
224
196
 
225
- /**
226
- * Built-in icons keyed by the editor's id, used to resolve the icon for a
227
- * built-in editor (Neovim, Vim) declared without an inline `iconSvg`. Custom
228
- * editors added via settings can supply their own `iconSvg` or fall back to
229
- * the generic text-editor icon.
230
- */
231
197
  export const BUILTIN_EDITOR_ICONS: Record<string, LabIcon> = {
232
198
  nvim: neovimIcon,
233
199
  vim: vimIcon
@@ -25,26 +25,10 @@ import { IAgentRegistry } from './tokens';
25
25
  const PLUGIN_ID = 'xtralab:launcher';
26
26
 
27
27
  /**
28
- * The xtralab launcher plugin. Replaces the stock JupyterLab launcher
29
- * (which is disabled via `package.json`'s `jupyterlab.disabledExtensions`)
30
- * with an agent-focused dashboard: an optional initial prompt, a row of
31
- * agent buttons (Claude, Codex, Antigravity, …), and a collapsible list of
32
- * changed files (clickable into the diff viewer) below them. Clicking an
33
- * agent opens a fresh terminal and pipes the agent's command into it; if
34
- * the prompt textarea is non-empty, the prompt is shell-quoted and
35
- * spliced into the launch line per the agent's `promptArgs` recipe.
36
- *
37
- * The agent list is the merge of xtralab's defaults with the user's
38
- * `xtralab:launcher` settings, then filtered by a server-side `which`
39
- * check so users only see agents that are actually installed. Agents with
40
- * `requireAvailable: false` skip the filter, as does any built-in whose
41
- * `command` the user has overridden (a user-chosen command — often a shell
42
- * alias the server can't resolve — is trusted and always shown).
43
- *
44
- * The plugin deliberately does NOT provide the `ILauncher` token: other
45
- * extensions register notebook/console/terminal cards on it as a side
46
- * effect, which would defeat the point of the agent-only launcher. If an
47
- * extension needs to surface itself, we'll add it here explicitly.
28
+ * The xtralab launcher plugin: replaces the stock JupyterLab launcher
29
+ * (disabled via `jupyterlab.disabledExtensions`) with an agent-focused
30
+ * dashboard. It deliberately does NOT provide `ILauncher` — other extensions
31
+ * would register their cards on it and defeat the agent-only design.
48
32
  */
49
33
  const plugin: JupyterFrontEndPlugin<IAgentRegistry> = {
50
34
  id: PLUGIN_ID,
@@ -72,23 +56,13 @@ const plugin: JupyterFrontEndPlugin<IAgentRegistry> = {
72
56
  const { commands, shell } = app;
73
57
  const trans = (translator ?? nullTranslator).load('jupyterlab');
74
58
 
75
- // The shared agent registry this plugin provides on `IAgentRegistry`.
76
- // It holds the active agent list (updated whenever settings change), so
77
- // every freshly-created launcher widget — and any other plugin that
78
- // consumes the token, e.g. the terminals panel — renders the latest list.
79
59
  const registry = new AgentRegistry();
80
60
 
81
- // Track command registrations so a settings change can wipe them
82
- // before re-registering — without this the command palette would
83
- // accumulate stale entries when the agent list shrinks.
84
61
  let registered: IDisposable | null = null;
85
62
 
86
63
  const applyAgents = async (overrides: IAgentSettings[]): Promise<void> => {
87
64
  const agents = mergeAgents(overrides);
88
65
 
89
- // Probe the server's `$PATH` so we only surface agents that are actually
90
- // installed. Agents with `requireAvailable: false` opt out (their command
91
- // may be a shell alias that `which` can't see) and are kept regardless.
92
66
  const probe = Array.from(
93
67
  new Set(
94
68
  agents
@@ -131,8 +105,6 @@ const plugin: JupyterFrontEndPlugin<IAgentRegistry> = {
131
105
  });
132
106
  } catch (reason) {
133
107
  console.error('xtralab: failed to load launcher settings', reason);
134
- // Settings load failed — fall back to defaults so the launcher
135
- // still has cards instead of going silent.
136
108
  await applyAgents([]);
137
109
  }
138
110
  } else {
@@ -144,11 +116,8 @@ const plugin: JupyterFrontEndPlugin<IAgentRegistry> = {
144
116
  execute: (args: ReadonlyPartialJSONObject) => {
145
117
  const id = Private.nextId();
146
118
  const onAgentLaunch = (item: Widget): void => {
147
- // When an agent command returns a Widget that ends up in the main
148
- // area, slot it where this launcher used to sit so opening an
149
- // agent feels like the launcher transformed into the terminal.
150
- // Disposing the inner ReactWidget cascades to the MainAreaWidget
151
- // host via its `content.disposed` connection.
119
+ // Slot the widget where this launcher sits; disposing the inner
120
+ // ReactWidget cascades to its MainAreaWidget host via `content.disposed`.
152
121
  if (find(shell.widgets('main'), w => w === item)) {
153
122
  shell.add(item, 'main', { ref: id });
154
123
  launcher.dispose();
@@ -160,9 +129,6 @@ const plugin: JupyterFrontEndPlugin<IAgentRegistry> = {
160
129
  editor: editorRegistry?.current ?? null,
161
130
  agentSessions,
162
131
  onAgentLaunch,
163
- // Empty repoPath/cwd matches the JupyterLab convention used by
164
- // the git panel and the stock launcher: let the server resolve
165
- // the working tree from its root directory.
166
132
  repoPath: '',
167
133
  cwd: '',
168
134
  trans
@@ -171,9 +137,7 @@ const plugin: JupyterFrontEndPlugin<IAgentRegistry> = {
171
137
  launcher.title.label = trans.__('Launcher');
172
138
 
173
139
  const main = new MainAreaWidget({ content: launcher });
174
- // Hide the close button when the launcher is the only thing in the
175
- // main area: closing it would leave the user staring at an empty
176
- // shell with no way back.
140
+ // Closing the only main-area widget would leave an empty shell.
177
141
  main.title.closable = !!Array.from(shell.widgets('main')).length;
178
142
  main.id = id;
179
143
 
@@ -210,9 +174,7 @@ const plugin: JupyterFrontEndPlugin<IAgentRegistry> = {
210
174
  labShell.layoutModified.connect(() => {
211
175
  maybeCreate();
212
176
  });
213
- // Layout has settled by the time `app.restored` resolves; if it's
214
- // empty (fresh start, no restored widgets) the connect above won't
215
- // fire on its own — kick it off here.
177
+ // If the restored layout is already empty the connect never fires.
216
178
  maybeCreate();
217
179
  });
218
180
 
@@ -230,14 +192,9 @@ const plugin: JupyterFrontEndPlugin<IAgentRegistry> = {
230
192
  };
231
193
 
232
194
  /**
233
- * Drop agents whose command isn't on `$PATH`, except entries that opt out via
234
- * `requireAvailable: false` (e.g. shell aliases the user wants surfaced
235
- * regardless). `available` is the resolved set from
236
- * {@link fetchAvailableCommands}.
237
- *
238
- * When `available` is `null` (the endpoint couldn't be reached) we fail open
239
- * and return the input unchanged — better to show an unreachable agent than
240
- * to hide the entire launcher because the server extension didn't load.
195
+ * Drop agents whose command isn't on `$PATH`, keeping `requireAvailable:
196
+ * false` entries. When `available` is `null` (probe failed), fail open and
197
+ * return the input unchanged.
241
198
  */
242
199
  function filterAgents(
243
200
  agents: IAgent[],
@@ -255,7 +212,7 @@ namespace Private {
255
212
  let counter = 0;
256
213
 
257
214
  /**
258
- * Returns the next unique launcher widget id.
215
+ * Generate a unique id for a launcher widget.
259
216
  */
260
217
  export function nextId(): string {
261
218
  return `launcher-${counter++}`;
@@ -1,18 +1,9 @@
1
1
  import type { IAgent } from './agents';
2
2
 
3
3
  /**
4
- * Quote a string so it can be safely passed as a single argument on a
5
- * bash/zsh command line that is *typed into an interactive terminal* —
6
- * not the usual file-parsing context.
7
- *
8
- * The distinction matters: an actual newline byte (0x0A) sent into a PTY
9
- * is interpreted by the line discipline as Enter regardless of whether
10
- * it sits inside `'...'` quotes, so the shell never sees the closing
11
- * quote. We therefore use ANSI-C `$'...'` quoting and emit `\n` (and
12
- * `\r`) as their two-character escape sequences; the shell expands them
13
- * to real newlines after the line is submitted, when readline is no
14
- * longer involved. Backslashes and single quotes are escaped first so
15
- * the source text round-trips unchanged.
4
+ * Quote a string for a command line typed into an interactive terminal. A raw
5
+ * newline sent to a PTY acts as Enter even inside '…' quotes, so ANSI-C
6
+ * `$'…'` quoting emits `\n`/`\r` as escapes the shell expands after submit.
16
7
  */
17
8
  function shellQuote(value: string): string {
18
9
  const escaped = value
@@ -24,16 +15,10 @@ function shellQuote(value: string): string {
24
15
  }
25
16
 
26
17
  /**
27
- * Compose the literal command line typed into a fresh terminal for `agent`.
28
- * When `prompt` is empty (or whitespace-only), this is just `agent.command`.
29
- * When non-empty, the prompt is shell-quoted and spliced in using the
30
- * agent's `promptArgs` recipe — see `IAgent.promptArgs` for the encoding.
31
- *
32
- * Returns `agent.command` unchanged when the agent declares no prompt
33
- * support (`promptArgs === undefined`), so a stray prompt typed into the
34
- * launcher does not silently mutate the launch line for that agent. The
35
- * launcher UI separately prevents the click in that case so the dropped
36
- * prompt is never surprising to the user.
18
+ * Compose the literal command line typed into a fresh terminal for `agent`:
19
+ * the shell-quoted prompt is spliced in per the `promptArgs` recipe. Returns
20
+ * bare `agent.command` when the prompt is empty or the agent has no prompt
21
+ * support.
37
22
  */
38
23
  export function buildAgentInvocation(agent: IAgent, prompt: string): string {
39
24
  const trimmed = prompt.trim();
@@ -4,24 +4,26 @@ import type { IAgent } from './agents';
4
4
  import type { IAgentRegistry } from './tokens';
5
5
 
6
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.
7
+ * Concrete {@link IAgentRegistry} the launcher plugin provides. The launcher
8
+ * is the only writer, so {@link setAgents} is kept off the shared token.
12
9
  */
13
10
  export class AgentRegistry implements IAgentRegistry {
11
+ /**
12
+ * The current agents, filtered by availability and sorted by rank.
13
+ */
14
14
  get agents(): IAgent[] {
15
15
  return this._agents;
16
16
  }
17
17
 
18
+ /**
19
+ * Emitted whenever {@link agents} changes.
20
+ */
18
21
  get changed(): ISignal<IAgentRegistry, void> {
19
22
  return this._changed;
20
23
  }
21
24
 
22
25
  /**
23
- * Replace the agent list and notify observers. Called by the launcher
24
- * after merging the user's settings and filtering by availability.
26
+ * Replace the agent list and notify observers.
25
27
  */
26
28
  setAgents(agents: IAgent[]): void {
27
29
  this._agents = agents;
@@ -4,35 +4,26 @@ import type { ISignal } from '@lumino/signaling';
4
4
  import type { IAgent } from './agents';
5
5
 
6
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.
7
+ * A read-only, observable view of the launcher's available agents — the
8
+ * merged, availability-filtered list the launcher renders — shared so other
9
+ * plugins (e.g. the terminals panel) can surface the same agents and icons.
16
10
  */
17
11
  export interface IAgentRegistry {
18
12
  /**
19
- * The current agents, already filtered by availability and sorted by
20
- * rank — the same array the launcher renders.
13
+ * The current agents, filtered by availability and sorted by rank.
21
14
  */
22
15
  readonly agents: IAgent[];
23
16
 
24
17
  /**
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.
18
+ * Emitted whenever {@link agents} changes.
28
19
  */
29
20
  readonly changed: ISignal<IAgentRegistry, void>;
30
21
  }
31
22
 
32
23
  /**
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`.
24
+ * DI token for {@link IAgentRegistry}. Provided by `xtralab:launcher`;
25
+ * consumers depend on it optionally so they survive the launcher being
26
+ * disabled.
36
27
  */
37
28
  export const IAgentRegistry = new Token<IAgentRegistry>(
38
29
  'xtralab:IAgentRegistry',
@@ -40,11 +31,8 @@ export const IAgentRegistry = new Token<IAgentRegistry>(
40
31
  );
41
32
 
42
33
  /**
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.
34
+ * The command id that launches a given agent in a new terminal, defined in
35
+ * the shared contract module so the launcher and consumers agree on it.
48
36
  */
49
37
  export function agentCommandId(agentId: string): string {
50
38
  return `xtralab:start-agent:${agentId}`;
@@ -18,25 +18,15 @@ const OPEN_COMMAND = 'xtralab:open-main-menu';
18
18
  const TOGGLE_COMMAND = 'xtralab:toggle-menu-bar';
19
19
 
20
20
  /**
21
- * How long after the popup closes a button press still counts as "close
22
- * only". An open Lumino menu closes itself on any outside press from a
23
- * document `pointerdown` listener in the capture phase, which runs before
24
- * the button's own mousedown handler — so without this window, pressing the
25
- * button while the popup is open would close it and instantly reopen it.
21
+ * Guard after the popup closes: Lumino menus close on a capture-phase document
22
+ * pointerdown, before the button's mousedown, which would instantly reopen.
26
23
  */
27
24
  const REOPEN_GUARD_MS = 250;
28
25
 
29
26
  /**
30
- * Collapse the main menu bar into a compact menu button.
31
- *
32
- * The menu bar is hidden and a hamburger button in the top bar opens the same
33
- * menus as a vertical popup. The popup reuses the live `RankedMenu` instances
34
- * owned by the `MainMenu`, so menus added or removed at runtime are reflected
35
- * without duplication.
36
- *
37
- * Hiding covers the `jp-menu-panel` container too, which has its own
38
- * `min-height` and would otherwise leave an empty strip. The "Show Menu Bar"
39
- * toggle restores the classic bar.
27
+ * Collapse the main menu bar into a hamburger button whose popup reuses the
28
+ * live `RankedMenu` instances owned by `MainMenu`. Hiding covers the
29
+ * `jp-menu-panel` container too — its `min-height` would leave an empty strip.
40
30
  */
41
31
  const plugin: JupyterFrontEndPlugin<void> = {
42
32
  id: PLUGIN_ID,
@@ -84,9 +74,8 @@ const plugin: JupyterFrontEndPlugin<void> = {
84
74
 
85
75
  const openMenu = (): void => {
86
76
  if (visible) {
87
- // The classic bar is on screen; behave like keyboard menu
88
- // activation instead of opening a redundant popup that would
89
- // compete with the bar for the same Menu instances.
77
+ // With the classic bar on screen, act like keyboard menu activation —
78
+ // a popup would compete with the bar for the same Menu instances.
90
79
  mainMenu.activeIndex = 0;
91
80
  mainMenu.openActiveMenu();
92
81
  return;
@@ -95,13 +84,8 @@ const plugin: JupyterFrontEndPlugin<void> = {
95
84
  return;
96
85
  }
97
86
 
98
- // One popup instance, rebuilt from the bar's current menus on every
99
- // open so runtime menu changes stay reflected. It is a plain Lumino
100
- // Menu rather than a MenuSvg: MenuSvg.insertItem re-wraps each
101
- // submenu's renderer and insertItem on every call, which would pile
102
- // wrappers onto the shared menus across reopens. The svg renderer and
103
- // themed-container class are applied directly instead, matching how
104
- // the real menus are constructed in MainMenu.
87
+ // A plain Menu rebuilt on every open; MenuSvg.insertItem would re-wrap
88
+ // each submenu's renderer per reopen, piling wrappers onto the shared menus.
105
89
  if (popup === null) {
106
90
  popup = new Menu({ commands, renderer: MenuSvg.defaultRenderer });
107
91
  popup.addClass('jp-ThemedContainer');
@@ -183,9 +167,8 @@ const plugin: JupyterFrontEndPlugin<void> = {
183
167
  loaded.changed.connect(update);
184
168
  })
185
169
  .catch(reason => {
186
- // Without settings the collapsed state cannot be managed (and the
187
- // button was never added) — reveal the stock bar rather than leave
188
- // the app with no reachable menus.
170
+ // Without settings the collapsed state cannot be managed and the button
171
+ // was never added; reveal the stock bar so menus stay reachable.
189
172
  applyVisibility(true);
190
173
  console.error(`Failed to load settings for ${PLUGIN_ID}`, reason);
191
174
  });