jupyterlab_claude_code_extension 1.0.56 → 1.0.57
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/lib/widget.d.ts +9 -0
- package/lib/widget.js +38 -8
- package/package.json +1 -1
- package/src/widget.ts +37 -8
package/lib/widget.d.ts
CHANGED
|
@@ -38,6 +38,15 @@ export declare class ClaudeCodeSessionsWidget extends Widget {
|
|
|
38
38
|
private _remove;
|
|
39
39
|
private _resumeInTerminal;
|
|
40
40
|
private _doResumeInTerminal;
|
|
41
|
+
/**
|
|
42
|
+
* Bring a terminal tab to the front AND hand it keyboard focus, so the
|
|
43
|
+
* user can start typing without an extra click. `activateById` only
|
|
44
|
+
* reveals the tab; the xterm inside doesn't always grab DOM focus,
|
|
45
|
+
* especially when the click originated in this sidebar. We defer the
|
|
46
|
+
* `term.focus()` to the next frame so the widget is attached and visible
|
|
47
|
+
* first.
|
|
48
|
+
*/
|
|
49
|
+
private _focusTerminal;
|
|
41
50
|
private _findTerminalForCwd;
|
|
42
51
|
private _showCloseExistingDialog;
|
|
43
52
|
/** Show a modal with a spinner while the terminal is being launched. The
|
package/lib/widget.js
CHANGED
|
@@ -3,7 +3,7 @@ import { CommandRegistry } from '@lumino/commands';
|
|
|
3
3
|
import { Menu, Widget } from '@lumino/widgets';
|
|
4
4
|
import { requestAPI } from './request';
|
|
5
5
|
import { claudeIcon, refreshIcon, removeIcon, shieldIcon, starFilledIcon } from './icons';
|
|
6
|
-
const POLL_INTERVAL_MS =
|
|
6
|
+
const POLL_INTERVAL_MS = 30000;
|
|
7
7
|
const DEFAULT_RECENT_LIMIT = 10;
|
|
8
8
|
const EXPANDED_STORAGE_KEY = 'jupyterlab_claude_code_extension:expanded';
|
|
9
9
|
const DEFAULT_PRESENTATION_MODE = 'session';
|
|
@@ -244,7 +244,10 @@ export class ClaudeCodeSessionsWidget extends Widget {
|
|
|
244
244
|
// ------------------------------------------------------------------ data
|
|
245
245
|
async _fetch() {
|
|
246
246
|
var _a;
|
|
247
|
-
|
|
247
|
+
// `cache: 'no-store'` so the manual refresh button (and the post-launch
|
|
248
|
+
// refresh) always re-read the server's view of ~/.claude rather than a
|
|
249
|
+
// possibly-stale browser-cached response.
|
|
250
|
+
const data = await requestAPI('sessions', this._serverSettings, { cache: 'no-store' });
|
|
248
251
|
this._sessions = (_a = data.sessions) !== null && _a !== void 0 ? _a : [];
|
|
249
252
|
this._render();
|
|
250
253
|
}
|
|
@@ -315,10 +318,8 @@ export class ClaudeCodeSessionsWidget extends Widget {
|
|
|
315
318
|
if (cached && !cached.isDisposed) {
|
|
316
319
|
if (forceDangerous) {
|
|
317
320
|
await this._showCloseExistingDialog();
|
|
318
|
-
this._app.shell.activateById(cached.id);
|
|
319
|
-
return;
|
|
320
321
|
}
|
|
321
|
-
this.
|
|
322
|
+
this._focusTerminal(cached);
|
|
322
323
|
return;
|
|
323
324
|
}
|
|
324
325
|
// 2. Walk every live terminal widget JL knows about.
|
|
@@ -328,10 +329,8 @@ export class ClaudeCodeSessionsWidget extends Widget {
|
|
|
328
329
|
this._wireTerminalDisposal(session.project_path, found);
|
|
329
330
|
if (forceDangerous) {
|
|
330
331
|
await this._showCloseExistingDialog();
|
|
331
|
-
this._app.shell.activateById(found.id);
|
|
332
|
-
return;
|
|
333
332
|
}
|
|
334
|
-
this.
|
|
333
|
+
this._focusTerminal(found);
|
|
335
334
|
return;
|
|
336
335
|
}
|
|
337
336
|
// 3. No matching terminal - spawn a new one with `claude --resume <id>`
|
|
@@ -357,6 +356,7 @@ export class ClaudeCodeSessionsWidget extends Widget {
|
|
|
357
356
|
if (widget === null || widget === void 0 ? void 0 : widget.id) {
|
|
358
357
|
this._terminalsByPath.set(session.project_path, widget);
|
|
359
358
|
this._wireTerminalDisposal(session.project_path, widget);
|
|
359
|
+
this._focusTerminal(widget);
|
|
360
360
|
}
|
|
361
361
|
}
|
|
362
362
|
finally {
|
|
@@ -366,6 +366,36 @@ export class ClaudeCodeSessionsWidget extends Widget {
|
|
|
366
366
|
catch (err) {
|
|
367
367
|
this._showError(err);
|
|
368
368
|
}
|
|
369
|
+
finally {
|
|
370
|
+
// Reuse or fresh launch, either way the picture changed (a session may
|
|
371
|
+
// now be remote-controlled, a row may have appeared). Pull fresh state.
|
|
372
|
+
void this._fetch().catch(() => {
|
|
373
|
+
/* a poll tick will retry; nothing actionable here */
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Bring a terminal tab to the front AND hand it keyboard focus, so the
|
|
379
|
+
* user can start typing without an extra click. `activateById` only
|
|
380
|
+
* reveals the tab; the xterm inside doesn't always grab DOM focus,
|
|
381
|
+
* especially when the click originated in this sidebar. We defer the
|
|
382
|
+
* `term.focus()` to the next frame so the widget is attached and visible
|
|
383
|
+
* first.
|
|
384
|
+
*/
|
|
385
|
+
_focusTerminal(widget) {
|
|
386
|
+
if (!widget || widget.isDisposed) {
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
this._app.shell.activateById(widget.id);
|
|
390
|
+
requestAnimationFrame(() => {
|
|
391
|
+
var _a, _b, _c;
|
|
392
|
+
try {
|
|
393
|
+
(_c = (_b = (_a = widget.content) === null || _a === void 0 ? void 0 : _a.term) === null || _b === void 0 ? void 0 : _b.focus) === null || _c === void 0 ? void 0 : _c.call(_b);
|
|
394
|
+
}
|
|
395
|
+
catch (_err) {
|
|
396
|
+
/* terminal may have been disposed in the meantime - ignore */
|
|
397
|
+
}
|
|
398
|
+
});
|
|
369
399
|
}
|
|
370
400
|
async _findTerminalForCwd(projectPath) {
|
|
371
401
|
var _a, _b;
|
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.57",
|
|
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/widget.ts
CHANGED
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
ISessionsListResponse
|
|
23
23
|
} from './types';
|
|
24
24
|
|
|
25
|
-
const POLL_INTERVAL_MS =
|
|
25
|
+
const POLL_INTERVAL_MS = 30_000;
|
|
26
26
|
const DEFAULT_RECENT_LIMIT = 10;
|
|
27
27
|
const EXPANDED_STORAGE_KEY = 'jupyterlab_claude_code_extension:expanded';
|
|
28
28
|
|
|
@@ -296,9 +296,13 @@ export class ClaudeCodeSessionsWidget extends Widget {
|
|
|
296
296
|
// ------------------------------------------------------------------ data
|
|
297
297
|
|
|
298
298
|
private async _fetch(): Promise<void> {
|
|
299
|
+
// `cache: 'no-store'` so the manual refresh button (and the post-launch
|
|
300
|
+
// refresh) always re-read the server's view of ~/.claude rather than a
|
|
301
|
+
// possibly-stale browser-cached response.
|
|
299
302
|
const data = await requestAPI<ISessionsListResponse>(
|
|
300
303
|
'sessions',
|
|
301
|
-
this._serverSettings
|
|
304
|
+
this._serverSettings,
|
|
305
|
+
{ cache: 'no-store' }
|
|
302
306
|
);
|
|
303
307
|
this._sessions = data.sessions ?? [];
|
|
304
308
|
this._render();
|
|
@@ -390,10 +394,8 @@ export class ClaudeCodeSessionsWidget extends Widget {
|
|
|
390
394
|
if (cached && !cached.isDisposed) {
|
|
391
395
|
if (forceDangerous) {
|
|
392
396
|
await this._showCloseExistingDialog();
|
|
393
|
-
this._app.shell.activateById(cached.id);
|
|
394
|
-
return;
|
|
395
397
|
}
|
|
396
|
-
this.
|
|
398
|
+
this._focusTerminal(cached);
|
|
397
399
|
return;
|
|
398
400
|
}
|
|
399
401
|
|
|
@@ -404,10 +406,8 @@ export class ClaudeCodeSessionsWidget extends Widget {
|
|
|
404
406
|
this._wireTerminalDisposal(session.project_path, found);
|
|
405
407
|
if (forceDangerous) {
|
|
406
408
|
await this._showCloseExistingDialog();
|
|
407
|
-
this._app.shell.activateById(found.id);
|
|
408
|
-
return;
|
|
409
409
|
}
|
|
410
|
-
this.
|
|
410
|
+
this._focusTerminal(found);
|
|
411
411
|
return;
|
|
412
412
|
}
|
|
413
413
|
|
|
@@ -441,15 +441,44 @@ export class ClaudeCodeSessionsWidget extends Widget {
|
|
|
441
441
|
if (widget?.id) {
|
|
442
442
|
this._terminalsByPath.set(session.project_path, widget);
|
|
443
443
|
this._wireTerminalDisposal(session.project_path, widget);
|
|
444
|
+
this._focusTerminal(widget);
|
|
444
445
|
}
|
|
445
446
|
} finally {
|
|
446
447
|
spinner.resolve();
|
|
447
448
|
}
|
|
448
449
|
} catch (err) {
|
|
449
450
|
this._showError(err);
|
|
451
|
+
} finally {
|
|
452
|
+
// Reuse or fresh launch, either way the picture changed (a session may
|
|
453
|
+
// now be remote-controlled, a row may have appeared). Pull fresh state.
|
|
454
|
+
void this._fetch().catch(() => {
|
|
455
|
+
/* a poll tick will retry; nothing actionable here */
|
|
456
|
+
});
|
|
450
457
|
}
|
|
451
458
|
}
|
|
452
459
|
|
|
460
|
+
/**
|
|
461
|
+
* Bring a terminal tab to the front AND hand it keyboard focus, so the
|
|
462
|
+
* user can start typing without an extra click. `activateById` only
|
|
463
|
+
* reveals the tab; the xterm inside doesn't always grab DOM focus,
|
|
464
|
+
* especially when the click originated in this sidebar. We defer the
|
|
465
|
+
* `term.focus()` to the next frame so the widget is attached and visible
|
|
466
|
+
* first.
|
|
467
|
+
*/
|
|
468
|
+
private _focusTerminal(widget: any): void {
|
|
469
|
+
if (!widget || widget.isDisposed) {
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
this._app.shell.activateById(widget.id);
|
|
473
|
+
requestAnimationFrame(() => {
|
|
474
|
+
try {
|
|
475
|
+
widget.content?.term?.focus?.();
|
|
476
|
+
} catch (_err) {
|
|
477
|
+
/* terminal may have been disposed in the meantime - ignore */
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
|
|
453
482
|
private async _findTerminalForCwd(projectPath: string): Promise<any | null> {
|
|
454
483
|
if (!this._terminalTracker) {
|
|
455
484
|
return null;
|