jpsync 1.0.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.
- jpsync-1.0.0/.gitignore +58 -0
- jpsync-1.0.0/CHANGELOG.md +113 -0
- jpsync-1.0.0/LICENSE +21 -0
- jpsync-1.0.0/PKG-INFO +406 -0
- jpsync-1.0.0/README.md +370 -0
- jpsync-1.0.0/docs/architecture.md +60 -0
- jpsync-1.0.0/docs/commands.md +170 -0
- jpsync-1.0.0/docs/security.md +56 -0
- jpsync-1.0.0/docs/vscode-remote-cwd.md +216 -0
- jpsync-1.0.0/pyproject.toml +93 -0
- jpsync-1.0.0/src/jp/__init__.py +23 -0
- jpsync-1.0.0/src/jp/__main__.py +10 -0
- jpsync-1.0.0/src/jp/api.py +538 -0
- jpsync-1.0.0/src/jp/cli.py +91 -0
- jpsync-1.0.0/src/jp/commands/__init__.py +39 -0
- jpsync-1.0.0/src/jp/commands/_context.py +99 -0
- jpsync-1.0.0/src/jp/commands/_mirror.py +100 -0
- jpsync-1.0.0/src/jp/commands/_report.py +47 -0
- jpsync-1.0.0/src/jp/commands/clone.py +97 -0
- jpsync-1.0.0/src/jp/commands/config_cmd.py +132 -0
- jpsync-1.0.0/src/jp/commands/diff.py +93 -0
- jpsync-1.0.0/src/jp/commands/doctor.py +103 -0
- jpsync-1.0.0/src/jp/commands/ignore_cmd.py +41 -0
- jpsync-1.0.0/src/jp/commands/init.py +66 -0
- jpsync-1.0.0/src/jp/commands/kernel.py +238 -0
- jpsync-1.0.0/src/jp/commands/login.py +137 -0
- jpsync-1.0.0/src/jp/commands/ls.py +40 -0
- jpsync-1.0.0/src/jp/commands/pull.py +58 -0
- jpsync-1.0.0/src/jp/commands/push.py +58 -0
- jpsync-1.0.0/src/jp/commands/rm.py +204 -0
- jpsync-1.0.0/src/jp/commands/status.py +69 -0
- jpsync-1.0.0/src/jp/commands/update.py +210 -0
- jpsync-1.0.0/src/jp/commands/version.py +18 -0
- jpsync-1.0.0/src/jp/config.py +246 -0
- jpsync-1.0.0/src/jp/credentials.py +259 -0
- jpsync-1.0.0/src/jp/errors.py +108 -0
- jpsync-1.0.0/src/jp/ignore.py +141 -0
- jpsync-1.0.0/src/jp/index.py +121 -0
- jpsync-1.0.0/src/jp/paths.py +427 -0
- jpsync-1.0.0/src/jp/settings_schema.py +86 -0
- jpsync-1.0.0/src/jp/sync.py +594 -0
- jpsync-1.0.0/src/jp/tui.py +533 -0
- jpsync-1.0.0/src/jp/ui.py +184 -0
- jpsync-1.0.0/src/jp/urls.py +103 -0
- jpsync-1.0.0/tests/conftest.py +273 -0
- jpsync-1.0.0/tests/test_api.py +306 -0
- jpsync-1.0.0/tests/test_cli.py +106 -0
- jpsync-1.0.0/tests/test_clone_init.py +66 -0
- jpsync-1.0.0/tests/test_config.py +143 -0
- jpsync-1.0.0/tests/test_credentials.py +101 -0
- jpsync-1.0.0/tests/test_doctor.py +60 -0
- jpsync-1.0.0/tests/test_gitignore_guard.py +49 -0
- jpsync-1.0.0/tests/test_index_ignore.py +74 -0
- jpsync-1.0.0/tests/test_kernel.py +132 -0
- jpsync-1.0.0/tests/test_login.py +100 -0
- jpsync-1.0.0/tests/test_mirror.py +111 -0
- jpsync-1.0.0/tests/test_paths.py +293 -0
- jpsync-1.0.0/tests/test_redact.py +100 -0
- jpsync-1.0.0/tests/test_rm.py +136 -0
- jpsync-1.0.0/tests/test_sync_pull.py +149 -0
- jpsync-1.0.0/tests/test_sync_push.py +216 -0
- jpsync-1.0.0/tests/test_tui.py +122 -0
- jpsync-1.0.0/tests/test_update.py +47 -0
- jpsync-1.0.0/tests/test_urls.py +95 -0
jpsync-1.0.0/.gitignore
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Local agent / editor state
|
|
2
|
+
.claude/
|
|
3
|
+
|
|
4
|
+
# Byte-compiled / optimized / caches
|
|
5
|
+
__pycache__/
|
|
6
|
+
*.py[cod]
|
|
7
|
+
*.egg-info/
|
|
8
|
+
.eggs/
|
|
9
|
+
*.egg
|
|
10
|
+
|
|
11
|
+
# Build artifacts
|
|
12
|
+
build/
|
|
13
|
+
dist/
|
|
14
|
+
*.pyz
|
|
15
|
+
*.spec
|
|
16
|
+
|
|
17
|
+
# Distribution binaries produced by PyInstaller (anchored to repo root so the
|
|
18
|
+
# `src/jp/` package directory is NEVER ignored)
|
|
19
|
+
/jp
|
|
20
|
+
/jp.exe
|
|
21
|
+
|
|
22
|
+
# Virtual environments
|
|
23
|
+
.venv/
|
|
24
|
+
venv/
|
|
25
|
+
env/
|
|
26
|
+
ENV/
|
|
27
|
+
|
|
28
|
+
# Tooling caches
|
|
29
|
+
.pytest_cache/
|
|
30
|
+
.ruff_cache/
|
|
31
|
+
.mypy_cache/
|
|
32
|
+
.coverage
|
|
33
|
+
htmlcov/
|
|
34
|
+
.tox/
|
|
35
|
+
|
|
36
|
+
# Editors / OS
|
|
37
|
+
.DS_Store
|
|
38
|
+
*.swp
|
|
39
|
+
.idea/
|
|
40
|
+
.vscode/
|
|
41
|
+
.serena/
|
|
42
|
+
|
|
43
|
+
# Secrets / credentials -- NEVER commit a token
|
|
44
|
+
*.token
|
|
45
|
+
*token*
|
|
46
|
+
.jupyter_token
|
|
47
|
+
.env
|
|
48
|
+
.env.*
|
|
49
|
+
|
|
50
|
+
# jp workspace metadata (created in a user's synced folder, never in this repo)
|
|
51
|
+
.jp/
|
|
52
|
+
|
|
53
|
+
# Internal research notes and scratch (kept locally, not published)
|
|
54
|
+
research/
|
|
55
|
+
refine_review_workflow.js
|
|
56
|
+
|
|
57
|
+
# uv lockfile (project is zero-dependency; not tracked)
|
|
58
|
+
uv.lock
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.0.0](https://github.com/pehqge/jpsync/compare/v0.3.1...v1.0.0) (2026-05-31)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Miscellaneous Chores
|
|
12
|
+
|
|
13
|
+
* rebrand PyPI package and repo to jpsync ([a26db5c](https://github.com/pehqge/jpsync/commit/a26db5c4981f462e5695d7432e003426dac694eb))
|
|
14
|
+
|
|
15
|
+
## [0.3.1](https://github.com/pehqge/jpsync/compare/v0.3.0...v0.3.1) (2026-05-31)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Bug Fixes
|
|
19
|
+
|
|
20
|
+
* **readme:** use shields static/v1 badge so release-please can't eat the color ([7c9377b](https://github.com/pehqge/jpsync/commit/7c9377b860b5f75942a93c662b7bdab5b7afa83f))
|
|
21
|
+
* **update:** detect editable installs instead of faking a successful upgrade ([d12722b](https://github.com/pehqge/jpsync/commit/d12722b47d9ba6192198c1062e01502f4d87be22))
|
|
22
|
+
|
|
23
|
+
## [0.3.0](https://github.com/pehqge/jpsync/compare/v0.2.0...v0.3.0) (2026-05-31)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
### Features
|
|
27
|
+
|
|
28
|
+
* **kernel:** add --link, fix docs, print privacy verdict ([13d42ca](https://github.com/pehqge/jpsync/commit/13d42cab82df2463d444c5387da59609e8a84e12))
|
|
29
|
+
* **kernel:** add `jp kernel` to fix VS Code remote-kernel cwd ([7ba887c](https://github.com/pehqge/jpsync/commit/7ba887c782d7cdf341e262fbd398609d8e7d0864))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
### Bug Fixes
|
|
33
|
+
|
|
34
|
+
* **credentials:** skip world-readable warning on Windows ([44fa06a](https://github.com/pehqge/jpsync/commit/44fa06a4af1b9f3983bfaad12e1a9380257f36c9))
|
|
35
|
+
* **readme:** use a static release badge auto-bumped by release-please ([1176d07](https://github.com/pehqge/jpsync/commit/1176d07d4885562db9dd2f4fb9ccfab9128a97ee))
|
|
36
|
+
* **tests:** isolate Windows home dir and simplify release badge ([9eeb6ac](https://github.com/pehqge/jpsync/commit/9eeb6acbd7931ee30384b6c74dd9c3214805e425))
|
|
37
|
+
* **tests:** keep USERPROFILE comment within ruff line-length ([86f3d68](https://github.com/pehqge/jpsync/commit/86f3d68fea3614e930a1d06278ba11017f8d02bb))
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
### Documentation
|
|
41
|
+
|
|
42
|
+
* fix jp login flow to match actual behavior ([c378c4e](https://github.com/pehqge/jpsync/commit/c378c4e7538632155a17fd1d70a28901fe7fac40))
|
|
43
|
+
* fix two more stale README details ([1fefd67](https://github.com/pehqge/jpsync/commit/1fefd6719a35457f374ffc33d7ec3ad244910781))
|
|
44
|
+
* **readme:** add FAQ entry for the VS Code remote-kernel workflow ([d4e0436](https://github.com/pehqge/jpsync/commit/d4e043644df21357d39156964954a164c68d3cfa))
|
|
45
|
+
|
|
46
|
+
## [0.2.0](https://github.com/pehqge/jpsync/compare/v0.1.0...v0.2.0) (2026-05-31)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
### Features
|
|
50
|
+
|
|
51
|
+
* **config:** activate color setting and add dotfile "protect" policy ([38c9ac7](https://github.com/pehqge/jpsync/commit/38c9ac7ebe963156e0eb92015ec98c147d617a00))
|
|
52
|
+
* named credentials with interactive jp login ([ec290db](https://github.com/pehqge/jpsync/commit/ec290db9233c427a7150ecebb3cfad836d4b0e68))
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
### Bug Fixes
|
|
56
|
+
|
|
57
|
+
* add top-level release-type to release-please config ([695d456](https://github.com/pehqge/jpsync/commit/695d456d42511a371a33bad1d3e327af61c45b88))
|
|
58
|
+
* tag releases as vX.Y.Z without component prefix ([c2e9794](https://github.com/pehqge/jpsync/commit/c2e9794065feb93f13afa24a947f5db26bae820c))
|
|
59
|
+
* **tui:** repair interactive settings render and add navigation hints ([0e6b673](https://github.com/pehqge/jpsync/commit/0e6b673ab1bab1ad4e396679b1874b1284b93cf7))
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
### Documentation
|
|
63
|
+
|
|
64
|
+
* replace real JupyterHub URL with generic example placeholder ([54612b7](https://github.com/pehqge/jpsync/commit/54612b7e269d8e8f2c1e4757c6427aeaec625314))
|
|
65
|
+
|
|
66
|
+
## [Unreleased]
|
|
67
|
+
|
|
68
|
+
### Added
|
|
69
|
+
|
|
70
|
+
- Named credentials: `jp login` is now interactive — it explains how to get a
|
|
71
|
+
JupyterHub API token, reads it with hidden input, asks for a name, and saves it
|
|
72
|
+
**globally** (`~/.config/jp/`) or **locally** (a workspace's `.jp/`). Token
|
|
73
|
+
values are written to private `600` files and never leave the machine; only the
|
|
74
|
+
credential name is recorded in config. Scriptable via `--name`,
|
|
75
|
+
`--global`/`--local`, `--token-stdin`, `--token-path`, `--force`.
|
|
76
|
+
- `jp clone` / `jp init` select a saved credential automatically when only one
|
|
77
|
+
exists, or prompt to choose when several do (`--credential <name>` to skip the
|
|
78
|
+
prompt); the choice is recorded in the workspace config.
|
|
79
|
+
- New `credential` config key and a `credentials.json` registry per scope.
|
|
80
|
+
- Every `.jp/` workspace now gets a `.jp/.gitignore` (`*`) so a workspace that is
|
|
81
|
+
also a git repo can never commit its local state or a local token file.
|
|
82
|
+
|
|
83
|
+
### Changed
|
|
84
|
+
|
|
85
|
+
- Token resolution order is now `$JP_TOKEN` → `$JP_TOKEN_FILE` → the workspace's
|
|
86
|
+
saved credential → legacy `token_path` / `~/.config/jp/token` (the legacy paths
|
|
87
|
+
remain supported).
|
|
88
|
+
|
|
89
|
+
## [0.1.0] - 2026-05-30
|
|
90
|
+
|
|
91
|
+
### Added
|
|
92
|
+
|
|
93
|
+
- Initial release of `jp` (PyPI package `jpsync`, command `jp`).
|
|
94
|
+
- Git-like commands: `clone`, `pull`, `push`, `status`, `diff`, `init`,
|
|
95
|
+
`config`, `auth`, `version`.
|
|
96
|
+
- Three-way sync model (local / remote / base) with conflict detection.
|
|
97
|
+
- Conflict-safe synchronization: conflicting files are never overwritten
|
|
98
|
+
silently (exit code `6`).
|
|
99
|
+
- Token-based authentication resolved as `--token-file` > `$JP_TOKEN_FILE` /
|
|
100
|
+
`$JUPYTER_TOKEN` > repo config > global config > `~/.jupyter_token`;
|
|
101
|
+
only the token path is stored in config, and the token file is expected to be
|
|
102
|
+
mode `0600`.
|
|
103
|
+
- Path-jail confinement: remote paths are resolved inside the clone root, with
|
|
104
|
+
`..` traversal, absolute paths, and boundary-crossing symlinks rejected.
|
|
105
|
+
- `.jpignore` support plus built-in defaults (`.jp/`, dotfiles, `.git/`,
|
|
106
|
+
`__pycache__/`, `*.pyc`); hidden files opt-in via `allow_hidden`.
|
|
107
|
+
- Pure standard-library HTTP client (`urllib`) for the Jupyter Contents API,
|
|
108
|
+
with retry/backoff on network errors.
|
|
109
|
+
- Distribution via PyPI, a single-file `jp.pyz` zipapp, standalone per-OS
|
|
110
|
+
PyInstaller binaries, and `install.sh` / `install.ps1` installers.
|
|
111
|
+
|
|
112
|
+
[Unreleased]: https://github.com/pehqge/jpsync/compare/v0.1.0...HEAD
|
|
113
|
+
[0.1.0]: https://github.com/pehqge/jpsync/releases/tag/v0.1.0
|
jpsync-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Pedro Gimenez
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
jpsync-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: jpsync
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A git-like CLI to sync local folders with a remote JupyterHub via the Contents API.
|
|
5
|
+
Project-URL: Homepage, https://github.com/pehqge/jpsync
|
|
6
|
+
Project-URL: Repository, https://github.com/pehqge/jpsync
|
|
7
|
+
Project-URL: Issues, https://github.com/pehqge/jpsync/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/pehqge/jpsync/blob/main/CHANGELOG.md
|
|
9
|
+
Author-email: Pedro Gimenez <pehqge@gmail.com>
|
|
10
|
+
Maintainer-email: Pedro Gimenez <pehqge@gmail.com>
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: cli,contents-api,jupyter,jupyterhub,notebook,sync
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Environment :: Console
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: Intended Audience :: Science/Research
|
|
18
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
19
|
+
Classifier: Operating System :: OS Independent
|
|
20
|
+
Classifier: Programming Language :: Python
|
|
21
|
+
Classifier: Programming Language :: Python :: 3
|
|
22
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
26
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
27
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
28
|
+
Classifier: Topic :: Software Development :: Version Control
|
|
29
|
+
Classifier: Topic :: Utilities
|
|
30
|
+
Requires-Python: >=3.9
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: mypy>=1.8; extra == 'dev'
|
|
33
|
+
Requires-Dist: pytest>=7; extra == 'dev'
|
|
34
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
|
|
37
|
+
<h1 align="center">jp</h1>
|
|
38
|
+
|
|
39
|
+
<p align="center">
|
|
40
|
+
<em>A git-like CLI to safely sync local folders with a remote JupyterHub — zero dependencies, pure Python.</em>
|
|
41
|
+
</p>
|
|
42
|
+
|
|
43
|
+
<p align="center">
|
|
44
|
+
<a href="https://github.com/pehqge/jpsync/actions/workflows/ci.yml"><img src="https://github.com/pehqge/jpsync/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
45
|
+
<a href="https://github.com/pehqge/jpsync/releases/latest"><img src="https://img.shields.io/static/v1?label=release&message=v1.0.0&color=blue" alt="Release"></a> <!-- x-release-please-version -->
|
|
46
|
+
<img src="https://img.shields.io/badge/python-3.9%2B-blue" alt="Python 3.9+">
|
|
47
|
+
<a href="https://github.com/pehqge/jpsync/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-yellow" alt="License: MIT"></a>
|
|
48
|
+
<img src="https://img.shields.io/badge/dependencies-zero-brightgreen" alt="Zero dependencies">
|
|
49
|
+
</p>
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
`jp` keeps a local folder in sync with a directory on a **JupyterHub** server —
|
|
54
|
+
the way `git` keeps you in sync with a remote. You edit notebooks and scripts on
|
|
55
|
+
your laptop, `jp push` to send them up, run your training on the server's GPUs,
|
|
56
|
+
and `jp pull` the results back down.
|
|
57
|
+
|
|
58
|
+
It talks to the JupyterHub REST API directly, has **zero third-party
|
|
59
|
+
dependencies** (pure Python standard library), and runs anywhere Python 3.9+
|
|
60
|
+
runs — macOS, Windows, Linux.
|
|
61
|
+
|
|
62
|
+
```console
|
|
63
|
+
$ jp clone https://jupyter.example.com/user/you/lab/tree/your-folder
|
|
64
|
+
cloning your-folder -> ./your-folder
|
|
65
|
+
✓ clone: 12 transferred
|
|
66
|
+
|
|
67
|
+
$ cd your-folder
|
|
68
|
+
$ # ...edit files locally...
|
|
69
|
+
$ jp push
|
|
70
|
+
push: train.py
|
|
71
|
+
push: data/config.yaml
|
|
72
|
+
✓ push: 2 transferred, 10 up to date, 0 skipped, 0 conflict(s), 0 failed
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Why jp?
|
|
76
|
+
|
|
77
|
+
- **Git-like workflow** — `jp clone`, `jp status`, `jp push`, `jp pull`. Same muscle memory.
|
|
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.
|
|
79
|
+
- **Zero dependencies** — one install, no dependency hell; ships as a wheel, a single `.pyz`, or a standalone binary.
|
|
80
|
+
- **Cross-platform** — macOS, Windows, Linux; Python 3.9 → 3.13.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Installation
|
|
85
|
+
|
|
86
|
+
> Recommended: install in an isolated environment with **uv** or **pipx** so the
|
|
87
|
+
> `jp` command lands on your `PATH` without touching system Python.
|
|
88
|
+
|
|
89
|
+
**With uv** (fastest):
|
|
90
|
+
```bash
|
|
91
|
+
uv tool install jpsync
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**With pipx**:
|
|
95
|
+
```bash
|
|
96
|
+
pipx install jpsync
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Straight from GitHub** (before the first PyPI release):
|
|
100
|
+
```bash
|
|
101
|
+
pipx install "git+https://github.com/pehqge/jpsync"
|
|
102
|
+
# or: uv tool install "git+https://github.com/pehqge/jpsync"
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**Install script (macOS / Linux)** — downloads the standalone binary, no Python needed:
|
|
106
|
+
```bash
|
|
107
|
+
curl -fsSL https://raw.githubusercontent.com/pehqge/jpsync/main/scripts/install.sh | sh
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Install script (Windows, PowerShell)**:
|
|
111
|
+
```powershell
|
|
112
|
+
powershell -ExecutionPolicy ByPass -c "irm https://raw.githubusercontent.com/pehqge/jpsync/main/scripts/install.ps1 | iex"
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**Single file, no install** — grab `jp.pyz` from the
|
|
116
|
+
[latest release](https://github.com/pehqge/jpsync/releases/latest) and run it
|
|
117
|
+
with any Python 3.9+:
|
|
118
|
+
```bash
|
|
119
|
+
python jp.pyz --help
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Verify:
|
|
123
|
+
```bash
|
|
124
|
+
jp --version
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
> If `jp: command not found` after a pipx/uv install, run `pipx ensurepath` (or
|
|
128
|
+
> `uv tool update-shell`) and reopen your terminal.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Getting started
|
|
133
|
+
|
|
134
|
+
### 1. Get your JupyterHub API token
|
|
135
|
+
|
|
136
|
+
`jp` authenticates with a personal API token from your JupyterHub.
|
|
137
|
+
|
|
138
|
+
1. Open your JupyterHub in a browser and log in (e.g. `https://jupyter.example.com`).
|
|
139
|
+
2. Go to the **Token** page — usually the **Token** link in the top bar, or
|
|
140
|
+
visit `https://<your-hub>/hub/token` directly.
|
|
141
|
+
3. Type a note (e.g. `jp`), leave the scopes blank (full access to what *you*
|
|
142
|
+
can already do), and click **Request new API token**.
|
|
143
|
+
4. **Copy the token now** — JupyterHub shows it only once.
|
|
144
|
+
|
|
145
|
+
> Security: the token is like a password for your account. `jp` stores only the
|
|
146
|
+
> *path* to a token file, never the token value, and never logs or commits it.
|
|
147
|
+
|
|
148
|
+
### 2. Log in with `jp login`
|
|
149
|
+
|
|
150
|
+
Run `jp login` and follow the prompts. It asks for a short name, walks you
|
|
151
|
+
through getting the token, then you paste it (your input stays **hidden**).
|
|
152
|
+
Credentials are saved **globally** by default (usable from anywhere); pass
|
|
153
|
+
`--local` to keep it only in the current workspace:
|
|
154
|
+
|
|
155
|
+
```console
|
|
156
|
+
$ jp login
|
|
157
|
+
Name this server/credential (e.g. myserver): myserver
|
|
158
|
+
To get a JupyterHub API token:
|
|
159
|
+
1. Open your JupyterHub in a browser and log in.
|
|
160
|
+
2. Go to the Token page (the 'Token' link, or <your-hub>/hub/token).
|
|
161
|
+
3. Click 'Request new API token' and copy it (it is shown only once).
|
|
162
|
+
|
|
163
|
+
Paste your API token (input hidden):
|
|
164
|
+
✓ saved global credential 'myserver'
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Everything stays on your machine.** `jp` writes the token to a private file
|
|
168
|
+
(permissions `600`) under `~/.config/jp/` (or the workspace's `.jp/` for a local
|
|
169
|
+
credential) and records only the credential's *name* in config — never the token
|
|
170
|
+
value. The token is never printed, logged, committed, or sent anywhere except as
|
|
171
|
+
the `Authorization` header to your own hub.
|
|
172
|
+
|
|
173
|
+
Run `jp login` again any time to add another server — keep as many credentials as
|
|
174
|
+
you like and pick one when you clone. See [Credentials](#credentials--jp-login).
|
|
175
|
+
|
|
176
|
+
### 3. Make sure your server is running
|
|
177
|
+
|
|
178
|
+
`jp` talks to your *single-user* server, so it must be started: open JupyterHub
|
|
179
|
+
and, if needed, click **Start My Server**. (`jp doctor` will tell you if it's
|
|
180
|
+
stopped.)
|
|
181
|
+
|
|
182
|
+
### 4. Clone your folder
|
|
183
|
+
|
|
184
|
+
Copy the URL of the folder from your browser's address bar — the `lab/tree/...`
|
|
185
|
+
URL works directly:
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
jp clone https://jupyter.example.com/user/<you>/lab/tree/your-folder
|
|
189
|
+
cd your-folder
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
That creates a `your-folder/` folder with a `.jp/` workspace inside (like `.git/`)
|
|
193
|
+
and downloads the remote tree. If you saved more than one credential, `jp` asks
|
|
194
|
+
which one to use; with a single one it just uses it. The choice is remembered in
|
|
195
|
+
the workspace (`jp clone … --credential <name>` to skip the prompt).
|
|
196
|
+
|
|
197
|
+
### 5. Work like git
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
jp status # what changed, locally vs the server
|
|
201
|
+
jp push # send local changes up
|
|
202
|
+
jp pull # bring remote changes (e.g. training output) down
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
That's it. From any subdirectory of the workspace, `jp` finds its root
|
|
206
|
+
automatically (it walks up looking for `.jp/`, stopping at your home folder).
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## Command reference
|
|
211
|
+
|
|
212
|
+
| Command | What it does |
|
|
213
|
+
|---|---|
|
|
214
|
+
| `jp clone <url> [dir]` | Clone a remote Jupyter folder into a new local directory. Accepts a `lab/tree` URL or `--base-url`/`--prefix`. |
|
|
215
|
+
| `jp init <url>` | Turn the current folder into a jp workspace (no download). |
|
|
216
|
+
| `jp login` | Save a named API-token credential (name the server, paste the token; defaults to global; use `--local` for workspace-only). |
|
|
217
|
+
| `jp status` | Show local vs. remote differences. Read-only. |
|
|
218
|
+
| `jp push` | Upload local changes. Additive by default. |
|
|
219
|
+
| `jp pull` | Download remote changes. Additive by default. |
|
|
220
|
+
| `jp diff [path]` | Show file-level differences. |
|
|
221
|
+
| `jp ls [remote-path]` | List a remote directory (no local writes). |
|
|
222
|
+
| `jp config` | Interactive settings editor (see below). Also `config get/set/list`. |
|
|
223
|
+
| `jp ignore [pattern]` | Manage `.jpignore` patterns. |
|
|
224
|
+
| `jp rm <path>` | Delete on the remote — gated, dry-run + typed confirmation. The only deleter. |
|
|
225
|
+
| `jp kernel` | Set up a VS Code remote kernel to run notebooks in the right directory ([guide](docs/vscode-remote-cwd.md)). |
|
|
226
|
+
| `jp doctor` | Diagnose token, connectivity, server status. |
|
|
227
|
+
| `jp update` | Update jp to the latest version. |
|
|
228
|
+
| `jp version` | Print the version (also `jp --version`). |
|
|
229
|
+
|
|
230
|
+
Global flags: `-q/--quiet`, `--no-color`. Every command has `--help`.
|
|
231
|
+
|
|
232
|
+
### `jp config` — interactive settings
|
|
233
|
+
|
|
234
|
+
Run `jp config` with no arguments in a terminal for a settings screen:
|
|
235
|
+
|
|
236
|
+
```
|
|
237
|
+
Mirror mode (allow deletes) false
|
|
238
|
+
> Dotfile policy skip
|
|
239
|
+
Colored output auto
|
|
240
|
+
Network timeout (s) 30.0
|
|
241
|
+
|
|
242
|
+
Up/Down move · Space change · i info · / search · Enter save · Esc cancel
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
- **↑/↓** move · **Space** cycle the value · **i** show help for the selected
|
|
246
|
+
setting · **/** search · **Enter** save · **Esc** cancel.
|
|
247
|
+
|
|
248
|
+
For scripts, the classic forms still work: `jp config list`,
|
|
249
|
+
`jp config get <key>`, `jp config set <key> <value>`.
|
|
250
|
+
|
|
251
|
+
### Mirror mode (deleting files to match the other side)
|
|
252
|
+
|
|
253
|
+
By default `jp push`/`jp pull` are **additive** — they never delete. If you want
|
|
254
|
+
true mirroring (delete on the remote when you delete locally, and vice-versa),
|
|
255
|
+
turn on **mirror mode**:
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
jp config set mirror true # persist it, or use --mirror for one run
|
|
259
|
+
jp push --mirror # one-off
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
With mirror on, after the normal sync jp finds files that exist on one side but
|
|
263
|
+
not the other and — **always, before deleting anything** — shows you the list
|
|
264
|
+
and lets you choose, with the arrow keys, which to **keep** and which to
|
|
265
|
+
**delete**:
|
|
266
|
+
|
|
267
|
+
```
|
|
268
|
+
Mirror mode: 2 file(s) exist on remote but not on the other side.
|
|
269
|
+
Choose which to DELETE on remote. Default is KEEP.
|
|
270
|
+
|
|
271
|
+
> [keep] old_experiment.py
|
|
272
|
+
[keep] scratch.ipynb
|
|
273
|
+
|
|
274
|
+
Up/Down move · Space toggle · a delete-all · n keep-all · Enter confirm · Esc cancel
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Nothing is deleted unless you mark it. In a non-interactive shell, mirror
|
|
278
|
+
deletions are refused unless you pass `--yes`. Conflicts (both sides changed) are
|
|
279
|
+
*never* deleted or overwritten.
|
|
280
|
+
|
|
281
|
+
### Credentials & `jp login`
|
|
282
|
+
|
|
283
|
+
`jp login` is how you give `jp` your JupyterHub API token. It is fully
|
|
284
|
+
interactive and **everything happens locally** — the token never leaves your
|
|
285
|
+
machine and is never printed:
|
|
286
|
+
|
|
287
|
+
- You give the credential a **name** (usually the server, e.g. `myserver`).
|
|
288
|
+
- It shows you how to get a token, then prompts you to paste it with the input
|
|
289
|
+
**hidden** (no echo).
|
|
290
|
+
- The **scope** defaults to **global** (stored in `~/.config/jp/`, usable from
|
|
291
|
+
any directory). Pass `--local` to store the credential only in the current
|
|
292
|
+
workspace's `.jp/`.
|
|
293
|
+
- The token value goes into a private `600` file; only its *name* is recorded in
|
|
294
|
+
the workspace config (`credential` key).
|
|
295
|
+
- A **local** credential lives in the workspace's `.jp/`, and `jp` drops a
|
|
296
|
+
`.jp/.gitignore` (`*`) so that — even if the workspace is also a git repo — git
|
|
297
|
+
ignores the whole `.jp/` directory and the token can never be committed.
|
|
298
|
+
|
|
299
|
+
Save as many as you like — run `jp login` once per server:
|
|
300
|
+
|
|
301
|
+
```bash
|
|
302
|
+
jp login # interactive: name, paste token; saves globally
|
|
303
|
+
jp login --name myserver --global # scriptable form
|
|
304
|
+
jp login --token-stdin --name lab-gpu --local < token.txt
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
When you `jp clone` / `jp init`, `jp` reads the credentials available **globally
|
|
308
|
+
and locally**: with one it's used automatically, with several you pick which
|
|
309
|
+
server to use (or pass `--credential <name>`). The choice is saved in the
|
|
310
|
+
workspace so later `push`/`pull` just work.
|
|
311
|
+
|
|
312
|
+
At sync time the token is resolved, in order: `$JP_TOKEN` (a value, for CI) →
|
|
313
|
+
`$JP_TOKEN_FILE` (a path) → the workspace's saved credential → legacy
|
|
314
|
+
`token_path` / `~/.config/jp/token`. `jp` warns if any token file is readable by
|
|
315
|
+
other users.
|
|
316
|
+
|
|
317
|
+
### Keeping jp up to date
|
|
318
|
+
|
|
319
|
+
```bash
|
|
320
|
+
jp update # detects pipx / uv / pip and upgrades in place
|
|
321
|
+
jp update --check # just check; don't install
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
For a standalone binary install, `jp update` prints the one-line reinstall
|
|
325
|
+
command for your OS.
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
## Configuration
|
|
330
|
+
|
|
331
|
+
Each workspace stores its settings in `.jp/config.json` (JSON, never the token
|
|
332
|
+
value). Keys: `base_url`, `prefix`, `credential`, `token_path`, `mirror`,
|
|
333
|
+
`dotfiles`, `color`, `timeout`. See [docs/commands.md](docs/commands.md) and
|
|
334
|
+
[docs/architecture.md](docs/architecture.md).
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
## Security
|
|
339
|
+
|
|
340
|
+
`jp` is built for a **shared** machine where a mistake can destroy someone
|
|
341
|
+
else's research. The guarantees:
|
|
342
|
+
|
|
343
|
+
- **`push`/`pull` never delete** unless you opt into mirror mode — and even then
|
|
344
|
+
jp asks you, file by file, defaulting to keep.
|
|
345
|
+
- **Conflicts are never silently overwritten.** If both sides changed since the
|
|
346
|
+
last sync, jp aborts that file and tells you.
|
|
347
|
+
- **Path-jailing.** Every remote operation is confined to your workspace's
|
|
348
|
+
prefix. The server root and shared spaces (`compartilhado`, `lapix`,
|
|
349
|
+
`shared`, …) are refused outright.
|
|
350
|
+
- **Untrusted server on download.** File names from the server are sanitized
|
|
351
|
+
before anything is written locally (anti path-traversal / Zip-Slip), and
|
|
352
|
+
writes are atomic and never follow a symlink.
|
|
353
|
+
- **Your token never leaks** — stored by path only, sent in the `Authorization`
|
|
354
|
+
header (never a URL), redacted from all output, never committed.
|
|
355
|
+
|
|
356
|
+
Found a vulnerability? See [SECURITY.md](SECURITY.md) — please don't open a
|
|
357
|
+
public issue.
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
|
|
361
|
+
## FAQ
|
|
362
|
+
|
|
363
|
+
**Is `jp` related to git?** No — it borrows git's *workflow*, not its internals.
|
|
364
|
+
There's no remote version history on a JupyterHub.
|
|
365
|
+
|
|
366
|
+
**Does it need Jupyter installed locally?** No. Just Python 3.9+; it talks to the
|
|
367
|
+
Hub over HTTPS.
|
|
368
|
+
|
|
369
|
+
**Why won't my `.gitignore` (or any dotfile) upload?** Most JupyterHub servers
|
|
370
|
+
run with `allow_hidden=False`, which rejects creating hidden files (names
|
|
371
|
+
starting with `.`). `jp` detects this and *skips* dotfiles on push, reporting
|
|
372
|
+
them instead of failing — your `.git/`, `.gitignore`, `.env` etc. simply stay
|
|
373
|
+
local (which is usually what you want). A nice side effect: secrets in dotfiles
|
|
374
|
+
never get pushed by accident.
|
|
375
|
+
|
|
376
|
+
**Will it overwrite my work?** Never silently. A conflict aborts that file;
|
|
377
|
+
remote deletes are opt-in (mirror mode) and confirmed file-by-file.
|
|
378
|
+
|
|
379
|
+
**Can I edit notebooks locally in VS Code but run them on the remote GPUs?** Yes —
|
|
380
|
+
that's a core workflow. Sync with jp, then connect VS Code to your remote kernel.
|
|
381
|
+
One catch: a remote kernel starts in the server's home, not your notebook's
|
|
382
|
+
folder, so relative paths fail. Run `jp kernel` once to fix it. The full
|
|
383
|
+
walkthrough (connecting the kernel + the cwd fix) is in
|
|
384
|
+
[docs/vscode-remote-cwd.md](docs/vscode-remote-cwd.md).
|
|
385
|
+
|
|
386
|
+
## Troubleshooting
|
|
387
|
+
|
|
388
|
+
- **`jp: command not found`** — run `pipx ensurepath` / `uv tool update-shell`, reopen the terminal.
|
|
389
|
+
- **`your JupyterHub server appears to be stopped`** — open the Hub UI and click *Start My Server*.
|
|
390
|
+
- **`authentication failed` / HTTP 403** — your token expired; create a new one and `jp login` again (or update the token file).
|
|
391
|
+
- **A big upload times out** — raise the timeout: `jp config set timeout 120`.
|
|
392
|
+
- **`FileNotFoundError` / relative paths fail in VS Code with a remote kernel** — the kernel starts in the server's home, not your notebook's folder. Run `jp kernel` (see the [VS Code remote-kernel guide](docs/vscode-remote-cwd.md)).
|
|
393
|
+
|
|
394
|
+
Run `jp doctor` for a guided check.
|
|
395
|
+
|
|
396
|
+
---
|
|
397
|
+
|
|
398
|
+
## Contributing
|
|
399
|
+
|
|
400
|
+
Contributions welcome — see [CONTRIBUTING.md](CONTRIBUTING.md) and the
|
|
401
|
+
[Code of Conduct](CODE_OF_CONDUCT.md). The project is standard-library only;
|
|
402
|
+
please keep it dependency-free.
|
|
403
|
+
|
|
404
|
+
## License
|
|
405
|
+
|
|
406
|
+
[MIT](LICENSE) © Pedro Gimenez
|