jpsync 1.2.0__tar.gz → 1.3.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.
Files changed (142) hide show
  1. {jpsync-1.2.0 → jpsync-1.3.0}/.gitignore +1 -0
  2. {jpsync-1.2.0 → jpsync-1.3.0}/CHANGELOG.md +12 -0
  3. {jpsync-1.2.0 → jpsync-1.3.0}/PKG-INFO +27 -5
  4. {jpsync-1.2.0 → jpsync-1.3.0}/README.md +26 -4
  5. jpsync-1.3.0/docs/jp-live.md +253 -0
  6. {jpsync-1.2.0 → jpsync-1.3.0}/pyproject.toml +3 -0
  7. {jpsync-1.2.0 → jpsync-1.3.0}/src/jp/__init__.py +1 -1
  8. jpsync-1.3.0/src/jp/_agent/__init__.py +0 -0
  9. jpsync-1.3.0/src/jp/_agent/agentd.py +347 -0
  10. jpsync-1.3.0/src/jp/_sim.py +72 -0
  11. {jpsync-1.2.0 → jpsync-1.3.0}/src/jp/_ws.py +51 -13
  12. jpsync-1.3.0/src/jp/agent_loader.py +40 -0
  13. {jpsync-1.2.0 → jpsync-1.3.0}/src/jp/api.py +100 -4
  14. {jpsync-1.2.0 → jpsync-1.3.0}/src/jp/changelog.py +50 -3
  15. {jpsync-1.2.0 → jpsync-1.3.0}/src/jp/commands/__init__.py +2 -0
  16. {jpsync-1.2.0 → jpsync-1.3.0}/src/jp/commands/_context.py +57 -4
  17. {jpsync-1.2.0 → jpsync-1.3.0}/src/jp/commands/changelog.py +9 -4
  18. {jpsync-1.2.0 → jpsync-1.3.0}/src/jp/commands/config_cmd.py +33 -4
  19. jpsync-1.3.0/src/jp/commands/live.py +669 -0
  20. {jpsync-1.2.0 → jpsync-1.3.0}/src/jp/commands/run.py +11 -8
  21. {jpsync-1.2.0 → jpsync-1.3.0}/src/jp/commands/terminal.py +59 -21
  22. jpsync-1.3.0/src/jp/fsrpc.py +46 -0
  23. jpsync-1.3.0/src/jp/kernel_conn.py +188 -0
  24. jpsync-1.3.0/src/jp/kernel_proto.py +146 -0
  25. jpsync-1.3.0/src/jp/live_session.py +104 -0
  26. jpsync-1.3.0/src/jp/mount/__init__.py +0 -0
  27. jpsync-1.3.0/src/jp/mount/live_state.py +163 -0
  28. jpsync-1.3.0/src/jp/mount/os_mount.py +344 -0
  29. jpsync-1.3.0/src/jp/mount/vscode_launch.py +87 -0
  30. jpsync-1.3.0/src/jp/mount/webdav_server.py +729 -0
  31. {jpsync-1.2.0 → jpsync-1.3.0}/src/jp/pty.py +135 -0
  32. jpsync-1.3.0/src/jp/remote_fs.py +135 -0
  33. jpsync-1.3.0/src/jp/stats.py +70 -0
  34. {jpsync-1.2.0 → jpsync-1.3.0}/src/jp/tui.py +74 -0
  35. jpsync-1.3.0/src/jp/user_settings.py +129 -0
  36. jpsync-1.3.0/src/jp/vfs_cache.py +300 -0
  37. {jpsync-1.2.0 → jpsync-1.3.0}/tests/conftest.py +19 -0
  38. jpsync-1.3.0/tests/test_agent_loader.py +27 -0
  39. jpsync-1.3.0/tests/test_agentd.py +243 -0
  40. {jpsync-1.2.0 → jpsync-1.3.0}/tests/test_api.py +106 -0
  41. {jpsync-1.2.0 → jpsync-1.3.0}/tests/test_changelog.py +32 -0
  42. jpsync-1.3.0/tests/test_config_from_url.py +123 -0
  43. jpsync-1.3.0/tests/test_config_global_keys.py +77 -0
  44. jpsync-1.3.0/tests/test_fake_jupyter.py +26 -0
  45. jpsync-1.3.0/tests/test_fsrpc.py +27 -0
  46. jpsync-1.3.0/tests/test_kernel_conn.py +222 -0
  47. jpsync-1.3.0/tests/test_kernel_proto.py +62 -0
  48. jpsync-1.3.0/tests/test_live_cmd.py +526 -0
  49. jpsync-1.3.0/tests/test_live_dryrun.py +106 -0
  50. jpsync-1.3.0/tests/test_live_session.py +84 -0
  51. jpsync-1.3.0/tests/test_live_state.py +117 -0
  52. jpsync-1.3.0/tests/test_os_mount.py +218 -0
  53. jpsync-1.3.0/tests/test_real_jupyter.py +265 -0
  54. jpsync-1.3.0/tests/test_release_notes_ai.py +72 -0
  55. jpsync-1.3.0/tests/test_remote_fs.py +85 -0
  56. {jpsync-1.2.0 → jpsync-1.3.0}/tests/test_run.py +8 -6
  57. jpsync-1.3.0/tests/test_stats.py +34 -0
  58. jpsync-1.3.0/tests/test_terminal_url.py +211 -0
  59. jpsync-1.3.0/tests/test_terminal_windows.py +54 -0
  60. {jpsync-1.2.0 → jpsync-1.3.0}/tests/test_tui.py +49 -0
  61. jpsync-1.3.0/tests/test_user_settings.py +85 -0
  62. jpsync-1.3.0/tests/test_vfs_cache.py +347 -0
  63. jpsync-1.3.0/tests/test_vscode_launch.py +111 -0
  64. jpsync-1.3.0/tests/test_webdav_server.py +534 -0
  65. {jpsync-1.2.0 → jpsync-1.3.0}/tests/test_ws.py +41 -0
  66. jpsync-1.2.0/docs/superpowers/plans/2026-06-05-jp-run-remote.md +0 -1009
  67. jpsync-1.2.0/docs/superpowers/plans/2026-06-05-update-notifier.md +0 -1798
  68. jpsync-1.2.0/docs/superpowers/specs/2026-06-05-credential-site-link-design.md +0 -168
  69. jpsync-1.2.0/docs/superpowers/specs/2026-06-05-jp-run-remote-design.md +0 -228
  70. jpsync-1.2.0/docs/superpowers/specs/2026-06-05-update-notifier-design.md +0 -298
  71. jpsync-1.2.0/tests/test_config_global_keys.py +0 -37
  72. jpsync-1.2.0/tests/test_release_notes_ai.py +0 -38
  73. {jpsync-1.2.0 → jpsync-1.3.0}/LICENSE +0 -0
  74. {jpsync-1.2.0 → jpsync-1.3.0}/NOTICE +0 -0
  75. {jpsync-1.2.0 → jpsync-1.3.0}/docs/architecture.md +0 -0
  76. {jpsync-1.2.0 → jpsync-1.3.0}/docs/commands.md +0 -0
  77. {jpsync-1.2.0 → jpsync-1.3.0}/docs/security.md +0 -0
  78. {jpsync-1.2.0 → jpsync-1.3.0}/docs/vscode-remote-cwd.md +0 -0
  79. {jpsync-1.2.0 → jpsync-1.3.0}/src/jp/__main__.py +0 -0
  80. {jpsync-1.2.0 → jpsync-1.3.0}/src/jp/cli.py +0 -0
  81. {jpsync-1.2.0 → jpsync-1.3.0}/src/jp/clipboard.py +0 -0
  82. {jpsync-1.2.0 → jpsync-1.3.0}/src/jp/commands/_mirror.py +0 -0
  83. {jpsync-1.2.0 → jpsync-1.3.0}/src/jp/commands/_report.py +0 -0
  84. {jpsync-1.2.0 → jpsync-1.3.0}/src/jp/commands/clone.py +0 -0
  85. {jpsync-1.2.0 → jpsync-1.3.0}/src/jp/commands/credentials_cmd.py +0 -0
  86. {jpsync-1.2.0 → jpsync-1.3.0}/src/jp/commands/diff.py +0 -0
  87. {jpsync-1.2.0 → jpsync-1.3.0}/src/jp/commands/doctor.py +0 -0
  88. {jpsync-1.2.0 → jpsync-1.3.0}/src/jp/commands/ignore_cmd.py +0 -0
  89. {jpsync-1.2.0 → jpsync-1.3.0}/src/jp/commands/init.py +0 -0
  90. {jpsync-1.2.0 → jpsync-1.3.0}/src/jp/commands/kernel.py +0 -0
  91. {jpsync-1.2.0 → jpsync-1.3.0}/src/jp/commands/login.py +0 -0
  92. {jpsync-1.2.0 → jpsync-1.3.0}/src/jp/commands/ls.py +0 -0
  93. {jpsync-1.2.0 → jpsync-1.3.0}/src/jp/commands/open_cmd.py +0 -0
  94. {jpsync-1.2.0 → jpsync-1.3.0}/src/jp/commands/pull.py +0 -0
  95. {jpsync-1.2.0 → jpsync-1.3.0}/src/jp/commands/push.py +0 -0
  96. {jpsync-1.2.0 → jpsync-1.3.0}/src/jp/commands/rm.py +0 -0
  97. {jpsync-1.2.0 → jpsync-1.3.0}/src/jp/commands/status.py +0 -0
  98. {jpsync-1.2.0 → jpsync-1.3.0}/src/jp/commands/update.py +0 -0
  99. {jpsync-1.2.0 → jpsync-1.3.0}/src/jp/commands/update_check.py +0 -0
  100. {jpsync-1.2.0 → jpsync-1.3.0}/src/jp/commands/version.py +0 -0
  101. {jpsync-1.2.0 → jpsync-1.3.0}/src/jp/config.py +0 -0
  102. {jpsync-1.2.0 → jpsync-1.3.0}/src/jp/credentials.py +0 -0
  103. {jpsync-1.2.0 → jpsync-1.3.0}/src/jp/errors.py +0 -0
  104. {jpsync-1.2.0 → jpsync-1.3.0}/src/jp/global_prefs.py +0 -0
  105. {jpsync-1.2.0 → jpsync-1.3.0}/src/jp/ignore.py +0 -0
  106. {jpsync-1.2.0 → jpsync-1.3.0}/src/jp/index.py +0 -0
  107. {jpsync-1.2.0 → jpsync-1.3.0}/src/jp/paths.py +0 -0
  108. {jpsync-1.2.0 → jpsync-1.3.0}/src/jp/prefs.py +0 -0
  109. {jpsync-1.2.0 → jpsync-1.3.0}/src/jp/settings_schema.py +0 -0
  110. {jpsync-1.2.0 → jpsync-1.3.0}/src/jp/sync.py +0 -0
  111. {jpsync-1.2.0 → jpsync-1.3.0}/src/jp/ui.py +0 -0
  112. {jpsync-1.2.0 → jpsync-1.3.0}/src/jp/update_notify.py +0 -0
  113. {jpsync-1.2.0 → jpsync-1.3.0}/src/jp/urls.py +0 -0
  114. {jpsync-1.2.0 → jpsync-1.3.0}/tests/test_changelog_cmd.py +0 -0
  115. {jpsync-1.2.0 → jpsync-1.3.0}/tests/test_cli.py +0 -0
  116. {jpsync-1.2.0 → jpsync-1.3.0}/tests/test_cli_notify_hook.py +0 -0
  117. {jpsync-1.2.0 → jpsync-1.3.0}/tests/test_clone_init.py +0 -0
  118. {jpsync-1.2.0 → jpsync-1.3.0}/tests/test_config.py +0 -0
  119. {jpsync-1.2.0 → jpsync-1.3.0}/tests/test_credentials.py +0 -0
  120. {jpsync-1.2.0 → jpsync-1.3.0}/tests/test_credentials_cmd.py +0 -0
  121. {jpsync-1.2.0 → jpsync-1.3.0}/tests/test_doctor.py +0 -0
  122. {jpsync-1.2.0 → jpsync-1.3.0}/tests/test_gitignore_guard.py +0 -0
  123. {jpsync-1.2.0 → jpsync-1.3.0}/tests/test_global_prefs.py +0 -0
  124. {jpsync-1.2.0 → jpsync-1.3.0}/tests/test_index_ignore.py +0 -0
  125. {jpsync-1.2.0 → jpsync-1.3.0}/tests/test_kernel.py +0 -0
  126. {jpsync-1.2.0 → jpsync-1.3.0}/tests/test_login.py +0 -0
  127. {jpsync-1.2.0 → jpsync-1.3.0}/tests/test_mirror.py +0 -0
  128. {jpsync-1.2.0 → jpsync-1.3.0}/tests/test_open_cmd.py +0 -0
  129. {jpsync-1.2.0 → jpsync-1.3.0}/tests/test_paths.py +0 -0
  130. {jpsync-1.2.0 → jpsync-1.3.0}/tests/test_pty.py +0 -0
  131. {jpsync-1.2.0 → jpsync-1.3.0}/tests/test_redact.py +0 -0
  132. {jpsync-1.2.0 → jpsync-1.3.0}/tests/test_rm.py +0 -0
  133. {jpsync-1.2.0 → jpsync-1.3.0}/tests/test_sync_pull.py +0 -0
  134. {jpsync-1.2.0 → jpsync-1.3.0}/tests/test_sync_push.py +0 -0
  135. {jpsync-1.2.0 → jpsync-1.3.0}/tests/test_terminal.py +0 -0
  136. {jpsync-1.2.0 → jpsync-1.3.0}/tests/test_tui_credential_manager.py +0 -0
  137. {jpsync-1.2.0 → jpsync-1.3.0}/tests/test_tui_select_credential.py +0 -0
  138. {jpsync-1.2.0 → jpsync-1.3.0}/tests/test_update.py +0 -0
  139. {jpsync-1.2.0 → jpsync-1.3.0}/tests/test_update_check_cmd.py +0 -0
  140. {jpsync-1.2.0 → jpsync-1.3.0}/tests/test_update_notify.py +0 -0
  141. {jpsync-1.2.0 → jpsync-1.3.0}/tests/test_urls.py +0 -0
  142. {jpsync-1.2.0 → jpsync-1.3.0}/tests/test_version_changelog.py +0 -0
