jcollab 0.1.0__tar.gz
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.
- jcollab-0.1.0/.github/workflows/publish.yml +35 -0
- jcollab-0.1.0/.gitignore +7 -0
- jcollab-0.1.0/PKG-INFO +14 -0
- jcollab-0.1.0/README.md +37 -0
- jcollab-0.1.0/labext/install.json +1 -0
- jcollab-0.1.0/labext/jcollab_scroll/__init__.py +2 -0
- jcollab-0.1.0/labext/jcollab_scroll/labextension/package.json +33 -0
- jcollab-0.1.0/labext/jcollab_scroll/labextension/static/509.841856aacf23f4236460.js +1 -0
- jcollab-0.1.0/labext/jcollab_scroll/labextension/static/remoteEntry.01308e5321350a3592c6.js +1 -0
- jcollab-0.1.0/labext/jcollab_scroll/labextension/static/style.js +4 -0
- jcollab-0.1.0/labext/jcollab_scroll/labextension/static/third-party-licenses.json +3 -0
- jcollab-0.1.0/labext/package-lock.json +6625 -0
- jcollab-0.1.0/labext/package.json +9 -0
- jcollab-0.1.0/labext/pyproject.toml +13 -0
- jcollab-0.1.0/labext/src/index.ts +72 -0
- jcollab-0.1.0/labext/tsconfig.json +3 -0
- jcollab-0.1.0/pyproject.toml +27 -0
- jcollab-0.1.0/src/jcollab/__init__.py +2 -0
- jcollab-0.1.0/src/jcollab/discovery.py +133 -0
- jcollab-0.1.0/src/jcollab/doc.py +325 -0
- jcollab-0.1.0/src/jcollab/kernel.py +236 -0
- jcollab-0.1.0/src/jcollab/rest.py +58 -0
- jcollab-0.1.0/src/jcollab/server.py +463 -0
- jcollab-0.1.0/tests/integration.py +67 -0
- jcollab-0.1.0/tests/stdio_smoke.py +14 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
name: publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
build:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
- uses: actions/setup-python@v5
|
|
16
|
+
with:
|
|
17
|
+
python-version: "3.12"
|
|
18
|
+
- run: python -m pip install build
|
|
19
|
+
- run: python -m build --outdir dist .
|
|
20
|
+
- uses: actions/upload-artifact@v4
|
|
21
|
+
with:
|
|
22
|
+
name: dist
|
|
23
|
+
path: dist/
|
|
24
|
+
|
|
25
|
+
publish:
|
|
26
|
+
needs: build
|
|
27
|
+
runs-on: ubuntu-latest
|
|
28
|
+
permissions:
|
|
29
|
+
id-token: write
|
|
30
|
+
steps:
|
|
31
|
+
- uses: actions/download-artifact@v4
|
|
32
|
+
with:
|
|
33
|
+
name: dist
|
|
34
|
+
path: dist/
|
|
35
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
jcollab-0.1.0/.gitignore
ADDED
jcollab-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: jcollab
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server for live co-editing and running of JupyterLab notebooks (Yjs, verified writes, multi-instance discovery)
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Requires-Dist: httpx>=0.27
|
|
8
|
+
Requires-Dist: jupyter-core
|
|
9
|
+
Requires-Dist: jupyter-nbmodel-client<2,>=1.5.1
|
|
10
|
+
Requires-Dist: jupyter-ydoc<4,>=3.0
|
|
11
|
+
Requires-Dist: mcp<3,>=2
|
|
12
|
+
Requires-Dist: nbformat
|
|
13
|
+
Requires-Dist: pycrdt<0.14,>=0.12.50
|
|
14
|
+
Requires-Dist: websockets>=13
|
jcollab-0.1.0/README.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# jcollab
|
|
2
|
+
|
|
3
|
+
MCP server (stdio) that lets Claude read, edit and run cells in live JupyterLab notebooks through the shared Yjs
|
|
4
|
+
document, so open browser tabs update immediately and cells look exactly as if the user had run them.
|
|
5
|
+
|
|
6
|
+
* **Discovery, no setup:** finds all running Jupyter servers of the user via `~/.local/share/jupyter/runtime/jpserver-*.json`
|
|
7
|
+
(pid alive + `/api/status` probe). Tools take `instance` (port, `root:port` or root dir name); optional if only one runs.
|
|
8
|
+
* **Verified writes:** every mutating tool reads the file back and reports `persisted`; duplicate cell ids (client/server
|
|
9
|
+
Yjs version skew) are detected and explained.
|
|
10
|
+
* **Execution fidelity:** `[*]` busy state, streamed outputs, kernel execution count, `metadata.execution` timings
|
|
11
|
+
(jupyterlab-execute-time). `run_code` is silent (`store_history=False`), consumes no execution count.
|
|
12
|
+
* **Tools:** list_instances, list_notebooks, read_notebook, read_cell (images), insert_cell, insert_cell_at_start, edit_cell,
|
|
13
|
+
change_cell_type, delete_cell, move_cell, split_cell, merge_cells, get_metadata, set_metadata (cell/notebook),
|
|
14
|
+
run_cell, run_cells, run_code, clear_outputs, inspect_variables, kernel_status, interrupt_kernel, restart_kernel,
|
|
15
|
+
sync_health, user_focus, point_at, find_mentions, add_comment.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
From PyPI:
|
|
20
|
+
|
|
21
|
+
pip install jcollab
|
|
22
|
+
claude mcp add --scope user jcollab -- jcollab
|
|
23
|
+
|
|
24
|
+
From source:
|
|
25
|
+
|
|
26
|
+
python3 -m venv .venv && .venv/bin/pip install .
|
|
27
|
+
claude mcp add --scope user jcollab -- $PWD/.venv/bin/jcollab
|
|
28
|
+
|
|
29
|
+
Optional lab extension `labext/` (`jcollab-scroll`): scrolls the user's notebook to the cell `point_at` targets. Install it
|
|
30
|
+
into the interpreter of `jupyter-lab` with `pip install ./labext`, then restart JupyterLab.
|
|
31
|
+
|
|
32
|
+
Requires a JupyterLab with `jupyter-collaboration`. Keep `jupyter-ydoc` of this package on the same major version as the
|
|
33
|
+
server's (pinned `>=3,<4`; see `sync_health`).
|
|
34
|
+
|
|
35
|
+
## Tests
|
|
36
|
+
|
|
37
|
+
`tests/integration.py [instance]` expects a throwaway JupyterLab (token-less) whose root contains `spike.ipynb`.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"packageManager": "python", "packageName": "jcollab-scroll", "uninstallInstructions": "pip uninstall jcollab-scroll"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "jcollab-scroll",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"description": "Scroll JupyterLab notebooks to the cell a collaborator (Metis) points at",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"files": [
|
|
8
|
+
"lib/**/*.js"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build:lib": "tsc",
|
|
12
|
+
"build:labextension": "jupyter labextension build .",
|
|
13
|
+
"build": "npm run build:lib && npm run build:labextension"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@jupyter/ydoc": "^3.1.0",
|
|
17
|
+
"@jupyterlab/application": "^4.0.0",
|
|
18
|
+
"@jupyterlab/apputils": "^4.0.0",
|
|
19
|
+
"@jupyterlab/notebook": "^4.0.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@jupyterlab/builder": "~4.5.7",
|
|
23
|
+
"typescript": "~5.5.4"
|
|
24
|
+
},
|
|
25
|
+
"jupyterlab": {
|
|
26
|
+
"extension": true,
|
|
27
|
+
"outputDir": "jcollab_scroll/labextension",
|
|
28
|
+
"_build": {
|
|
29
|
+
"load": "static/remoteEntry.01308e5321350a3592c6.js",
|
|
30
|
+
"extension": "./extension"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";(self.webpackChunkjcollab_scroll=self.webpackChunkjcollab_scroll||[]).push([[509],{509(o,e,t){t.r(e);var n=t(537);function c(o){const e=o&&o.scrollTo;return e&&"string"==typeof e.cell&&"string"==typeof e.nonce?e:null}function l(o){const e=o&&o.user||{};return e.display_name||e.name||"A collaborator"}function a(o,e){return o.content.widgets.findIndex(o=>o.model.id===e)}function s(o,e){const t=o.content,n=t.activeCell;return"edit"===t.mode&&!!n&&!!n.editor&&n.editor.hasFocus()&&t.activeCellIndex!==e}function r(o,e){const t=a(o,e);t>=0&&o.content.scrollToItem(t,"smart").catch(o=>console.warn("jcollab-scroll:",o))}const i={id:"jcollab-scroll:plugin",autoStart:!0,requires:[t(615).INotebookTracker],activate:(o,e)=>{const t=new WeakSet,i=o=>{t.has(o)||(t.add(o),o.context.ready.then(()=>function(o){const e=o.context.model.sharedModel.awareness;if(!e)return void console.warn("jcollab-scroll: no awareness (jupyter-collaboration off?)");const t=new Map;e.getStates().forEach((o,e)=>{const n=c(o);n&&t.set(e,n.nonce)});const i=i=>{for(const d of[...i.added,...i.updated]){if(d===e.clientID)continue;const i=e.getStates().get(d),u=c(i);if(!u||t.get(d)===u.nonce)continue;t.set(d,u.nonce);const f=a(o,u.cell);f<0||(u.force||!s(o,f)?r(o,u.cell):n.Notification.info(`${l(i)} points at cell ${f+1}${u.label?": "+u.label:""}`,{autoClose:1e4,actions:[{label:"Show",callback:()=>r(o,u.cell)}]}))}};e.on("change",i),o.disposed.connect(()=>e.off("change",i))}(o)).catch(()=>{}))};e.forEach(i),e.widgetAdded.connect((o,e)=>i(e)),console.log("jcollab-scroll activated")}};t.d(e,["default",0,i])}}]);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var _JUPYTERLAB;(()=>{"use strict";var e={283(e,t,r){const n={"./index":()=>r.e(509).then(()=>()=>r(509)),"./extension":()=>r.e(509).then(()=>()=>r(509))},o=(e,t)=>(r.R=t,t=r.o(n,e)?n[e]():Promise.resolve().then(()=>{throw new Error('Module "'+e+'" does not exist in container.')}),r.R=void 0,t),i=(e,t)=>{if(!r.S)return;const n="default",o=r.S[n];if(o&&o!==e)throw new Error("Container initialization failed as it has already been initialized with a different share scope");return r.S[n]=e,r.I(n,t)};r.d(t,{get:()=>o,init:()=>i})}};const t={};function r(n){const o=t[n];if(void 0!==o)return o.exports;const i=t[n]={exports:{}};return e[n](i,i.exports,r),i.exports}r.m=e,r.c=t,r.n=e=>{const t=e&&e.__esModule?()=>e.default:()=>e;return r.d(t,{a:t}),t},r.d=(e,t)=>{if(Array.isArray(t))for(var n=0;n<t.length;){var o=t[n++],i=t[n++],l=0===i?{enumerable:!0,value:t[n++]}:{enumerable:!0,get:i};r.o(e,o)||Object.defineProperty(e,o,l)}else for(var o in t)r.o(t,o)&&!r.o(e,o)&&Object.defineProperty(e,o,{enumerable:!0,get:t[o]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce((t,n)=>(r.f[n](e,t),t),[])),r.u=e=>e+".841856aacf23f4236460.js?v=841856aacf23f4236460",r.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{const e={},t="jcollab-scroll:";r.l=(n,o,i,l)=>{if(e[n])return void e[n].push(o);let s,a;if(void 0!==i){const e=document.getElementsByTagName("script");for(var c=0;c<e.length;c++){const r=e[c];if(r.getAttribute("src")==n||r.getAttribute("data-webpack")==t+i){s=r;break}}}s||(a=!0,s=document.createElement("script"),s.charset="utf-8",r.nc&&s.setAttribute("nonce",r.nc),s.setAttribute("data-webpack",t+i),s.src=n),e[n]=[o];const u=(t,r)=>{s.onerror=s.onload=null,clearTimeout(f);const o=e[n];if(delete e[n],s.parentNode?.removeChild(s),o?.forEach(e=>e(r)),t)return t(r)},f=setTimeout(u.bind(null,void 0,{type:"timeout",target:s}),12e4);s.onerror=u.bind(null,s.onerror),s.onload=u.bind(null,s.onload),a&&document.head.appendChild(s)}})(),r.r=e=>{Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{r.S={};const e={},t={};r.I=(n,o)=>{o||(o=[]);let i=t[n];if(i||(i=t[n]={}),o.indexOf(i)>=0)return;if(o.push(i),e[n])return e[n];r.o(r.S,n)||(r.S[n]={});const l=r.S[n],s="jcollab-scroll",a=[];return"default"===n&&((e,t,n,o)=>{const i=l[e]=l[e]||{},a=i[t];(!a||!a.loaded&&(1!=!a.eager?o:s>a.from))&&(i[t]={get:()=>r.e(509).then(()=>()=>r(509)),from:s,eager:!1})})("jcollab-scroll","0.1.0"),e[n]=a.length?Promise.all(a).then(()=>e[n]=1):1}})(),(()=>{let e;r.g.importScripts&&(e=r.g.location+"");const t=r.g.document;if(!e&&t&&("SCRIPT"===t.currentScript?.tagName.toUpperCase()&&(e=t.currentScript.src),!e)){const r=t.getElementsByTagName("script");if(r.length){let t=r.length-1;for(;t>-1&&(!e||!/^https?:/.test(e));)e=r[t--].src}}if(!e)throw new Error("Automatic publicPath is not supported in this browser");e=e.replace(/^blob:|[?#].*$/g,"").replace(/\/[^/]+$/,"/"),r.p=e})(),(()=>{var e=e=>{var t=e=>e.split(".").map(e=>+e==e?+e:e),r=/^([^-+]+)?(?:-([^+]+))?(?:\+(.+))?$/.exec(e),n=r[1]?t(r[1]):[];return r[2]&&(n.length++,n.push.apply(n,t(r[2]))),r[3]&&(n.push([]),n.push.apply(n,t(r[3]))),n},t=e=>{var r=e[0],n="";if(1===e.length)return"*";if(r+.5){n+=0==r?">=":-1==r?"<":1==r?"^":2==r?"~":r>0?"=":"!=";for(var o=1,i=1;i<e.length;i++)o--,n+="u"==(typeof(s=e[i]))[0]?"-":(o>0?".":"")+(o=2,s);return n}var l=[];for(i=1;i<e.length;i++){var s=e[i];l.push(0===s?"not("+a()+")":1===s?"("+a()+" || "+a()+")":2===s?l.pop()+" "+l.pop():t(s))}return a();function a(){return l.pop().replace(/^\((.+)\)$/,"$1")}},n=(t,r)=>{if(0 in t){r=e(r);var o=t[0],i=o<0;i&&(o=-o-1);for(var l=0,s=1,a=!0;;s++,l++){var c,u,f=s<t.length?(typeof t[s])[0]:"";if(l>=r.length||"o"==(u=(typeof(c=r[l]))[0]))return!a||("u"==f?s>o&&!i:""==f!=i);if("u"==u){if(!a||"u"!=f)return!1}else if(a)if(f==u)if(s<=o){if(c!=t[s])return!1}else{if(i?c>t[s]:c<t[s])return!1;c!=t[s]&&(a=!1)}else if("s"!=f&&"n"!=f){if(i||s<=o)return!1;a=!1,s--}else{if(s<=o||u<f!=i)return!1;a=!1}else"s"!=f&&"n"!=f&&(a=!1,s--)}}var d=[],p=d.pop.bind(d);for(l=1;l<t.length;l++){var h=t[l];d.push(1==h?p()|p():2==h?p()&p():h?n(h,r):!p())}return!!p()};const o=(t,r,n)=>{const o=n?(e=>Object.keys(e).reduce((t,r)=>(e[r].eager&&(t[r]=e[r]),t),{}))(t[r]):t[r];return Object.keys(o).reduce((t,r)=>!t||!o[t].loaded&&((t,r)=>{t=e(t),r=e(r);for(var n=0;;){if(n>=t.length)return n<r.length&&"u"!=(typeof r[n])[0];var o=t[n],i=(typeof o)[0];if(n>=r.length)return"u"==i;var l=r[n],s=(typeof l)[0];if(i!=s)return"o"==i&&"n"==s||"s"==s||"u"==i;if("o"!=i&&"u"!=i&&o!=l)return o<l;n++}})(t,r)?r:t,0)},i=(e,t,r)=>r?r():((e,t)=>(e=>{throw new Error(e)})("Shared module "+t+" doesn't exist in shared scope "+e))(e,t),l=(e=>function(t,n,o,i,l){const s=r.I(t);return s?.then&&!o?s.then(e.bind(e,t,r.S[t],n,!1,i,l)):e(t,r.S[t],n,o,i,l)})((e,l,s,a,c,u)=>{if(!((e,t)=>e&&r.o(e,t))(l,s))return i(e,s,u);const f=o(l,s,a);return n(c,f)||(p=((e,r,n,o)=>"Unsatisfied version "+n+" from "+(n&&e[r][n].from)+" of shared singleton module "+r+" (required "+t(o)+")")(l,s,f,c),"undefined"!=typeof console&&console.warn&&console.warn(p)),(d=l[s][f]).loaded=1,d.get();var d,p}),s={},a={537:()=>l("default","@jupyterlab/apputils",!1,[1,4,6,7]),615:()=>l("default","@jupyterlab/notebook",!1,[1,4,5,7])},c={509:[537,615]},u={};r.f.consumes=(e,t)=>{r.o(c,e)&&c[e].forEach(e=>{if(r.o(s,e))return t.push(s[e]);if(!u[e]){const n=t=>{s[e]=0,r.m[e]=n=>{delete r.c[e],n.exports=t()}};u[e]=!0;const o=t=>{delete s[e],r.m[e]=n=>{throw delete r.c[e],t}};try{const r=a[e]();r.then?t.push(s[e]=r.then(n).catch(o)):n(r)}catch(e){o(e)}}})}})(),(()=>{const e={2:0};r.f.j=(t,n)=>{let o=r.o(e,t)?e[t]:void 0;if(0!==o)if(o)n.push(o[2]);else{const i=new Promise((r,n)=>o=e[t]=[r,n]);n.push(o[2]=i);const l=new Error,s=n=>{if(r.o(e,t)&&(o=e[t],0!==o&&(e[t]=void 0),o)){const e=n&&("load"===n.type?"missing":n.type),r=n&&n.target&&n.target.src;l.message="Loading chunk "+t+" failed.\n("+e+": "+r+")",l.name="ChunkLoadError",l.type=e,l.request=r,l.event=n,o[1](l)}};r.l(r.p+r.u(t),s,"chunk-"+t,t)}};const t=(t,n)=>{let[o,i,l]=n;var s,a,c=0;if(o.some(t=>0!==e[t])){for(s in i)r.o(i,s)&&(r.m[s]=i[s]);l&&l(r)}for(t&&t(n);c<o.length;c++)a=o[c],r.o(e,a)&&e[a]&&e[a][0](),e[a]=0},n=self.webpackChunkjcollab_scroll=self.webpackChunkjcollab_scroll||[];n.forEach(t.bind(null,0)),n.push=t.bind(null,n.push.bind(n))})();let n=r(283);(_JUPYTERLAB=void 0===_JUPYTERLAB?{}:_JUPYTERLAB)["jcollab-scroll"]=n})();
|