jupyterlab_claude_code_extension 1.2.40 → 1.2.42

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 CHANGED
@@ -36,6 +36,7 @@ Chat-panel extensions re-implement the agent loop and trail the real tool. This
36
36
  - **Branch session** - fork the current conversation into a new named session via the right-click menu (normal or skip-permissions mode); uses Claude's native `--fork-session`, opens in a new terminal, the chosen name is stamped automatically, and the fork becomes the row's current conversation
37
37
  - **Copy session id** - a right-click "Copy Session ID" item copies the row's current conversation id to the clipboard; the Manage Sessions popup adds a copy button on every row, so any parallel conversation's id is one click away
38
38
  - **Activity at a glance** - each row shows its last activity (`now`, `5m ago`, `2h ago`, `3d ago`) in an aligned column, with the favourite star in its own column beside it; rows active within the last minute light up in the theme's brand colour (including the `now` label), rows idle for over a week dim slightly
39
+ - **Coloured terminal tabs** - each Claude session's colour (set with `/color`, or auto-assigned) tints its terminal tab, so open sessions are easy to tell apart; needs the companion `jupyterlab_colourful_tab_extension` (>= 1.0.19), which supplies the tab-colouring API
39
40
  - **Search** - fuzzy filter toggled by the funnel button next to refresh
40
41
  - **Presentation modes** - label rows by session name (so a `/rename` shows through), folder name, or path relative to the JupyterLab root
41
42
  - **Hover tooltip** with project path, last activity, message count, branch, and session id
package/lib/index.js CHANGED
@@ -2,6 +2,7 @@ import { ILabShell, ILayoutRestorer } from '@jupyterlab/application';
2
2
  import { IDefaultFileBrowser } from '@jupyterlab/filebrowser';
3
3
  import { ISettingRegistry } from '@jupyterlab/settingregistry';
4
4
  import { ITerminalTracker } from '@jupyterlab/terminal';
5
+ import { IColourfulTabs } from 'jupyterlab_colourful_tab_extension';
5
6
  import { requestAPI } from './request';
6
7
  import { ClaudeCodeSessionsWidget } from './widget';
7
8
  const PLUGIN_ID = 'jupyterlab_claude_code_extension:plugin';
@@ -15,9 +16,10 @@ const plugin = {
15
16
  ILayoutRestorer,
16
17
  ISettingRegistry,
17
18
  ITerminalTracker,
18
- IDefaultFileBrowser
19
+ IDefaultFileBrowser,
20
+ IColourfulTabs
19
21
  ],