@@ -1,5 +1,6 @@
1
1
  # Local agent / editor state
2
2
  .claude/
3
+ docs/superpowers/
3
4
 
4
5
  # Byte-compiled / optimized / caches
5
6
  __pycache__/
@@ -5,6 +5,18 @@ All notable changes to this project are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.3.0](https://github.com/pehqge/jpsync/compare/v1.2.0...v1.3.0) (2026-06-05)
9
+
10
+
11
+ ### Features
12
+
13
+ * **live:** jp live — mount your remote Jupyter folder locally, edit + run over the kernel ([#26](https://github.com/pehqge/jpsync/issues/26)) ([5c14b23](https://github.com/pehqge/jpsync/commit/5c14b23810bf646a933c72dc24a757424ed961dc))
14
+
15
+
16
+ ### Bug Fixes
17
+
18
+ * AI release-notes gh bug + config works outside a workspace ([#27](https://github.com/pehqge/jpsync/issues/27)) ([9c9c963](https://github.com/pehqge/jpsync/commit/9c9c9637aeea8d2ed08a105b6d7301960af1d09c))
19
+
8
20
  ## [1.2.0](https://github.com/pehqge/jpsync/compare/v1.1.1...v1.2.0) (2026-06-05)
9
21
 
10
22
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jpsync
3
- Version: 1.2.0
3
+ Version: 1.3.0
4
4
  Summary: A git-like CLI to sync local folders with a remote JupyterHub via the Contents API.
5
5
  Project-URL: Homepage, https://github.com/pehqge/jpsync
6
6
  Project-URL: Repository, https://github.com/pehqge/jpsync
@@ -74,6 +74,7 @@ runs — macOS, Windows, Linux.
74
74
  ## Why jp?
75
75
 
76
76
  - **Git-like workflow** — `jp clone`, `jp status`, `jp push`, `jp pull`. Same muscle memory.
77
+ - **Or skip the copy** — `jp live <url>` mounts the remote folder as a local folder you edit in place, and `jp terminal` drops you into a shell on the server. No SSH, no FUSE, no server-side install.
77
78
  - **Safe by default** — on a *shared* research machine, jp never deletes remote files unless you explicitly turn that on, and even then it asks you file-by-file. Conflicts are never silently overwritten.
78
79
  - **Zero dependencies** — one install, no dependency hell; ships as a wheel, a single `.pyz`, or a standalone binary.
79
80
  - **Cross-platform** — macOS, Windows, Linux; Python 3.9 → 3.13.
@@ -228,6 +229,26 @@ It only opens an ephemeral terminal session (it never touches your files), and
228
229
  the session is cleaned up when you exit. See the
229
230
  [command reference](docs/commands.md#remote-shell) for the details.
230
231
 
232
+ ### Bonus: edit the remote folder live (no clone)
233
+
234
+ Don't want a local copy at all — just open the remote folder and edit it in
235
+ place? `jp live <url>` mounts it as a normal folder on your machine over the same
236
+ kernel transport. Reads and (by default) writes travel straight to the server.
237
+
238
+ ```bash
239
+ jp live https://jupyter.example.com/user/<you>/lab/tree/your-folder
240
+ # → confirms writable vs read-only, then mounts it under ./your-folder/
241
+ # edit with any tool; Ctrl-C (or `jp live unmount`) to stop.
242
+
243
+ jp live <url> --code # open it in VS Code with a remote shell wired up
244
+ jp live <url> --read-only # browse without any risk of writing
245
+ ```
246
+
247
+ It mounts under one auto-managed handle (a folder on macOS/Linux, a drive letter
248
+ on Windows) using the OS's built-in WebDAV client — no FUSE, no third-party
249
+ deps. The mount is loopback-only and gated behind a per-session secret. Full
250
+ guide and the cross-OS notes: [docs/jp-live.md](docs/jp-live.md).
251
+
231
252
  ### Bonus: run a local script on the server — without pushing it
232
253
 
233
254
  Editing a script locally and want to run it remotely without `jp push` on every
@@ -252,8 +273,8 @@ and your files are never overwritten. For Python, `sys.argv[0]`/`__file__`/
252
273
  tracebacks show the real name, not the temp file. You see only the program's
253
274
  output, streamed live — no remote shell prompt, and `input()` works just like
254
275
  locally. Any **data** files the script opens must already exist on the remote
255
- (`jp push` those first). POSIX only for now (macOS + Linux); Windows support lands
256
- with the `jp terminal` Windows fix.
276
+ (`jp push` those first). Works on macOS, Linux and Windows (the interactive raw
277
+ proxy uses the same `jp.pty` backend as `jp terminal`).
257
278
 
258
279
  ---
259
280
 
@@ -270,13 +291,14 @@ with the `jp terminal` Windows fix.
270
291
  | `jp pull` | Download remote changes. Additive by default. |
271
292
  | `jp diff [path]` | Show file-level differences. |
272
293
  | `jp ls [remote-path]` | List a remote directory (no local writes). |
294
+ | `jp live <url>` | Mount a remote folder as a local folder over the kernel websocket — edit it in your own tools; changes travel to the server. Writable by default (confirmed); `--read-only` opts out. `--code` opens it in VS Code with a remote shell. `jp live unmount` (from inside) stops it; `jp live --defaults` sets the defaults. ([guide](docs/jp-live.md)) |
273
295
  | `jp open` | Open this workspace's folder (or the subfolder you're in) in the Jupyter web UI. Confirms first, shows the URL, and can copy instead of opening. Refuses folders jp never syncs (`.jp/`, hidden dot-names, `.jpignore` matches) since they aren't on the remote. Press `r` in the prompt to remember your choice (skip it next time); `jp open --ask` forgets it. Token-free URL; no network. |
274
296
  | `jp config` | Interactive settings editor (see below). Also `config get/set/list`. |
275
297
  | `jp ignore [pattern]` | Manage `.jpignore` patterns. |
276
298
  | `jp rm <path>` | Delete on the remote — gated, dry-run + typed confirmation. The only deleter. |
277
299
  | `jp kernel` | Set up a VS Code remote kernel to run notebooks in the right directory ([guide](docs/vscode-remote-cwd.md)). |
278
- | `jp terminal` | Open the remote machine's shell in your terminal, in the workspace folder. Creates/deletes only an ephemeral terminal session; touches no files. |
279
- | `jp run <file> [args…]` | Run a local script on the remote in the current mapped folder, without pushing it. Interpreter from shebang/extension or `--as`; `--dry-run` to preview. Propagates the exit code. POSIX only. |
300
+ | `jp terminal [url]` | Open the remote machine's shell in your terminal, in the workspace folder. With a `<url>` it works standalone (no workspace needed). Creates/deletes only an ephemeral terminal session; touches no files. |
301
+ | `jp run <file> [args…]` | Run a local script on the remote in the current mapped folder, without pushing it. Interpreter from shebang/extension or `--as`; `--dry-run` to preview. Propagates the exit code. macOS/Linux/Windows. |
280
302
  | `jp doctor` | Diagnose token, connectivity, server status. |
281
303
  | `jp update` | Update jp to the latest version. |
282
304
  | `jp changelog` | Show release notes (newer releases, a specific version, or `--all`). |
@@ -37,6 +37,7 @@ runs — macOS, Windows, Linux.
37
37
  ## Why jp?
38
38
 
39
39
  - **Git-like workflow** — `jp clone`, `jp status`, `jp push`, `jp pull`. Same muscle memory.
40
+ - **Or skip the copy** — `jp live <url>` mounts the remote folder as a local folder you edit in place, and `jp terminal` drops you into a shell on the server. No SSH, no FUSE, no server-side install.
40
41
  - **Safe by default** — on a *shared* research machine, jp never deletes remote files unless you explicitly turn that on, and even then it asks you file-by-file. Conflicts are never silently overwritten.
41
42
  - **Zero dependencies** — one install, no dependency hell; ships as a wheel, a single `.pyz`, or a standalone binary.
42
43
  - **Cross-platform** — macOS, Windows, Linux; Python 3.9 → 3.13.
@@ -191,6 +192,26 @@ It only opens an ephemeral terminal session (it never touches your files), and
191
192
  the session is cleaned up when you exit. See the
192
193
  [command reference](docs/commands.md#remote-shell) for the details.
193
194
 
195
+ ### Bonus: edit the remote folder live (no clone)
196
+
197
+ Don't want a local copy at all — just open the remote folder and edit it in
198
+ place? `jp live <url>` mounts it as a normal folder on your machine over the same
199
+ kernel transport. Reads and (by default) writes travel straight to the server.
200
+
201
+ ```bash
202
+ jp live https://jupyter.example.com/user/<you>/lab/tree/your-folder
203
+ # → confirms writable vs read-only, then mounts it under ./your-folder/
204
+ # edit with any tool; Ctrl-C (or `jp live unmount`) to stop.
205
+
206
+ jp live <url> --code # open it in VS Code with a remote shell wired up
207
+ jp live <url> --read-only # browse without any risk of writing
208
+ ```
209
+
210
+ It mounts under one auto-managed handle (a folder on macOS/Linux, a drive letter
211
+ on Windows) using the OS's built-in WebDAV client — no FUSE, no third-party
212
+ deps. The mount is loopback-only and gated behind a per-session secret. Full
213
+ guide and the cross-OS notes: [docs/jp-live.md](docs/jp-live.md).
214
+
194
215
  ### Bonus: run a local script on the server — without pushing it
195
216
 
196
217
  Editing a script locally and want to run it remotely without `jp push` on every
@@ -215,8 +236,8 @@ and your files are never overwritten. For Python, `sys.argv[0]`/`__file__`/
215
236
  tracebacks show the real name, not the temp file. You see only the program's
216
237
  output, streamed live — no remote shell prompt, and `input()` works just like
217
238
  locally. Any **data** files the script opens must already exist on the remote
218
- (`jp push` those first). POSIX only for now (macOS + Linux); Windows support lands
219
- with the `jp terminal` Windows fix.
239
+ (`jp push` those first). Works on macOS, Linux and Windows (the interactive raw
240
+ proxy uses the same `jp.pty` backend as `jp terminal`).
220
241
 
221
242
  ---
222
243
 
@@ -233,13 +254,14 @@ with the `jp terminal` Windows fix.
233
254
  | `jp pull` | Download remote changes. Additive by default. |
234
255
  | `jp diff [path]` | Show file-level differences. |
235
256
  | `jp ls [remote-path]` | List a remote directory (no local writes). |
257
+ | `jp live <url>` | Mount a remote folder as a local folder over the kernel websocket — edit it in your own tools; changes travel to the server. Writable by default (confirmed); `--read-only` opts out. `--code` opens it in VS Code with a remote shell. `jp live unmount` (from inside) stops it; `jp live --defaults` sets the defaults. ([guide](docs/jp-live.md)) |
236
258
  | `jp open` | Open this workspace's folder (or the subfolder you're in) in the Jupyter web UI. Confirms first, shows the URL, and can copy instead of opening. Refuses folders jp never syncs (`.jp/`, hidden dot-names, `.jpignore` matches) since they aren't on the remote. Press `r` in the prompt to remember your choice (skip it next time); `jp open --ask` forgets it. Token-free URL; no network. |
237
259
  | `jp config` | Interactive settings editor (see below). Also `config get/set/list`. |
238
260
  | `jp ignore [pattern]` | Manage `.jpignore` patterns. |
239
261
  | `jp rm <path>` | Delete on the remote — gated, dry-run + typed confirmation. The only deleter. |
240
262
  | `jp kernel` | Set up a VS Code remote kernel to run notebooks in the right directory ([guide](docs/vscode-remote-cwd.md)). |
241
- | `jp terminal` | Open the remote machine's shell in your terminal, in the workspace folder. Creates/deletes only an ephemeral terminal session; touches no files. |
242
- | `jp run <file> [args…]` | Run a local script on the remote in the current mapped folder, without pushing it. Interpreter from shebang/extension or `--as`; `--dry-run` to preview. Propagates the exit code. POSIX only. |
263
+ | `jp terminal [url]` | Open the remote machine's shell in your terminal, in the workspace folder. With a `<url>` it works standalone (no workspace needed). Creates/deletes only an ephemeral terminal session; touches no files. |
264
+ | `jp run <file> [args…]` | Run a local script on the remote in the current mapped folder, without pushing it. Interpreter from shebang/extension or `--as`; `--dry-run` to preview. Propagates the exit code. macOS/Linux/Windows. |
243
265
  | `jp doctor` | Diagnose token, connectivity, server status. |
244
266
  | `jp update` | Update jp to the latest version. |
245
267
  | `jp changelog` | Show release notes (newer releases, a specific version, or `--all`). |
@@ -0,0 +1,253 @@
1
+ # `jp live` — your remote folder, mounted locally
2
+
3
+ `jp live <URL>` makes a folder on your Jupyter/JupyterHub server appear as a
4
+ normal folder on your machine. You open files in your own editor; reads and
5
+ (by default) writes travel to the server.
6
+
7
+ It does this **over the kernel websocket you already have access to** — the same
8
+ transport a notebook uses. There is **no SSH, no server-side install, and no new
9
+ service**. The only credential involved is your existing API token.
10
+
11
+ ```sh
12
+ # mount the folder the URL points at, under ./jp-live-test/ (writable by default)
13
+ jp live https://host/user/you/lab/tree/privado/jp-live-test
14
+
15
+ # read-only, no prompt
16
+ jp live <URL> --read-only
17
+
18
+ # open it in VS Code with a remote shell already wired up
19
+ jp live <URL> --code
20
+ ```
21
+
22
+ `jp live` is **workspace-free**: it does NOT need `jp init`/`jp clone` and does
23
+ NOT create a `.jp/` folder. The URL is the same kind you paste into `jp clone`.
24
+
25
+ ## How it works
26
+
27
+ `jp live <URL>`:
28
+
29
+ 1. parses the URL into a server + a remote sub-path (the *prefix*),
30
+ 2. starts a kernel on your server,
31
+ 3. injects a small **file agent** into that kernel (a single `execute_request`;
32
+ see `--print-agent` to read the exact code),
33
+ 4. opens a Jupyter `comm` channel to talk to the agent,
34
+ 5. serves that remote folder over a **loopback-only** WebDAV server (`127.0.0.1`),
35
+ and
36
+ 6. mounts it under **one auto-managed local handle named after the prefix leaf**
37
+ — a folder on macOS/Linux, a drive letter on Windows.
38
+
39
+ When you exit (`Ctrl-C`), the mount is removed, anything `jp` created is cleaned
40
+ up, and the kernel is **deleted**, so it does not sit on a GPU/CPU slot.
41
+
42
+ ## Command interface
43
+
44
+ ```
45
+ jp live <URL> [--read-only] [--credential NAME] [--code] [--yes] [--mount POINT]
46
+ jp live --dry-run --root <folder> [--read-only] [--mount POINT] [--stats] # offline self-test
47
+ jp live --print-agent [--read-only] # transparency
48
+ ```
49
+
50
+ - `<URL>` — the Jupyter URL of the folder, copied from your browser address bar
51
+ (e.g. `https://host/user/you/lab/tree/privado/jp-live-test`).
52
+ - **Writable is the default.** `--read-only` opts out (and skips the prompt).
53
+ - `--credential NAME` — pick a saved credential (otherwise: the single one is
54
+ used silently, or you are prompted; run `jp login` first if none).
55
+ - `--code` — open the mounted folder in VS Code with a remote shell running.
56
+ - `--yes` — skip the writable confirmation (assumes writable). Required for
57
+ unattended / non-tty runs.
58
+ - `--mount POINT` — override the auto target (advanced): a folder on
59
+ macOS/Linux, or a drive letter (`Z:`) on Windows. You own the target; it is
60
+ **not** removed on cleanup.
61
+ - `--dry-run --root <folder>` — offline self-test of the whole transport against
62
+ a **local** folder. No network. Writable is the default here too; `--read-only`
63
+ opts out.
64
+ - `--print-agent` — print the **exact** agent code that would be injected, then
65
+ exit (no network). With a `<URL>` it uses that URL's prefix; otherwise it falls
66
+ back to a workspace config.
67
+
68
+ ## The writable confirmation
69
+
70
+ Before mounting, `jp live` lists the top of the folder so you can confirm it is
71
+ the folder you expect, then folds the write/read choice into a single prompt:
72
+
73
+ ```
74
+ top of 'privado/jp-live-test':
75
+ • dummy.txt
76
+ Mount at ./jp-live-test -- [W]ritable (edits/deletes reach the server) or [r]ead-only? ([W]/r, c=cancel):
77
+ ```
78
+
79
+ - **Enter** or `w` → writable. `r` → read-only. `c` (or anything else) → cancel.
80
+ - `--read-only` forces read-only and skips the prompt.
81
+ - `--yes` assumes writable and skips the prompt.
82
+ - A non-interactive shell (no tty) **without** `--yes` is refused.
83
+
84
+ The prompt (and the refresh warning, below) name the **per-OS handle** — the
85
+ folder on macOS/Linux, the drive letter on Windows — not a hardcoded path.
86
+
87
+ ## Refresh semantics (important)
88
+
89
+ After mounting, `jp live` prints:
90
+
91
+ > Edits you make here save to the server immediately. Changes made ON the server
92
+ > appear here when your editor/file-browser re-reads a file (open or reload it)
93
+ > — there is no live push. For a live view of server-side changes (logs, job
94
+ > output), use a remote shell + `tail -f` (e.g. `jp terminal <URL>`).
95
+
96
+ ## Cross-OS support matrix
97
+
98
+ | Capability | macOS | Linux | Windows |
99
+ | --- | --- | --- | --- |
100
+ | Mount + edit files | folder `./leaf/` | folder `./leaf/` (symlink→gvfs) | drive `Z:` |
101
+ | Write/read prompt, credential picker, workspace guard | ✅ | ✅ | ✅ |
102
+ | `jp terminal <URL>` standalone | ✅ PTY | ✅ PTY | ✅ PTY (VT console; web fallback pre-Win10 1511) |
103
+ | `--code` opens VS Code | `open -a` | `code` / `vscode://` URI | `code` / `vscode://` URI |
104
+ | `--code` integrated remote shell | ✅ | ✅ | ✅ (web fallback pre-Win10 1511) |
105
+
106
+ The auto handle is named after the **prefix leaf**: `privado/jp-live-test`
107
+ mounts under `./jp-live-test/` (macOS/Linux) or the first free drive letter
108
+ (Windows).
109
+
110
+ ## `--code`: VS Code with a remote shell
111
+
112
+ `jp live <URL> --code`:
113
+
114
+ 1. mounts the folder as above,
115
+ 2. writes a **local** `<leaf>.code-workspace` next to the launch dir — never
116
+ inside the mount, because the server rejects dotfiles,
117
+ 3. opens it in VS Code (macOS: `open -a "Visual Studio Code"`; else the `code`
118
+ CLI if on PATH; else the `vscode://file/...` URI; else it prints the path),
119
+ 4. VS Code shows a one-time **"Allow Automatic Tasks"** prompt and then runs
120
+ `jp terminal "<URL>"` in an integrated terminal — a real raw remote shell on
121
+ macOS, Linux **and** Windows (the Windows path drives the console's
122
+ virtual-terminal modes directly; on a Windows too old for VT, pre-Win10 1511,
123
+ it falls back to the Jupyter web terminal).
124
+
125
+ On exit, the `.code-workspace` file `jp` created is removed along with the mount.
126
+
127
+ ## Safety model
128
+
129
+ `jp live` is deliberately paranoid because the server is often shared:
130
+
131
+ - **Refuses to run inside a `.jp` workspace.** If you are in a `jp` workspace
132
+ tree (the root or any subfolder), `jp live` refuses, so a live mount can never
133
+ be nested inside something a `push`/`pull` would walk. `cd` to a plain
134
+ directory first.
135
+ - **Never deletes your data.** The auto handle is created only if absent, reused
136
+ only if empty, and **refused** if it is a non-empty existing folder. Cleanup
137
+ uses `rmdir` (empty-only) / removes only the symlink and the `.code-workspace`
138
+ file `jp` itself created. Never `rm -rf`. With `--mount POINT` the target is
139
+ yours and is never removed.
140
+ - **Writable is gated.** Writable is the default, but a single confirmation
141
+ (showing the folder's top entries) precedes the mount; `--read-only` forces
142
+ read-only, `--yes` assumes writable, and a non-tty without `--yes` is refused.
143
+ - **Never recursive remote delete.** A directory delete only succeeds if the
144
+ directory is already empty; a non-empty directory is refused. There is no code
145
+ path that recursively deletes the remote.
146
+ - **Double path-jail.** The remote prefix is validated locally
147
+ (`paths.validate_prefix`) *and* enforced again inside the agent on the server.
148
+ Shared/too-broad names (`shared`, `public`, `common`, `compartilhado`,
149
+ `lapix`, the server root, `..`, …) are refused before any connection is made.
150
+ - **No automatic undo when writable.** In writable mode the agent overwrites
151
+ remote files in place. There is **no automatic server-side undo** — keep your
152
+ own backup before writing. (Removals are still never recursive.)
153
+ - **`--code` writes nothing to the server.** The `.code-workspace` is local.
154
+ - **Kernel released on exit.** The serving loop deletes the kernel in a
155
+ `finally`, even on `Ctrl-C` or error.
156
+ - **Token never in a URL.** Your token travels only in the `Authorization`
157
+ header on the websocket handshake.
158
+ - **Capability-secret on the mount.** Loopback is not a per-user boundary: any
159
+ local process that finds the ephemeral port could otherwise read (or, when
160
+ writable, write) the mounted files. So the server serves only under a random
161
+ 128-bit secret path segment — the URL is `http://127.0.0.1:<port>/<secret>/`
162
+ — and refuses any request without it (HTTP 404). This closes the trivial
163
+ port-scan vector.
164
+
165
+ **Residual, be honest about it:** while the mount is active, the full URL
166
+ (secret included) appears in the OS mount table — `mount` on macOS/Linux,
167
+ `net use` on Windows — which any local user can read. So the secret defends
168
+ against blind local port-scanners, **not** against a local user who actively
169
+ inspects the mount table on the *same* machine. Native OS mounting records
170
+ the URL by design; fully closing this would require a non-native transport
171
+ (e.g. a Unix-domain socket with per-user permissions), which would drop the
172
+ "mount with the OS's built-in client, no FUSE" property. On a hostile
173
+ multi-user host: prefer read-only, and unmount (`Ctrl-C`) when idle rather
174
+ than leaving a writable mount up.
175
+
176
+ ## Overriding the mount target
177
+
178
+ Auto-mount is the default. Pass `--mount <point>` to choose your own target — a
179
+ folder (macOS/Linux) or a drive letter like `Z:` (Windows). `jp live` mounts
180
+ there and, on exit, only **unmounts** it: the target is yours and is never
181
+ removed. If the mount fails, `jp live` prints the exact command to run by hand.
182
+ The WebDAV URL is `http://127.0.0.1:<port>/<secret>/` (printed each run).
183
+
184
+ Manual mount commands (the `<port>` stands in for the printed value):
185
+
186
+ - **macOS** (`mount_webdav`):
187
+
188
+ ```sh
189
+ mkdir -p ./jp-live-test
190
+ mount_webdav -S http://127.0.0.1:<port>/<secret>/ ./jp-live-test
191
+ umount ./jp-live-test
192
+ ```
193
+
194
+ - **Windows** (WebClient redirector):
195
+
196
+ ```bat
197
+ net use Z: http://127.0.0.1:<port>/<secret>/
198
+ net use Z: /delete /y
199
+ ```
200
+
201
+ - **Linux** (GVfs, userspace, no root):
202
+
203
+ ```sh
204
+ gio mount dav://127.0.0.1:<port>/<secret>/
205
+ gio mount -u dav://127.0.0.1:<port>/<secret>/
206
+ ```
207
+
208
+ `davfs2` (`mount -t davfs http://127.0.0.1:<port>/<secret>/ /mnt/jp`) also
209
+ works if you prefer a system mount.
210
+
211
+ Leave `jp live` running while you use the folder; it keeps the kernel alive
212
+ (pinging every ~30s to defeat idle-culling) until you press `Ctrl-C`.
213
+
214
+ ## Auditing the injected code: `--print-agent`
215
+
216
+ You do not have to trust a description of what runs on the server — you can read
217
+ it. `jp live --print-agent` prints the **exact** bootstrap code that the mount
218
+ would inject and exits without touching the network:
219
+
220
+ ```sh
221
+ jp live <URL> --print-agent # the (writable-by-default) agent
222
+ jp live <URL> --print-agent --read-only # the read-only variant
223
+ ```
224
+
225
+ The output is the verbatim agent source plus the small registration shim, so you
226
+ can review precisely what the kernel will execute before you trust it.
227
+
228
+ ## Manual per-OS test checklist
229
+
230
+ Run against a real, **personal** test folder on a server you control. Repeat per
231
+ OS (macOS, Linux, Windows).
232
+
233
+ 1. **Mount.** `jp live <URL>` → confirm the top-of-folder listing matches, press
234
+ Enter (writable). Verify the handle appears: `./<leaf>/` (macOS/Linux) or the
235
+ drive letter (Windows).
236
+ 2. **ls / cat.** List the mounted folder and open a known file in your editor;
237
+ the contents match the server.
238
+ 3. **Add propagates.** Create a new file in the mount; confirm it appears on the
239
+ server (Jupyter file browser).
240
+ 4. **Edit propagates.** Edit and save a file; confirm the server copy updates.
241
+ 5. **Delete propagates.** Delete a file; confirm it is gone on the server. (A
242
+ non-empty directory delete is refused — expected.)
243
+ 6. **Read-only.** Re-run with `--read-only`; confirm writes/deletes are rejected.
244
+ 7. **`--code`.** `jp live <URL> --code` opens VS Code on the folder; accept
245
+ "Allow Automatic Tasks"; a `jp terminal` shell appears in the integrated
246
+ terminal (web fallback on Windows). Confirm the `.code-workspace` file is in
247
+ the launch dir, **not** inside the mount.
248
+ 8. **Ctrl-C cleanup.** Press `Ctrl-C`; confirm the mount is gone, the auto
249
+ folder/symlink and the `.code-workspace` file are removed (an empty auto
250
+ folder is `rmdir`-ed; a `--mount` target is left in place), and the kernel is
251
+ deleted on the server.
252
+ 9. **Workspace guard.** From inside a `jp` workspace tree, `jp live <URL>`
253
+ refuses with the "must run OUTSIDE a workspace" message.
@@ -92,3 +92,6 @@ no_implicit_optional = true
92
92
  [tool.pytest.ini_options]
93
93
  testpaths = ["tests"]
94
94
  addopts = "-ra"
95
+ markers = [
96
+ "realjupyter: end-to-end tests against a REAL local jupyter_server (skipped unless the throwaway venv at /tmp/jpreal is present)",
97
+ ]
@@ -10,7 +10,7 @@ import contextlib
10
10
 
11
11
  __all__ = ["__version__"]
12
12
 
13
- __version__ = "1.2.0" # x-release-please-version
13
+ __version__ = "1.3.0" # x-release-please-version
14
14
 
15
15
  try: # Prefer the installed distribution's version when present.
16
16
  from importlib.metadata import PackageNotFoundError
File without changes