jupyterlab_claude_code_extension 1.0.16
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/LICENSE +29 -0
- package/README.md +51 -0
- package/lib/icons.d.ts +5 -0
- package/lib/icons.js +44 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.js +57 -0
- package/lib/request.d.ts +10 -0
- package/lib/request.js +35 -0
- package/lib/types.d.ts +36 -0
- package/lib/types.js +1 -0
- package/lib/widget.d.ts +60 -0
- package/lib/widget.js +643 -0
- package/package.json +217 -0
- package/src/__tests__/jupyterlab_claude_code_extension.spec.ts +62 -0
- package/src/icons.ts +52 -0
- package/src/index.ts +95 -0
- package/src/request.ts +51 -0
- package/src/types.ts +42 -0
- package/src/widget.ts +764 -0
- package/style/base.css +246 -0
- package/style/index.css +1 -0
- package/style/index.js +1 -0
package/package.json
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "jupyterlab_claude_code_extension",
|
|
3
|
+
"version": "1.0.16",
|
|
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
|
+
"keywords": [
|
|
6
|
+
"jupyter",
|
|
7
|
+
"jupyterlab",
|
|
8
|
+
"jupyterlab-extension"
|
|
9
|
+
],
|
|
10
|
+
"homepage": "https://github.com/stellarshenson/jupyterlab_claude_code_extension",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/stellarshenson/jupyterlab_claude_code_extension/issues"
|
|
13
|
+
},
|
|
14
|
+
"license": "BSD-3-Clause",
|
|
15
|
+
"author": {
|
|
16
|
+
"name": "Stellars Henson",
|
|
17
|
+
"email": "konrad.jelen@gmail.com"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
|
|
21
|
+
"style/**/*.{css,js,eot,gif,html,jpg,json,png,svg,woff2,ttf}",
|
|
22
|
+
"src/**/*.{ts,tsx}"
|
|
23
|
+
],
|
|
24
|
+
"main": "lib/index.js",
|
|
25
|
+
"types": "lib/index.d.ts",
|
|
26
|
+
"style": "style/index.css",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "https://github.com/stellarshenson/jupyterlab_claude_code_extension.git"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "jlpm build:lib && jlpm build:labextension:dev",
|
|
33
|
+
"build:prod": "jlpm clean && jlpm build:lib:prod && jlpm build:labextension",
|
|
34
|
+
"build:labextension": "jupyter labextension build .",
|
|
35
|
+
"build:labextension:dev": "jupyter labextension build --development True .",
|
|
36
|
+
"build:lib": "tsc --sourceMap",
|
|
37
|
+
"build:lib:prod": "tsc",
|
|
38
|
+
"clean": "jlpm clean:lib",
|
|
39
|
+
"clean:lib": "rimraf lib tsconfig.tsbuildinfo",
|
|
40
|
+
"clean:lintcache": "rimraf .eslintcache .stylelintcache",
|
|
41
|
+
"clean:labextension": "rimraf jupyterlab_claude_code_extension/labextension jupyterlab_claude_code_extension/_version.py",
|
|
42
|
+
"clean:all": "jlpm clean:lib && jlpm clean:labextension && jlpm clean:lintcache",
|
|
43
|
+
"eslint": "jlpm eslint:check --fix",
|
|
44
|
+
"eslint:check": "eslint . --cache --ext .ts,.tsx",
|
|
45
|
+
"install:extension": "jlpm build",
|
|
46
|
+
"lint": "jlpm stylelint && jlpm prettier && jlpm eslint",
|
|
47
|
+
"lint:check": "jlpm stylelint:check && jlpm prettier:check && jlpm eslint:check",
|
|
48
|
+
"prettier": "jlpm prettier:base --write --list-different",
|
|
49
|
+
"prettier:base": "prettier \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}\"",
|
|
50
|
+
"prettier:check": "jlpm prettier:base --check",
|
|
51
|
+
"stylelint": "jlpm stylelint:check --fix",
|
|
52
|
+
"stylelint:check": "stylelint --cache \"style/**/*.css\"",
|
|
53
|
+
"test": "jest --coverage",
|
|
54
|
+
"watch": "run-p watch:src watch:labextension",
|
|
55
|
+
"watch:src": "tsc -w --sourceMap",
|
|
56
|
+
"watch:labextension": "jupyter labextension watch ."
|
|
57
|
+
},
|
|
58
|
+
"dependencies": {
|
|
59
|
+
"@jupyterlab/application": "^4.0.0",
|
|
60
|
+
"@jupyterlab/apputils": "^4.0.0",
|
|
61
|
+
"@jupyterlab/coreutils": "^6.0.0",
|
|
62
|
+
"@jupyterlab/services": "^7.0.0",
|
|
63
|
+
"@jupyterlab/settingregistry": "^4.0.0",
|
|
64
|
+
"@jupyterlab/terminal": "^4.0.0",
|
|
65
|
+
"@jupyterlab/ui-components": "^4.0.0",
|
|
66
|
+
"@lumino/commands": "^2.0.0",
|
|
67
|
+
"@lumino/widgets": "^2.0.0"
|
|
68
|
+
},
|
|
69
|
+
"devDependencies": {
|
|
70
|
+
"@jupyterlab/builder": "^4.0.0",
|
|
71
|
+
"@jupyterlab/testutils": "^4.0.0",
|
|
72
|
+
"@types/jest": "^29.2.0",
|
|
73
|
+
"@types/json-schema": "^7.0.11",
|
|
74
|
+
"@types/react": "^18.0.26",
|
|
75
|
+
"@types/react-addons-linked-state-mixin": "^0.14.22",
|
|
76
|
+
"@typescript-eslint/eslint-plugin": "^6.1.0",
|
|
77
|
+
"@typescript-eslint/parser": "^6.1.0",
|
|
78
|
+
"css-loader": "^6.7.1",
|
|
79
|
+
"eslint": "^8.36.0",
|
|
80
|
+
"eslint-config-prettier": "^8.8.0",
|
|
81
|
+
"eslint-plugin-prettier": "^5.0.0",
|
|
82
|
+
"jest": "^29.2.0",
|
|
83
|
+
"mkdirp": "^1.0.3",
|
|
84
|
+
"npm-run-all2": "^7.0.1",
|
|
85
|
+
"prettier": "^3.0.0",
|
|
86
|
+
"rimraf": "^5.0.1",
|
|
87
|
+
"source-map-loader": "^1.0.2",
|
|
88
|
+
"style-loader": "^3.3.1",
|
|
89
|
+
"stylelint": "^15.10.1",
|
|
90
|
+
"stylelint-config-recommended": "^13.0.0",
|
|
91
|
+
"stylelint-config-standard": "^34.0.0",
|
|
92
|
+
"stylelint-csstree-validator": "^3.0.0",
|
|
93
|
+
"stylelint-prettier": "^4.0.0",
|
|
94
|
+
"typescript": "~5.5.4",
|
|
95
|
+
"yjs": "^13.5.0"
|
|
96
|
+
},
|
|
97
|
+
"resolutions": {
|
|
98
|
+
"lib0": "0.2.111"
|
|
99
|
+
},
|
|
100
|
+
"sideEffects": [
|
|
101
|
+
"style/*.css",
|
|
102
|
+
"style/index.js"
|
|
103
|
+
],
|
|
104
|
+
"styleModule": "style/index.js",
|
|
105
|
+
"publishConfig": {
|
|
106
|
+
"access": "public"
|
|
107
|
+
},
|
|
108
|
+
"jupyterlab": {
|
|
109
|
+
"discovery": {
|
|
110
|
+
"server": {
|
|
111
|
+
"managers": [
|
|
112
|
+
"pip"
|
|
113
|
+
],
|
|
114
|
+
"base": {
|
|
115
|
+
"name": "jupyterlab_claude_code_extension"
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
"extension": true,
|
|
120
|
+
"schemaDir": "schema",
|
|
121
|
+
"outputDir": "jupyterlab_claude_code_extension/labextension"
|
|
122
|
+
},
|
|
123
|
+
"eslintIgnore": [
|
|
124
|
+
"node_modules",
|
|
125
|
+
"dist",
|
|
126
|
+
"coverage",
|
|
127
|
+
"**/*.d.ts",
|
|
128
|
+
"tests",
|
|
129
|
+
"**/__tests__",
|
|
130
|
+
"ui-tests"
|
|
131
|
+
],
|
|
132
|
+
"eslintConfig": {
|
|
133
|
+
"extends": [
|
|
134
|
+
"eslint:recommended",
|
|
135
|
+
"plugin:@typescript-eslint/eslint-recommended",
|
|
136
|
+
"plugin:@typescript-eslint/recommended",
|
|
137
|
+
"plugin:prettier/recommended"
|
|
138
|
+
],
|
|
139
|
+
"parser": "@typescript-eslint/parser",
|
|
140
|
+
"parserOptions": {
|
|
141
|
+
"project": "tsconfig.json",
|
|
142
|
+
"sourceType": "module"
|
|
143
|
+
},
|
|
144
|
+
"plugins": [
|
|
145
|
+
"@typescript-eslint"
|
|
146
|
+
],
|
|
147
|
+
"rules": {
|
|
148
|
+
"@typescript-eslint/naming-convention": [
|
|
149
|
+
"error",
|
|
150
|
+
{
|
|
151
|
+
"selector": "interface",
|
|
152
|
+
"format": [
|
|
153
|
+
"PascalCase"
|
|
154
|
+
],
|
|
155
|
+
"custom": {
|
|
156
|
+
"regex": "^I[A-Z]",
|
|
157
|
+
"match": true
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
],
|
|
161
|
+
"@typescript-eslint/no-unused-vars": [
|
|
162
|
+
"warn",
|
|
163
|
+
{
|
|
164
|
+
"args": "none"
|
|
165
|
+
}
|
|
166
|
+
],
|
|
167
|
+
"@typescript-eslint/no-explicit-any": "off",
|
|
168
|
+
"@typescript-eslint/no-namespace": "off",
|
|
169
|
+
"@typescript-eslint/no-use-before-define": "off",
|
|
170
|
+
"@typescript-eslint/quotes": [
|
|
171
|
+
"error",
|
|
172
|
+
"single",
|
|
173
|
+
{
|
|
174
|
+
"avoidEscape": true,
|
|
175
|
+
"allowTemplateLiterals": false
|
|
176
|
+
}
|
|
177
|
+
],
|
|
178
|
+
"curly": [
|
|
179
|
+
"error",
|
|
180
|
+
"all"
|
|
181
|
+
],
|
|
182
|
+
"eqeqeq": "error",
|
|
183
|
+
"prefer-arrow-callback": "error"
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
"prettier": {
|
|
187
|
+
"singleQuote": true,
|
|
188
|
+
"trailingComma": "none",
|
|
189
|
+
"arrowParens": "avoid",
|
|
190
|
+
"endOfLine": "auto",
|
|
191
|
+
"overrides": [
|
|
192
|
+
{
|
|
193
|
+
"files": "package.json",
|
|
194
|
+
"options": {
|
|
195
|
+
"tabWidth": 4
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
]
|
|
199
|
+
},
|
|
200
|
+
"stylelint": {
|
|
201
|
+
"extends": [
|
|
202
|
+
"stylelint-config-recommended",
|
|
203
|
+
"stylelint-config-standard",
|
|
204
|
+
"stylelint-prettier/recommended"
|
|
205
|
+
],
|
|
206
|
+
"plugins": [
|
|
207
|
+
"stylelint-csstree-validator"
|
|
208
|
+
],
|
|
209
|
+
"rules": {
|
|
210
|
+
"csstree/validator": true,
|
|
211
|
+
"property-no-vendor-prefix": null,
|
|
212
|
+
"selector-class-pattern": "^([a-z][A-z\\d]*)(-[A-z\\d]+)*$",
|
|
213
|
+
"selector-no-vendor-prefix": null,
|
|
214
|
+
"value-no-vendor-prefix": null
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { ISession } from '../types';
|
|
2
|
+
|
|
3
|
+
const session = (over: Partial<ISession> = {}): ISession => ({
|
|
4
|
+
project_path: '/p',
|
|
5
|
+
encoded_path: '-p',
|
|
6
|
+
session_id: 'sid',
|
|
7
|
+
name: 'P',
|
|
8
|
+
summary: '',
|
|
9
|
+
first_prompt: '',
|
|
10
|
+
message_count: 0,
|
|
11
|
+
created: null,
|
|
12
|
+
modified: null,
|
|
13
|
+
file_mtime: 0,
|
|
14
|
+
git_branch: null,
|
|
15
|
+
remote_control: false,
|
|
16
|
+
favourite: false,
|
|
17
|
+
...over
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('session sorting', () => {
|
|
21
|
+
it('orders by file_mtime descending', () => {
|
|
22
|
+
const items = [
|
|
23
|
+
session({ project_path: 'a', file_mtime: 1 }),
|
|
24
|
+
session({ project_path: 'b', file_mtime: 3 }),
|
|
25
|
+
session({ project_path: 'c', file_mtime: 2 })
|
|
26
|
+
];
|
|
27
|
+
const sorted = [...items].sort((a, b) => b.file_mtime - a.file_mtime);
|
|
28
|
+
expect(sorted.map(s => s.project_path)).toEqual(['b', 'c', 'a']);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('orders alphabetically by display name', () => {
|
|
32
|
+
const items = [
|
|
33
|
+
session({ name: 'Charlie' }),
|
|
34
|
+
session({ name: 'alpha' }),
|
|
35
|
+
session({ name: 'Beta' })
|
|
36
|
+
];
|
|
37
|
+
const sorted = [...items].sort((a, b) => a.name.localeCompare(b.name));
|
|
38
|
+
expect(sorted.map(s => s.name)).toEqual(['alpha', 'Beta', 'Charlie']);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('filters favourites only', () => {
|
|
42
|
+
const items = [
|
|
43
|
+
session({ project_path: 'a', favourite: true }),
|
|
44
|
+
session({ project_path: 'b', favourite: false }),
|
|
45
|
+
session({ project_path: 'c', favourite: true })
|
|
46
|
+
];
|
|
47
|
+
const favs = items.filter(s => s.favourite).map(s => s.project_path);
|
|
48
|
+
expect(favs).toEqual(['a', 'c']);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('caps recent at the configured limit', () => {
|
|
52
|
+
const items = Array.from({ length: 25 }, (_, i) =>
|
|
53
|
+
session({ project_path: `p${i}`, file_mtime: i })
|
|
54
|
+
);
|
|
55
|
+
const recent = [...items]
|
|
56
|
+
.sort((a, b) => b.file_mtime - a.file_mtime)
|
|
57
|
+
.slice(0, 10);
|
|
58
|
+
expect(recent).toHaveLength(10);
|
|
59
|
+
expect(recent[0].project_path).toBe('p24');
|
|
60
|
+
expect(recent[9].project_path).toBe('p15');
|
|
61
|
+
});
|
|
62
|
+
});
|
package/src/icons.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { LabIcon } from '@jupyterlab/ui-components';
|
|
2
|
+
|
|
3
|
+
const claudeSvgStr = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16">
|
|
4
|
+
<g class="jp-icon3" fill="#616161">
|
|
5
|
+
<path d="m14.375 6.48.49.28v.209l-.14.489-5.937 1.397-.558-1.387zm0 0"/>
|
|
6
|
+
<path d="m12.155 2.373.683.143.182.224.173.535-.072.342-3.983 5.447L7.81 7.737l3.673-4.82z"/>
|
|
7
|
+
<path d="m8.719 1.522.419-.28.349.14.349.49-.957 5.748-.65-.441-.279-.769.49-4.33z"/>
|
|
8
|
+
<path d="m4.239 1.614.43-.55L4.95 1l.558.081.275.216 2.004 4.442.724 2.11-.848.471-3.231-5.864z"/>
|
|
9
|
+
<path d="m2.154 4.665-.14-.56.42-.488.488.07h.14l2.933 2.165.908.698 1.257.978-.698 1.187-.629-.489-.419-.419-4.05-2.863z"/>
|
|
10
|
+
<path d="M1.316 8.296 1 7.946v-.31l.316-.108 3.562.21 3.491.279-.113.695-6.66-.346z"/>
|
|
11
|
+
<path d="M3.411 11.931h-.698l-.278-.32v-.382l1.186-.838 4.82-3.068.487.833z"/>
|
|
12
|
+
<path d="m4.738 13.883-.28.07-.418-.21.07-.35 4.12-5.446.558.768-3.072 4.05z"/>
|
|
13
|
+
<path d="m8.23 14.581-.21.28-.419.14-.349-.28-.21-.42L8.09 8.646l.629.07z"/>
|
|
14
|
+
<path d="M11.791 13.045v.558l-.07.21-.279.14-.489-.066-3.356-4.996 1.331-1.014 1.117 2.025.105.733z"/>
|
|
15
|
+
<path d="m13.398 12.207.07.349-.21.279-.21-.07-1.187-.838-1.815-1.606-1.397-.978.419-1.326.698.419.42.768z"/>
|
|
16
|
+
<path d="m12.49 8.645 1.746.14.419.28.279.418v.302l-.768.327-3.911-.978-1.606-.07.419-1.466 1.117.838z"/>
|
|
17
|
+
</g>
|
|
18
|
+
</svg>`;
|
|
19
|
+
|
|
20
|
+
export const claudeIcon = new LabIcon({
|
|
21
|
+
name: 'jupyterlab_claude_code_extension:claude',
|
|
22
|
+
svgstr: claudeSvgStr
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const starFilledSvgStr = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16">
|
|
26
|
+
<path class="jp-icon3" fill="#616161" d="M12 2.5l2.9 6.4 7 .7-5.3 4.7 1.6 6.9L12 17.7l-6.2 3.5 1.6-6.9-5.3-4.7 7-.7z"/>
|
|
27
|
+
</svg>`;
|
|
28
|
+
|
|
29
|
+
export const starFilledIcon = new LabIcon({
|
|
30
|
+
name: 'jupyterlab_claude_code_extension:star-filled',
|
|
31
|
+
svgstr: starFilledSvgStr
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Material-style icons matched verbatim to jupyterlab_trash_mgmt_extension so
|
|
35
|
+
// header/menu icons render at identical size and theme.
|
|
36
|
+
const refreshSvgStr = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16">
|
|
37
|
+
<path class="jp-icon3" fill="#616161" d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/>
|
|
38
|
+
</svg>`;
|
|
39
|
+
|
|
40
|
+
export const refreshIcon = new LabIcon({
|
|
41
|
+
name: 'jupyterlab_claude_code_extension:refresh',
|
|
42
|
+
svgstr: refreshSvgStr
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const removeSvgStr = `<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="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM8 9h8v10H8V9zm7.5-5l-1-1h-5l-1 1H5v2h14V4h-3.5z"/>
|
|
47
|
+
</svg>`;
|
|
48
|
+
|
|
49
|
+
export const removeIcon = new LabIcon({
|
|
50
|
+
name: 'jupyterlab_claude_code_extension:remove',
|
|
51
|
+
svgstr: removeSvgStr
|
|
52
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ILabShell,
|
|
3
|
+
ILayoutRestorer,
|
|
4
|
+
JupyterFrontEnd,
|
|
5
|
+
JupyterFrontEndPlugin
|
|
6
|
+
} from '@jupyterlab/application';
|
|
7
|
+
import { ISettingRegistry } from '@jupyterlab/settingregistry';
|
|
8
|
+
import { ITerminalTracker } from '@jupyterlab/terminal';
|
|
9
|
+
|
|
10
|
+
import { requestAPI } from './request';
|
|
11
|
+
import { IStatusResponse } from './types';
|
|
12
|
+
import { ClaudeCodeSessionsWidget } from './widget';
|
|
13
|
+
|
|
14
|
+
const PLUGIN_ID = 'jupyterlab_claude_code_extension:plugin';
|
|
15
|
+
const WIDGET_ID = 'jupyterlab-claude-code-extension';
|
|
16
|
+
|
|
17
|
+
const plugin: JupyterFrontEndPlugin<void> = {
|
|
18
|
+
id: PLUGIN_ID,
|
|
19
|
+
description:
|
|
20
|
+
'Side panel listing Claude Code sessions per project folder, with remote-control indicator, favourites, and one-click resume in a terminal.',
|
|
21
|
+
autoStart: true,
|
|
22
|
+
requires: [ILabShell],
|
|
23
|
+
optional: [ILayoutRestorer, ISettingRegistry, ITerminalTracker],
|
|
24
|
+
activate: async (
|
|
25
|
+
app: JupyterFrontEnd,
|
|
26
|
+
labShell: ILabShell,
|
|
27
|
+
restorer: ILayoutRestorer | null,
|
|
28
|
+
settingRegistry: ISettingRegistry | null,
|
|
29
|
+
terminalTracker: ITerminalTracker | null
|
|
30
|
+
) => {
|
|
31
|
+
const settings = app.serviceManager.serverSettings;
|
|
32
|
+
|
|
33
|
+
let status: IStatusResponse;
|
|
34
|
+
try {
|
|
35
|
+
status = await requestAPI<IStatusResponse>('status', settings);
|
|
36
|
+
} catch (err) {
|
|
37
|
+
console.error(
|
|
38
|
+
'[jupyterlab_claude_code_extension] status check failed; panel will not be registered.',
|
|
39
|
+
err
|
|
40
|
+
);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!status.enabled) {
|
|
45
|
+
console.info(
|
|
46
|
+
'[jupyterlab_claude_code_extension] `claude` binary not found on PATH; panel disabled.'
|
|
47
|
+
);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const widget = new ClaudeCodeSessionsWidget(
|
|
52
|
+
app,
|
|
53
|
+
status.root_dir || '',
|
|
54
|
+
terminalTracker
|
|
55
|
+
);
|
|
56
|
+
labShell.add(widget, 'left', { rank: 600 });
|
|
57
|
+
|
|
58
|
+
if (settingRegistry) {
|
|
59
|
+
try {
|
|
60
|
+
const settings = await settingRegistry.load(PLUGIN_ID);
|
|
61
|
+
const apply = (): void => {
|
|
62
|
+
const resolve = settings.get('resolveSessionNames')
|
|
63
|
+
.composite as boolean;
|
|
64
|
+
widget.setResolveSessionNames(resolve !== false);
|
|
65
|
+
};
|
|
66
|
+
apply();
|
|
67
|
+
settings.changed.connect(apply);
|
|
68
|
+
} catch (err) {
|
|
69
|
+
console.warn(
|
|
70
|
+
'[jupyterlab_claude_code_extension] failed to load settings; using defaults',
|
|
71
|
+
err
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Register with the layout restorer so JL remembers whether the panel
|
|
77
|
+
// was active/visible across browser reloads and restarts.
|
|
78
|
+
if (restorer) {
|
|
79
|
+
restorer.add(widget, WIDGET_ID);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
app.commands.addCommand('claude-code-sessions:refresh', {
|
|
83
|
+
label: 'Refresh Claude Code Sessions',
|
|
84
|
+
execute: () => widget.refresh()
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
console.log(
|
|
88
|
+
'[jupyterlab_claude_code_extension] panel registered (claude:',
|
|
89
|
+
status.claude_path,
|
|
90
|
+
')'
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export default plugin;
|
package/src/request.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { URLExt } from '@jupyterlab/coreutils';
|
|
2
|
+
|
|
3
|
+
import { ServerConnection } from '@jupyterlab/services';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Call the server extension
|
|
7
|
+
*
|
|
8
|
+
* @param endPoint API REST end point for the extension
|
|
9
|
+
* @param serverSettings The server settings to use for the request
|
|
10
|
+
* @param init Initial values for the request
|
|
11
|
+
* @returns The response body interpreted as JSON
|
|
12
|
+
*/
|
|
13
|
+
export async function requestAPI<T>(
|
|
14
|
+
endPoint: string,
|
|
15
|
+
serverSettings: ServerConnection.ISettings,
|
|
16
|
+
init: RequestInit = {}
|
|
17
|
+
): Promise<T> {
|
|
18
|
+
// Make request to Jupyter API
|
|
19
|
+
const requestUrl = URLExt.join(
|
|
20
|
+
serverSettings.baseUrl,
|
|
21
|
+
'jupyterlab-claude-code-extension', // our server extension's API namespace
|
|
22
|
+
endPoint
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
let response: Response;
|
|
26
|
+
try {
|
|
27
|
+
response = await ServerConnection.makeRequest(
|
|
28
|
+
requestUrl,
|
|
29
|
+
init,
|
|
30
|
+
serverSettings
|
|
31
|
+
);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
throw new ServerConnection.NetworkError(error as any);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let data: any = await response.text();
|
|
37
|
+
|
|
38
|
+
if (data.length > 0) {
|
|
39
|
+
try {
|
|
40
|
+
data = JSON.parse(data);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.log('Not a JSON response body.', response);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!response.ok) {
|
|
47
|
+
throw new ServerConnection.ResponseError(response, data.message || data);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return data;
|
|
51
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export interface ISession {
|
|
2
|
+
project_path: string;
|
|
3
|
+
encoded_path: string;
|
|
4
|
+
session_id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
summary: string;
|
|
7
|
+
first_prompt: string;
|
|
8
|
+
message_count: number;
|
|
9
|
+
created: string | null;
|
|
10
|
+
modified: string | null;
|
|
11
|
+
file_mtime: number;
|
|
12
|
+
git_branch: string | null;
|
|
13
|
+
remote_control: boolean;
|
|
14
|
+
favourite: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ISessionsListResponse {
|
|
18
|
+
sessions: ISession[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface IStatusResponse {
|
|
22
|
+
enabled: boolean;
|
|
23
|
+
claude_path: string | null;
|
|
24
|
+
root_dir: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface IFavouriteRequest {
|
|
28
|
+
project_path: string;
|
|
29
|
+
favourite: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface IFavouriteResponse {
|
|
33
|
+
favourites: string[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface IRemoveRequest {
|
|
37
|
+
encoded_path: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface IRemoveResponse {
|
|
41
|
+
removed: string;
|
|
42
|
+
}
|