xtralab 0.14.1 → 0.15.1

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 (236) hide show
  1. package/README.md +10 -0
  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.d.ts +7 -0
  29. package/lib/customPanel/index.js +89 -0
  30. package/lib/editorBreadcrumbs/index.d.ts +3 -6
  31. package/lib/editorBreadcrumbs/index.js +10 -6
  32. package/lib/editorBreadcrumbs/widget.d.ts +17 -4
  33. package/lib/editorBreadcrumbs/widget.js +11 -17
  34. package/lib/editorIndent/index.d.ts +4 -12
  35. package/lib/editorIndent/index.js +8 -23
  36. package/lib/fileBrowser/commands.d.ts +28 -19
  37. package/lib/fileBrowser/commands.js +51 -98
  38. package/lib/fileBrowser/contents.d.ts +14 -0
  39. package/lib/fileBrowser/contents.js +28 -0
  40. package/lib/fileBrowser/dragAndDrop.d.ts +79 -0
  41. package/lib/fileBrowser/dragAndDrop.js +110 -0
  42. package/lib/fileBrowser/fileBrowser.d.ts +21 -9
  43. package/lib/fileBrowser/fileBrowser.js +211 -405
  44. package/lib/fileBrowser/gitStatus.d.ts +4 -9
  45. package/lib/fileBrowser/gitStatus.js +7 -17
  46. package/lib/fileBrowser/gitignore.d.ts +4 -17
  47. package/lib/fileBrowser/gitignore.js +4 -25
  48. package/lib/fileBrowser/icons.d.ts +7 -17
  49. package/lib/fileBrowser/icons.js +25 -80
  50. package/lib/fileBrowser/index.js +41 -10
  51. package/lib/fileBrowser/widget.d.ts +170 -53
  52. package/lib/fileBrowser/widget.js +242 -18
  53. package/lib/fileTypeIcons/index.js +0 -10
  54. package/lib/git/api.d.ts +5 -13
  55. package/lib/git/api.js +10 -36
  56. package/lib/git/askRequest.d.ts +4 -8
  57. package/lib/git/askRequest.js +6 -20
  58. package/lib/git/commands.d.ts +40 -5
  59. package/lib/git/commands.js +16 -10
  60. package/lib/git/diffModel.js +4 -0
  61. package/lib/git/diffSurface.d.ts +41 -55
  62. package/lib/git/diffSurface.js +42 -117
  63. package/lib/git/diffWidget.d.ts +81 -2
  64. package/lib/git/diffWidget.js +62 -12
  65. package/lib/git/imageDiff.d.ts +5 -6
  66. package/lib/git/imageDiff.js +18 -48
  67. package/lib/git/index.d.ts +0 -3
  68. package/lib/git/index.js +3 -8
  69. package/lib/git/notebookDiff.d.ts +110 -33
  70. package/lib/git/notebookDiff.js +52 -195
  71. package/lib/git/tokens.d.ts +73 -31
  72. package/lib/git/tokens.js +2 -3
  73. package/lib/highlight/index.d.ts +4 -10
  74. package/lib/highlight/index.js +10 -41
  75. package/lib/index.d.ts +0 -9
  76. package/lib/index.js +2 -9
  77. package/lib/launcher/agents.d.ts +55 -51
  78. package/lib/launcher/agents.js +25 -54
  79. package/lib/launcher/availability.d.ts +3 -9
  80. package/lib/launcher/availability.js +3 -9
  81. package/lib/launcher/commands.d.ts +7 -32
  82. package/lib/launcher/commands.js +14 -45
  83. package/lib/launcher/dashboard.d.ts +19 -24
  84. package/lib/launcher/dashboard.js +21 -67
  85. package/lib/launcher/editorRegistry.d.ts +10 -22
  86. package/lib/launcher/editorRegistry.js +14 -18
  87. package/lib/launcher/editors.d.ts +39 -45
  88. package/lib/launcher/editors.js +8 -34
  89. package/lib/launcher/icons.d.ts +0 -12
  90. package/lib/launcher/icons.js +14 -37
  91. package/lib/launcher/index.js +12 -55
  92. package/lib/launcher/invocation.d.ts +4 -10
  93. package/lib/launcher/invocation.js +7 -22
  94. package/lib/launcher/registry.d.ts +9 -7
  95. package/lib/launcher/registry.js +9 -7
  96. package/lib/launcher/schemaDefaults.d.ts +2 -9
  97. package/lib/launcher/schemaDefaults.js +37 -13
  98. package/lib/launcher/tokens.d.ts +10 -22
  99. package/lib/launcher/tokens.js +5 -8
  100. package/lib/menuBar/index.d.ts +3 -10
  101. package/lib/menuBar/index.js +11 -28
  102. package/lib/menus/index.d.ts +3 -9
  103. package/lib/menus/index.js +14 -30
  104. package/lib/omnibox/files.d.ts +2 -4
  105. package/lib/omnibox/files.js +5 -11
  106. package/lib/omnibox/index.d.ts +4 -14
  107. package/lib/omnibox/index.js +9 -27
  108. package/lib/omnibox/model.d.ts +38 -10
  109. package/lib/omnibox/model.js +15 -33
  110. package/lib/omnibox/recents.d.ts +15 -17
  111. package/lib/omnibox/recents.js +12 -17
  112. package/lib/omnibox/tokens.d.ts +5 -10
  113. package/lib/omnibox/tokens.js +2 -6
  114. package/lib/omnibox/widget.d.ts +11 -7
  115. package/lib/omnibox/widget.js +8 -16
  116. package/lib/searchReplace/index.d.ts +4 -20
  117. package/lib/searchReplace/index.js +7 -27
  118. package/lib/showOutput/index.d.ts +3 -12
  119. package/lib/showOutput/index.js +6 -22
  120. package/lib/sidebar/index.d.ts +4 -13
  121. package/lib/sidebar/index.js +73 -46
  122. package/lib/terminalNotifications/index.d.ts +4 -10
  123. package/lib/terminalNotifications/index.js +6 -20
  124. package/lib/terminals/agentTerminals.d.ts +12 -5
  125. package/lib/terminals/agentTerminals.js +25 -44
  126. package/lib/terminals/detection.d.ts +4 -8
  127. package/lib/terminals/detection.js +4 -8
  128. package/lib/terminals/index.d.ts +0 -33
  129. package/lib/terminals/index.js +24 -102
  130. package/lib/terminals/model.d.ts +64 -139
  131. package/lib/terminals/model.js +80 -220
  132. package/lib/terminals/tokens.d.ts +11 -22
  133. package/lib/terminals/tokens.js +2 -2
  134. package/lib/terminals/widget.d.ts +72 -38
  135. package/lib/terminals/widget.js +173 -73
  136. package/lib/topBar/icons.d.ts +3 -8
  137. package/lib/topBar/icons.js +3 -8
  138. package/lib/topBar/index.d.ts +4 -11
  139. package/lib/topBar/index.js +10 -38
  140. package/lib/walkthrough/index.d.ts +4 -9
  141. package/lib/walkthrough/index.js +6 -20
  142. package/lib/walkthrough/panel.d.ts +40 -9
  143. package/lib/walkthrough/panel.js +4 -10
  144. package/package.json +2 -2
  145. package/schema/launcher.json +2 -2
  146. package/schema/sidebar.json +47 -7
  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 +119 -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 +70 -98
  165. package/src/fileBrowser/contents.ts +33 -0
  166. package/src/fileBrowser/dragAndDrop.ts +188 -0
  167. package/src/fileBrowser/fileBrowser.tsx +259 -440
  168. package/src/fileBrowser/gitStatus.ts +7 -17
  169. package/src/fileBrowser/gitignore.ts +4 -25
  170. package/src/fileBrowser/icons.ts +25 -80
  171. package/src/fileBrowser/index.ts +43 -10
  172. package/src/fileBrowser/widget.tsx +344 -64
  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 +70 -10
  177. package/src/git/diffModel.ts +25 -0
  178. package/src/git/diffSurface.tsx +71 -159
  179. package/src/git/diffWidget.tsx +104 -14
  180. package/src/git/imageDiff.tsx +45 -51
  181. package/src/git/index.ts +3 -8
  182. package/src/git/notebookDiff.tsx +211 -238
  183. package/src/git/tokens.ts +73 -31
  184. package/src/highlight/index.ts +10 -41
  185. package/src/index.ts +2 -9
  186. package/src/launcher/agents.ts +74 -92
  187. package/src/launcher/availability.ts +3 -9
  188. package/src/launcher/commands.ts +14 -51
  189. package/src/launcher/dashboard.tsx +49 -88
  190. package/src/launcher/editorRegistry.ts +21 -29
  191. package/src/launcher/editors.ts +39 -58
  192. package/src/launcher/icons.ts +15 -37
  193. package/src/launcher/index.ts +12 -55
  194. package/src/launcher/invocation.ts +7 -22
  195. package/src/launcher/registry.ts +9 -7
  196. package/src/launcher/schemaDefaults.ts +43 -13
  197. package/src/launcher/tokens.ts +10 -22
  198. package/src/menuBar/index.ts +11 -28
  199. package/src/menus/index.ts +14 -30
  200. package/src/omnibox/files.ts +10 -14
  201. package/src/omnibox/index.ts +9 -27
  202. package/src/omnibox/model.ts +50 -38
  203. package/src/omnibox/recents.ts +17 -22
  204. package/src/omnibox/tokens.ts +5 -10
  205. package/src/omnibox/widget.tsx +14 -19
  206. package/src/searchReplace/index.ts +7 -27
  207. package/src/showOutput/index.ts +6 -22
  208. package/src/sidebar/index.ts +103 -52
  209. package/src/terminalNotifications/index.ts +42 -25
  210. package/src/terminals/agentTerminals.ts +25 -49
  211. package/src/terminals/detection.ts +4 -8
  212. package/src/terminals/index.ts +31 -102
  213. package/src/terminals/model.ts +130 -246
  214. package/src/terminals/tokens.ts +11 -22
  215. package/src/terminals/widget.tsx +229 -113
  216. package/src/topBar/icons.ts +3 -8
  217. package/src/topBar/index.ts +21 -44
  218. package/src/walkthrough/index.ts +6 -20
  219. package/src/walkthrough/panel.ts +40 -10
  220. package/style/about.css +2 -7
  221. package/style/askAgent.css +8 -40
  222. package/style/base.css +25 -73
  223. package/style/chrome.css +108 -308
  224. package/style/commandBar.css +4 -20
  225. package/style/customPanel.css +6 -0
  226. package/style/git.css +22 -184
  227. package/style/highlight.css +1 -3
  228. package/style/index.js +1 -0
  229. package/style/launcher.css +6 -70
  230. package/style/omnibox.css +2 -14
  231. package/style/showOutput.css +0 -6
  232. package/style/sidebar.css +29 -9
  233. package/style/tabs.css +5 -13
  234. package/style/terminals.css +18 -123
  235. package/style/topBar.css +2 -22
  236. package/style/walkthrough.css +0 -8
