jupyterlab_claude_code_extension 1.0.50 → 1.0.53
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 +9 -10
- package/lib/index.js +20 -1
- package/lib/types.d.ts +1 -0
- package/lib/widget.d.ts +8 -1
- package/lib/widget.js +31 -6
- package/package.json +1 -1
- package/src/index.ts +23 -1
- package/src/types.ts +1 -0
- package/src/widget.ts +36 -6
package/README.md
CHANGED
|
@@ -8,21 +8,20 @@
|
|
|
8
8
|
[](https://kolomolo.com)
|
|
9
9
|
[](https://www.paypal.com/donate/?hosted_button_id=B4KPBJDLLXTSA)
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Browse, resume, and manage your Claude Code sessions from a JupyterLab side panel. One click reactivates the right terminal, no duplicate tabs, with a live indicator showing which sessions are currently active.
|
|
12
12
|
|
|
13
13
|

|
|
14
14
|
|
|
15
15
|
## Features
|
|
16
16
|
|
|
17
|
-
- **Three-section side panel** - Favourites, Recent
|
|
18
|
-
- **Live
|
|
19
|
-
- **One-click resume** - click a row to
|
|
20
|
-
- **
|
|
21
|
-
- **
|
|
22
|
-
- **
|
|
23
|
-
- **
|
|
24
|
-
- **Auto-disabled** when the
|
|
25
|
-
- **Hover tooltip** with relative path (vs JL root), last activity, message count, branch, first prompt, session id
|
|
17
|
+
- **Three-section side panel** - Favourites, Recent, and All projects, each scrolling independently
|
|
18
|
+
- **Live indicator** - a green dot marks sessions that are currently running somewhere
|
|
19
|
+
- **One-click resume** - click a row to jump back into that session in a terminal. If a terminal for the project is already open, it's reused instead of duplicated
|
|
20
|
+
- **Favourites** - star projects you keep coming back to via the right-click menu
|
|
21
|
+
- **Search** - fuzzy filter at the top of the panel
|
|
22
|
+
- **Presentation modes** - label rows by session name, folder name, or path relative to the JupyterLab root
|
|
23
|
+
- **Hover tooltip** with project path, last activity, message count, branch, and session id
|
|
24
|
+
- **Auto-disabled** when the Claude Code CLI is not installed
|
|
26
25
|
|
|
27
26
|
## Requirements
|
|
28
27
|
|
package/lib/index.js
CHANGED
|
@@ -26,10 +26,17 @@ const plugin = {
|
|
|
26
26
|
return;
|
|
27
27
|
}
|
|
28
28
|
const widget = new ClaudeCodeSessionsWidget(app, status.root_dir || '', terminalTracker);
|
|
29
|
-
|
|
29
|
+
// Read the sidebar setting before docking so we add the widget to the
|
|
30
|
+
// user's preferred side on first paint. Default to left.
|
|
31
|
+
let currentSidebar = 'left';
|
|
30
32
|
if (settingRegistry) {
|
|
31
33
|
try {
|
|
32
34
|
const settings = await settingRegistry.load(PLUGIN_ID);
|
|
35
|
+
const initialSidebar = settings.get('sidebar').composite;
|
|
36
|
+
if (initialSidebar === 'left' || initialSidebar === 'right') {
|
|
37
|
+
currentSidebar = initialSidebar;
|
|
38
|
+
}
|
|
39
|
+
labShell.add(widget, currentSidebar, { rank: 600 });
|
|
33
40
|
const apply = () => {
|
|
34
41
|
const mode = settings.get('presentationMode').composite;
|
|
35
42
|
if (mode === 'session' || mode === 'folder' || mode === 'path') {
|
|
@@ -42,14 +49,26 @@ const plugin = {
|
|
|
42
49
|
const dangerous = settings.get('dangerouslySkipPermissions')
|
|
43
50
|
.composite;
|
|
44
51
|
widget.setDangerouslySkipPermissions(!!dangerous);
|
|
52
|
+
const sidebar = settings.get('sidebar').composite;
|
|
53
|
+
if ((sidebar === 'left' || sidebar === 'right') &&
|
|
54
|
+
sidebar !== currentSidebar) {
|
|
55
|
+
currentSidebar = sidebar;
|
|
56
|
+
// Lumino re-parents the widget cleanly when add() is called
|
|
57
|
+
// against a different area.
|
|
58
|
+
labShell.add(widget, sidebar, { rank: 600 });
|
|
59
|
+
}
|
|
45
60
|
};
|
|
46
61
|
apply();
|
|
47
62
|
settings.changed.connect(apply);
|
|
48
63
|
}
|
|
49
64
|
catch (err) {
|
|
50
65
|
console.warn('[jupyterlab_claude_code_extension] failed to load settings; using defaults', err);
|
|
66
|
+
labShell.add(widget, currentSidebar, { rank: 600 });
|
|
51
67
|
}
|
|
52
68
|
}
|
|
69
|
+
else {
|
|
70
|
+
labShell.add(widget, currentSidebar, { rank: 600 });
|
|
71
|
+
}
|
|
53
72
|
// Register with the layout restorer so JL remembers whether the panel
|
|
54
73
|
// was active/visible across browser reloads and restarts.
|
|
55
74
|
if (restorer) {
|
package/lib/types.d.ts
CHANGED
package/lib/widget.d.ts
CHANGED
|
@@ -49,7 +49,14 @@ export declare class ClaudeCodeSessionsWidget extends Widget {
|
|
|
49
49
|
* handled separately in ``_disambiguate``). */
|
|
50
50
|
private _displayName;
|
|
51
51
|
private _basename;
|
|
52
|
-
/** Walk path tails until every name in a colliding group is unique.
|
|
52
|
+
/** Walk path tails until every name in a colliding group is unique.
|
|
53
|
+
*
|
|
54
|
+
* User-set rename names (``name_source === 'rename'``) survive
|
|
55
|
+
* disambiguation untouched: a Claude ``/rename`` should never be rolled
|
|
56
|
+
* back to a path tail just because some other row happens to share its
|
|
57
|
+
* folder basename. Basename-derived rows in the same group get the path
|
|
58
|
+
* tail suffix, picked so it stays distinct from every rename label too.
|
|
59
|
+
*/
|
|
53
60
|
private _disambiguate;
|
|
54
61
|
private _render;
|
|
55
62
|
private _renderSection;
|
package/lib/widget.js
CHANGED
|
@@ -472,7 +472,14 @@ export class ClaudeCodeSessionsWidget extends Widget {
|
|
|
472
472
|
const parts = p.split('/').filter(Boolean);
|
|
473
473
|
return parts[parts.length - 1] || '';
|
|
474
474
|
}
|
|
475
|
-
/** Walk path tails until every name in a colliding group is unique.
|
|
475
|
+
/** Walk path tails until every name in a colliding group is unique.
|
|
476
|
+
*
|
|
477
|
+
* User-set rename names (``name_source === 'rename'``) survive
|
|
478
|
+
* disambiguation untouched: a Claude ``/rename`` should never be rolled
|
|
479
|
+
* back to a path tail just because some other row happens to share its
|
|
480
|
+
* folder basename. Basename-derived rows in the same group get the path
|
|
481
|
+
* tail suffix, picked so it stays distinct from every rename label too.
|
|
482
|
+
*/
|
|
476
483
|
_disambiguate(rows) {
|
|
477
484
|
var _a;
|
|
478
485
|
const out = new Map();
|
|
@@ -486,20 +493,38 @@ export class ClaudeCodeSessionsWidget extends Widget {
|
|
|
486
493
|
out.set(group[0].project_path, name);
|
|
487
494
|
continue;
|
|
488
495
|
}
|
|
489
|
-
const
|
|
496
|
+
const renames = group.filter(r => r.name_source === 'rename');
|
|
497
|
+
const basenames = group.filter(r => r.name_source !== 'rename');
|
|
498
|
+
// Rename rows win unchanged. The ``taken`` set guards basename
|
|
499
|
+
// disambiguation from colliding back into a rename's label.
|
|
500
|
+
const taken = new Set();
|
|
501
|
+
for (const r of renames) {
|
|
502
|
+
out.set(r.project_path, r.name);
|
|
503
|
+
taken.add(r.name);
|
|
504
|
+
}
|
|
505
|
+
if (basenames.length === 0) {
|
|
506
|
+
continue;
|
|
507
|
+
}
|
|
508
|
+
// Walk path tails for basename rows: tail must be unique among
|
|
509
|
+
// basenames AND not equal to any rename label already taken.
|
|
510
|
+
const segs = basenames.map(r => r.project_path.split('/').filter(Boolean));
|
|
490
511
|
const max = Math.max(...segs.map(s => s.length));
|
|
491
512
|
let depth = 1;
|
|
513
|
+
let resolved = false;
|
|
492
514
|
while (depth <= max) {
|
|
493
515
|
const tails = segs.map(s => s.slice(-depth).join('/'));
|
|
494
|
-
|
|
495
|
-
|
|
516
|
+
const unique = new Set(tails).size === tails.length;
|
|
517
|
+
const noConflict = tails.every(t => !taken.has(t));
|
|
518
|
+
if (unique && noConflict) {
|
|
519
|
+
basenames.forEach((r, i) => out.set(r.project_path, tails[i]));
|
|
520
|
+
resolved = true;
|
|
496
521
|
break;
|
|
497
522
|
}
|
|
498
523
|
depth += 1;
|
|
499
524
|
}
|
|
500
|
-
if (!
|
|
525
|
+
if (!resolved) {
|
|
501
526
|
// Fallback to absolute path
|
|
502
|
-
|
|
527
|
+
basenames.forEach(r => out.set(r.project_path, r.project_path));
|
|
503
528
|
}
|
|
504
529
|
}
|
|
505
530
|
return out;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jupyterlab_claude_code_extension",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.53",
|
|
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",
|
package/src/index.ts
CHANGED
|
@@ -53,11 +53,20 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
53
53
|
status.root_dir || '',
|
|
54
54
|
terminalTracker
|
|
55
55
|
);
|
|
56
|
-
|
|
56
|
+
|
|
57
|
+
// Read the sidebar setting before docking so we add the widget to the
|
|
58
|
+
// user's preferred side on first paint. Default to left.
|
|
59
|
+
let currentSidebar: 'left' | 'right' = 'left';
|
|
57
60
|
|
|
58
61
|
if (settingRegistry) {
|
|
59
62
|
try {
|
|
60
63
|
const settings = await settingRegistry.load(PLUGIN_ID);
|
|
64
|
+
const initialSidebar = settings.get('sidebar').composite as string;
|
|
65
|
+
if (initialSidebar === 'left' || initialSidebar === 'right') {
|
|
66
|
+
currentSidebar = initialSidebar;
|
|
67
|
+
}
|
|
68
|
+
labShell.add(widget, currentSidebar, { rank: 600 });
|
|
69
|
+
|
|
61
70
|
const apply = (): void => {
|
|
62
71
|
const mode = settings.get('presentationMode').composite as string;
|
|
63
72
|
if (mode === 'session' || mode === 'folder' || mode === 'path') {
|
|
@@ -70,6 +79,16 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
70
79
|
const dangerous = settings.get('dangerouslySkipPermissions')
|
|
71
80
|
.composite as boolean;
|
|
72
81
|
widget.setDangerouslySkipPermissions(!!dangerous);
|
|
82
|
+
const sidebar = settings.get('sidebar').composite as string;
|
|
83
|
+
if (
|
|
84
|
+
(sidebar === 'left' || sidebar === 'right') &&
|
|
85
|
+
sidebar !== currentSidebar
|
|
86
|
+
) {
|
|
87
|
+
currentSidebar = sidebar;
|
|
88
|
+
// Lumino re-parents the widget cleanly when add() is called
|
|
89
|
+
// against a different area.
|
|
90
|
+
labShell.add(widget, sidebar, { rank: 600 });
|
|
91
|
+
}
|
|
73
92
|
};
|
|
74
93
|
apply();
|
|
75
94
|
settings.changed.connect(apply);
|
|
@@ -78,7 +97,10 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
78
97
|
'[jupyterlab_claude_code_extension] failed to load settings; using defaults',
|
|
79
98
|
err
|
|
80
99
|
);
|
|
100
|
+
labShell.add(widget, currentSidebar, { rank: 600 });
|
|
81
101
|
}
|
|
102
|
+
} else {
|
|
103
|
+
labShell.add(widget, currentSidebar, { rank: 600 });
|
|
82
104
|
}
|
|
83
105
|
|
|
84
106
|
// Register with the layout restorer so JL remembers whether the panel
|
package/src/types.ts
CHANGED
package/src/widget.ts
CHANGED
|
@@ -566,7 +566,14 @@ export class ClaudeCodeSessionsWidget extends Widget {
|
|
|
566
566
|
return parts[parts.length - 1] || '';
|
|
567
567
|
}
|
|
568
568
|
|
|
569
|
-
/** Walk path tails until every name in a colliding group is unique.
|
|
569
|
+
/** Walk path tails until every name in a colliding group is unique.
|
|
570
|
+
*
|
|
571
|
+
* User-set rename names (``name_source === 'rename'``) survive
|
|
572
|
+
* disambiguation untouched: a Claude ``/rename`` should never be rolled
|
|
573
|
+
* back to a path tail just because some other row happens to share its
|
|
574
|
+
* folder basename. Basename-derived rows in the same group get the path
|
|
575
|
+
* tail suffix, picked so it stays distinct from every rename label too.
|
|
576
|
+
*/
|
|
570
577
|
private _disambiguate(rows: ISession[]): Map<string, string> {
|
|
571
578
|
const out = new Map<string, string>();
|
|
572
579
|
const groups = new Map<string, ISession[]>();
|
|
@@ -579,20 +586,43 @@ export class ClaudeCodeSessionsWidget extends Widget {
|
|
|
579
586
|
out.set(group[0].project_path, name);
|
|
580
587
|
continue;
|
|
581
588
|
}
|
|
582
|
-
|
|
589
|
+
|
|
590
|
+
const renames = group.filter(r => r.name_source === 'rename');
|
|
591
|
+
const basenames = group.filter(r => r.name_source !== 'rename');
|
|
592
|
+
|
|
593
|
+
// Rename rows win unchanged. The ``taken`` set guards basename
|
|
594
|
+
// disambiguation from colliding back into a rename's label.
|
|
595
|
+
const taken = new Set<string>();
|
|
596
|
+
for (const r of renames) {
|
|
597
|
+
out.set(r.project_path, r.name);
|
|
598
|
+
taken.add(r.name);
|
|
599
|
+
}
|
|
600
|
+
if (basenames.length === 0) {
|
|
601
|
+
continue;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// Walk path tails for basename rows: tail must be unique among
|
|
605
|
+
// basenames AND not equal to any rename label already taken.
|
|
606
|
+
const segs = basenames.map(r =>
|
|
607
|
+
r.project_path.split('/').filter(Boolean)
|
|
608
|
+
);
|
|
583
609
|
const max = Math.max(...segs.map(s => s.length));
|
|
584
610
|
let depth = 1;
|
|
611
|
+
let resolved = false;
|
|
585
612
|
while (depth <= max) {
|
|
586
613
|
const tails = segs.map(s => s.slice(-depth).join('/'));
|
|
587
|
-
|
|
588
|
-
|
|
614
|
+
const unique = new Set(tails).size === tails.length;
|
|
615
|
+
const noConflict = tails.every(t => !taken.has(t));
|
|
616
|
+
if (unique && noConflict) {
|
|
617
|
+
basenames.forEach((r, i) => out.set(r.project_path, tails[i]));
|
|
618
|
+
resolved = true;
|
|
589
619
|
break;
|
|
590
620
|
}
|
|
591
621
|
depth += 1;
|
|
592
622
|
}
|
|
593
|
-
if (!
|
|
623
|
+
if (!resolved) {
|
|
594
624
|
// Fallback to absolute path
|
|
595
|
-
|
|
625
|
+
basenames.forEach(r => out.set(r.project_path, r.project_path));
|
|
596
626
|
}
|
|
597
627
|
}
|
|
598
628
|
return out;
|