xtralab 0.11.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -229
- package/lib/agentSessions.d.ts +6 -2
- package/lib/askAgent/editorSelection.d.ts +53 -0
- package/lib/askAgent/editorSelection.js +95 -0
- package/lib/askAgent/icons.d.ts +7 -0
- package/lib/askAgent/icons.js +15 -0
- package/lib/askAgent/index.d.ts +32 -0
- package/lib/askAgent/index.js +741 -0
- package/lib/askAgent/popup.d.ts +83 -0
- package/lib/askAgent/popup.js +221 -0
- package/lib/askAgent/prompt.d.ts +24 -0
- package/lib/askAgent/prompt.js +116 -0
- package/lib/askAgent/queue.d.ts +93 -0
- package/lib/askAgent/queue.js +207 -0
- package/lib/askAgent/queuePanel.d.ts +76 -0
- package/lib/askAgent/queuePanel.js +188 -0
- package/lib/askAgent/targetPicker.d.ts +58 -0
- package/lib/askAgent/targetPicker.js +74 -0
- package/lib/askAgent/tokens.d.ts +124 -0
- package/lib/askAgent/tokens.js +13 -0
- package/lib/commandBar/index.d.ts +12 -8
- package/lib/commandBar/index.js +58 -41
- package/lib/editorBreadcrumbs/index.js +9 -4
- package/lib/editorBreadcrumbs/widget.d.ts +7 -7
- package/lib/editorBreadcrumbs/widget.js +6 -8
- package/lib/fileBrowser/commands.d.ts +2 -8
- package/lib/fileBrowser/commands.js +39 -30
- package/lib/fileBrowser/contents.d.ts +8 -3
- package/lib/fileBrowser/fileBrowser.d.ts +2 -1
- package/lib/fileBrowser/widget.d.ts +5 -9
- package/lib/fileBrowser/widget.js +1 -1
- package/lib/fileTypeIcons/index.js +5 -2
- package/lib/git/api.d.ts +0 -26
- package/lib/git/api.js +2 -44
- package/lib/git/askRequest.d.ts +23 -0
- package/lib/git/askRequest.js +90 -0
- package/lib/git/commands.d.ts +14 -2
- package/lib/git/commands.js +16 -13
- package/lib/git/diffModel.d.ts +1 -9
- package/lib/git/diffModel.js +1 -1
- package/lib/git/diffProvider.js +8 -3
- package/lib/git/diffSurface.d.ts +19 -2
- package/lib/git/diffSurface.js +64 -18
- package/lib/git/diffTheme.d.ts +0 -4
- package/lib/git/diffTheme.js +1 -1
- package/lib/git/diffWidget.d.ts +9 -0
- package/lib/git/diffWidget.js +38 -10
- package/lib/git/imageDiff.d.ts +17 -4
- package/lib/git/imageDiff.js +90 -30
- package/lib/git/index.js +7 -2
- package/lib/git/notebookDiff.d.ts +10 -67
- package/lib/git/notebookDiff.js +62 -69
- package/lib/git/tokens.d.ts +5 -2
- package/lib/highlight/index.js +12 -8
- package/lib/index.js +2 -0
- package/lib/launcher/agents.d.ts +11 -1
- package/lib/launcher/agents.js +26 -8
- package/lib/launcher/commands.d.ts +6 -2
- package/lib/launcher/dashboard.d.ts +8 -1
- package/lib/launcher/dashboard.js +39 -33
- package/lib/launcher/editorRegistry.js +5 -3
- package/lib/launcher/editors.d.ts +24 -7
- package/lib/launcher/editors.js +14 -5
- package/lib/launcher/icons.d.ts +0 -37
- package/lib/launcher/icons.js +11 -12
- package/lib/launcher/index.js +5 -2
- package/lib/launcher/invocation.d.ts +0 -15
- package/lib/launcher/invocation.js +1 -1
- package/lib/launcher/schemaDefaults.d.ts +3 -1
- package/lib/launcher/schemaDefaults.js +3 -1
- package/lib/mimeData.d.ts +7 -0
- package/lib/mimeData.js +16 -0
- package/lib/omnibox/index.d.ts +4 -0
- package/lib/omnibox/index.js +43 -2
- package/lib/omnibox/model.d.ts +43 -14
- package/lib/omnibox/model.js +117 -14
- package/lib/omnibox/recents.d.ts +65 -0
- package/lib/omnibox/recents.js +109 -0
- package/lib/omnibox/tokens.d.ts +6 -2
- package/lib/omnibox/widget.d.ts +23 -6
- package/lib/omnibox/widget.js +26 -7
- package/lib/showOutput/index.js +15 -18
- package/lib/terminals/agentTerminals.d.ts +63 -0
- package/lib/terminals/agentTerminals.js +157 -0
- package/lib/terminals/index.d.ts +7 -1
- package/lib/terminals/index.js +50 -30
- package/lib/terminals/model.d.ts +11 -2
- package/lib/terminals/model.js +27 -8
- package/lib/terminals/tokens.d.ts +66 -0
- package/lib/terminals/tokens.js +7 -0
- package/lib/terminals/widget.d.ts +3 -6
- package/lib/terminals/widget.js +1 -1
- package/lib/topBar/index.js +3 -3
- package/lib/walkthrough/index.js +14 -3
- package/lib/walkthrough/panel.d.ts +12 -4
- package/lib/walkthrough/panel.js +9 -3
- package/package.json +49 -51
- package/schema/command-bar.json +10 -0
- package/schema/launcher.json +4 -4
- package/schema/omnibox.json +15 -0
- package/src/agentSessions.ts +6 -2
- package/src/askAgent/editorSelection.ts +138 -0
- package/src/askAgent/icons.ts +16 -0
- package/src/askAgent/index.ts +915 -0
- package/src/askAgent/popup.tsx +407 -0
- package/src/askAgent/prompt.ts +144 -0
- package/src/askAgent/queue.ts +269 -0
- package/src/askAgent/queuePanel.tsx +418 -0
- package/src/askAgent/targetPicker.tsx +209 -0
- package/src/askAgent/tokens.ts +139 -0
- package/src/commandBar/index.ts +75 -49
- package/src/editorBreadcrumbs/index.ts +14 -4
- package/src/editorBreadcrumbs/widget.ts +11 -9
- package/src/fileBrowser/commands.ts +45 -31
- package/src/fileBrowser/contents.ts +7 -3
- package/src/fileBrowser/fileBrowser.tsx +1 -1
- package/src/fileBrowser/widget.tsx +5 -3
- package/src/fileTypeIcons/index.ts +6 -3
- package/src/git/api.ts +2 -61
- package/src/git/askRequest.ts +113 -0
- package/src/git/commands.ts +41 -14
- package/src/git/diffModel.ts +1 -1
- package/src/git/diffProvider.tsx +11 -3
- package/src/git/diffSurface.tsx +114 -22
- package/src/git/diffTheme.ts +1 -1
- package/src/git/diffWidget.tsx +54 -7
- package/src/git/imageDiff.tsx +169 -58
- package/src/git/index.ts +10 -2
- package/src/git/notebookDiff.tsx +91 -57
- package/src/git/tokens.ts +4 -2
- package/src/highlight/index.ts +12 -8
- package/src/index.ts +2 -0
- package/src/launcher/agents.ts +31 -7
- package/src/launcher/commands.ts +6 -2
- package/src/launcher/dashboard.tsx +86 -38
- package/src/launcher/editorRegistry.ts +10 -4
- package/src/launcher/editors.ts +34 -9
- package/src/launcher/icons.ts +11 -12
- package/src/launcher/index.ts +5 -2
- package/src/launcher/invocation.ts +1 -1
- package/src/launcher/schemaDefaults.ts +3 -1
- package/src/mimeData.ts +20 -0
- package/src/omnibox/index.ts +48 -1
- package/src/omnibox/model.ts +173 -33
- package/src/omnibox/recents.ts +155 -0
- package/src/omnibox/tokens.ts +6 -2
- package/src/omnibox/widget.tsx +54 -13
- package/src/showOutput/index.ts +20 -23
- package/src/terminals/agentTerminals.ts +238 -0
- package/src/terminals/index.ts +54 -30
- package/src/terminals/model.ts +28 -7
- package/src/terminals/tokens.ts +77 -0
- package/src/terminals/widget.tsx +4 -2
- package/src/topBar/index.ts +12 -6
- package/src/walkthrough/index.ts +19 -4
- package/src/walkthrough/panel.ts +15 -5
- package/style/askAgent.css +544 -0
- package/style/commandBar.css +12 -33
- package/style/git.css +72 -31
- package/style/index.css +1 -0
- package/style/index.js +2 -0
- package/style/topBar.css +4 -4
package/README.md
CHANGED
|
@@ -1,261 +1,69 @@
|
|
|
1
1
|

