jupyterlab_claude_code_extension 1.0.19 → 1.0.26

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/icons.d.ts CHANGED
@@ -3,3 +3,4 @@ export declare const claudeIcon: LabIcon;
3
3
  export declare const starFilledIcon: LabIcon;
4
4
  export declare const refreshIcon: LabIcon;
5
5
  export declare const removeIcon: LabIcon;
6
+ export declare const shieldIcon: LabIcon;
package/lib/icons.js CHANGED
@@ -42,3 +42,10 @@ export const removeIcon = new LabIcon({
42
42
  name: 'jupyterlab_claude_code_extension:remove',
43
43
  svgstr: removeSvgStr
44
44
  });
45
+ const shieldSvgStr = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16">
46
+ <path class="jp-icon3" fill="#616161" d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm0 10.99h7c-.53 4.12-3.28 7.79-7 8.94V12H5V6.3l7-3.11v8.8z"/>
47
+ </svg>`;
48
+ export const shieldIcon = new LabIcon({
49
+ name: 'jupyterlab_claude_code_extension:shield',
50
+ svgstr: shieldSvgStr
51
+ });
package/lib/index.js CHANGED
@@ -38,6 +38,9 @@ const plugin = {
38
38
  if (typeof limit === 'number') {
39
39
  widget.setRecentLimit(limit);
40
40
  }
41
+ const dangerous = settings.get('dangerouslySkipPermissions')
42
+ .composite;
43
+ widget.setDangerouslySkipPermissions(!!dangerous);
41
44
  };
42
45
  apply();
43
46
  settings.changed.connect(apply);
package/lib/types.d.ts CHANGED
@@ -37,6 +37,7 @@ export interface IRemoveResponse {
37
37
  export interface ILaunchTerminalRequest {
38
38
  project_path: string;
39
39
  session_id: string;
40
+ dangerously_skip_permissions?: boolean;
40
41
  }
41
42
  export interface ILaunchTerminalResponse {
42
43
  terminal_name: string;
package/lib/widget.d.ts CHANGED
@@ -9,12 +9,26 @@ export declare class ClaudeCodeSessionsWidget extends Widget {
9
9
  setResolveSessionNames(on: boolean): void;
10
10
  /** Set how many rows the Recent section displays. */
11
11
  setRecentLimit(n: number): void;
12
+ /** Toggle the --dangerously-skip-permissions flag on launched sessions. */
13
+ setDangerouslySkipPermissions(on: boolean): void;
12
14
  protected onAfterShow(_msg: Message): void;
13
15
  protected onBeforeHide(_msg: Message): void;
14
16
  protected onCloseRequest(msg: Message): void;
15
17
  private _buildShell;
16
- /** Lowercase substring + subsequence match. */
18
+ /** Normalise strings for filter comparison: NFD-decompose, strip combining
19
+ * diacritic marks, lowercase, and collapse separators (`-`, `_`, `.`, `/`,
20
+ * whitespace) entirely. So "foo-bar", "foo_bar", "foo bar", "Foo Bar" all
21
+ * compare equal as "foobar".
22
+ */
23
+ private _normalize;
24
+ /** Fuzzy match at a 95% threshold: substring on normalised strings,
25
+ * with up to 5% Levenshtein tolerance. For short queries the budget
26
+ * still rounds to zero so behaviour is substring-only there - the
27
+ * relaxation only kicks in for queries long enough that 5% reaches a
28
+ * full edit (10+ chars).
29
+ */
17
30
  private _fuzzyMatch;
31
+ private _levenshtein;
18
32
  private _matchesFilter;
19
33
  private _showLoading;
20
34
  private _showError;
@@ -61,6 +75,7 @@ export declare class ClaudeCodeSessionsWidget extends Widget {
61
75
  private readonly _rootDir;
62
76
  private _resolveNames;
63
77
  private _recentLimit;
78
+ private _dangerouslySkip;
64
79
  private _displayNames;
65
80
  private _filter;
66
81
  }
package/lib/widget.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { CommandRegistry } from '@lumino/commands';
2
2
  import { Menu, Widget } from '@lumino/widgets';
3
3
  import { requestAPI } from './request';
4
- import { claudeIcon, refreshIcon, removeIcon, starFilledIcon } from './icons';
4
+ import { claudeIcon, refreshIcon, removeIcon, shieldIcon, starFilledIcon } from './icons';
5
5
  const POLL_INTERVAL_MS = 10000;
6
6
  const DEFAULT_RECENT_LIMIT = 10;
7
7
  const EXPANDED_STORAGE_KEY = 'jupyterlab_claude_code_extension:expanded';
@@ -66,6 +66,7 @@ export class ClaudeCodeSessionsWidget extends Widget {
66
66
  this._pendingByPath = new Map();
67
67
  this._resolveNames = true;
68
68
  this._recentLimit = DEFAULT_RECENT_LIMIT;
69
+ this._dangerouslySkip = false;
69
70
  this._displayNames = new Map();
70
71
  this._filter = '';
71
72
  this._app = app;
@@ -103,6 +104,10 @@ export class ClaudeCodeSessionsWidget extends Widget {
103
104
  this._recentLimit = clamped;
104
105
  this._render();
105
106
  }
107
+ /** Toggle the --dangerously-skip-permissions flag on launched sessions. */
108
+ setDangerouslySkipPermissions(on) {
109
+ this._dangerouslySkip = !!on;
110
+ }
106
111
  onAfterShow(_msg) {
107
112
  this.refresh();
108
113
  this._startPolling();
@@ -151,23 +156,76 @@ export class ClaudeCodeSessionsWidget extends Widget {
151
156
  this._bodyEl = body;
152
157
  this._statusEl = status;
153
158
  }
154
- /** Lowercase substring + subsequence match. */
159
+ /** Normalise strings for filter comparison: NFD-decompose, strip combining
160
+ * diacritic marks, lowercase, and collapse separators (`-`, `_`, `.`, `/`,
161
+ * whitespace) entirely. So "foo-bar", "foo_bar", "foo bar", "Foo Bar" all
162
+ * compare equal as "foobar".
163
+ */
164
+ _normalize(s) {
165
+ return s
166
+ .normalize('NFD')
167
+ .replace(/[̀-ͯ]/g, '')
168
+ .toLowerCase()
169
+ .replace(/[\s\-_./]+/g, '');
170
+ }
171
+ /** Fuzzy match at a 95% threshold: substring on normalised strings,
172
+ * with up to 5% Levenshtein tolerance. For short queries the budget
173
+ * still rounds to zero so behaviour is substring-only there - the
174
+ * relaxation only kicks in for queries long enough that 5% reaches a
175
+ * full edit (10+ chars).
176
+ */
155
177
  _fuzzyMatch(haystack, needle) {
156
178
  if (!needle) {
157
179
  return true;
158
180
  }
159
- const h = haystack.toLowerCase();
160
- const n = needle.toLowerCase();
181
+ const h = this._normalize(haystack);
182
+ const n = this._normalize(needle);
183
+ if (!n) {
184
+ return true;
185
+ }
161
186
  if (h.includes(n)) {
162
187
  return true;
163
188
  }
164
- let j = 0;
165
- for (let i = 0; i < h.length && j < n.length; i++) {
166
- if (h[i] === n[j]) {
167
- j += 1;
189
+ const tol = Math.round(n.length * 0.05);
190
+ if (tol === 0) {
191
+ return false;
192
+ }
193
+ for (let len = n.length - tol; len <= n.length + tol; len += 1) {
194
+ if (len <= 0) {
195
+ continue;
196
+ }
197
+ for (let i = 0; i + len <= h.length; i += 1) {
198
+ if (this._levenshtein(h.slice(i, i + len), n) <= tol) {
199
+ return true;
200
+ }
201
+ }
202
+ }
203
+ return false;
204
+ }
205
+ _levenshtein(a, b) {
206
+ const m = a.length;
207
+ const n = b.length;
208
+ if (m === 0) {
209
+ return n;
210
+ }
211
+ if (n === 0) {
212
+ return m;
213
+ }
214
+ const dp = new Array(n + 1);
215
+ for (let j = 0; j <= n; j += 1) {
216
+ dp[j] = j;
217
+ }
218
+ for (let i = 1; i <= m; i += 1) {
219
+ let prev = dp[0];
220
+ dp[0] = i;
221
+ for (let j = 1; j <= n; j += 1) {
222
+ const tmp = dp[j];
223
+ dp[j] =
224
+ a[i - 1] === b[j - 1] ? prev : 1 + Math.min(prev, dp[j], dp[j - 1]);
225
+ prev = tmp;
168
226
  }
169
227
  }
170
- return j === n.length;
228
+ return dp[n];
171
229
  }
172
230
  _matchesFilter(s) {
173
231
  const q = this._filter.trim();
@@ -235,20 +293,20 @@ export class ClaudeCodeSessionsWidget extends Widget {
235
293
  }
236
294
  }
237
295
  // -------------------------------------------------------------- terminal
238
- async _resumeInTerminal(session) {
296
+ async _resumeInTerminal(session, forceDangerous = false) {
239
297
  // Coalesce concurrent clicks on the same row - subsequent clicks attach
240
298
  // to the in-flight promise instead of creating their own terminal.
241
299
  const inFlight = this._pendingByPath.get(session.project_path);
242
300
  if (inFlight) {
243
301
  return inFlight;
244
302
  }
245
- const promise = this._doResumeInTerminal(session).finally(() => {
303
+ const promise = this._doResumeInTerminal(session, forceDangerous).finally(() => {
246
304
  this._pendingByPath.delete(session.project_path);
247
305
  });
248
306
  this._pendingByPath.set(session.project_path, promise);
249
307
  return promise;
250
308
  }
251
- async _doResumeInTerminal(session) {
309
+ async _doResumeInTerminal(session, forceDangerous) {
252
310
  try {
253
311
  // 1. In-memory microcache - covers rapid-click and post-creation reuse
254
312
  // before the new widget propagates fully through the tracker. Cleared
@@ -277,7 +335,8 @@ export class ClaudeCodeSessionsWidget extends Widget {
277
335
  method: 'POST',
278
336
  body: JSON.stringify({
279
337
  project_path: session.project_path,
280
- session_id: session.session_id
338
+ session_id: session.session_id,
339
+ dangerously_skip_permissions: forceDangerous || this._dangerouslySkip
281
340
  })
282
341
  });
283
342
  const widget = await this._app.commands.execute('terminal:open', {
@@ -626,6 +685,15 @@ export class ClaudeCodeSessionsWidget extends Widget {
626
685
  }
627
686
  }
628
687
  });
688
+ this._commands.addCommand('claude-code-sessions:resume-dangerous', {
689
+ label: 'Resume (Skip Permissions)',
690
+ icon: shieldIcon,
691
+ execute: () => {
692
+ if (this._activeSession) {
693
+ void this._resumeInTerminal(this._activeSession, true);
694
+ }
695
+ }
696
+ });
629
697
  this._commands.addCommand('claude-code-sessions:remove', {
630
698
  label: 'Remove from Claude',
631
699
  icon: removeIcon,
@@ -638,6 +706,9 @@ export class ClaudeCodeSessionsWidget extends Widget {
638
706
  this._contextMenu = new Menu({ commands: this._commands });
639
707
  this._contextMenu.addClass('jp-ClaudeSessionsContextMenu');
640
708
  this._contextMenu.addItem({ command: 'claude-code-sessions:resume' });
709
+ this._contextMenu.addItem({
710
+ command: 'claude-code-sessions:resume-dangerous'
711
+ });
641
712
  this._contextMenu.addItem({
642
713
  command: 'claude-code-sessions:toggle-favourite'
643
714
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jupyterlab_claude_code_extension",
3
- "version": "1.0.19",
3
+ "version": "1.0.26",
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/icons.ts CHANGED
@@ -50,3 +50,12 @@ export const removeIcon = new LabIcon({
50
50
  name: 'jupyterlab_claude_code_extension:remove',
51
51
  svgstr: removeSvgStr
52
52
  });
53
+
54
+ const shieldSvgStr = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16">
55
+ <path class="jp-icon3" fill="#616161" d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm0 10.99h7c-.53 4.12-3.28 7.79-7 8.94V12H5V6.3l7-3.11v8.8z"/>
56
+ </svg>`;
57
+
58
+ export const shieldIcon = new LabIcon({
59
+ name: 'jupyterlab_claude_code_extension:shield',
60
+ svgstr: shieldSvgStr
61
+ });
package/src/index.ts CHANGED
@@ -66,6 +66,9 @@ const plugin: JupyterFrontEndPlugin<void> = {
66
66
  if (typeof limit === 'number') {
67
67
  widget.setRecentLimit(limit);
68
68
  }
69
+ const dangerous = settings.get('dangerouslySkipPermissions')
70
+ .composite as boolean;
71
+ widget.setDangerouslySkipPermissions(!!dangerous);
69
72
  };