package/src/git/tokens.ts CHANGED
@@ -1,21 +1,33 @@
1
1
  /**
2
- * Types shared by the git plugin. Mirror the response shapes returned by the
3
- * `jupyterlab_git` server extension's REST API so the frontend can stay
4
- * tightly coupled to a single version of those endpoints.
2
+ * Types shared by the git plugin, mirroring the response shapes of the
3
+ * `jupyterlab_git` server extension's REST API.
5
4
  */
6
5
 
7
6
  /**
8
- * A single entry in the `files` array returned by `POST /git/<path>/status`.
9
- * `x` and `y` are the porcelain-format index/worktree status codes (see
10
- * `git status --porcelain`); `to` is the file's current path relative to the
11
- * git repository root, `from` is the source path for renames (and equal to
12
- * `to` for any other status).
7
+ * One entry of the `files` array from `POST /git/<path>/status`. `x`/`y` are
8
+ * the porcelain index/worktree codes; `to` is the current repo-relative path
9
+ * and `from` the rename source (equal to `to` for any other status).
13
10
  */
14
11
  export interface IGitStatusFile {
12
+ /**
13
+ * The porcelain status code for the index side.
14
+ */
15
15
  x: string;
16
+ /**
17
+ * The porcelain status code for the worktree side.
18
+ */
16
19
  y: string;
20
+ /**
21
+ * The current repo-relative path of the file.
22
+ */
17
23
  to: string;
24
+ /**
25
+ * The rename source path; equal to `to` for any other status.
26
+ */
18
27
  from: string;
28
+ /**
29
+ * Whether the file content is binary; `null` when undetermined.
30
+ */
19
31
  is_binary: boolean | null;
20
32
  }
