xtralab 0.1.2 → 0.3.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 +129 -46
- package/lib/fileBrowser/commands.d.ts +2 -2
- package/lib/fileBrowser/commands.js +2 -2
- package/lib/fileBrowser/fileBrowser.js +72 -2
- package/lib/fileBrowser/gitignore.d.ts +29 -0
- package/lib/fileBrowser/gitignore.js +74 -0
- package/lib/fileBrowser/icons.d.ts +4 -4
- package/lib/fileBrowser/icons.js +4 -4
- package/lib/fileBrowser/widget.d.ts +2 -2
- package/lib/fileBrowser/widget.js +2 -2
- package/lib/git/api.d.ts +11 -0
- package/lib/git/api.js +19 -0
- package/lib/git/commands.d.ts +3 -1
- package/lib/git/commands.js +10 -4
- package/lib/git/diffWidget.d.ts +11 -0
- package/lib/git/diffWidget.js +90 -9
- package/lib/git/icons.d.ts +1 -1
- package/lib/git/icons.js +1 -1
- package/lib/git/index.js +22 -6
- package/lib/git/panelComponent.d.ts +3 -1
- package/lib/git/panelComponent.js +6 -3
- package/lib/index.js +9 -1
- package/lib/launcher/agents.d.ts +78 -0
- package/lib/launcher/agents.js +168 -0
- package/lib/launcher/availability.d.ts +12 -0
- package/lib/launcher/availability.js +53 -0
- package/lib/launcher/commands.d.ts +22 -0
- package/lib/launcher/commands.js +89 -0
- package/lib/launcher/dashboard.d.ts +46 -0
- package/lib/launcher/dashboard.js +214 -0
- package/lib/launcher/icons.d.ts +33 -0
- package/lib/launcher/icons.js +113 -0
- package/lib/launcher/index.d.ts +23 -0
- package/lib/launcher/index.js +191 -0
- package/lib/launcher/invocation.d.ts +29 -0
- package/lib/launcher/invocation.js +42 -0
- package/lib/menus/index.d.ts +14 -0
- package/lib/menus/index.js +102 -0
- package/lib/sidebar/index.d.ts +18 -0
- package/lib/sidebar/index.js +155 -0
- package/lib/statusBar/index.d.ts +14 -0
- package/lib/statusBar/index.js +89 -0
- package/lib/statusBar/widget.d.ts +121 -0
- package/lib/statusBar/widget.js +237 -0
- package/package.json +25 -3
- package/schema/launcher.json +70 -0
- package/schema/sidebar.json +57 -0
- package/src/fileBrowser/commands.ts +2 -2
- package/src/fileBrowser/fileBrowser.tsx +76 -2
- package/src/fileBrowser/gitignore.ts +86 -0
- package/src/fileBrowser/icons.ts +4 -4
- package/src/fileBrowser/widget.tsx +3 -3
- package/src/git/api.ts +20 -0
- package/src/git/commands.ts +19 -5
- package/src/git/diffWidget.tsx +109 -13
- package/src/git/icons.ts +1 -1
- package/src/git/index.ts +31 -5
- package/src/git/panelComponent.tsx +17 -5
- package/src/index.ts +9 -1
- package/src/launcher/agents.ts +248 -0
- package/src/launcher/availability.ts +73 -0
- package/src/launcher/commands.ts +103 -0
- package/src/launcher/dashboard.tsx +462 -0
- package/src/launcher/icons.ts +123 -0
- package/src/launcher/index.ts +228 -0
- package/src/launcher/invocation.ts +45 -0
- package/src/menus/index.ts +124 -0
- package/src/sidebar/index.ts +195 -0
- package/src/statusBar/index.ts +115 -0
- package/src/statusBar/widget.tsx +318 -0
- package/style/git.css +4 -8
- package/style/index.css +2 -0
- package/style/index.js +2 -0
- package/style/launcher.css +323 -0
- package/style/statusBar.css +95 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { URLExt } from '@jupyterlab/coreutils';
|
|
2
|
+
import { ServerConnection } from '@jupyterlab/services';
|
|
3
|
+
/**
|
|
4
|
+
* Hit the xtralab server extension's `which`-proxy endpoint and turn the
|
|
5
|
+
* response into a Set of commands that resolved to a real binary on the
|
|
6
|
+
* server's `$PATH`. The frontend uses this set to filter the launcher's
|
|
7
|
+
* agent cards.
|
|
8
|
+
*
|
|
9
|
+
* On any failure (server extension not loaded, network error, malformed
|
|
10
|
+
* response) the returned Set is `null` — callers should treat that as
|
|
11
|
+
* "availability unknown" and either fall back to showing all agents or
|
|
12
|
+
* surface the error, depending on context.
|
|
13
|
+
*/
|
|
14
|
+
export async function fetchAvailableCommands(commands) {
|
|
15
|
+
if (commands.length === 0) {
|
|
16
|
+
return new Set();
|
|
17
|
+
}
|
|
18
|
+
const settings = ServerConnection.makeSettings();
|
|
19
|
+
const url = URLExt.join(settings.baseUrl, 'xtralab', 'agents', 'availability');
|
|
20
|
+
let response;
|
|
21
|
+
try {
|
|
22
|
+
response = await ServerConnection.makeRequest(url, {
|
|
23
|
+
method: 'POST',
|
|
24
|
+
body: JSON.stringify({ commands })
|
|
25
|
+
}, settings);
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
console.warn('xtralab: availability check failed', error);
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
if (!response.ok) {
|
|
32
|
+
console.warn(`xtralab: availability endpoint returned HTTP ${response.status}`);
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
let payload;
|
|
36
|
+
try {
|
|
37
|
+
payload = await response.json();
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
console.warn('xtralab: availability response was not JSON', error);
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
if (!payload || typeof payload !== 'object') {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
const available = new Set();
|
|
47
|
+
for (const [command, path] of Object.entries(payload)) {
|
|
48
|
+
if (typeof path === 'string' && path.length > 0) {
|
|
49
|
+
available.add(command);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return available;
|
|
53
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { JupyterFrontEnd } from '@jupyterlab/application';
|
|
2
|
+
import { type IDisposable } from '@lumino/disposable';
|
|
3
|
+
import type { IAgent } from './agents';
|
|
4
|
+
/**
|
|
5
|
+
* The command id for opening the launcher.
|
|
6
|
+
*/
|
|
7
|
+
export declare const CREATE_LAUNCHER_COMMAND = "launcher:create";
|
|
8
|
+
/**
|
|
9
|
+
* Build a JupyterLab command id for a given agent. Centralised so the
|
|
10
|
+
* plugin and the launcher both use the same names.
|
|
11
|
+
*/
|
|
12
|
+
export declare function agentCommandId(agentId: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Register a JupyterLab command per agent. Each command opens a new
|
|
15
|
+
* terminal via `terminal:create-new` and feeds the agent's shell command
|
|
16
|
+
* into the fresh session as if the user had typed it.
|
|
17
|
+
*
|
|
18
|
+
* Returns a single disposable that tears down every registered command —
|
|
19
|
+
* the launcher disposes the previous set before re-registering on settings
|
|
20
|
+
* changes so the command palette stays in sync with the configured agents.
|
|
21
|
+
*/
|
|
22
|
+
export declare function registerAgentCommands(app: JupyterFrontEnd, agents: IAgent[]): IDisposable;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { DisposableSet } from '@lumino/disposable';
|
|
2
|
+
import { buildAgentInvocation } from './invocation';
|
|
3
|
+
/**
|
|
4
|
+
* The command id for opening the launcher.
|
|
5
|
+
*/
|
|
6
|
+
export const CREATE_LAUNCHER_COMMAND = 'launcher:create';
|
|
7
|
+
/**
|
|
8
|
+
* Build a JupyterLab command id for a given agent. Centralised so the
|
|
9
|
+
* plugin and the launcher both use the same names.
|
|
10
|
+
*/
|
|
11
|
+
export function agentCommandId(agentId) {
|
|
12
|
+
return `xtralab:start-agent:${agentId}`;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Register a JupyterLab command per agent. Each command opens a new
|
|
16
|
+
* terminal via `terminal:create-new` and feeds the agent's shell command
|
|
17
|
+
* into the fresh session as if the user had typed it.
|
|
18
|
+
*
|
|
19
|
+
* Returns a single disposable that tears down every registered command —
|
|
20
|
+
* the launcher disposes the previous set before re-registering on settings
|
|
21
|
+
* changes so the command palette stays in sync with the configured agents.
|
|
22
|
+
*/
|
|
23
|
+
export function registerAgentCommands(app, agents) {
|
|
24
|
+
const disposables = new DisposableSet();
|
|
25
|
+
for (const agent of agents) {
|
|
26
|
+
const disposable = app.commands.addCommand(agentCommandId(agent.id), {
|
|
27
|
+
label: agent.label,
|
|
28
|
+
caption: agent.caption,
|
|
29
|
+
icon: agent.icon,
|
|
30
|
+
execute: async (args) => {
|
|
31
|
+
const cwd = args['cwd'];
|
|
32
|
+
const promptArg = args['prompt'];
|
|
33
|
+
const prompt = typeof promptArg === 'string' ? promptArg : '';
|
|
34
|
+
const invocation = buildAgentInvocation(agent, prompt);
|
|
35
|
+
const main = (await app.commands.execute('terminal:create-new', {
|
|
36
|
+
cwd
|
|
37
|
+
}));
|
|
38
|
+
// `MainAreaWidget.revealed` chains off the `reveal` promise the
|
|
39
|
+
// terminal extension passes in (the xterm.js widget's own `ready`
|
|
40
|
+
// promise), so awaiting it guarantees the Terminal widget has
|
|
41
|
+
// finished its constructor and connected its session listeners.
|
|
42
|
+
await main.revealed;
|
|
43
|
+
const session = main.content.session;
|
|
44
|
+
const sendCommand = () => {
|
|
45
|
+
session.send({
|
|
46
|
+
type: 'stdin',
|
|
47
|
+
content: [invocation + '\r']
|
|
48
|
+
});
|
|
49
|
+
// Give the tab and the status-bar list a meaningful default
|
|
50
|
+
// label. The XTerm widget's `_initialConnection` listener
|
|
51
|
+
// overwrites the title with `Terminal {N}` when the WebSocket
|
|
52
|
+
// first reports `connected`; we connect *after* that listener
|
|
53
|
+
// (and call `applyAgentLabel` after `sendCommand`), so by the
|
|
54
|
+
// time we run the upstream listener has already had its turn.
|
|
55
|
+
// Programs that publish a real xterm title escape sequence
|
|
56
|
+
// still win — `XTerm.onTitleChange` resets the label whenever
|
|
57
|
+
// it fires.
|
|
58
|
+
applyAgentLabel();
|
|
59
|
+
};
|
|
60
|
+
const applyAgentLabel = () => {
|
|
61
|
+
if (main.isDisposed || main.content.isDisposed) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
main.content.title.label = agent.label;
|
|
65
|
+
};
|
|
66
|
+
// Match how the Terminal widget itself sends its `initialCommand`:
|
|
67
|
+
// either fire immediately if the session is already connected, or
|
|
68
|
+
// wait for the next `connectionStatusChanged` that flips it to
|
|
69
|
+
// `connected`. Without this the stdin write races the websocket
|
|
70
|
+
// handshake and the agent command silently disappears.
|
|
71
|
+
if (session.connectionStatus === 'connected') {
|
|
72
|
+
sendCommand();
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
const onStatus = () => {
|
|
76
|
+
if (session.connectionStatus === 'connected') {
|
|
77
|
+
session.connectionStatusChanged.disconnect(onStatus);
|
|
78
|
+
sendCommand();
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
session.connectionStatusChanged.connect(onStatus);
|
|
82
|
+
}
|
|
83
|
+
return main;
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
disposables.add(disposable);
|
|
87
|
+
}
|
|
88
|
+
return disposables;
|
|
89
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import type { CommandRegistry } from '@lumino/commands';
|
|
3
|
+
import { ReactWidget } from '@jupyterlab/ui-components';
|
|
4
|
+
import type { Widget } from '@lumino/widgets';
|
|
5
|
+
import type { IAgent } from './agents';
|
|
6
|
+
/**
|
|
7
|
+
* Public construction options for the launcher dashboard widget. The
|
|
8
|
+
* plugin supplies an `onAgentLaunch` callback that performs the same
|
|
9
|
+
* shell placement the stock JupyterLab launcher does (swap the launcher
|
|
10
|
+
* tab for the freshly created terminal). Keeping it as an opaque
|
|
11
|
+
* callback means the widget itself does not import `app.shell`.
|
|
12
|
+
*/
|
|
13
|
+
export interface ILauncherDashboardOptions {
|
|
14
|
+
commands: CommandRegistry;
|
|
15
|
+
/**
|
|
16
|
+
* The agent list to render. Already filtered by availability + sorted by
|
|
17
|
+
* rank; the widget renders them as-is.
|
|
18
|
+
*/
|
|
19
|
+
agents: IAgent[];
|
|
20
|
+
/**
|
|
21
|
+
* Called after an agent command has returned its top-level widget. The
|
|
22
|
+
* plugin uses this to swap the new terminal into the launcher's tab.
|
|
23
|
+
*/
|
|
24
|
+
onAgentLaunch: (item: Widget) => void;
|
|
25
|
+
/**
|
|
26
|
+
* The git server-relative repo path used by the changes section. Empty
|
|
27
|
+
* string means "use the JupyterLab server's root and let git resolve the
|
|
28
|
+
* enclosing repo" — same convention as the git panel.
|
|
29
|
+
*/
|
|
30
|
+
repoPath: string;
|
|
31
|
+
/**
|
|
32
|
+
* The cwd forwarded to `terminal:create-new` when an agent is launched.
|
|
33
|
+
* Empty string lets the terminal extension default to the server root.
|
|
34
|
+
*/
|
|
35
|
+
cwd: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Lumino host for the React-based launcher dashboard. Mirrors the
|
|
39
|
+
* `ReactWidget` pattern used by the git panel so the rest of the plugin
|
|
40
|
+
* (layout placement, signals, lifecycle) stays consistent.
|
|
41
|
+
*/
|
|
42
|
+
export declare class LauncherDashboard extends ReactWidget {
|
|
43
|
+
constructor(options: ILauncherDashboardOptions);
|
|
44
|
+
protected render(): React.ReactElement;
|
|
45
|
+
private _options;
|
|
46
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { LabIcon, ReactWidget, consoleIcon, notebookIcon, refreshIcon, terminalIcon } from '@jupyterlab/ui-components';
|
|
3
|
+
import { expandStatusFiles, status } from '../git/api';
|
|
4
|
+
import { CommandIDs as GitCommandIDs } from '../git/commands';
|
|
5
|
+
import { agentCommandId } from './commands';
|
|
6
|
+
/**
|
|
7
|
+
* Lumino host for the React-based launcher dashboard. Mirrors the
|
|
8
|
+
* `ReactWidget` pattern used by the git panel so the rest of the plugin
|
|
9
|
+
* (layout placement, signals, lifecycle) stays consistent.
|
|
10
|
+
*/
|
|
11
|
+
export class LauncherDashboard extends ReactWidget {
|
|
12
|
+
constructor(options) {
|
|
13
|
+
super();
|
|
14
|
+
this.addClass('jp-xtralab-Launcher');
|
|
15
|
+
this._options = options;
|
|
16
|
+
}
|
|
17
|
+
render() {
|
|
18
|
+
return React.createElement(LauncherDashboardComponent, { ...this._options });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Refresh interval for the changes list. A bit slower than the git panel
|
|
23
|
+
* (which sits in the sidebar and is always visible) — the launcher tab is
|
|
24
|
+
* only opened intentionally, and the grid does not need to be live.
|
|
25
|
+
*/
|
|
26
|
+
const CHANGES_POLL_INTERVAL_MS = 8000;
|
|
27
|
+
function LauncherDashboardComponent(props) {
|
|
28
|
+
const { commands, agents, onAgentLaunch, repoPath, cwd } = props;
|
|
29
|
+
const [prompt, setPrompt] = React.useState('');
|
|
30
|
+
const [git, setGit] = React.useState({
|
|
31
|
+
loading: true,
|
|
32
|
+
result: null,
|
|
33
|
+
error: null
|
|
34
|
+
});
|
|
35
|
+
const refresh = React.useCallback(async () => {
|
|
36
|
+
try {
|
|
37
|
+
const result = await status(repoPath);
|
|
38
|
+
setGit({ loading: false, result, error: null });
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
setGit({
|
|
42
|
+
loading: false,
|
|
43
|
+
result: null,
|
|
44
|
+
error: error instanceof Error ? error.message : String(error)
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}, [repoPath]);
|
|
48
|
+
React.useEffect(() => {
|
|
49
|
+
void refresh();
|
|
50
|
+
const handle = window.setInterval(() => {
|
|
51
|
+
void refresh();
|
|
52
|
+
}, CHANGES_POLL_INTERVAL_MS);
|
|
53
|
+
return () => window.clearInterval(handle);
|
|
54
|
+
}, [refresh]);
|
|
55
|
+
const launch = React.useCallback(async (agent) => {
|
|
56
|
+
const args = { cwd };
|
|
57
|
+
const trimmed = prompt.trim();
|
|
58
|
+
if (trimmed.length > 0 && agent.promptArgs !== undefined) {
|
|
59
|
+
args.prompt = trimmed;
|
|
60
|
+
}
|
|
61
|
+
const result = (await commands.execute(agentCommandId(agent.id), args));
|
|
62
|
+
if (result) {
|
|
63
|
+
onAgentLaunch(result);
|
|
64
|
+
}
|
|
65
|
+
}, [commands, cwd, prompt, onAgentLaunch]);
|
|
66
|
+
const launchTerminal = React.useCallback(async () => {
|
|
67
|
+
const result = (await commands.execute('terminal:create-new', {
|
|
68
|
+
cwd
|
|
69
|
+
}));
|
|
70
|
+
if (result) {
|
|
71
|
+
onAgentLaunch(result);
|
|
72
|
+
}
|
|
73
|
+
}, [commands, cwd, onAgentLaunch]);
|
|
74
|
+
const launchNotebook = React.useCallback(async () => {
|
|
75
|
+
// No `kernelName` — let the notebook extension fall back to the default
|
|
76
|
+
// kernel or prompt the user, matching the stock launcher's notebook
|
|
77
|
+
// tile behavior.
|
|
78
|
+
const result = (await commands.execute('notebook:create-new', {
|
|
79
|
+
cwd
|
|
80
|
+
}));
|
|
81
|
+
if (result) {
|
|
82
|
+
onAgentLaunch(result);
|
|
83
|
+
}
|
|
84
|
+
}, [commands, cwd, onAgentLaunch]);
|
|
85
|
+
const launchConsole = React.useCallback(async () => {
|
|
86
|
+
const result = (await commands.execute('console:create', {
|
|
87
|
+
cwd
|
|
88
|
+
}));
|
|
89
|
+
if (result) {
|
|
90
|
+
onAgentLaunch(result);
|
|
91
|
+
}
|
|
92
|
+
}, [commands, cwd, onAgentLaunch]);
|
|
93
|
+
const openDiff = React.useCallback((change) => {
|
|
94
|
+
const args = { repoPath, change };
|
|
95
|
+
void commands.execute(GitCommandIDs.openDiff, args);
|
|
96
|
+
}, [commands, repoPath]);
|
|
97
|
+
return (React.createElement("div", { className: "jp-xtralab-Launcher-body" },
|
|
98
|
+
React.createElement(AgentSection, { agents: agents, prompt: prompt, onPromptChange: setPrompt, onLaunch: launch }),
|
|
99
|
+
React.createElement(OpenSection, { onLaunchTerminal: launchTerminal, onLaunchNotebook: launchNotebook, onLaunchConsole: launchConsole }),
|
|
100
|
+
React.createElement(ChangesSection, { git: git, onOpen: openDiff, onRefresh: () => void refresh() })));
|
|
101
|
+
}
|
|
102
|
+
function ChangesSection(props) {
|
|
103
|
+
const { git, onOpen, onRefresh } = props;
|
|
104
|
+
const files = git.result ? expandStatusFiles(git.result.files) : [];
|
|
105
|
+
// Native `<details>` rather than a hand-rolled toggle: we get the
|
|
106
|
+
// disclosure triangle, keyboard handling, and aria-expanded semantics
|
|
107
|
+
// for free, and the user's open/closed choice is tab-local state that
|
|
108
|
+
// we don't need to persist.
|
|
109
|
+
return (React.createElement("details", { className: "jp-xtralab-Launcher-section jp-xtralab-Launcher-changes-section", open: true },
|
|
110
|
+
React.createElement("summary", { className: "jp-xtralab-Launcher-section-summary" },
|
|
111
|
+
React.createElement("span", { className: "jp-xtralab-Launcher-section-title" }, "Changes"),
|
|
112
|
+
files.length > 0 && (React.createElement("span", { className: "jp-xtralab-Launcher-section-count", "aria-label": `${files.length} changed file${files.length === 1 ? '' : 's'}` }, files.length)),
|
|
113
|
+
React.createElement("button", { type: "button", className: "jp-xtralab-Launcher-section-action", "aria-label": "Refresh changes",
|
|
114
|
+
// Stop the click from toggling the surrounding `<details>` —
|
|
115
|
+
// refreshing while the section is open shouldn't collapse it.
|
|
116
|
+
onClick: event => {
|
|
117
|
+
event.preventDefault();
|
|
118
|
+
event.stopPropagation();
|
|
119
|
+
onRefresh();
|
|
120
|
+
} },
|
|
121
|
+
React.createElement(LabIcon.resolveReact, { icon: refreshIcon, tag: "span" }))),
|
|
122
|
+
React.createElement("div", { className: "jp-xtralab-Launcher-section-body" },
|
|
123
|
+
git.loading && files.length === 0 && (React.createElement("p", { className: "jp-xtralab-Launcher-empty" }, "Loading\u2026")),
|
|
124
|
+
!git.loading && git.error !== null && (React.createElement("p", { className: "jp-xtralab-Launcher-empty" }, git.error)),
|
|
125
|
+
!git.loading && git.error === null && files.length === 0 && (React.createElement("p", { className: "jp-xtralab-Launcher-empty" }, "No changes.")),
|
|
126
|
+
files.length > 0 && (React.createElement("ul", { className: "jp-xtralab-Launcher-changes" }, files.map((change, index) => (React.createElement("li", { key: `${change.group}:${change.path}:${index}` },
|
|
127
|
+
React.createElement("button", { type: "button", className: "jp-xtralab-Launcher-change", onClick: () => onOpen(change), title: `Open diff for ${change.path}` },
|
|
128
|
+
React.createElement("span", { className: `jp-xtralab-Launcher-change-badge jp-xtralab-Launcher-change-${change.status}`, "aria-hidden": "true" }, statusBadge(change.status)),
|
|
129
|
+
React.createElement("span", { className: "jp-xtralab-Launcher-change-path" }, change.path),
|
|
130
|
+
React.createElement("span", { className: "jp-xtralab-Launcher-change-group", "aria-hidden": "true" }, change.group === 'staged' ? 'staged' : ''))))))))));
|
|
131
|
+
}
|
|
132
|
+
function statusBadge(status) {
|
|
133
|
+
switch (status) {
|
|
134
|
+
case 'modified':
|
|
135
|
+
return 'M';
|
|
136
|
+
case 'added':
|
|
137
|
+
return 'A';
|
|
138
|
+
case 'deleted':
|
|
139
|
+
return 'D';
|
|
140
|
+
case 'renamed':
|
|
141
|
+
return 'R';
|
|
142
|
+
case 'untracked':
|
|
143
|
+
return 'U';
|
|
144
|
+
case 'unmerged':
|
|
145
|
+
return '!';
|
|
146
|
+
case 'typechange':
|
|
147
|
+
return 'T';
|
|
148
|
+
default:
|
|
149
|
+
return '?';
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function AgentSection(props) {
|
|
153
|
+
var _a;
|
|
154
|
+
const { agents, prompt, onPromptChange, onLaunch } = props;
|
|
155
|
+
const trimmed = prompt.trim();
|
|
156
|
+
const promptActive = trimmed.length > 0;
|
|
157
|
+
// The "primary" agent is the first prompt-capable one in the rendered
|
|
158
|
+
// order. We mark it visually so the keyboard shortcut target isn't a
|
|
159
|
+
// mystery, and reuse the same agent when Cmd/Ctrl+Enter fires from the
|
|
160
|
+
// textarea — picking by the same rule keeps the two behaviors in sync.
|
|
161
|
+
const primaryAgent = agents.find(agent => agent.promptArgs !== undefined);
|
|
162
|
+
const hintId = 'jp-xtralab-Launcher-agent-hint';
|
|
163
|
+
return (React.createElement("section", { className: "jp-xtralab-Launcher-section" },
|
|
164
|
+
React.createElement("h2", { className: "jp-xtralab-Launcher-section-title" }, "Start an agent"),
|
|
165
|
+
React.createElement("textarea", { className: "jp-xtralab-Launcher-prompt", placeholder: "Initial prompt (optional) \u2014 passed to the selected agent.", value: prompt, spellCheck: false, rows: 3, onChange: event => onPromptChange(event.target.value), onKeyDown: event => {
|
|
166
|
+
if (event.key === 'Enter' &&
|
|
167
|
+
(event.metaKey || event.ctrlKey) &&
|
|
168
|
+
trimmed.length > 0 &&
|
|
169
|
+
primaryAgent !== undefined) {
|
|
170
|
+
event.preventDefault();
|
|
171
|
+
onLaunch(primaryAgent);
|
|
172
|
+
}
|
|
173
|
+
} }),
|
|
174
|
+
React.createElement("p", { id: hintId, className: "jp-xtralab-Launcher-agent-hint", "aria-live": "polite" }, promptActive
|
|
175
|
+
? `Cmd/Ctrl+Enter launches ${(_a = primaryAgent === null || primaryAgent === void 0 ? void 0 : primaryAgent.label) !== null && _a !== void 0 ? _a : 'the first prompt-capable agent'}. Agents that don't accept an initial prompt are dimmed.`
|
|
176
|
+
: 'Type a prompt to send it to the chosen agent. Some agents only launch without a prompt.'),
|
|
177
|
+
React.createElement("div", { className: "jp-xtralab-Launcher-agents" }, agents.map(agent => {
|
|
178
|
+
const supportsPrompt = agent.promptArgs !== undefined;
|
|
179
|
+
const disabled = promptActive && !supportsPrompt;
|
|
180
|
+
const isPrimary = agent === primaryAgent;
|
|
181
|
+
const tooltip = disabled
|
|
182
|
+
? `${agent.label} doesn't accept an initial prompt — clear the prompt to launch.`
|
|
183
|
+
: agent.caption;
|
|
184
|
+
const classes = ['jp-xtralab-Launcher-agent'];
|
|
185
|
+
if (isPrimary) {
|
|
186
|
+
classes.push('jp-xtralab-Launcher-agent-primary');
|
|
187
|
+
}
|
|
188
|
+
return (React.createElement("button", { key: agent.id, type: "button", className: classes.join(' '), title: tooltip, "aria-label": agent.label, "aria-describedby": hintId, disabled: disabled, onClick: () => onLaunch(agent) },
|
|
189
|
+
React.createElement(LabIcon.resolveReact, { icon: agent.icon, tag: "span", className: "jp-xtralab-Launcher-agent-icon" }),
|
|
190
|
+
React.createElement("span", { className: "jp-xtralab-Launcher-agent-label" }, agent.label)));
|
|
191
|
+
}))));
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* The non-agent launch tiles — a plain terminal, a new notebook, and a
|
|
195
|
+
* new console. They sit in a separate section below the agent grid so
|
|
196
|
+
* they don't compete with the AI agents visually, and they're always
|
|
197
|
+
* available regardless of the agent prompt textarea (which doesn't
|
|
198
|
+
* apply to them).
|
|
199
|
+
*/
|
|
200
|
+
function OpenSection(props) {
|
|
201
|
+
const { onLaunchTerminal, onLaunchNotebook, onLaunchConsole } = props;
|
|
202
|
+
return (React.createElement("section", { className: "jp-xtralab-Launcher-section" },
|
|
203
|
+
React.createElement("h2", { className: "jp-xtralab-Launcher-section-title" }, "Open"),
|
|
204
|
+
React.createElement("div", { className: "jp-xtralab-Launcher-agents" },
|
|
205
|
+
React.createElement("button", { type: "button", className: "jp-xtralab-Launcher-agent", title: "Open a new terminal.", "aria-label": "Open a new terminal", onClick: onLaunchTerminal },
|
|
206
|
+
React.createElement(LabIcon.resolveReact, { icon: terminalIcon, tag: "span", className: "jp-xtralab-Launcher-agent-icon" }),
|
|
207
|
+
React.createElement("span", { className: "jp-xtralab-Launcher-agent-label" }, "Terminal")),
|
|
208
|
+
React.createElement("button", { type: "button", className: "jp-xtralab-Launcher-agent", title: "Create a new notebook.", "aria-label": "Create a new notebook", onClick: onLaunchNotebook },
|
|
209
|
+
React.createElement(LabIcon.resolveReact, { icon: notebookIcon, tag: "span", className: "jp-xtralab-Launcher-agent-icon" }),
|
|
210
|
+
React.createElement("span", { className: "jp-xtralab-Launcher-agent-label" }, "Notebook")),
|
|
211
|
+
React.createElement("button", { type: "button", className: "jp-xtralab-Launcher-agent", title: "Create a new console.", "aria-label": "Create a new console", onClick: onLaunchConsole },
|
|
212
|
+
React.createElement(LabIcon.resolveReact, { icon: consoleIcon, tag: "span", className: "jp-xtralab-Launcher-agent-icon" }),
|
|
213
|
+
React.createElement("span", { className: "jp-xtralab-Launcher-agent-label" }, "Console")))));
|
|
214
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { LabIcon } from '@jupyterlab/ui-components';
|
|
2
|
+
/**
|
|
3
|
+
* Brand icons for the agent launcher cards. The artwork is taken verbatim
|
|
4
|
+
* from `jupyter-ai-contrib/jupyter-ai-acp-client`
|
|
5
|
+
* (`jupyter_ai_acp_client/static/*.svg`, BSD-3-Clause), with small
|
|
6
|
+
* adjustments to play nicely with JupyterLab themes:
|
|
7
|
+
* - Monochrome marks (Codex, Copilot, Goose) use `currentColor` so they
|
|
8
|
+
* inherit the surrounding launcher card's text color rather than
|
|
9
|
+
* hard-coding black, which disappears on the dark theme.
|
|
10
|
+
* - The Goose mark upstream is rendered on a white rounded rect; we drop
|
|
11
|
+
* the rect so the silhouette can sit on the launcher card directly.
|
|
12
|
+
* - SVG ids that would collide if two copies of the same artwork ended up
|
|
13
|
+
* on the page (gradient/mask defs) are namespaced under `xtralab-…`.
|
|
14
|
+
*
|
|
15
|
+
* Brand-colored icons (Claude, Gemini, Kiro, Mistral Vibe, OpenCode) keep
|
|
16
|
+
* their upstream colors; the dark/light contrast of the launcher card behind
|
|
17
|
+
* them is the same as on `jupyter-ai-acp-client`.
|
|
18
|
+
*/
|
|
19
|
+
export declare const claudeIcon: LabIcon;
|
|
20
|
+
export declare const codexIcon: LabIcon;
|
|
21
|
+
export declare const copilotIcon: LabIcon;
|
|
22
|
+
export declare const geminiIcon: LabIcon;
|
|
23
|
+
export declare const gooseIcon: LabIcon;
|
|
24
|
+
export declare const kiroIcon: LabIcon;
|
|
25
|
+
export declare const mistralVibeIcon: LabIcon;
|
|
26
|
+
export declare const opencodeIcon: LabIcon;
|
|
27
|
+
/**
|
|
28
|
+
* Built-in icons keyed by the agent's id, used to resolve the icon for an
|
|
29
|
+
* agent declared without an inline `iconSvg` override (i.e. one of xtralab's
|
|
30
|
+
* defaults). Custom agents added via settings can either supply their own
|
|
31
|
+
* `iconSvg` or fall back to the generic terminal icon.
|
|
32
|
+
*/
|
|
33
|
+
export declare const BUILTIN_AGENT_ICONS: Record<string, LabIcon>;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { LabIcon } from '@jupyterlab/ui-components';
|
|
2
|
+
/**
|
|
3
|
+
* Brand icons for the agent launcher cards. The artwork is taken verbatim
|
|
4
|
+
* from `jupyter-ai-contrib/jupyter-ai-acp-client`
|
|
5
|
+
* (`jupyter_ai_acp_client/static/*.svg`, BSD-3-Clause), with small
|
|
6
|
+
* adjustments to play nicely with JupyterLab themes:
|
|
7
|
+
* - Monochrome marks (Codex, Copilot, Goose) use `currentColor` so they
|
|
8
|
+
* inherit the surrounding launcher card's text color rather than
|
|
9
|
+
* hard-coding black, which disappears on the dark theme.
|
|
10
|
+
* - The Goose mark upstream is rendered on a white rounded rect; we drop
|
|
11
|
+
* the rect so the silhouette can sit on the launcher card directly.
|
|
12
|
+
* - SVG ids that would collide if two copies of the same artwork ended up
|
|
13
|
+
* on the page (gradient/mask defs) are namespaced under `xtralab-…`.
|
|
14
|
+
*
|
|
15
|
+
* Brand-colored icons (Claude, Gemini, Kiro, Mistral Vibe, OpenCode) keep
|
|
16
|
+
* their upstream colors; the dark/light contrast of the launcher card behind
|
|
17
|
+
* them is the same as on `jupyter-ai-acp-client`.
|
|
18
|
+
*/
|
|
19
|
+
export const claudeIcon = new LabIcon({
|
|
20
|
+
name: 'xtralab:agent-claude',
|
|
21
|
+
svgstr: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 1200">
|
|
22
|
+
<path fill="#d97757" d="M 233.959793 800.214905 L 468.644287 668.536987 L 472.590637 657.100647 L 468.644287 650.738403 L 457.208069 650.738403 L 417.986633 648.322144 L 283.892639 644.69812 L 167.597321 639.865845 L 54.926208 633.825623 L 26.577238 627.785339 L 3.3e-05 592.751709 L 2.73832 575.27533 L 26.577238 559.248352 L 60.724873 562.228149 L 136.187973 567.382629 L 249.422867 575.194763 L 331.570496 580.026978 L 453.261841 592.671082 L 472.590637 592.671082 L 475.328857 584.859009 L 468.724915 580.026978 L 463.570557 575.194763 L 346.389313 495.785217 L 219.543671 411.865906 L 153.100723 363.543762 L 117.181267 339.060425 L 99.060455 316.107361 L 91.248367 266.01355 L 123.865784 230.093994 L 167.677887 233.073853 L 178.872513 236.053772 L 223.248367 270.201477 L 318.040283 343.570496 L 441.825592 434.738342 L 459.946411 449.798706 L 467.194672 444.64447 L 468.080597 441.020203 L 459.946411 427.409485 L 392.617493 305.718323 L 320.778564 181.932983 L 288.80542 130.630859 L 280.348999 99.865845 C 277.369171 87.221436 275.194641 76.590698 275.194641 63.624268 L 312.322174 13.20813 L 332.8591 6.604126 L 382.389313 13.20813 L 403.248352 31.328979 L 434.013519 101.71814 L 483.865753 212.537048 L 561.181274 363.221497 L 583.812134 407.919434 L 595.892639 449.315491 L 600.40271 461.959839 L 608.214783 461.959839 L 608.214783 454.711609 L 614.577271 369.825623 L 626.335632 265.61084 L 637.771851 131.516846 L 641.718201 93.745117 L 660.402832 48.483276 L 697.530334 24.000122 L 726.52356 37.852417 L 750.362549 72 L 747.060486 94.067139 L 732.886047 186.201416 L 705.100708 330.52356 L 686.979919 427.167847 L 697.530334 427.167847 L 709.61084 415.087341 L 758.496704 350.174561 L 840.644348 247.490051 L 876.885925 206.738342 L 919.167847 161.71814 L 946.308838 140.29541 L 997.61084 140.29541 L 1035.38269 196.429626 L 1018.469849 254.416199 L 965.637634 321.422852 L 921.825562 378.201538 L 859.006714 462.765259 L 819.785278 530.41626 L 823.409424 535.812073 L 832.75177 534.92627 L 974.657776 504.724915 L 1051.328979 490.872559 L 1142.818848 475.167786 L 1184.214844 494.496582 L 1188.724854 514.147644 L 1172.456421 554.335693 L 1074.604126 578.496765 L 959.838989 601.449829 L 788.939636 641.879272 L 786.845764 643.409485 L 789.261841 646.389343 L 866.255127 653.637634 L 899.194702 655.409424 L 979.812134 655.409424 L 1129.932861 666.604187 L 1169.154419 692.537109 L 1192.671265 724.268677 L 1188.724854 748.429688 L 1128.322144 779.194641 L 1046.818848 759.865845 L 856.590759 714.604126 L 791.355774 698.335754 L 782.335693 698.335754 L 782.335693 703.731567 L 836.69812 756.885986 L 936.322205 846.845581 L 1061.073975 962.81897 L 1067.436279 991.490112 L 1051.409424 1014.120911 L 1034.496704 1011.704712 L 924.885986 929.234924 L 882.604126 892.107544 L 786.845764 811.48999 L 780.483276 811.48999 L 780.483276 819.946289 L 802.550415 852.241699 L 919.087341 1027.409424 L 925.127625 1081.127686 L 916.671204 1098.604126 L 886.469849 1109.154419 L 853.288696 1103.114136 L 785.073914 1007.355835 L 714.684631 899.516785 L 657.906067 802.872498 L 650.979858 806.81897 L 617.476624 1167.704834 L 601.771851 1186.147705 L 565.530212 1200 L 535.328857 1177.046997 L 519.302124 1139.919556 L 535.328857 1066.550537 L 554.657776 970.792053 L 570.362488 894.68457 L 584.536926 800.134277 L 592.993347 768.724976 L 592.429626 766.630859 L 585.503479 767.516968 L 514.22821 865.369263 L 405.825531 1011.865906 L 320.053711 1103.677979 L 299.516815 1111.812256 L 263.919525 1093.369263 L 267.221497 1060.429688 L 287.114136 1031.114136 L 405.825531 880.107361 L 477.422913 786.52356 L 523.651062 732.483276 L 523.328918 724.671265 L 520.590698 724.671265 L 205.288605 929.395935 L 149.154434 936.644409 L 124.993355 914.01355 L 127.973183 876.885986 L 139.409409 864.80542 L 234.201385 799.570435 L 233.879227 799.8927 Z"/>
|
|
23
|
+
</svg>`
|
|
24
|
+
});
|
|
25
|
+
export const codexIcon = new LabIcon({
|
|
26
|
+
name: 'xtralab:agent-codex',
|
|
27
|
+
svgstr: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none">
|
|
28
|
+
<path stroke="currentColor" stroke-linecap="round" stroke-width="2.484" d="M22.356 19.797H17.17M9.662 12.29l1.979 3.576a.511.511 0 0 1-.005.504l-1.974 3.409M30.758 16c0 8.15-6.607 14.758-14.758 14.758-8.15 0-14.758-6.607-14.758-14.758C1.242 7.85 7.85 1.242 16 1.242c8.15 0 14.758 6.608 14.758 14.758Z"/>
|
|
29
|
+
</svg>`
|
|
30
|
+
});
|
|
31
|
+
export const copilotIcon = new LabIcon({
|
|
32
|
+
name: 'xtralab:agent-copilot',
|
|
33
|
+
svgstr: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor">
|
|
34
|
+
<path d="M7.998 15.035c-4.562 0-7.873-2.914-7.998-3.749V9.338c.085-.628.677-1.686 1.588-2.065.013-.07.024-.143.036-.218.029-.183.06-.384.126-.612-.201-.508-.254-1.084-.254-1.656 0-.87.128-1.769.693-2.484.579-.733 1.494-1.124 2.724-1.261 1.206-.134 2.262.034 2.944.765.05.053.096.108.139.165.044-.057.094-.112.143-.165.682-.731 1.738-.899 2.944-.765 1.23.137 2.145.528 2.724 1.261.566.715.693 1.614.693 2.484 0 .572-.053 1.148-.254 1.656.066.228.098.429.126.612.012.076.024.148.037.218.924.385 1.522 1.471 1.591 2.095v1.872c0 .766-3.351 3.795-8.002 3.795Zm0-1.485c2.28 0 4.584-1.11 5.002-1.433V7.862l-.023-.116c-.49.21-1.075.291-1.727.291-1.146 0-2.059-.327-2.71-.991A3.222 3.222 0 0 1 8 6.303a3.24 3.24 0 0 1-.544.743c-.65.664-1.563.991-2.71.991-.652 0-1.236-.081-1.727-.291l-.023.116v4.255c.419.323 2.722 1.433 5.002 1.433ZM6.762 2.83c-.193-.206-.637-.413-1.682-.297-1.019.113-1.479.404-1.713.7-.247.312-.369.789-.369 1.554 0 .793.129 1.171.308 1.371.162.181.519.379 1.442.379.853 0 1.339-.235 1.638-.54.315-.322.527-.827.617-1.553.117-.935-.037-1.395-.241-1.614Zm4.155-.297c-1.044-.116-1.488.091-1.681.297-.204.219-.359.679-.242 1.614.091.726.303 1.231.618 1.553.299.305.784.54 1.638.54.922 0 1.28-.198 1.442-.379.179-.2.308-.578.308-1.371 0-.765-.123-1.242-.37-1.554-.233-.296-.693-.587-1.713-.7Z"/>
|
|
35
|
+
<path d="M6.25 9.037a.75.75 0 0 1 .75.75v1.501a.75.75 0 0 1-1.5 0V9.787a.75.75 0 0 1 .75-.75Zm4.25.75v1.501a.75.75 0 0 1-1.5 0V9.787a.75.75 0 0 1 1.5 0Z"/>
|
|
36
|
+
</svg>`
|
|
37
|
+
});
|
|
38
|
+
export const geminiIcon = new LabIcon({
|
|
39
|
+
name: 'xtralab:agent-gemini',
|
|
40
|
+
svgstr: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none">
|
|
41
|
+
<path d="M16 8.016A8.522 8.522 0 008.016 16h-.032A8.521 8.521 0 000 8.016v-.032A8.521 8.521 0 007.984 0h.032A8.522 8.522 0 0016 7.984v.032z" fill="url(#xtralab-gemini-radial)"/>
|
|
42
|
+
<defs>
|
|
43
|
+
<radialGradient id="xtralab-gemini-radial" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(16.1326 5.4553 -43.70045 129.2322 1.588 6.503)">
|
|
44
|
+
<stop offset=".067" stop-color="#9168C0"/>
|
|
45
|
+
<stop offset=".343" stop-color="#5684D1"/>
|
|
46
|
+
<stop offset=".672" stop-color="#1BA1E3"/>
|
|
47
|
+
</radialGradient>
|
|
48
|
+
</defs>
|
|
49
|
+
</svg>`
|
|
50
|
+
});
|
|
51
|
+
export const gooseIcon = new LabIcon({
|
|
52
|
+
name: 'xtralab:agent-goose',
|
|
53
|
+
svgstr: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048">
|
|
54
|
+
<g transform="translate(128, 94) scale(3)" fill="currentColor">
|
|
55
|
+
<path d="M455.145 485.687C473.786 481.639 493.559 470.622 493.559 470.622L458.952 442.115C441.844 428.034 427.282 411.116 415.898 392.108C400.167 365.845 378.431 343.683 352.485 327.445L339.813 320.066C335.471 317.048 332.444 312.344 332.013 307.037C331.737 303.615 332.557 300.556 334.47 297.864C341.068 288.564 375.172 248.097 381.422 242.926C389.471 236.273 398.44 230.739 406.761 224.403C407.944 223.501 409.131 222.604 410.3 221.689C410.341 221.648 410.4 221.616 410.436 221.58C413.106 219.472 415.626 217.233 417.629 214.578C424.862 206.202 426.62 198.797 427.051 195.52C426.072 192.366 423.153 185.314 415.018 177.179C420.117 177.492 426.29 181.525 431.887 186.27C435.644 180.265 439.578 173.92 443.507 167.562C446.131 163.315 442.238 160.138 442.129 160.039L442.107 160.034C442.107 160.034 442.107 160.016 442.102 160.012C442.002 159.903 438.825 156.01 434.588 158.634C425.528 164.24 416.473 169.851 408.42 174.949C408.42 174.949 398.875 174.75 387.572 184.512C384.912 186.52 382.673 189.039 380.57 191.704C380.529 191.745 380.493 191.799 380.461 191.84C379.541 193.005 378.644 194.192 377.747 195.38C371.406 203.705 365.877 212.67 359.224 220.719C354.057 226.973 313.586 261.072 304.286 267.671C301.594 269.584 298.539 270.408 295.113 270.127C289.811 269.701 285.102 266.669 282.083 262.328L274.705 249.656C258.467 223.701 236.305 201.974 210.042 186.243C191.034 174.859 174.12 160.292 160.035 143.188L131.519 108.581C131.519 108.581 120.497 128.35 116.454 146.995C122.078 153.834 136.749 170.807 153.826 183.769C135.466 175.135 121.893 168.459 111.188 162.876C109.538 175.085 110.182 193.549 111.881 207.848C123.47 212.919 143.202 220.732 163.289 224.884C147.222 228.641 129.597 229.325 115.987 229.058C118.385 237.909 121.72 246.937 126.207 256.019C128.115 260.22 130.204 264.322 132.461 268.337C139.663 270.318 167.939 274.437 182.985 271.056C168.016 276.4 142.885 285.378 142.885 285.378C162.187 309.393 183.52 328.931 183.52 328.931C215.969 311.483 223.366 309.099 247.703 293.182C208.292 325.251 198.063 338.344 187 351.8L179.291 362.618C175.285 368.238 171.809 374.207 168.904 380.465C159.183 401.381 145.423 445.949 145.423 445.949C142.967 453.717 148.659 459.41 156.2 456.727C156.2 456.727 200.76 442.967 221.684 433.246C227.939 430.336 233.917 426.86 239.532 422.858L250.35 415.149C253.98 412.163 257.588 409.235 261.549 405.782C261.549 405.782 288.782 438.045 316.776 459.269C316.776 459.269 325.755 434.139 331.098 419.169C327.712 434.22 331.832 462.496 333.817 469.693C337.823 471.95 341.934 474.039 346.135 475.947C355.222 480.438 364.245 483.77 373.097 486.167C372.834 472.553 373.514 454.932 377.271 438.866C381.422 458.952 389.231 478.689 394.307 490.273C408.605 491.973 427.069 492.616 439.279 490.966C433.695 480.262 427.019 466.679 418.386 448.329C431.347 465.406 448.32 480.076 455.159 485.7L455.145 485.687Z"/>
|
|
56
|
+
</g>
|
|
57
|
+
</svg>`
|
|
58
|
+
});
|
|
59
|
+
export const kiroIcon = new LabIcon({
|
|
60
|
+
name: 'xtralab:agent-kiro',
|
|
61
|
+
svgstr: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 1200" fill="none">
|
|
62
|
+
<rect width="1200" height="1200" rx="260" fill="#9046FF"/>
|
|
63
|
+
<mask id="xtralab-kiro-mask" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="272" y="202" width="655" height="796">
|
|
64
|
+
<path d="M926.578 202.793H272.637V997.857H926.578V202.793Z" fill="white"/>
|
|
65
|
+
</mask>
|
|
66
|
+
<g mask="url(#xtralab-kiro-mask)">
|
|
67
|
+
<path d="M398.554 818.914C316.315 1001.03 491.477 1046.74 620.672 940.156C658.687 1059.66 801.052 970.473 852.234 877.795C964.787 673.567 919.318 465.357 907.64 422.374C827.637 129.443 427.623 128.946 358.8 423.865C342.651 475.544 342.402 534.18 333.458 595.051C328.986 625.86 325.507 645.488 313.83 677.785C306.873 696.424 297.68 712.819 282.773 740.645C259.915 783.881 269.604 867.113 387.87 823.883L399.051 818.914H398.554Z" fill="white"/>
|
|
68
|
+
<path d="M636.123 549.353C603.328 549.353 598.359 510.097 598.359 486.742C598.359 465.623 602.086 448.977 609.293 438.293C615.504 428.852 624.697 424.131 636.123 424.131C647.555 424.131 657.492 428.852 664.447 438.541C672.398 449.474 676.623 466.12 676.623 486.742C676.623 525.998 661.471 549.353 636.375 549.353H636.123Z" fill="black"/>
|
|
69
|
+
<path d="M771.24 549.353C738.445 549.353 733.477 510.097 733.477 486.742C733.477 465.623 737.203 448.977 744.41 438.293C750.621 428.852 759.814 424.131 771.24 424.131C782.672 424.131 792.609 428.852 799.564 438.541C807.516 449.474 811.74 466.12 811.74 486.742C811.74 525.998 796.588 549.353 771.492 549.353H771.24Z" fill="black"/>
|
|
70
|
+
</g>
|
|
71
|
+
</svg>`
|
|
72
|
+
});
|
|
73
|
+
export const mistralVibeIcon = new LabIcon({
|
|
74
|
+
name: 'xtralab:agent-mistral-vibe',
|
|
75
|
+
svgstr: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="none">
|
|
76
|
+
<g transform="translate(48 71) scale(0.84)">
|
|
77
|
+
<path d="M54.3221 0H27.1531V27.0892H54.3221V0Z" fill="#FFD800"/>
|
|
78
|
+
<path d="M162.984 0H135.815V27.0892H162.984V0Z" fill="#FFD800"/>
|
|
79
|
+
<path d="M81.4823 27.0913H27.1531V54.1805H81.4823V27.0913Z" fill="#FFAF00"/>
|
|
80
|
+
<path d="M162.99 27.0913H108.661V54.1805H162.99V27.0913Z" fill="#FFAF00"/>
|
|
81
|
+
<path d="M162.972 54.168H27.1531V81.2572H162.972V54.168Z" fill="#FF8205"/>
|
|
82
|
+
<path d="M54.3221 81.2593H27.1531V108.349H54.3221V81.2593Z" fill="#FA500F"/>
|
|
83
|
+
<path d="M108.661 81.2593H81.4917V108.349H108.661V81.2593Z" fill="#FA500F"/>
|
|
84
|
+
<path d="M162.984 81.2593H135.815V108.349H162.984V81.2593Z" fill="#FA500F"/>
|
|
85
|
+
<path d="M81.4879 108.339H-0.00146484V135.429H81.4879V108.339Z" fill="#E10500"/>
|
|
86
|
+
<path d="M190.159 108.339H108.661V135.429H190.159V108.339Z" fill="#E10500"/>
|
|
87
|
+
</g>
|
|
88
|
+
</svg>`
|
|
89
|
+
});
|
|
90
|
+
export const opencodeIcon = new LabIcon({
|
|
91
|
+
name: 'xtralab:agent-opencode',
|
|
92
|
+
svgstr: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="none">
|
|
93
|
+
<rect width="512" height="512" fill="#131010"/>
|
|
94
|
+
<path d="M320 224V352H192V224H320Z" fill="#5A5858"/>
|
|
95
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M384 416H128V96H384V416ZM320 160H192V352H320V160Z" fill="white"/>
|
|
96
|
+
</svg>`
|
|
97
|
+
});
|
|
98
|
+
/**
|
|
99
|
+
* Built-in icons keyed by the agent's id, used to resolve the icon for an
|
|
100
|
+
* agent declared without an inline `iconSvg` override (i.e. one of xtralab's
|
|
101
|
+
* defaults). Custom agents added via settings can either supply their own
|
|
102
|
+
* `iconSvg` or fall back to the generic terminal icon.
|
|
103
|
+
*/
|
|
104
|
+
export const BUILTIN_AGENT_ICONS = {
|
|
105
|
+
claude: claudeIcon,
|
|
106
|
+
codex: codexIcon,
|
|
107
|
+
copilot: copilotIcon,
|
|
108
|
+
gemini: geminiIcon,
|
|
109
|
+
goose: gooseIcon,
|
|
110
|
+
kiro: kiroIcon,
|
|
111
|
+
'mistral-vibe': mistralVibeIcon,
|
|
112
|
+
opencode: opencodeIcon
|
|
113
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { JupyterFrontEndPlugin } from '@jupyterlab/application';
|
|
2
|
+
/**
|
|
3
|
+
* The xtralab launcher plugin. Replaces the stock JupyterLab launcher
|
|
4
|
+
* (which is disabled via `package.json`'s `jupyterlab.disabledExtensions`)
|
|
5
|
+
* with an agent-focused dashboard: an optional initial prompt, a row of
|
|
6
|
+
* agent buttons (Claude, Codex, Gemini, …), and a collapsible list of
|
|
7
|
+
* changed files (clickable into the diff viewer) below them. Clicking an
|
|
8
|
+
* agent opens a fresh terminal and pipes the agent's command into it; if
|
|
9
|
+
* the prompt textarea is non-empty, the prompt is shell-quoted and
|
|
10
|
+
* spliced into the launch line per the agent's `promptArgs` recipe.
|
|
11
|
+
*
|
|
12
|
+
* The agent list is the merge of xtralab's defaults with the user's
|
|
13
|
+
* `xtralab:launcher` settings, then filtered by a server-side `which`
|
|
14
|
+
* check so users only see agents that are actually installed. Agents with
|
|
15
|
+
* `requireAvailable: false` (e.g. shell aliases) skip the filter.
|
|
16
|
+
*
|
|
17
|
+
* The plugin deliberately does NOT provide the `ILauncher` token: other
|
|
18
|
+
* extensions register notebook/console/terminal cards on it as a side
|
|
19
|
+
* effect, which would defeat the point of the agent-only launcher. If an
|
|
20
|
+
* extension needs to surface itself, we'll add it here explicitly.
|
|
21
|
+
*/
|
|
22
|
+
declare const plugin: JupyterFrontEndPlugin<void>;
|
|
23
|
+
export default plugin;
|