|
|
2
2
|
|
|
3
|
-
An opinionated JupyterLab meta-package for
|
|
3
|
+
An opinionated JupyterLab meta-package for coding agents.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
xtralab reshapes JupyterLab around terminal coding agents. It bundles a curated
|
|
6
|
+
set of extensions, a path-first file browser, rich git diffs, an agent launcher,
|
|
7
|
+
a Model Context Protocol server that lets agents drive the app, and a quieter
|
|
8
|
+
set of defaults. It builds on [`ajlab`](https://github.com/jtpio/ajlab), the
|
|
9
|
+
agent-ready JupyterLab base.
|
|
8
10
|
|
|
9
|
-

|
|
12
|
+
|
|
13
|
+
The [documentation](https://jtpio.github.io/xtralab/) covers everything in
|
|
14
|
+
detail: [installation](https://jtpio.github.io/xtralab/installation/), a
|
|
15
|
+
[tour of the features](https://jtpio.github.io/xtralab/getting-started/),
|
|
16
|
+
[connecting agents over MCP](https://jtpio.github.io/xtralab/agents/mcp/),
|
|
17
|
+
[agent skills](https://jtpio.github.io/xtralab/agents/skills/), and the
|
|
18
|
+
[desktop app](https://jtpio.github.io/xtralab/desktop/).
|
|
10
19
|
|
|
11
20
|
## Install
|
|
12
21
|
|
|
13
22
|
```bash
|
|
14
23
|
pip install xtralab
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
## Usage
|
|
18
|
-
|
|
19
|
-
### As a JupyterLab package
|
|
20
|
-
|
|
21
|
-
Once installed, start JupyterLab the usual way:
|
|
22
|
-
|
|
23
|
-
```bash
|
|
24
24
|
jupyter lab
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
from `main` are uploaded as workflow artifacts on the
|
|
33
|
-
[Actions tab](https://github.com/jtpio/xtralab/actions). See
|
|
34
|
-
[CONTRIBUTING.md](./CONTRIBUTING.md) for the desktop app architecture and local
|
|
35
|
-
build instructions.
|
|
36
|
-
|
|
37
|
-
On macOS the build is not notarized yet (it has no Apple Developer ID), so
|
|
38
|
-
Gatekeeper blocks it on first launch, and agent notifications fall back to
|
|
39
|
-
`osascript`, appearing as "Script Editor". In the meantime you can self-sign it
|
|
40
|
-
for free: this lets it launch and gives native notifications instead, branded as
|
|
41
|
-
xtralab and clickable to jump to the terminal that fired them. Create a
|
|
42
|
-
code-signing certificate once in Keychain Access (Certificate Assistant, Create
|
|
43
|
-
a Certificate, with Identity Type "Self Signed Root" and Certificate Type "Code
|
|
44
|
-
Signing") named `xtralab-selfsign`, then sign the app you copied out of the DMG:
|
|
45
|
-
|
|
46
|
-
```bash
|
|
47
|
-
codesign --force --deep --sign "xtralab-selfsign" --timestamp=none "/Applications/xtralab.app"
|
|
48
|
-
xattr -dr com.apple.quarantine "/Applications/xtralab.app"
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
macOS prompts once to let `codesign` use the key; choose Always Allow. None of
|
|
52
|
-
this will be needed once the app ships with a Developer ID signature.
|
|
53
|
-
|
|
54
|
-
## What's included
|
|
27
|
+
A standalone desktop app (DMG on macOS, AppImage on Linux) ships with every
|
|
28
|
+
tagged release on the
|
|
29
|
+
[releases page](https://github.com/jtpio/xtralab/releases/latest); see the
|
|
30
|
+
[desktop app docs](https://jtpio.github.io/xtralab/desktop/) for the
|
|
31
|
+
Gatekeeper notes and how it manages projects and kernels.
|
|
55
32
|
|
|
56
|
-
|
|
57
|
-
JupyterLab base. On top of it, xtralab pulls in JupyterLab 4.6+ with
|
|
58
|
-
`jupyterlab-git`, `jupyterlab-lsp`, `jupyterlab-quickopen`,
|
|
59
|
-
`jupyterlab-search-replace`, `jupyterlab-vim`, and a set of light and dark
|
|
60
|
-
themes (`jupyterlab-cursor-light`, `jupyterlab-cursor-dark`,
|
|
61
|
-
`jupyterlab-day`, `jupyterlab-night`). See
|
|
62
|
-
[`pyproject.toml`](./pyproject.toml) for the full list and pinned versions.
|
|
33
|
+
## Highlights
|
|
63
34
|
|
|
64
|
-
The
|
|
35
|
+

|
|
65
36
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
Vibe), a collapsible list of changed files, and an Open row for a terminal,
|
|
74
|
-
notebook, console, or your terminal editor (Neovim or Vim).
|
|
75
|
-
- A Terminals panel listing the running terminal sessions, each badged with the
|
|
76
|
-
agent or editor detected inside it. Open terminal tabs in the main area carry
|
|
77
|
-
the same icon.
|
|
78
|
-
- Sidebar toggle buttons in the top bar for the left and right areas.
|
|
37
|
+
The launcher starts any agent installed on your machine, with an optional
|
|
38
|
+
prompt and a **Changes** list that jumps straight into a side-by-side git diff
|
|
39
|
+
you can edit in place. Select code in an editor, a notebook, or a diff to send
|
|
40
|
+
a prompt about it to a new or already-running agent, or queue prompts while
|
|
41
|
+
you read. One omnibox searches files and commands, the Terminals panel shows
|
|
42
|
+
which agent runs where, and quieter defaults keep the workspace calm. The
|
|
43
|
+
[documentation](https://jtpio.github.io/xtralab/) walks through each of these.
|
|
79
44
|
|
|
80
|
-
|
|
81
|
-
quieter workspace, the activity bar sits at the top, and autocompletion,
|
|
82
|
-
continuous LSP hinting, code folding, and gitignore-aware quick open are on by
|
|
83
|
-
default. See [the bundled labconfig overrides](./jupyter-config/labconfig) for
|
|
84
|
-
the full set.
|
|
85
|
-
|
|
86
|
-
## Connecting agents to Jupyter (MCP)
|
|
45
|
+
## Connecting agents to JupyterLab (MCP)
|
|
87
46
|
|
|
88
47
|
xtralab runs a [Model Context Protocol][mcp-spec] server inside JupyterLab,
|
|
89
|
-
provided by [`jupyter-server-mcp`][mcp]
|
|
90
|
-
|
|
91
|
-
|
|
48
|
+
provided by [`jupyter-server-mcp`][mcp], so an agent can open files, run
|
|
49
|
+
cells, and read notebooks. Register the bundled proxy from a terminal inside
|
|
50
|
+
xtralab:
|
|
92
51
|
|
|
93
52
|
```bash
|
|
94
53
|
claude mcp add jupyter -- jupyter-server-mcp-proxy
|
|
95
|
-
codex mcp add jupyter -- jupyter-server-mcp-proxy
|
|
96
|
-
copilot mcp add jupyter -- jupyter-server-mcp-proxy
|
|
97
54
|
```
|
|
98
55
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
surfaces these commands with copy buttons, filtered to the agents you have
|
|
103
|
-
installed.
|
|
104
|
-
|
|
105
|
-
See the [`jupyter-server-mcp` README][mcp] for other MCP clients.
|
|
56
|
+
The launcher surfaces the matching command for every agent you have
|
|
57
|
+
installed, and the [MCP docs page](https://jtpio.github.io/xtralab/agents/mcp/)
|
|
58
|
+
covers the details.
|
|
106
59
|
|
|
107
60
|
[mcp]: https://github.com/jupyter-ai-contrib/jupyter-server-mcp
|
|
108
61
|
[mcp-spec]: https://modelcontextprotocol.io
|
|
109
62
|
|
|
110
|
-
## Agent skills
|
|
111
|
-
|
|
112
|
-
xtralab ships [Agent Skills][skills] at [`agent-skill/`](./agent-skill) that
|
|
113
|
-
teach any coding agent how to work with the app:
|
|
114
|
-
|
|
115
|
-
- **customize-jupyterlab** turns plain-English requests like "hide the status
|
|
116
|
-
bar", "change the theme", or "add a language server" into the right
|
|
117
|
-
config-file edits.
|
|
118
|
-
- **guided-code-walkthrough** drives the running app over the MCP command
|
|
119
|
-
bridge (see the section above) to open files, jump to and highlight specific
|
|
120
|
-
lines (`xtralab:highlight-lines`), and build a read-only Walkthrough panel of
|
|
121
|
-
prose, snippets, diagrams, and clickable code links beside the editor
|
|
122
|
-
(`xtralab:walkthrough`, no notebook or kernel needed), so the agent can _show_
|
|
123
|
-
you something at your own pace instead of only describing it in chat.
|
|
124
|
-
|
|
125
|
-
The same SKILL.md files work with Claude Code, Codex CLI, Gemini CLI, GitHub
|
|
126
|
-
Copilot, and other tools that read the Agent Skills format.
|
|
127
|
-
|
|
128
|
-
For Claude Code, this repository doubles as a one-plugin marketplace. Inside a
|
|
129
|
-
Claude Code session:
|
|
130
|
-
|
|
131
|
-
```text
|
|
132
|
-
/plugin marketplace add jtpio/xtralab
|
|
133
|
-
/plugin install xtralab-skills@xtralab
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
For agents that read from `~/.agents/skills/`, such as Codex CLI or Gemini CLI,
|
|
137
|
-
clone and copy the skill directories:
|
|
138
|
-
|
|
139
|
-
```bash
|
|
140
|
-
git clone --depth=1 https://github.com/jtpio/xtralab.git /tmp/xtralab
|
|
141
|
-
mkdir -p ~/.agents/skills
|
|
142
|
-
cp -r /tmp/xtralab/agent-skill/skills/* ~/.agents/skills/
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
See [`agent-skill/README.md`](./agent-skill/README.md) for additional install
|
|
146
|
-
paths, the list of supported agents, and what the skill knows.
|
|
147
|
-
|
|
148
|
-
[skills]: https://agentskills.io
|
|
149
|
-
|
|
150
|
-
## Language servers
|
|
151
|
-
|
|
152
|
-
xtralab ships [`jupyterlab-lsp`][lsp] with two language servers pre-registered:
|
|
153
|
-
|
|
154
|
-
- Python, using [`ty`](https://github.com/astral-sh/ty), which is bundled and
|
|
155
|
-
works out of the box.
|
|
156
|
-
- TypeScript and JavaScript, using `typescript-language-server`, which you
|
|
157
|
-
install yourself:
|
|
158
|
-
|
|
159
|
-
```bash
|
|
160
|
-
npm install -g typescript-language-server typescript
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
Restart JupyterLab afterwards to pick it up.
|
|
164
|
-
|
|
165
|
-
To enable another server (bash, yaml, json, pyright, and more), install its
|
|
166
|
-
binary and drop a JSON spec into a `jupyter_server_config.d/` directory. For
|
|
167
|
-
`pip install xtralab`, run `jupyter --paths` to find one (typically
|
|
168
|
-
`~/.jupyter/jupyter_server_config.d/`). For the desktop app, use
|
|
169
|
-
`~/Library/Application Support/xtralab/jupyter/config/jupyter_server_config.d/`
|
|
170
|
-
on macOS or `~/.config/xtralab/jupyter/config/jupyter_server_config.d/` on
|
|
171
|
-
Linux.
|
|
172
|
-
|
|
173
|
-
```json
|
|
174
|
-
{
|
|
175
|
-
"LanguageServerManager": {
|
|
176
|
-
"language_servers": {
|
|
177
|
-
"bash-language-server": {
|
|
178
|
-
"version": 2,
|
|
179
|
-
"argv": ["bash-language-server", "start"],
|
|
180
|
-
"languages": ["bash", "sh"],
|
|
181
|
-
"mime_types": ["text/x-sh", "application/x-sh"],
|
|
182
|
-
"display_name": "bash-language-server"
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
See the [`jupyterlab-lsp` documentation][lsp-config] for the full spec.
|
|
190
|
-
|
|
191
|
-
[lsp]: https://github.com/jupyter-lsp/jupyterlab-lsp
|
|
192
|
-
[lsp-config]: https://jupyterlab-lsp.readthedocs.io/en/latest/Configuring.html
|
|
193
|
-
|
|
194
|
-
## Customizing the launcher
|
|
195
|
-
|
|
196
|
-
Open `Settings → Settings Editor → xtralab launcher` to override, hide, or add
|
|
197
|
-
launcher entries. The editor shows xtralab's full built-in list as the default
|
|
198
|
-
for both the agents and editors settings, so you can read every shipped entry
|
|
199
|
-
and copy one into your user preferences to tweak in place. Both lists merge with
|
|
200
|
-
xtralab's defaults by `id`, so an override only needs the `id` plus the fields
|
|
201
|
-
you want to change.
|
|
202
|
-
|
|
203
|
-
### Agents
|
|
204
|
-
|
|
205
|
-
Edit the `agents` array:
|
|
206
|
-
|
|
207
|
-
```jsonc
|
|
208
|
-
{
|
|
209
|
-
"agents": [
|
|
210
|
-
// Hide an agent
|
|
211
|
-
{ "id": "kiro", "enabled": false },
|
|
212
|
-
|
|
213
|
-
// Override an agent's command (e.g. point Claude at a shell alias)
|
|
214
|
-
{ "id": "claude", "command": "cl", "requireAvailable": false },
|
|
215
|
-
|
|
216
|
-
// Add a new agent; promptArgs: [] appends the prompt as a positional arg
|
|
217
|
-
{ "id": "aider", "label": "Aider", "command": "aider", "promptArgs": [] }
|
|
218
|
-
]
|
|
219
|
-
}
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
Fields: `id` (required), `label`, `caption`, `command`, `promptArgs` (how the
|
|
223
|
-
prompt is spliced: `[]` for positional, `["--flag"]` for flagged, `null` to opt
|
|
224
|
-
out), `iconSvg`, `rank`, `enabled`, `requireAvailable`.
|
|
225
|
-
|
|
226
|
-
### Editors
|
|
227
|
-
|
|
228
|
-
The **Open** section's terminal-editor tile (Neovim, falling back to Vim) is
|
|
229
|
-
configured the same way through an `editors` array:
|
|
230
|
-
|
|
231
|
-
```jsonc
|
|
232
|
-
{
|
|
233
|
-
"editors": [
|
|
234
|
-
// Hide Neovim so the tile falls back to Vim (or disappears if Vim
|
|
235
|
-
// isn't installed either)
|
|
236
|
-
{ "id": "nvim", "enabled": false },
|
|
237
|
-
|
|
238
|
-
// Add Helix, preferred over the built-ins
|
|
239
|
-
{
|
|
240
|
-
"id": "helix",
|
|
241
|
-
"label": "Helix",
|
|
242
|
-
"command": "hx",
|
|
243
|
-
"rank": -1,
|
|
244
|
-
"iconSvg": "<svg>…</svg>"
|
|
245
|
-
}
|
|
246
|
-
]
|
|
247
|
-
}
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
The launcher shows a single tile: the first editor, by `rank`, whose `command`
|
|
251
|
-
is on `PATH`. Disable both built-ins (`nvim`, `vim`) to remove the tile
|
|
252
|
-
entirely. Fields: `id` (required), `label`, `caption`, `command`, `iconSvg`,
|
|
253
|
-
`rank`, `enabled`, `requireAvailable`.
|
|
254
|
-
|
|
255
63
|
## Contributing
|
|
256
64
|
|
|
257
|
-
See [CONTRIBUTING.md](./CONTRIBUTING.md) for the development setup, the
|
|
258
|
-
desktop app architecture, and the
|
|
65
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md) for the development setup, the
|
|
66
|
+
desktop app architecture, and the documentation sources in [`docs/`](./docs).
|
|
259
67
|
|
|
260
68
|
## License
|
|
261
69
|
|
package/lib/agentSessions.d.ts
CHANGED
|
@@ -26,9 +26,13 @@ export interface IAgentSessions {
|
|
|
26
26
|
* this ran).
|
|
27
27
|
*/
|
|
28
28
|
get(sessionName: string): string | null;
|
|
29
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* Record that a session was launched as the given agent command.
|
|
31
|
+
*/
|
|
30
32
|
set(sessionName: string, command: string): void;
|
|
31
|
-
/**
|
|
33
|
+
/**
|
|
34
|
+
* Forget a session — called when it shuts down so the map stays bounded.
|
|
35
|
+
*/
|
|
32
36
|
delete(sessionName: string): void;
|
|
33
37
|
/**
|
|
34
38
|
* Emitted with the affected session name whenever a record is added or
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { JupyterFrontEnd } from '@jupyterlab/application';
|
|
2
|
+
import type { EditorView } from '@codemirror/view';
|
|
3
|
+
import type { Widget } from '@lumino/widgets';
|
|
4
|
+
import type { IAskAgentRequest } from './tokens';
|
|
5
|
+
/**
|
|
6
|
+
* A CodeMirror view a selection can be asked about, plus the context needed
|
|
7
|
+
* to describe it to an agent: the document path and, for notebooks, which
|
|
8
|
+
* cell the view belongs to.
|
|
9
|
+
*/
|
|
10
|
+
interface IEditorTarget {
|
|
11
|
+
view: EditorView;
|
|
12
|
+
path: string;
|
|
13
|
+
cell?: {
|
|
14
|
+
index: number;
|
|
15
|
+
type: string;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Resolve the CodeMirror view a selection in `widget` would live in, or
|
|
20
|
+
* `null` when the widget holds no such editor. Two widget shapes are
|
|
21
|
+
* recognized:
|
|
22
|
+
*
|
|
23
|
+
* - a notebook panel, where the active cell carries the editor and
|
|
24
|
+
* `activeCellIndex` names its 0-based position in the nbformat `cells`
|
|
25
|
+
* array. A rendered markdown cell keeps its (hidden) editor, but a
|
|
26
|
+
* selection in the rendered HTML never passes {@link domSelectionInView},
|
|
27
|
+
* so no pill appears there; and
|
|
28
|
+
* - a document widget whose content is a `FileEditor`. The wrapper is not
|
|
29
|
+
* checked with `instanceof DocumentWidget`: `@jupyterlab/docregistry` is
|
|
30
|
+
* not a core singleton, so another copy of the class could be in play.
|
|
31
|
+
* `FileEditor` and `NotebookPanel` come from singleton packages.
|
|
32
|
+
*/
|
|
33
|
+
export declare function resolveEditorTarget(widget: Widget | null): IEditorTarget | null;
|
|
34
|
+
/**
|
|
35
|
+
* Whether the current DOM selection lives inside `view`. Guards the
|
|
36
|
+
* selection-change listener against selections made elsewhere (a terminal,
|
|
37
|
+
* a sidebar, another notebook cell) while the widget owning `view` happens
|
|
38
|
+
* to be the shell's current widget.
|
|
39
|
+
*/
|
|
40
|
+
export declare function domSelectionInView(view: EditorView, selection: Selection): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Build an ask-agent request from the current selection in the shell's
|
|
43
|
+
* current widget — a file editor or the active notebook cell — or return
|
|
44
|
+
* `null` when that widget holds no CodeMirror document editor.
|
|
45
|
+
*
|
|
46
|
+
* With `allowEmpty`, a collapsed selection falls back to the cursor's line
|
|
47
|
+
* (used by the command/shortcut path so it works without a mouse
|
|
48
|
+
* selection); otherwise an empty selection yields `null`.
|
|
49
|
+
*/
|
|
50
|
+
export declare function editorAskRequest(app: JupyterFrontEnd, options?: {
|
|
51
|
+
allowEmpty?: boolean;
|
|
52
|
+
}): IAskAgentRequest | null;
|
|
53
|
+
export {};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { CodeMirrorEditor } from '@jupyterlab/codemirror';
|
|
2
|
+
import { FileEditor } from '@jupyterlab/fileeditor';
|
|
3
|
+
import { NotebookPanel } from '@jupyterlab/notebook';
|
|
4
|
+
/**
|
|
5
|
+
* Resolve the CodeMirror view a selection in `widget` would live in, or
|
|
6
|
+
* `null` when the widget holds no such editor. Two widget shapes are
|
|
7
|
+
* recognized:
|
|
8
|
+
*
|
|
9
|
+
* - a notebook panel, where the active cell carries the editor and
|
|
10
|
+
* `activeCellIndex` names its 0-based position in the nbformat `cells`
|
|
11
|
+
* array. A rendered markdown cell keeps its (hidden) editor, but a
|
|
12
|
+
* selection in the rendered HTML never passes {@link domSelectionInView},
|
|
13
|
+
* so no pill appears there; and
|
|
14
|
+
* - a document widget whose content is a `FileEditor`. The wrapper is not
|
|
15
|
+
* checked with `instanceof DocumentWidget`: `@jupyterlab/docregistry` is
|
|
16
|
+
* not a core singleton, so another copy of the class could be in play.
|
|
17
|
+
* `FileEditor` and `NotebookPanel` come from singleton packages.
|
|
18
|
+
*/
|
|
19
|
+
export function resolveEditorTarget(widget) {
|
|
20
|
+
if (widget instanceof NotebookPanel) {
|
|
21
|
+
const cell = widget.content.activeCell;
|
|
22
|
+
if (cell !== null && cell.editor instanceof CodeMirrorEditor) {
|
|
23
|
+
return {
|
|
24
|
+
view: cell.editor.editor,
|
|
25
|
+
path: widget.context.path,
|
|
26
|
+
cell: { index: widget.content.activeCellIndex, type: cell.model.type }
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
const content = widget === null || widget === void 0 ? void 0 : widget.content;
|
|
32
|
+
if (content instanceof FileEditor &&
|
|
33
|
+
content.editor instanceof CodeMirrorEditor) {
|
|
34
|
+
return { view: content.editor.editor, path: content.context.path };
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Whether the current DOM selection lives inside `view`. Guards the
|
|
40
|
+
* selection-change listener against selections made elsewhere (a terminal,
|
|
41
|
+
* a sidebar, another notebook cell) while the widget owning `view` happens
|
|
42
|
+
* to be the shell's current widget.
|
|
43
|
+
*/
|
|
44
|
+
export function domSelectionInView(view, selection) {
|
|
45
|
+
var _a;
|
|
46
|
+
const node = selection.anchorNode;
|
|
47
|
+
const element = node instanceof Element ? node : ((_a = node === null || node === void 0 ? void 0 : node.parentElement) !== null && _a !== void 0 ? _a : null);
|
|
48
|
+
return element !== null && view.dom.contains(element);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Build an ask-agent request from the current selection in the shell's
|
|
52
|
+
* current widget — a file editor or the active notebook cell — or return
|
|
53
|
+
* `null` when that widget holds no CodeMirror document editor.
|
|
54
|
+
*
|
|
55
|
+
* With `allowEmpty`, a collapsed selection falls back to the cursor's line
|
|
56
|
+
* (used by the command/shortcut path so it works without a mouse
|
|
57
|
+
* selection); otherwise an empty selection yields `null`.
|
|
58
|
+
*/
|
|
59
|
+
export function editorAskRequest(app, options = {}) {
|
|
60
|
+
const target = resolveEditorTarget(app.shell.currentWidget);
|
|
61
|
+
if (target === null) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
const { view, path, cell } = target;
|
|
65
|
+
const { state } = view;
|
|
66
|
+
const main = state.selection.main;
|
|
67
|
+
if (main.empty && options.allowEmpty !== true) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
const startLine = state.doc.lineAt(main.from).number;
|
|
71
|
+
let endLine;
|
|
72
|
+
let text;
|
|
73
|
+
if (main.empty) {
|
|
74
|
+
endLine = startLine;
|
|
75
|
+
text = state.doc.lineAt(main.from).text;
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
// A selection that ends exactly at the start of a line (the common
|
|
79
|
+
// "drag over whole lines" gesture) should not count that line.
|
|
80
|
+
const endPos = main.to === state.doc.lineAt(main.to).from ? main.to - 1 : main.to;
|
|
81
|
+
endLine = state.doc.lineAt(endPos).number;
|
|
82
|
+
text = state.sliceDoc(main.from, main.to);
|
|
83
|
+
}
|
|
84
|
+
// Anchor the popup at the selection head (where the cursor ended up).
|
|
85
|
+
// `coordsAtPos` returns `null` for positions scrolled out of view.
|
|
86
|
+
const coords = view.coordsAtPos(main.head);
|
|
87
|
+
const anchor = coords
|
|
88
|
+
? new DOMRect(coords.left, coords.top, Math.max(1, coords.right - coords.left), Math.max(1, coords.bottom - coords.top))
|
|
89
|
+
: null;
|
|
90
|
+
const context = { path, startLine, endLine, text };
|
|
91
|
+
if (cell !== undefined) {
|
|
92
|
+
context.cell = cell;
|
|
93
|
+
}
|
|
94
|
+
return { context, anchor };
|
|
95
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { LabIcon } from '@jupyterlab/ui-components';
|
|
2
|
+
/**
|
|
3
|
+
* Four-point sparkle used by the ask-agent affordances (selection pill,
|
|
4
|
+
* command palette entry). Drawn in-repo; the `jp-icon3` class lets the
|
|
5
|
+
* active theme recolor the glyph like other JupyterLab UI icons.
|
|
6
|
+
*/
|
|
7
|
+
export declare const askAgentIcon: LabIcon;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { LabIcon } from '@jupyterlab/ui-components';
|
|
2
|
+
/**
|
|
3
|
+
* Four-point sparkle used by the ask-agent affordances (selection pill,
|
|
4
|
+
* command palette entry). Drawn in-repo; the `jp-icon3` class lets the
|
|
5
|
+
* active theme recolor the glyph like other JupyterLab UI icons.
|
|
6
|
+
*/
|
|
7
|
+
export const askAgentIcon = new LabIcon({
|
|
8
|
+
name: 'xtralab:ask-agent',
|
|
9
|
+
svgstr: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
|
10
|
+
<g class="jp-icon3" fill="#616161">
|
|
11
|
+
<path d="M6 1l1 4 4 1-4 1-1 4-1-4-4-1 4-1 1-4z"/>
|
|
12
|
+
<path d="M12 9l.7 2.3L15 12l-2.3.7L12 15l-.7-2.3L9 12l2.3-.7L12 9z"/>
|
|
13
|
+
</g>
|
|
14
|
+
</svg>`
|
|
15
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { JupyterFrontEndPlugin } from '@jupyterlab/application';
|
|
2
|
+
import { IAskAgent } from './tokens';
|
|
3
|
+
/**
|
|
4
|
+
* Select code in a file editor (or pick diff lines) and prompt a coding
|
|
5
|
+
* agent about it.
|
|
6
|
+
*
|
|
7
|
+
* The plugin watches the document selection: a non-empty selection inside a
|
|
8
|
+
* CodeMirror file editor or notebook cell grows a small floating "Ask agent"
|
|
9
|
+
* pill next to the selection. Clicking it (or running `xtralab:ask-agent`,
|
|
10
|
+
* bound to Accel+. in editors and notebooks) opens a popup where the user
|
|
11
|
+
* types an instruction, picks one of the launcher's agents and picks where
|
|
12
|
+
* the prompt goes; submitting either starts that agent in a fresh terminal
|
|
13
|
+
* via `xtralab:start-agent:<id>`, or pastes the prompt into an agent already
|
|
14
|
+
* running in one of the open terminal sessions (via `IAgentTerminals`; the
|
|
15
|
+
* agent CLIs queue prompts that arrive while they are busy). Sends to a
|
|
16
|
+
* running agent stay in the background — focus never leaves the editor, and
|
|
17
|
+
* a success toast offers to open the terminal. Either way the prompt embeds
|
|
18
|
+
* the file path, cell index for notebooks, line range and selected snippet.
|
|
19
|
+
*
|
|
20
|
+
* The popup's Queue button (or Accel+Enter) defers the send instead: the
|
|
21
|
+
* comment lands in a persistent queue, stamped with the popup's selected
|
|
22
|
+
* destination — every queued prompt is independent and may aim at a
|
|
23
|
+
* different agent or session. A right-sidebar panel reviews the queue
|
|
24
|
+
* (edit, retarget, remove) and flushes it in one go, combining prompts that
|
|
25
|
+
* share a destination into a single numbered message. The queue survives
|
|
26
|
+
* page reloads.
|
|
27
|
+
*
|
|
28
|
+
* The same popup is provided on the `IAskAgent` token so the git diff
|
|
29
|
+
* viewers can open it for a selected diff line range.
|
|
30
|
+
*/
|
|
31
|
+
declare const plugin: JupyterFrontEndPlugin<IAskAgent>;
|
|
32
|
+
export default plugin;
|