20
- activate: async (app, labShell, restorer, settingRegistry, terminalTracker, fileBrowser) => {
22
+ activate: async (app, labShell, restorer, settingRegistry, terminalTracker, fileBrowser, colourfulTabs) => {
21
23
  const settings = app.serviceManager.serverSettings;
22
24
  let status;
23
25
  try {
@@ -31,7 +33,7 @@ const plugin = {
31
33
  console.info('[jupyterlab_claude_code_extension] `claude` binary not found on PATH; panel disabled.');
32
34
  return;
33
35
  }
34
- const widget = new ClaudeCodeSessionsWidget(app, status.root_dir || '', terminalTracker, fileBrowser);
36
+ const widget = new ClaudeCodeSessionsWidget(app, status.root_dir || '', terminalTracker, fileBrowser, colourfulTabs);
35
37
  // Read the sidebar setting before docking so we add the widget to the
36
38
  // user's preferred side on first paint. Default to right.
37
39
  let currentSidebar = 'right';
package/lib/types.d.ts CHANGED
@@ -14,6 +14,9 @@ export interface ISession {
14
14
  remote_control: boolean;
15
15
  favourite: boolean;
16
16
  extra_sessions: number;
17
+ /** Claude session colour from `/color` or auto-assignment (e.g. "blue"), or
18
+ * null when the session carries none. Drives the terminal tab tint. */
19
+ color: string | null;
17
20
  }
18
21
  export interface ISessionsListResponse {
19
22
  sessions: ISession[];
package/lib/widget.d.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  import { JupyterFrontEnd } from '@jupyterlab/application';
2
2
  import { IDefaultFileBrowser } from '@jupyterlab/filebrowser';
3
3
  import { ITerminalTracker } from '@jupyterlab/terminal';
4
+ import { IColourfulTabs } from 'jupyterlab_colourful_tab_extension';
4
5
  import { Widget } from '@lumino/widgets';
5
6
  import { Message } from '@lumino/messaging';
6
7
  export type PresentationMode = 'name' | 'path';
7
8
  export declare class ClaudeCodeSessionsWidget extends Widget {
8
- constructor(app: JupyterFrontEnd, rootDir: string, terminalTracker?: ITerminalTracker | null, fileBrowser?: IDefaultFileBrowser | null);
9
+ constructor(app: JupyterFrontEnd, rootDir: string, terminalTracker?: ITerminalTracker | null, fileBrowser?: IDefaultFileBrowser | null, colourfulTabs?: IColourfulTabs | null);
9
10
  refresh(): void;
10
11
  /** Choose how rows are labelled: by name (session name, initially the
11
12
  * folder name), or by path. */
@@ -81,6 +82,19 @@ export declare class ClaudeCodeSessionsWidget extends Widget {
81
82
  * first.
82
83
  */
83
84
  private _focusTerminal;
85
+ /** Tint a terminal's dock tab with the colour of the Claude session it runs,
86
+ * delegating to jupyterlab_colourful_tab_extension's `setColour` API so that
87
+ * extension owns the tab CSS and colour vocabulary. A no-op when the
88
+ * colourful-tab extension is not installed (the token is optional). An
89
+ * absent/unknown colour clears the tint. */
90
+ private _applyTerminalColour;
91
+ /** Re-tint every tracked terminal tab from the freshest session colours.
92
+ * Runs after each fetch (launch refresh + the 30s poll), so a `/color`
93
+ * change shows on the next tick. Matches on project AND conversation so a
94
+ * tab only takes the colour of the exact session it runs; a terminal
95
+ * running a branch other than the project's representative row stays
96
+ * untinted rather than borrowing a sibling's colour. */
97
+ private _reconcileTerminalColours;
84
98
  private _findTerminalForCwd;
85
99
  private _showCloseExistingDialog;
86
100
  /** Show a modal with a spinner while the terminal is being launched. The
@@ -204,6 +218,7 @@ export declare class ClaudeCodeSessionsWidget extends Widget {
204
218
  private readonly _removingPaths;
205
219
  private readonly _terminalTracker;
206
220
  private readonly _fileBrowser;
221
+ private readonly _colourfulTabs;
207
222
  private readonly _terminalsByPath;
208
223
  private readonly _pendingByPath;
209
224
  private readonly _rootDir;
package/lib/widget.js CHANGED
@@ -14,6 +14,21 @@ const BRANCH_WATCH_MAX_ATTEMPTS = 90; // ~3 minutes
14
14
  const DEFAULT_RECENT_LIMIT = 10;
15
15
  const EXPANDED_STORAGE_KEY = 'jupyterlab_claude_code_extension:expanded';
16
16
  const DEFAULT_PRESENTATION_MODE = 'name';
17
+ // Claude's per-session colour (set by `/color`, or auto-assigned for
18
+ // multi-session use) maps to a jupyterlab_colourful_tab_extension colour id.
19
+ // That extension owns the tab-tint CSS and the setColour API; we only feed it
20
+ // the colour. An absent or unrecognised colour resolves to null (clear tint).
21
+ const CLAUDE_TAB_COLOUR_ID = {
22
+ red: 'rose',
23
+ orange: 'peach',
24
+ yellow: 'lemon',
25
+ green: 'mint',
26
+ blue: 'sky',
27
+ purple: 'lavender'
28
+ };
29
+ function claudeTabColourId(color) {
30
+ return (color && CLAUDE_TAB_COLOUR_ID[color]) || null;
31
+ }
17
32
  const SECTION_LABELS = {
18
33
  favourites: 'Favorites',
19
34
  recent: 'Recent',
@@ -62,7 +77,7 @@ catch (_err) {
62
77
  // ignore
63
78
  }
64
79
  export class ClaudeCodeSessionsWidget extends Widget {
65
- constructor(app, rootDir, terminalTracker = null, fileBrowser = null) {
80
+ constructor(app, rootDir, terminalTracker = null, fileBrowser = null, colourfulTabs = null) {
66
81
  super();
67
82
  this._loadingEl = null;
68
83
  this._refreshBtn = null;
@@ -92,6 +107,7 @@ export class ClaudeCodeSessionsWidget extends Widget {
92
107
  this._rootDir = rootDir.replace(/\/+$/, '');
93
108
  this._terminalTracker = terminalTracker;
94
109
  this._fileBrowser = fileBrowser;
110
+ this._colourfulTabs = colourfulTabs;
95
111
  this.id = 'jupyterlab-claude-code-extension';
96
112
  this.title.icon = claudeIcon;
97
113
  this.title.caption = 'Claude Code Sessions';
@@ -337,6 +353,7 @@ export class ClaudeCodeSessionsWidget extends Widget {
337
353
  const data = await requestAPI('sessions', this._serverSettings, { cache: 'no-store' });
338
354
  this._sessions = (_a = data.sessions) !== null && _a !== void 0 ? _a : [];
339
355
  this._render();
356
+ this._reconcileTerminalColours();
340
357
  }
341
358
  async _toggleFavourite(session) {
342
359
  const next = !session.favourite;
@@ -641,6 +658,35 @@ export class ClaudeCodeSessionsWidget extends Widget {
641
658
  }
642
659
  });
643
660
  }
661
+ /** Tint a terminal's dock tab with the colour of the Claude session it runs,
662
+ * delegating to jupyterlab_colourful_tab_extension's `setColour` API so that
663
+ * extension owns the tab CSS and colour vocabulary. A no-op when the
664
+ * colourful-tab extension is not installed (the token is optional). An
665
+ * absent/unknown colour clears the tint. */
666
+ _applyTerminalColour(widget, color) {
667
+ if (!this._colourfulTabs || !widget || widget.isDisposed) {
668
+ return;
669
+ }
670
+ this._colourfulTabs.setColour(widget, claudeTabColourId(color));
671
+ }
672
+ /** Re-tint every tracked terminal tab from the freshest session colours.
673
+ * Runs after each fetch (launch refresh + the 30s poll), so a `/color`
674
+ * change shows on the next tick. Matches on project AND conversation so a
675
+ * tab only takes the colour of the exact session it runs; a terminal
676
+ * running a branch other than the project's representative row stays
677
+ * untinted rather than borrowing a sibling's colour. */
678
+ _reconcileTerminalColours() {
679
+ var _a;
680
+ const sessions = (_a = this._sessions) !== null && _a !== void 0 ? _a : [];
681
+ this._terminalsByPath.forEach((entry, projectPath) => {
682
+ var _a;
683
+ if (!entry.widget || entry.widget.isDisposed) {
684
+ return;
685
+ }
686
+ const match = sessions.find(s => s.project_path === projectPath && s.session_id === entry.sessionId);
687
+ this._applyTerminalColour(entry.widget, (_a = match === null || match === void 0 ? void 0 : match.color) !== null && _a !== void 0 ? _a : null);
688
+ });
689
+ }
644
690
  async _findTerminalForCwd(projectPath, wantedSessionId) {
645
691
  var _a, _b, _c;
646
692
  if (!this._terminalTracker) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jupyterlab_claude_code_extension",
3
- "version": "1.2.40",
3
+ "version": "1.2.42",
4
4
  "description": "Browse, resume, and manage your Claude Code CLI sessions from a JupyterLab side panel. One click reactivates the right terminal - no duplicate tabs, live remote-control indicator, and favourites for the projects you keep coming back to.",
5
5
  "keywords": [
6
6
  "jupyter",
@@ -64,7 +64,8 @@
64
64
  "@jupyterlab/terminal": "^4.0.0",
65
65
  "@jupyterlab/ui-components": "^4.0.0",
66
66
  "@lumino/commands": "^2.0.0",
67
- "@lumino/widgets": "^2.0.0"
67
+ "@lumino/widgets": "^2.0.0",
68
+ "jupyterlab_colourful_tab_extension": "^1.0.19"
68
69
  },
69
70
  "devDependencies": {
70
71
  "@jupyterlab/builder": "^4.0.0",
@@ -125,7 +126,13 @@
125
126
  },
126
127
  "extension": true,
127
128
  "schemaDir": "schema",
128
- "outputDir": "jupyterlab_claude_code_extension/labextension"
129
+ "outputDir": "jupyterlab_claude_code_extension/labextension",
130
+ "sharedPackages": {
131
+ "jupyterlab_colourful_tab_extension": {
132
+ "bundled": false,
133
+ "singleton": true
134
+ }
135
+ }
129
136
  },
130
137
  "eslintIgnore": [
131
138
  "node_modules",
package/src/index.ts CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  import { IDefaultFileBrowser } from '@jupyterlab/filebrowser';
8
8
  import { ISettingRegistry } from '@jupyterlab/settingregistry';
9
9
  import { ITerminalTracker } from '@jupyterlab/terminal';
10
+ import { IColourfulTabs } from 'jupyterlab_colourful_tab_extension';
10
11
 
11
12
  import { requestAPI } from './request';
12
13
  import { IStatusResponse } from './types';
@@ -25,7 +26,8 @@ const plugin: JupyterFrontEndPlugin<void> = {
25
26
  ILayoutRestorer,
26
27
  ISettingRegistry,
27
28
  ITerminalTracker,
28
- IDefaultFileBrowser
29
+ IDefaultFileBrowser,
30
+ IColourfulTabs
29
31
  ],
30
32
  activate: async (
31
33
  app: JupyterFrontEnd,
@@ -33,7 +35,8 @@ const plugin: JupyterFrontEndPlugin<void> = {
33
35
  restorer: ILayoutRestorer | null,
34
36
  settingRegistry: ISettingRegistry | null,
35
37
  terminalTracker: ITerminalTracker | null,
36
- fileBrowser: IDefaultFileBrowser | null
38
+ fileBrowser: IDefaultFileBrowser | null,
39
+ colourfulTabs: IColourfulTabs | null
37
40
  ) => {
38
41
  const settings = app.serviceManager.serverSettings;
39
42
 
@@ -59,7 +62,8 @@ const plugin: JupyterFrontEndPlugin<void> = {
59
62
  app,
60
63
  status.root_dir || '',
61
64
  terminalTracker,
62
- fileBrowser
65
+ fileBrowser,
66
+ colourfulTabs
63
67
  );
64
68
 
65
69
  // Read the sidebar setting before docking so we add the widget to the
package/src/types.ts CHANGED
@@ -14,6 +14,9 @@ export interface ISession {
14
14
  remote_control: boolean;
15
15
  favourite: boolean;
16
16
  extra_sessions: number;
17
+ /** Claude session colour from `/color` or auto-assignment (e.g. "blue"), or
18
+ * null when the session carries none. Drives the terminal tab tint. */
19
+ color: string | null;
17
20
  }
18
21
 
19
22
  export interface ISessionsListResponse {
package/src/widget.ts CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  import { ServerConnection } from '@jupyterlab/services';
10
10
  import { IDefaultFileBrowser } from '@jupyterlab/filebrowser';
11
11
  import { ITerminalTracker } from '@jupyterlab/terminal';
12
+ import { IColourfulTabs } from 'jupyterlab_colourful_tab_extension';
12
13
  import { copyIcon, folderIcon, terminalIcon } from '@jupyterlab/ui-components';
13
14
  import { CommandRegistry } from '@lumino/commands';
14
15
  import { UUID } from '@lumino/coreutils';
@@ -54,6 +55,23 @@ export type PresentationMode = 'name' | 'path';
54
55
 
55
56
  const DEFAULT_PRESENTATION_MODE: PresentationMode = 'name';
56
57
 
58
+ // Claude's per-session colour (set by `/color`, or auto-assigned for
59
+ // multi-session use) maps to a jupyterlab_colourful_tab_extension colour id.
60
+ // That extension owns the tab-tint CSS and the setColour API; we only feed it
61
+ // the colour. An absent or unrecognised colour resolves to null (clear tint).
62
+ const CLAUDE_TAB_COLOUR_ID: Record<string, string> = {
63
+ red: 'rose',
64
+ orange: 'peach',
65
+ yellow: 'lemon',
66
+ green: 'mint',
67
+ blue: 'sky',
68
+ purple: 'lavender'
69
+ };
70
+
71
+ function claudeTabColourId(color: string | null | undefined): string | null {
72
+ return (color && CLAUDE_TAB_COLOUR_ID[color]) || null;
73
+ }
74
+
57
75
  const SECTION_LABELS: Record<SectionKey, string> = {
58
76
  favourites: 'Favorites',
59
77
  recent: 'Recent',
@@ -119,7 +137,8 @@ export class ClaudeCodeSessionsWidget extends Widget {
119
137
  app: JupyterFrontEnd,
120
138
  rootDir: string,
121
139
  terminalTracker: ITerminalTracker | null = null,
122
- fileBrowser: IDefaultFileBrowser | null = null
140
+ fileBrowser: IDefaultFileBrowser | null = null,
141
+ colourfulTabs: IColourfulTabs | null = null
123
142
  ) {
124
143
  super();
125
144
  this._app = app;
@@ -127,6 +146,7 @@ export class ClaudeCodeSessionsWidget extends Widget {
127
146
  this._rootDir = rootDir.replace(/\/+$/, '');
128
147
  this._terminalTracker = terminalTracker;
129
148
  this._fileBrowser = fileBrowser;
149
+ this._colourfulTabs = colourfulTabs;
130
150
 
131
151
  this.id = 'jupyterlab-claude-code-extension';
132
152
  this.title.icon = claudeIcon;
@@ -408,6 +428,7 @@ export class ClaudeCodeSessionsWidget extends Widget {
408
428
  );
409
429
  this._sessions = data.sessions ?? [];
410
430
  this._render();
431
+ this._reconcileTerminalColours();
411
432
  }
412
433
 
413
434
  private async _toggleFavourite(session: ISession): Promise<void> {
@@ -762,6 +783,40 @@ export class ClaudeCodeSessionsWidget extends Widget {
762
783
  });
763
784
  }
764
785
 
786
+ /** Tint a terminal's dock tab with the colour of the Claude session it runs,
787
+ * delegating to jupyterlab_colourful_tab_extension's `setColour` API so that
788
+ * extension owns the tab CSS and colour vocabulary. A no-op when the
789
+ * colourful-tab extension is not installed (the token is optional). An
790
+ * absent/unknown colour clears the tint. */
791
+ private _applyTerminalColour(
792
+ widget: any,
793
+ color: string | null | undefined
794
+ ): void {
795
+ if (!this._colourfulTabs || !widget || widget.isDisposed) {
796
+ return;
797
+ }
798
+ this._colourfulTabs.setColour(widget, claudeTabColourId(color));
799
+ }
800
+
801
+ /** Re-tint every tracked terminal tab from the freshest session colours.
802
+ * Runs after each fetch (launch refresh + the 30s poll), so a `/color`
803
+ * change shows on the next tick. Matches on project AND conversation so a
804
+ * tab only takes the colour of the exact session it runs; a terminal
805
+ * running a branch other than the project's representative row stays
806
+ * untinted rather than borrowing a sibling's colour. */
807
+ private _reconcileTerminalColours(): void {
808
+ const sessions = this._sessions ?? [];
809
+ this._terminalsByPath.forEach((entry, projectPath) => {
810
+ if (!entry.widget || entry.widget.isDisposed) {
811
+ return;
812
+ }
813
+ const match = sessions.find(
814
+ s => s.project_path === projectPath && s.session_id === entry.sessionId
815
+ );
816
+ this._applyTerminalColour(entry.widget, match?.color ?? null);
817
+ });
818
+ }
819
+
765
820
  private async _findTerminalForCwd(
766
821
  projectPath: string,
767
822
  wantedSessionId: string | undefined
@@ -2205,6 +2260,7 @@ export class ClaudeCodeSessionsWidget extends Widget {
2205
2260
  private readonly _removingPaths: Set<string> = new Set();
2206
2261
  private readonly _terminalTracker: ITerminalTracker | null;
2207
2262
  private readonly _fileBrowser: IDefaultFileBrowser | null;
2263
+ private readonly _colourfulTabs: IColourfulTabs | null;
2208
2264
  // Microcache of the most-recent terminal per project, tagged with the
2209
2265
  // conversation it is running so reuse can tell a project's branches apart.
2210
2266
  private readonly _terminalsByPath: Map<