70
73
  apply();
71
74
  settings.changed.connect(apply);
package/src/types.ts CHANGED
@@ -44,6 +44,7 @@ export interface IRemoveResponse {
44
44
  export interface ILaunchTerminalRequest {
45
45
  project_path: string;
46
46
  session_id: string;
47
+ dangerously_skip_permissions?: boolean;
47
48
  }
48
49
 
49
50
  export interface ILaunchTerminalResponse {
package/src/widget.ts CHANGED
@@ -6,7 +6,13 @@ import { Menu, Widget } from '@lumino/widgets';
6
6
  import { Message } from '@lumino/messaging';
7
7
 
8
8
  import { requestAPI } from './request';
9
- import { claudeIcon, refreshIcon, removeIcon, starFilledIcon } from './icons';
9
+ import {
10
+ claudeIcon,
11
+ refreshIcon,
12
+ removeIcon,
13
+ shieldIcon,
14
+ starFilledIcon
15
+ } from './icons';
10
16
  import {
11
17
  IFavouriteResponse,
12
18
  ILaunchTerminalResponse,
@@ -125,6 +131,11 @@ export class ClaudeCodeSessionsWidget extends Widget {
125
131
  this._render();
126
132
  }
127
133
 
134
+ /** Toggle the --dangerously-skip-permissions flag on launched sessions. */
135
+ setDangerouslySkipPermissions(on: boolean): void {
136
+ this._dangerouslySkip = !!on;
137
+ }
138
+
128
139
  protected onAfterShow(_msg: Message): void {
129
140
  this.refresh();
130
141
  this._startPolling();
@@ -186,23 +197,78 @@ export class ClaudeCodeSessionsWidget extends Widget {
186
197
  this._statusEl = status;
187
198
  }
188
199
 
189
- /** Lowercase substring + subsequence match. */
200
+ /** Normalise strings for filter comparison: NFD-decompose, strip combining
201
+ * diacritic marks, lowercase, and collapse separators (`-`, `_`, `.`, `/`,
202
+ * whitespace) entirely. So "foo-bar", "foo_bar", "foo bar", "Foo Bar" all
203
+ * compare equal as "foobar".
204
+ */
205
+ private _normalize(s: string): string {
206
+ return s
207
+ .normalize('NFD')
208
+ .replace(/[̀-ͯ]/g, '')
209
+ .toLowerCase()
210
+ .replace(/[\s\-_./]+/g, '');
211
+ }
212
+
213
+ /** Fuzzy match at a 95% threshold: substring on normalised strings,
214
+ * with up to 5% Levenshtein tolerance. For short queries the budget
215
+ * still rounds to zero so behaviour is substring-only there - the
216
+ * relaxation only kicks in for queries long enough that 5% reaches a
217
+ * full edit (10+ chars).
218
+ */
190
219
  private _fuzzyMatch(haystack: string, needle: string): boolean {
191
220
  if (!needle) {
192
221
  return true;
193
222
  }
194
- const h = haystack.toLowerCase();
195
- const n = needle.toLowerCase();
223
+ const h = this._normalize(haystack);
224
+ const n = this._normalize(needle);
225
+ if (!n) {
226
+ return true;
227
+ }
196
228
  if (h.includes(n)) {
197
229
  return true;
198
230
  }
199
- let j = 0;
200
- for (let i = 0; i < h.length && j < n.length; i++) {
201
- if (h[i] === n[j]) {
202
- j += 1;
231
+ const tol = Math.round(n.length * 0.05);
232
+ if (tol === 0) {
233
+ return false;
234
+ }
235
+ for (let len = n.length - tol; len <= n.length + tol; len += 1) {
236
+ if (len <= 0) {
237
+ continue;
238
+ }
239
+ for (let i = 0; i + len <= h.length; i += 1) {
240
+ if (this._levenshtein(h.slice(i, i + len), n) <= tol) {
241
+ return true;
242
+ }
203
243
  }
204
244
  }
205
- return j === n.length;
245
+ return false;
246
+ }
247
+
248
+ private _levenshtein(a: string, b: string): number {
249
+ const m = a.length;
250
+ const n = b.length;
251
+ if (m === 0) {
252
+ return n;
253
+ }
254
+ if (n === 0) {
255
+ return m;
256
+ }
257
+ const dp: number[] = new Array(n + 1);
258
+ for (let j = 0; j <= n; j += 1) {
259
+ dp[j] = j;
260
+ }
261
+ for (let i = 1; i <= m; i += 1) {
262
+ let prev = dp[0];
263
+ dp[0] = i;
264
+ for (let j = 1; j <= n; j += 1) {
265
+ const tmp = dp[j];
266
+ dp[j] =
267
+ a[i - 1] === b[j - 1] ? prev : 1 + Math.min(prev, dp[j], dp[j - 1]);
268
+ prev = tmp;
269
+ }
270
+ }
271
+ return dp[n];
206
272
  }
207
273
 
208
274
  private _matchesFilter(s: ISession): boolean {
@@ -289,21 +355,29 @@ export class ClaudeCodeSessionsWidget extends Widget {
289
355
 
290
356
  // -------------------------------------------------------------- terminal
291
357
 
292
- private async _resumeInTerminal(session: ISession): Promise<void> {
358
+ private async _resumeInTerminal(
359
+ session: ISession,
360
+ forceDangerous: boolean = false
361
+ ): Promise<void> {
293
362
  // Coalesce concurrent clicks on the same row - subsequent clicks attach
294
363
  // to the in-flight promise instead of creating their own terminal.
295
364
  const inFlight = this._pendingByPath.get(session.project_path);
296
365
  if (inFlight) {
297
366
  return inFlight;
298
367
  }
299
- const promise = this._doResumeInTerminal(session).finally(() => {
300
- this._pendingByPath.delete(session.project_path);
301
- });
368
+ const promise = this._doResumeInTerminal(session, forceDangerous).finally(
369
+ () => {
370
+ this._pendingByPath.delete(session.project_path);
371
+ }
372
+ );
302
373
  this._pendingByPath.set(session.project_path, promise);
303
374
  return promise;
304
375
  }
305
376
 
306
- private async _doResumeInTerminal(session: ISession): Promise<void> {
377
+ private async _doResumeInTerminal(
378
+ session: ISession,
379
+ forceDangerous: boolean
380
+ ): Promise<void> {
307
381
  try {
308
382
  // 1. In-memory microcache - covers rapid-click and post-creation reuse
309
383
  // before the new widget propagates fully through the tracker. Cleared
@@ -337,7 +411,9 @@ export class ClaudeCodeSessionsWidget extends Widget {
337
411
  method: 'POST',
338
412
  body: JSON.stringify({
339
413
  project_path: session.project_path,
340
- session_id: session.session_id
414
+ session_id: session.session_id,
415
+ dangerously_skip_permissions:
416
+ forceDangerous || this._dangerouslySkip
341
417
  })
342
418
  }
343
419
  );
@@ -724,6 +800,16 @@ export class ClaudeCodeSessionsWidget extends Widget {
724
800
  }
725
801
  });
726
802
 
803
+ this._commands.addCommand('claude-code-sessions:resume-dangerous', {
804
+ label: 'Resume (Skip Permissions)',
805
+ icon: shieldIcon,
806
+ execute: () => {
807
+ if (this._activeSession) {
808
+ void this._resumeInTerminal(this._activeSession, true);
809
+ }
810
+ }
811
+ });
812
+
727
813
  this._commands.addCommand('claude-code-sessions:remove', {
728
814
  label: 'Remove from Claude',
729
815
  icon: removeIcon,
@@ -737,6 +823,9 @@ export class ClaudeCodeSessionsWidget extends Widget {
737
823
  this._contextMenu = new Menu({ commands: this._commands });
738
824
  this._contextMenu.addClass('jp-ClaudeSessionsContextMenu');
739
825
  this._contextMenu.addItem({ command: 'claude-code-sessions:resume' });
826
+ this._contextMenu.addItem({
827
+ command: 'claude-code-sessions:resume-dangerous'
828
+ });
740
829
  this._contextMenu.addItem({
741
830
  command: 'claude-code-sessions:toggle-favourite'
742
831
  });
@@ -793,6 +882,7 @@ export class ClaudeCodeSessionsWidget extends Widget {
793
882
  private readonly _rootDir: string;
794
883
  private _resolveNames: boolean = true;
795
884
  private _recentLimit: number = DEFAULT_RECENT_LIMIT;
885
+ private _dangerouslySkip: boolean = false;
796
886
  private _displayNames: Map<string, string> = new Map();
797
887
  private _filter: string = '';
798
888
  }