21
33
 
@@ -23,52 +35,68 @@ export interface IGitStatusFile {
23
35
  * Response shape of `POST /git/<path>/status`.
24
36
  */
25
37
  export interface IGitStatusResult {
38
+ /**
39
+ * The return code of the git command.
40
+ */
26
41
  code: number;
42
+ /**
43
+ * The current branch name; `null` when it is not available.
44
+ */
27
45
  branch: string | null;
46
+ /**
47
+ * The upstream remote branch; `null` when none is set.
48
+ */
28
49
  remote: string | null;
50
+ /**
51
+ * The number of commits ahead of the upstream branch.
52
+ */
29
53
  ahead: number;
54
+ /**
55
+ * The number of commits behind the upstream branch.
56
+ */
30
57
  behind: number;
58
+ /**
59
+ * The changed files reported by `git status`.
60
+ */
31
61
  files: IGitStatusFile[];
62
+ /**
63
+ * The in-progress repository state (merge, rebase, ...), when reported.
64
+ */
32
65
  state?: number;
66
+ /**
67
+ * An error message, present when the command fails.
68
+ */
33
69
  message?: string;
34
70
  }
35
71
 
36
72
  /**
37
- * Reference accepted by `POST /git/<path>/content` to identify which version
38
- * of a file to fetch. `WORKING` is the on-disk copy, `INDEX` is the staged
39
- * copy, and a `git` value is any commit-ish (`HEAD`, a SHA, …).
73
+ * Reference accepted by `POST /git/<path>/content`: `WORKING` is the on-disk
74
+ * copy, `INDEX` the staged copy, `git` any commit-ish.
40
75
  */
41
76
  export type GitReference =
42
77
  | { special: 'WORKING' | 'INDEX' | 'BASE' }
43
78
  | { git: string };
44
79
 
45
80
  /**
46
- * Response shape of `POST /git/<path>/content`. The server only returns text
47
- * content here; binary files are reported as binary in the status response,
48
- * and we surface them in the UI without attempting to render their diff.
81
+ * Response shape of `POST /git/<path>/content`.
49
82
  */
50
83
  export interface IGitContentResult {
84
+ /**
85
+ * The return code of the git command.
86
+ */
51
87
  code: number;
88
+ /**
89
+ * The file content at the requested reference.
90
+ */
52
91
  content: string;
92
+ /**
93
+ * An error message, present when the command fails.
94
+ */
53
95
  message?: string;
54
96
  }
55
97
 
56
- /**
57
- * Where a file's change lives relative to the index.
58
- *
59
- * - `staged` → present in the index, may differ from HEAD
60
- * - `unstaged` → present in the worktree, differs from the index
61
- *
62
- * A single file can appear in both groups when the worktree has further
63
- * changes on top of an already-staged version. The panel models that as two
64
- * separate entries, one per group.
65
- */
66
98
  type FileChangeGroup = 'staged' | 'unstaged';
67
99
 
68
- /**
69
- * The user-facing status of a file change. Drives the single-letter badge
70
- * (M/A/D/R/U/?) shown next to each entry in the panel.
71
- */
72
100
  export type FileChangeStatus =
73
101
  | 'modified'
74
102
  | 'added'
@@ -80,14 +108,28 @@ export type FileChangeStatus =
80
108
  | 'unknown';
81
109
 
82
110
  /**
83
- * One row in the changes panel. `path` is the file's path relative to the
84
- * git repository root. `from` is non-`undefined` only for renames, in which
85
- * case it carries the original path.
111
+ * One row in the changes panel. `path` is repo-relative; `from` is set only
112
+ * for renames and carries the original path.
86
113
  */
87
114
  export interface IFileChange {
115
+ /**
116
+ * The repo-relative path of the file.
117
+ */
88
118
  path: string;
119
+ /**
120
+ * The rename source path, set only for renames.
121
+ */
89
122
  from?: string;
123
+ /**
124
+ * Whether the change is staged or unstaged.
125
+ */
90
126
  group: FileChangeGroup;
127
+ /**
128
+ * The change status derived from the porcelain codes.
129
+ */
91
130
  status: FileChangeStatus;
131
+ /**
132
+ * Whether the file content is binary; `null` when undetermined.
133
+ */
92
134
  isBinary: boolean | null;
93
135
  }
@@ -13,22 +13,13 @@ import type { Widget } from '@lumino/widgets';
13
13
 
14
14
  const PLUGIN_ID = 'xtralab:highlight';
15
15
 
16
- /**
17
- * Highlight a contiguous range of lines, or clear every highlight. These are
18
- * the only two commands the plugin contributes; both are designed to be driven
19
- * by a coding agent over the MCP command bridge to walk a user through code.
20
- */
21
16
  const HIGHLIGHT_LINES_COMMAND = 'xtralab:highlight-lines';
22
17
  const CLEAR_HIGHLIGHTS_COMMAND = 'xtralab:clear-highlights';
23
18
 
24
- /**
25
- * CodeMirror line-decoration class; styled in style/highlight.css.
26
- */
27
19
  const HIGHLIGHT_LINE_CLASS = 'jp-xtralab-highlightLine';
28
20
 
29
21
  /**
30
- * A CodeMirror effect carrying the 1-indexed, inclusive line range to
31
- * highlight, or `null` to clear the editor's highlights.
22
+ * Carries the 1-indexed inclusive line range to highlight, or `null` to clear.
32
23
  */
33
24
  const setHighlight = StateEffect.define<{
34
25
  start: number;
@@ -36,9 +27,8 @@ const setHighlight = StateEffect.define<{
36
27
  } | null>();
37
28
 
38
29
  /**
39
- * Holds the highlight decorations for one editor. It is injected lazily (the
40
- * first time an editor is highlighted) via `StateEffect.appendConfig`, so
41
- * editors that are never highlighted pay nothing.
30
+ * Highlight decorations for one editor; injected lazily via
31
+ * `StateEffect.appendConfig` so editors never highlighted pay nothing.
42
32
  */
43
33
  const highlightField = StateField.define<DecorationSet>({
44
34
  create: () => Decoration.none,
@@ -68,11 +58,6 @@ const highlightField = StateField.define<DecorationSet>({
68
58
  provide: field => EditorView.decorations.from(field)
69
59
  });
70
60
 
71
- /**
72
- * Return the CodeMirror view backing a widget's file editor, or `null` when
73
- * the widget is not a CodeMirror-based text editor (a notebook, a terminal,
74
- * the settings editor, …).
75
- */
76
61
  function viewForWidget(widget: Widget | null): EditorView | null {
77
62
  const content = (widget as { content?: unknown } | null)?.content;
78
63
  return content instanceof FileEditor &&
@@ -81,26 +66,16 @@ function viewForWidget(widget: Widget | null): EditorView | null {
81
66
  : null;
82
67
  }
83
68
 
84
- /**
85
- * Coerce a JSON command argument to a positive integer line number, falling
86
- * back to `fallback` when it is missing or not a finite number.
87
- */
88
69
  function toLine(value: unknown, fallback: number): number {
89
70
  const n = Math.trunc(Number(value));
90
71
  return Number.isFinite(n) && n >= 1 ? n : fallback;
91
72
  }
92
73
 
93
74
  /**
94
- * Contribute `xtralab:highlight-lines` and `xtralab:clear-highlights`.
95
- *
96
- * The built-in command surface can open a file (`docmanager:open`) and move
97
- * the cursor to a line (`fileeditor:go-to-line`), but it cannot persistently
98
- * highlight a span of lines — `documentsearch:start` only marks text matches
99
- * and hijacks the find box. This plugin fills that gap with a CodeMirror line
100
- * decoration, so an agent can point at "lines 31–43 of src/index.ts" while it
101
- * narrates a walkthrough. `xtralab:highlight-lines` opens the file when needed,
102
- * scrolls the range into view, and replaces any previous highlight in that
103
- * editor; `xtralab:clear-highlights` removes every highlight.
75
+ * Contributes `xtralab:highlight-lines` and `xtralab:clear-highlights`,
76
+ * filling the persistent span-highlight gap in the core command surface
77
+ * (`documentsearch:start` only marks text matches and hijacks the find box)
78
+ * so an agent can point at lines while narrating a walkthrough.
104
79
  */
105
80
  const plugin: JupyterFrontEndPlugin<void> = {
106
81
  id: PLUGIN_ID,
@@ -117,8 +92,6 @@ const plugin: JupyterFrontEndPlugin<void> = {
117
92
  const { commands, shell } = app;
118
93
  const trans = (translator ?? nullTranslator).load('jupyterlab');
119
94
 
120
- // Editors that currently carry a highlight, so a single clear can reach
121
- // them all. Disposed editors are dropped on the next clear.
122
95
  const highlighted = new Set<EditorView>();
123
96
 
124
97
  const ensureField = (view: EditorView): void => {
@@ -130,8 +103,7 @@ const plugin: JupyterFrontEndPlugin<void> = {
130
103
  commands.addCommand(HIGHLIGHT_LINES_COMMAND, {
131
104
  label: trans.__('Highlight Lines'),
132
105
  caption: trans.__('Highlight a range of lines in a text editor'),
133
- // Advertise the argument shape so agents listing commands over the MCP
134
- // bridge can see how to call it, the same way core commands do.
106
+ // Advertise the argument shape to agents listing commands over the MCP bridge.
135
107
  describedBy: {
136
108
  args: {
137
109
  type: 'object',
@@ -165,9 +137,8 @@ const plugin: JupyterFrontEndPlugin<void> = {
165
137
  typeof args['path'] === 'string' ? args['path'] : undefined;
166
138
  const reveal = args['reveal'] !== false;
167
139
 
168
- // With a path, bind strictly to that document: never fall back to the
169
- // active widget, or an explicit path could silently highlight an
170
- // unrelated editor that happens to be focused.
140
+ // With a path, bind strictly to that document — falling back to the
141
+ // active widget could silently highlight an unrelated editor.
171
142
  let widget: Widget | null;
172
143
  if (path) {
173
144
  // Fail fast on a missing path instead of opening a phantom widget
@@ -181,7 +152,6 @@ const plugin: JupyterFrontEndPlugin<void> = {
181
152
  if (!opened) {
182
153
  throw new Error(`xtralab: could not open "${path}"`);
183
154
  }
184
- // Wait for the model and the editor view to be ready.
185
155
  await opened.context.ready;
186
156
  await opened.revealed;
187
157
  widget = opened;
@@ -213,7 +183,6 @@ const plugin: JupyterFrontEndPlugin<void> = {
213
183
  effects.push(EditorView.scrollIntoView(anchor, { y: 'center' }));
214
184
  }
215
185
  view.dispatch({ effects });
216
- // Forget editors that have since been closed before tracking this one.
217
186
  for (const tracked of highlighted) {
218
187
  if (!tracked.dom.isConnected) {
219
188
  highlighted.delete(tracked);
package/src/index.ts CHANGED
@@ -4,6 +4,7 @@ import aboutPlugin from './about';
4
4
  import agentSessionsPlugin from './agentSessions';
5
5
  import askAgentPlugin from './askAgent';
6
6
  import commandBarPlugin from './commandBar';
7
+ import customPanelPlugin from './customPanel';
7
8
  import fileBrowserPlugin from './fileBrowser';
8
9
  import fileTypeIconsPlugin from './fileTypeIcons';
9
10
  import editorBreadcrumbsPlugin from './editorBreadcrumbs';
@@ -22,20 +23,12 @@ import terminalsPlugin from './terminals';
22
23
  import topBarPlugin from './topBar';
23
24
  import walkthroughPlugin from './walkthrough';
24
25
 
25
- /**
26
- * Every plugin contributed by `xtralab`. The entry point of the
27
- * labextension is an array because the package bundles several independent
28
- * enhancements (file browser, git diff providers, …) — JupyterLab activates
29
- * each plugin individually and only the ones whose required tokens are
30
- * available end up running. `gitPlugins` and `launcherPlugins` are themselves
31
- * arrays (the git diff providers; the launcher plus its editor registry), so
32
- * they are spread in.
33
- */
34
26
  const plugins: JupyterFrontEndPlugin<unknown>[] = [
35
27
  aboutPlugin,
36
28
  agentSessionsPlugin,
37
29
  askAgentPlugin,
38
30
  commandBarPlugin,
31
+ customPanelPlugin,
39
32
  editorBreadcrumbsPlugin,
40
33
  editorIndentPlugin,
41
34
  fileBrowserPlugin,
@@ -3,91 +3,98 @@ import { LabIcon, terminalIcon } from '@jupyterlab/ui-components';
3
3
  import { BUILTIN_AGENT_ICONS } from './icons';
4
4
 
5
5
  /**
6
- * The shape of an agent card on the launcher. The `command` is the literal
7
- * text typed into the new terminal session — interactive shells expand
8
- * aliases, so users can point at `claude`, `cl`, or whatever runs their
9
- * preferred wrapper.
6
+ * An agent card on the launcher. `command` is the literal text typed into the
7
+ * new terminal, so interactive-shell aliases resolve.
10
8
  */
11
9
  export interface IAgent {
10
+ /**
11
+ * Stable id; keys the settings merge and the per-agent launch command.
12
+ */
12
13
  id: string;
14
+ /**
15
+ * Card label, e.g. "Claude".
16
+ */
13
17
  label: string;
18
+ /**
19
+ * Card tooltip; also the palette command caption.
20
+ */
14
21
  caption: string;
22
+ /**
23
+ * The literal command typed into the new terminal.
24
+ */
15
25
  command: string;
26
+ /**
27
+ * Brand icon for the card.
28
+ */
16
29
  icon: LabIcon;
30
+ /**
31
+ * Sort position among the launcher's agent cards.
32
+ */
17
33
  rank: number;
18
34
  /**
19
- * When false, the launcher skips the `which`-based availability check for
20
- * this entry — useful for aliases or shell functions that aren't on PATH
21
- * but still resolve when typed in a real terminal. Defaults to true.
35
+ * When false, skip the `which`-based availability check — for aliases or
36
+ * shell functions not on PATH. Defaults to true.
22
37
  */
23
38
  requireAvailable: boolean;
24
39
  /**
25
- * Argv tokens spliced between `command` and a shell-quoted prompt when
26
- * the user types one into the launcher's prompt box. The semantics:
27
- *
28
- * - `[]` → prompt is appended as a positional argument:
29
- * `<command> 'PROMPT'`. (Used by claude, codex, vibe.)
30
- * - `['-i']` / `['--prompt']` → the prompt is preceded by a flag:
31
- * `<command> -i 'PROMPT'`. (Used by copilot, opencode.)
32
- * - `undefined` → the agent does not accept an initial prompt. The
33
- * launcher dims the agent's button while the prompt textarea is
34
- * non-empty, so the user gets a clear signal rather than a silently
35
- * dropped prompt.
36
- *
37
- * The prompt itself is always single-quoted with embedded single quotes
38
- * escaped, so multi-line prompts and shell metacharacters are safe.
40
+ * Argv tokens spliced between `command` and the shell-quoted prompt:
41
+ * `[]` appends the prompt positionally, `['-i']` prefixes a flag, and
42
+ * `undefined` means no prompt support (the launcher dims the button).
39
43
  */
40
44
  promptArgs?: string[];
41
45
  }
42
46
 
43
47
  /**
44
- * The settings-side shape: every field except `id` is optional, so a user
45
- * can override a single field on a default agent (e.g. swap the command for
46
- * an alias) without restating the whole entry. New ids define brand-new
47
- * agent cards.
48
+ * The settings-side shape: every field except `id` is optional so a user can
49
+ * override a single field on a default agent; new ids define new agent cards.
48
50
  */
49
51
  export interface IAgentSettings {
52
+ /**
53
+ * Id of the agent to override; a new id defines a new card.
54
+ */
50
55
  id: string;
56
+ /**
57
+ * See `IAgent.label`.
58
+ */
51
59
  label?: string;
60
+ /**
61
+ * See `IAgent.caption`.
62
+ */
52
63
  caption?: string;
64
+ /**
65
+ * See `IAgent.command`.
66
+ */
53
67
  command?: string;
54
68
  /**
55
- * Inline SVG for a custom agent's icon. Required for new ids whose icon
56
- * isn't shipped with xtralab; ignored for default ids unless explicitly
57
- * set (in which case it overrides the built-in).
69
+ * Inline SVG icon. Required for new ids; overrides the built-in when set on
70
+ * a default id.
58
71
  */
59
72
  iconSvg?: string;
73
+ /**
74
+ * See `IAgent.rank`.
75
+ */
60
76
  rank?: number;
61
77
  /**
62
- * When false, the agent is hidden from the launcher and the command
63
- * palette. Defaults to true.
78
+ * When false, hides the agent from the launcher and the command palette.
64
79
  */
65
80
  enabled?: boolean;
66
81
  /**
67
- * See `IAgent.requireAvailable`. Defaults to true for a built-in agent that
68
- * still uses its shipped command, and to false once you override the
69
- * `command` (a user-chosen command — often a shell alias — is trusted and
70
- * always shown). Set it explicitly to force the check on or off.
82
+ * See `IAgent.requireAvailable`. Defaults to true, but flips to false once
83
+ * `command` is overridden (a user-chosen alias is trusted).
71
84
  */
72
85
  requireAvailable?: boolean;
73
86
  /**
74
- * See `IAgent.promptArgs`. Pass an empty array to mark the agent as
75
- * accepting a positional prompt; pass an array like `["-p"]` to use a
76
- * flag. Pass `null` to explicitly turn off prompt support for an agent
77
- * that has it on by default.
87
+ * See `IAgent.promptArgs`. `null` explicitly turns off an agent's default
88
+ * prompt support.
78
89
  */
79
90
  promptArgs?: string[] | null;
80
91
  }
81
92
 
82
93
  /**
83
- * Default agent cards shipped with xtralab. Seven of these (Claude, Codex,
84
- * Copilot, Goose, OpenCode, Kiro, Mistral Vibe) mirror first-class personas
85
- * in `jupyter-ai-contrib/jupyter-ai-acp-client` (which is also where their
86
- * icons come from), but use the bare CLI names a user would type in a
87
- * terminal rather than the ACP wrapper binaries that project spawns.
88
- * Antigravity (Google's agent-first IDE/CLI, `agy`) is the Google agent.
89
- * Anything not on the user's `$PATH` is filtered out at activation time, so
90
- * the wider list is harmless.
94
+ * Default agent cards. Most mirror `jupyter-ai-contrib/jupyter-ai-acp-client`
95
+ * personas but use the bare CLI names a user would type, not the ACP wrappers.
96
+ * Anything not on `$PATH` is filtered out at activation, so the wide list is
97
+ * harmless.
91
98
  */
92
99
  const DEFAULTS: IAgent[] = [
93
100
  {
@@ -118,13 +125,8 @@ const DEFAULTS: IAgent[] = [
118
125
  icon: BUILTIN_AGENT_ICONS.antigravity,
119
126
  rank: 2,
120
127
  requireAvailable: true
121
- // `agy` is the Antigravity launcher binary — a VS Code-derived editor
122
- // CLI that opens the Agent Manager / workspace, not a terminal REPL.
123
- // Its positional args are file/folder paths, so there is no inline
124
- // natural-language prompt form; like goose/kiro we omit promptArgs so a
125
- // typed prompt dims the button instead of being mangled into a path.
126
- // The command stays the bare binary (no `agy .`) so the server-side
127
- // `shutil.which` availability check still resolves it.
128
+ // `agy` opens the Antigravity editor; its positional args are paths, not
129
+ // prompts, and the bare binary keeps the server-side `which` check working.
128
130
  },
129
131
  {
130
132
  id: 'copilot',
@@ -134,8 +136,7 @@ const DEFAULTS: IAgent[] = [
134
136
  icon: BUILTIN_AGENT_ICONS.copilot,
135
137
  rank: 3,
136
138
  requireAvailable: true,
137
- // `-i "PROMPT"` is the documented way to start interactive mode and
138
- // auto-execute the prompt; `-p` exits after responding (non-interactive).
139
+ // `-i` starts interactive mode with the prompt; `-p` exits after responding.
139
140
  promptArgs: ['-i']
140
141
  },
141
142
  {
@@ -146,8 +147,7 @@ const DEFAULTS: IAgent[] = [
146
147
  icon: BUILTIN_AGENT_ICONS.goose,
147
148
  rank: 4,
148
149
  requireAvailable: true
149
- // `goose` does not accept an inline interactive prompt; users would
150
- // need to type the prompt after `goose session` starts.
150
+ // `goose` takes no inline prompt; it must be typed after `goose session`.
151
151
  },
152
152
  {
153
153
  id: 'opencode',
@@ -167,8 +167,7 @@ const DEFAULTS: IAgent[] = [
167
167
  icon: BUILTIN_AGENT_ICONS.kiro,
168
168
  rank: 6,
169
169
  requireAvailable: true
170
- // The Kiro chat session only takes initial prompts via the in-session
171
- // `/chat new <prompt>` slash command, not a CLI argument.
170
+ // Kiro only takes initial prompts via the in-session `/chat new` command.
172
171
  },
173
172
  {
174
173
  id: 'mistral-vibe',
@@ -179,14 +178,22 @@ const DEFAULTS: IAgent[] = [
179
178
  rank: 7,
180
179
  requireAvailable: true,
181
180
  promptArgs: []
181
+ },
182
+ {
183
+ id: 'pi',
184
+ label: 'Pi',
185
+ caption: 'Start Pi in a new terminal.',
186
+ command: 'pi',
187
+ icon: BUILTIN_AGENT_ICONS.pi,
188
+ rank: 8,
189
+ requireAvailable: true,
190
+ promptArgs: []
182
191
  }
183
192
  ];
184
193
 
185
194
  /**
186
- * The built-in agents projected into the JSON settings shape
187
- * ({@link IAgentSettings}), dropping the runtime-only {@link LabIcon}.
188
- * {@link registerLauncherSchemaDefaults} injects this as the `agents` setting's
189
- * schema default so the Settings Editor shows the shipped list.
195
+ * The built-in agents projected into the settings shape (no runtime LabIcon),
196
+ * injected as the `agents` schema default so the Settings Editor shows them.
190
197
  */
191
198
  export function defaultAgentSettings(): IAgentSettings[] {
192
199
  return DEFAULTS.map(agent => {
@@ -198,8 +205,6 @@ export function defaultAgentSettings(): IAgentSettings[] {
198
205
  rank: agent.rank,
199
206
  requireAvailable: agent.requireAvailable
200
207
  };
201
- // Omit `promptArgs` for agents that don't take an inline prompt, so the
202
- // default doesn't show a prompt form they lack.
203
208
  if (agent.promptArgs !== undefined) {
204
209
  entry.promptArgs = agent.promptArgs;
205
210
  }
@@ -215,15 +220,10 @@ function resolveIcon(id: string, iconSvg: string | undefined): LabIcon {
215
220
  }
216
221
 
217
222
  /**
218
- * Merge xtralab's defaults with the user's settings. Default entries keep
219
- * their built-in fields unless explicitly overridden; user-only entries are
220
- * appended. `enabled: false` filters an entry out of the result entirely
221
- * (so callers don't need to check the flag again).
222
- *
223
- * Overriding a built-in agent's `command` also turns its `requireAvailable`
224
- * off, so the card survives the launcher's `which`-based availability filter
225
- * even when the new command is a shell alias the server can't resolve. See the
226
- * built-in branch below for the rationale.
223
+ * Merge xtralab's defaults with the user's settings: defaults keep their
224
+ * fields unless overridden, user-only ids are appended, and `enabled: false`
225
+ * removes an entry entirely. Overriding a built-in's `command` also turns
226
+ * `requireAvailable` off so an aliased command survives the `which` filter.
227
227
  */
228
228
  export function mergeAgents(overrides: IAgentSettings[]): IAgent[] {
229
229
  const overrideById = new Map(overrides.map(entry => [entry.id, entry]));
@@ -249,24 +249,10 @@ export function mergeAgents(overrides: IAgentSettings[]): IAgent[] {
249
249
  ? resolveIcon(base.id, override.iconSvg)
250
250
  : base.icon,
251
251
  rank: override.rank ?? base.rank,
252
- // The availability filter exists to prune xtralab's *built-in* command
253
- // from the launcher when it isn't installed. Once the user points the
254
- // agent at their own command — e.g. the `ccm` alias wrapping `claude
255
- // --effort=max …` — the server's `shutil.which` probe can't see it
256
- // (aliases and shell functions only exist inside an interactive shell),
257
- // so keeping the check on would wrongly hide the card. We therefore only
258
- // require availability while the command is still xtralab's default; a
259
- // user-chosen command is trusted and always shown. An explicit
260
- // `requireAvailable` still applies to the unchanged default command, so a
261
- // user can alias `claude` itself and set `requireAvailable: false` (or
262
- // force the check back on).
263
252
  requireAvailable:
264
253
  command === base.command
265
254
  ? (override.requireAvailable ?? base.requireAvailable)
266
255
  : false,
267
- // `null` is the explicit way to opt out of an agent's default prompt
268
- // support; an absent key keeps the default. `undefined` from `??` is
269
- // pruned below.
270
256
  promptArgs:
271
257
  override.promptArgs === null
272
258
  ? undefined
@@ -274,10 +260,6 @@ export function mergeAgents(overrides: IAgentSettings[]): IAgent[] {
274
260
  });
275
261
  }
276
262
 
277
- // What remains in `overrideById` are entries with ids that don't match a
278
- // default — treat them as new agents the user is adding. Skip silently
279
- // when a required field is missing rather than throwing; the settings
280
- // schema validates the shape, so this is just a defensive fallback.
281
263
  let nextRank =
282
264
  merged.reduce((max, agent) => Math.max(max, agent.rank), -1) + 1;
283
265
  for (const entry of overrideById.values()) {
@@ -2,15 +2,9 @@ import { URLExt } from '@jupyterlab/coreutils';
2
2
  import { ServerConnection } from '@jupyterlab/services';
3
3
 
4
4
  /**
5
- * Hit the xtralab server extension's `which`-proxy endpoint and turn the
6
- * response into a Set of commands that resolved to a real binary on the
7
- * server's `$PATH`. The frontend uses this set to filter the launcher's
8
- * agent cards.
9
- *
10
- * On any failure (server extension not loaded, network error, malformed
11
- * response) the returned Set is `null` — callers should treat that as
12
- * "availability unknown" and either fall back to showing all agents or
13
- * surface the error, depending on context.
5
+ * Ask the server extension's `which`-proxy endpoint which commands resolve on
6
+ * its `$PATH`. Returns `null` on any failure — callers should treat that as
7
+ * "availability unknown", not as "nothing available".
14
8
  */
15
9
  export async function fetchAvailableCommands(
16
10
  commands: string[]