nuspace 0.6.0__tar.gz → 0.6.2__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 (136) hide show
  1. {nuspace-0.6.0 → nuspace-0.6.2}/CHANGELOG.md +20 -2
  2. {nuspace-0.6.0 → nuspace-0.6.2}/PKG-INFO +16 -13
  3. {nuspace-0.6.0 → nuspace-0.6.2}/README.md +9 -7
  4. {nuspace-0.6.0 → nuspace-0.6.2}/examples/space.py +2 -1
  5. {nuspace-0.6.0 → nuspace-0.6.2}/pyproject.toml +9 -6
  6. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/_cli/_installed.py +33 -11
  7. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/_cli/_meta.py +48 -25
  8. nuspace-0.6.2/src/nuspace/_cli/commands/init.py +60 -0
  9. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/_cli/commands/run.py +26 -11
  10. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/_cli/commands/telemetry.py +9 -19
  11. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/_telemetry/__init__.py +11 -8
  12. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/_telemetry/config.py +16 -6
  13. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/_telemetry/send.py +34 -10
  14. nuspace-0.6.2/src/nuspace/host/presets.py +173 -0
  15. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/host/space.py +8 -5
  16. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/ops/__init__.py +2 -0
  17. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/ops/cell.py +40 -15
  18. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/ops/extend.py +28 -4
  19. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/ops/plane.py +32 -6
  20. nuspace-0.6.2/src/nuspace/ops/usage.py +115 -0
  21. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/shapes/__init__.py +1 -0
  22. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/shapes/space.py +7 -2
  23. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/system/kernel/__init__.py +8 -0
  24. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/system/kernel/kernel.py +4 -6
  25. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/system/kernel/space.py +89 -22
  26. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/system/ui/home.py +2 -3
  27. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/system/ui/search.py +3 -3
  28. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/system/ui/seed.py +6 -9
  29. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/system/ui/settings.py +1 -2
  30. {nuspace-0.6.0 → nuspace-0.6.2}/tests/nuspace/test_cli.py +262 -33
  31. {nuspace-0.6.0 → nuspace-0.6.2}/tests/nuspace/test_host.py +55 -31
  32. {nuspace-0.6.0 → nuspace-0.6.2}/tests/nuspace/test_search.py +1 -1
  33. {nuspace-0.6.0 → nuspace-0.6.2}/tests/nuspace/test_settings.py +2 -6
  34. {nuspace-0.6.0 → nuspace-0.6.2}/tests/nuspace/test_telemetry.py +63 -16
  35. nuspace-0.6.0/src/nuspace/_cli/commands/init.py +0 -43
  36. nuspace-0.6.0/src/nuspace/host/presets.py +0 -116
  37. nuspace-0.6.0/src/nuspace/system/usage.py +0 -146
  38. {nuspace-0.6.0 → nuspace-0.6.2}/.gitignore +0 -0
  39. {nuspace-0.6.0 → nuspace-0.6.2}/.pre-commit-config.yaml +0 -0
  40. {nuspace-0.6.0 → nuspace-0.6.2}/LICENSE.md +0 -0
  41. {nuspace-0.6.0 → nuspace-0.6.2}/Makefile +0 -0
  42. {nuspace-0.6.0 → nuspace-0.6.2}/pkgs/ts/nuspace-ui/python/nuspace_ui/build +0 -0
  43. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/__init__.py +0 -0
  44. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/_branding/__init__.py +0 -0
  45. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/_branding/header.py +0 -0
  46. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/_branding/logo.py +0 -0
  47. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/_branding/palette.py +0 -0
  48. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/_cli/__init__.py +0 -0
  49. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/_cli/__main__.py +0 -0
  50. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/_cli/commands/__init__.py +0 -0
  51. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/_cli/commands/verse.py +0 -0
  52. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/_cli/main.py +0 -0
  53. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/content/__init__.py +0 -0
  54. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/content/kind.py +0 -0
  55. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/content/search.py +0 -0
  56. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/ext/__init__.py +0 -0
  57. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/ext/registry.py +0 -0
  58. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/ext/specs.py +0 -0
  59. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/host/__init__.py +0 -0
  60. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/ops/kernel.py +0 -0
  61. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/ops/pin.py +0 -0
  62. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/ops/read.py +0 -0
  63. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/ops/state.py +0 -0
  64. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/ops/tree.py +0 -0
  65. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/ops/utils.py +0 -0
  66. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/shapes/cell.py +0 -0
  67. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/shapes/connection.py +0 -0
  68. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/shapes/contents.py +0 -0
  69. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/shapes/kernel.py +0 -0
  70. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/shapes/plane.py +0 -0
  71. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/shapes/reroot.py +0 -0
  72. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/shapes/state.py +0 -0
  73. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/shapes/states.py +0 -0
  74. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/system/__init__.py +0 -0
  75. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/system/backends/__init__.py +0 -0
  76. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/system/backends/async_.py +0 -0
  77. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/system/backends/base.py +0 -0
  78. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/system/backends/mp.py +0 -0
  79. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/system/backends/pool.py +0 -0
  80. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/system/devices/__init__.py +0 -0
  81. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/system/devices/web/__init__.py +0 -0
  82. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/system/devices/web/device.py +0 -0
  83. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/system/devices/web/env.py +0 -0
  84. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/system/devices/web/session.py +0 -0
  85. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/system/devices/web/shell.py +0 -0
  86. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/system/devices/web/sidebar/__init__.py +0 -0
  87. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/system/devices/web/sidebar/feed.py +0 -0
  88. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/system/devices/web/sidebar/interactions.py +0 -0
  89. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/system/devices/web/sidebar/ref.py +0 -0
  90. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/system/devices/web/utils.py +0 -0
  91. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/system/devices/web/viewer/__init__.py +0 -0
  92. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/system/devices/web/viewer/feed.py +0 -0
  93. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/system/devices/web/viewer/interactions.py +0 -0
  94. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/system/devices/web/viewer/ref.py +0 -0
  95. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/system/info.py +0 -0
  96. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/system/kernel/body.py +0 -0
  97. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/system/kernel/dispatch.py +0 -0
  98. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/system/kernel/envs.py +0 -0
  99. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/system/kernel/out.py +0 -0
  100. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/system/kernel/reconcile.py +0 -0
  101. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/system/kernel/runs.py +0 -0
  102. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/system/kernel/utils.py +0 -0
  103. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/system/services/__init__.py +0 -0
  104. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/system/services/bootstrap.py +0 -0
  105. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/system/services/init.py +0 -0
  106. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/system/services/nav.py +0 -0
  107. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/system/services/reactions.py +0 -0
  108. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/system/services/reload.py +0 -0
  109. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/system/services/supervisor.py +0 -0
  110. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/system/ui/__init__.py +0 -0
  111. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/system/utils.py +0 -0
  112. {nuspace-0.6.0 → nuspace-0.6.2}/src/nuspace/ui.py +0 -0
  113. {nuspace-0.6.0 → nuspace-0.6.2}/tests/_ext/nuspace_testing/__init__.py +0 -0
  114. {nuspace-0.6.0 → nuspace-0.6.2}/tests/_ext/pyproject.toml +0 -0
  115. {nuspace-0.6.0 → nuspace-0.6.2}/tests/_lib/_support/__init__.py +0 -0
  116. {nuspace-0.6.0 → nuspace-0.6.2}/tests/_lib/_support/actions.py +0 -0
  117. {nuspace-0.6.0 → nuspace-0.6.2}/tests/_lib/_support/contents.py +0 -0
  118. {nuspace-0.6.0 → nuspace-0.6.2}/tests/_lib/_support/kernel.py +0 -0
  119. {nuspace-0.6.0 → nuspace-0.6.2}/tests/_lib/_support/kernel_envs.py +0 -0
  120. {nuspace-0.6.0 → nuspace-0.6.2}/tests/_lib/_support/made.py +0 -0
  121. {nuspace-0.6.0 → nuspace-0.6.2}/tests/_lib/_support/probe.py +0 -0
  122. {nuspace-0.6.0 → nuspace-0.6.2}/tests/_lib/_support/specs.py +0 -0
  123. {nuspace-0.6.0 → nuspace-0.6.2}/tests/conftest.py +0 -0
  124. {nuspace-0.6.0 → nuspace-0.6.2}/tests/nuspace/test_content.py +0 -0
  125. {nuspace-0.6.0 → nuspace-0.6.2}/tests/nuspace/test_home.py +0 -0
  126. {nuspace-0.6.0 → nuspace-0.6.2}/tests/nuspace/test_kernel.py +0 -0
  127. {nuspace-0.6.0 → nuspace-0.6.2}/tests/nuspace/test_ops_kernel.py +0 -0
  128. {nuspace-0.6.0 → nuspace-0.6.2}/tests/nuspace/test_ops_state.py +0 -0
  129. {nuspace-0.6.0 → nuspace-0.6.2}/tests/nuspace/test_ops_structure.py +0 -0
  130. {nuspace-0.6.0 → nuspace-0.6.2}/tests/nuspace/test_packaging.py +0 -0
  131. {nuspace-0.6.0 → nuspace-0.6.2}/tests/nuspace/test_reactions.py +0 -0
  132. {nuspace-0.6.0 → nuspace-0.6.2}/tests/nuspace/test_services.py +0 -0
  133. {nuspace-0.6.0 → nuspace-0.6.2}/tests/nuspace/test_session_pipe.py +0 -0
  134. {nuspace-0.6.0 → nuspace-0.6.2}/tests/nuspace/test_shapes.py +0 -0
  135. {nuspace-0.6.0 → nuspace-0.6.2}/tests/nuspace/test_ui.py +0 -0
  136. {nuspace-0.6.0 → nuspace-0.6.2}/tests/nuspace/test_web.py +0 -0
@@ -2,13 +2,31 @@
2
2
 
3
3
  ## Packages shipped from this repo
4
4
 
5
- - **nuspace** (the runtime, the `nuspace` command) — 0.6.0
6
- - **nuspace-ui** (the compiled web bundle) — 0.5.7
5
+ - **nuspace** (the runtime, the `nuspace` command) — 0.6.2
6
+ - **nuspace-ui** (the compiled web bundle) — 0.5.8
7
7
 
8
8
  Below is the changelog for **nuspace** - the full commit stream. Newest first.
9
9
  The `nuspace-ui` wheel is the same source tree's vite output and is not tracked
10
10
  separately here.
11
11
 
12
+ ## 0.6.2 — 2026-10-30
13
+
14
+ - Release nuspace 0.6.2 on nu 0.6.8 and nuverse 0.3.5
15
+ - Trim the README description to what nuspace is, dropping the example list
16
+ - Keep space data in .nuspace and find spaces like git, with a home default
17
+ - Export usage tracking from ops so planes can count first-run steps
18
+ - Package planes get their own id and reopen the same plane across runs
19
+ - Bare plane names default to nuverse, so first runs need no package name
20
+ - Restructure telemetry tracking
21
+ - Edit README and summary to match the new identity lines
22
+
23
+ ## 0.6.1 — 2026-10-27
24
+
25
+ - Release nuspace 0.6.1, built on nuspace-ui 0.5.8 and ui-kit 0.3.6
26
+ - Update sidebar and seed tests for the new rail layout
27
+ - Restructure the sidebar into nav rows, pinned, planes and extensions, one keyboard stop
28
+ - Pick plane icons from the whole lucide set, loaded on demand
29
+
12
30
  ## 0.6.0 — 2026-10-26
13
31
 
14
32
  - Release nuspace 0.6.0, built on nuspace-ui 0.5.7 and nu 0.6.7
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: nuspace
3
- Version: 0.6.0
4
- Summary: Computational space for everything you work with: notes, data, tools, agents.
3
+ Version: 0.6.2
4
+ Summary: Computational space for your data and tools.
5
5
  Project-URL: Repository, https://github.com/nustackdev/nuspace
6
6
  Author-email: Gor Arakelyan <gorarkln@gmail.com>
7
7
  License-Expression: AGPL-3.0-or-later
@@ -15,11 +15,12 @@ Classifier: Programming Language :: Python :: 3.11
15
15
  Classifier: Programming Language :: Python :: 3.12
16
16
  Classifier: Programming Language :: Python :: 3.13
17
17
  Requires-Python: >=3.10
18
+ Requires-Dist: certifi>=2023.7.22
18
19
  Requires-Dist: click>=8.0
19
- Requires-Dist: nucore>=0.6.7
20
- Requires-Dist: nuspace-ui>=0.5.7
21
- Requires-Dist: nustd[kv,mp-pool,proxy,ui,valkey]>=0.6.7
22
- Requires-Dist: nuverse>=0.3.4
20
+ Requires-Dist: nucore>=0.6.8
21
+ Requires-Dist: nuspace-ui>=0.5.8
22
+ Requires-Dist: nustd[kv,mp-pool,proxy,ui,valkey]>=0.6.8
23
+ Requires-Dist: nuverse>=0.3.5
23
24
  Requires-Dist: rich-click>=1.6
24
25
  Requires-Dist: rich>=10.7
25
26
  Requires-Dist: typing-extensions>=4.7.0
@@ -31,7 +32,8 @@ Description-Content-Type: text/markdown
31
32
  <div align="center">
32
33
  <img width="112" alt="nuspace" src="https://nustack.dev/brand/logo-square-512@2x.png" />
33
34
  <h1>nuspace</h1>
34
- <p>Computational space for everything you work with: notes, data, tools, agents.</p>
35
+ <p>Computational space for your data and tools. Human-first. Agent-native.</p>
36
+ <p><sub>Open-source · Self-hosted · Pure Python</sub></p>
35
37
 
36
38
  [![Python](https://img.shields.io/badge/python-%3E%3D%203.10-blue)](https://pypi.org/project/nuspace/)
37
39
  [![PyPI](https://img.shields.io/pypi/v/nuspace?color=yellow)](https://pypi.org/project/nuspace/)
@@ -44,7 +46,7 @@ Description-Content-Type: text/markdown
44
46
  </div>
45
47
 
46
48
 
47
- Elsewhere, a note is a dead document, data is scattered, tools are siloed and an agent's work is lost in chat. In nuspace, they're all connected cells: living programs that hold data, logic and look in one form.
49
+ In nuspace, your data and tools live in cells: tiny reactive programs that manage data, run logic and render UI. Cells are written in nu, a Python programming model with no glue code between layers, so a cell is a few lines, not a thousand. Humans get the space as UI, agents get it as code.
48
50
 
49
51
  **nuspace in action:**
50
52
 
@@ -186,10 +188,11 @@ Install from PyPI and run a space yourself. Python 3.10 or newer.
186
188
  2. **Run**
187
189
 
188
190
  ```bash
189
- nuspace run my.nuspace
191
+ nuspace run
190
192
  ```
191
193
 
192
- A browser opens on your space.
194
+ A browser opens on your space: the nearest one up from here, else your home space.
195
+ `nuspace init` makes the current directory a space of its own, data in `.nuspace/`.
193
196
 
194
197
  3. **Try a demo**
195
198
 
@@ -198,7 +201,7 @@ Install from PyPI and run a space yourself. Python 3.10 or newer.
198
201
  nuspace run --with nudemos.movies
199
202
  ```
200
203
 
201
- A throwaway space with a full app in it.
204
+ A full app in your space. Add `--tmp` for a throwaway space instead.
202
205
 
203
206
  </details>
204
207
 
@@ -300,8 +303,8 @@ Any agent that reads skills learns nu and nuspace from them, installs nuspace an
300
303
  | 01 | <code>the theory</code> <br/> [**interaction-model**](https://github.com/nustackdev/interaction-model) | A programming model where every resource is a Ref and Interactions are first-class, so you compose logic across systems. |
301
304
  | 02 | <code>the primitive</code> <br/> [**nu**](https://github.com/nustackdev/nu) | The interaction model, implemented in Python. |
302
305
  | 03 | <code>the std library</code> <br/> [**nustd**](https://github.com/nustackdev/nu/tree/main/pkgs/nustd) | Batteries for nu: storage, UI, workers and more, ready to use. |
303
- | 04 | <code>the space</code> <br/> [**nuspace**](https://github.com/nustackdev/nuspace) | A space that runs nu programs. Every cell is one. |
304
- | 05 | <code>the ecosystem</code> <br/> [**nuverse**](https://github.com/nustackdev/nuverse) | Ready-made spaces and extensions, one install away. |
306
+ | 04 | <code>the space</code> <br/> [**nuspace**](https://github.com/nustackdev/nuspace) | A space that runs nu programs. |
307
+ | 05 | <code>the ecosystem</code> <br/> [**nuverse**](https://github.com/nustackdev/nuverse) | Ready-made packages, one install away. |
305
308
  | + | **your space**, on top of it all | Everything below exists so your space can be anything. |
306
309
 
307
310
  ## Community
@@ -1,7 +1,8 @@
1
1
  <div align="center">
2
2
  <img width="112" alt="nuspace" src="https://nustack.dev/brand/logo-square-512@2x.png" />
3
3
  <h1>nuspace</h1>
4
- <p>Computational space for everything you work with: notes, data, tools, agents.</p>
4
+ <p>Computational space for your data and tools. Human-first. Agent-native.</p>
5
+ <p><sub>Open-source · Self-hosted · Pure Python</sub></p>
5
6
 
6
7
  [![Python](https://img.shields.io/badge/python-%3E%3D%203.10-blue)](https://pypi.org/project/nuspace/)
7
8
  [![PyPI](https://img.shields.io/pypi/v/nuspace?color=yellow)](https://pypi.org/project/nuspace/)
@@ -14,7 +15,7 @@
14
15
  </div>
15
16
 
16
17
 
17
- Elsewhere, a note is a dead document, data is scattered, tools are siloed and an agent's work is lost in chat. In nuspace, they're all connected cells: living programs that hold data, logic and look in one form.
18
+ In nuspace, your data and tools live in cells: tiny reactive programs that manage data, run logic and render UI. Cells are written in nu, a Python programming model with no glue code between layers, so a cell is a few lines, not a thousand. Humans get the space as UI, agents get it as code.
18
19
 
19
20
  **nuspace in action:**
20
21
 
@@ -156,10 +157,11 @@ Install from PyPI and run a space yourself. Python 3.10 or newer.
156
157
  2. **Run**
157
158
 
158
159
  ```bash
159
- nuspace run my.nuspace
160
+ nuspace run
160
161
  ```
161
162
 
162
- A browser opens on your space.
163
+ A browser opens on your space: the nearest one up from here, else your home space.
164
+ `nuspace init` makes the current directory a space of its own, data in `.nuspace/`.
163
165
 
164
166
  3. **Try a demo**
165
167
 
@@ -168,7 +170,7 @@ Install from PyPI and run a space yourself. Python 3.10 or newer.
168
170
  nuspace run --with nudemos.movies
169
171
  ```
170
172
 
171
- A throwaway space with a full app in it.
173
+ A full app in your space. Add `--tmp` for a throwaway space instead.
172
174
 
173
175
  </details>
174
176
 
@@ -270,8 +272,8 @@ Any agent that reads skills learns nu and nuspace from them, installs nuspace an
270
272
  | 01 | <code>the theory</code> <br/> [**interaction-model**](https://github.com/nustackdev/interaction-model) | A programming model where every resource is a Ref and Interactions are first-class, so you compose logic across systems. |
271
273
  | 02 | <code>the primitive</code> <br/> [**nu**](https://github.com/nustackdev/nu) | The interaction model, implemented in Python. |
272
274
  | 03 | <code>the std library</code> <br/> [**nustd**](https://github.com/nustackdev/nu/tree/main/pkgs/nustd) | Batteries for nu: storage, UI, workers and more, ready to use. |
273
- | 04 | <code>the space</code> <br/> [**nuspace**](https://github.com/nustackdev/nuspace) | A space that runs nu programs. Every cell is one. |
274
- | 05 | <code>the ecosystem</code> <br/> [**nuverse**](https://github.com/nustackdev/nuverse) | Ready-made spaces and extensions, one install away. |
275
+ | 04 | <code>the space</code> <br/> [**nuspace**](https://github.com/nustackdev/nuspace) | A space that runs nu programs. |
276
+ | 05 | <code>the ecosystem</code> <br/> [**nuverse**](https://github.com/nustackdev/nuverse) | Ready-made packages, one install away. |
275
277
  | + | **your space**, on top of it all | Everything below exists so your space can be anything. |
276
278
 
277
279
  ## Community
@@ -2,7 +2,8 @@
2
2
 
3
3
  Every Plane, Cell and content kind comes from an installed extension, found
4
4
  through its entry point.
5
- The space is the directory ``examples/example.nuspace``, kept between runs.
5
+ The space's root is ``examples/example.nuspace``, its data in ``.nuspace/``
6
+ inside it, kept between runs.
6
7
 
7
8
  Run it::
8
9
 
@@ -4,8 +4,8 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "nuspace"
7
- version = "0.6.0"
8
- description = "Computational space for everything you work with: notes, data, tools, agents."
7
+ version = "0.6.2"
8
+ description = "Computational space for your data and tools."
9
9
  readme = "README.md"
10
10
  license = "AGPL-3.0-or-later"
11
11
  authors = [{ name = "Gor Arakelyan", email = "gorarkln@gmail.com" }]
@@ -14,21 +14,24 @@ dependencies = [
14
14
  # The fabrics a space runs on, nothing more: kv for the store, valkey for
15
15
  # its change notifications, ui for the browser, mp_pool for the workers,
16
16
  # proxy (invisibles) for the worker's session.
17
- "nucore>=0.6.7",
18
- "nustd[kv,ui,valkey,mp_pool,proxy]>=0.6.7",
17
+ "nucore>=0.6.8",
18
+ "nustd[kv,ui,valkey,mp_pool,proxy]>=0.6.8",
19
19
  # The store's change notifications run over virtuals' redis backend.
20
20
  "virtuals-py[redis]>=0.1.10",
21
21
  # The compiled web bundle. A separate distribution only because PyPI caps
22
22
  # project size and a SPA rebuilt on every UI tweak would eat the quota --
23
23
  # not optional. `pip install nuspace` gives you the browser.
24
- "nuspace-ui>=0.5.7",
24
+ "nuspace-ui>=0.5.8",
25
25
  # The everyday Planes and Cells. A plugin like any other, found through
26
26
  # its entry point, but every space ships with it.
27
- "nuverse>=0.3.4",
27
+ "nuverse>=0.3.5",
28
28
  "typing-extensions>=4.7.0",
29
29
  "click>=8.0",
30
30
  "rich>=10.7",
31
31
  "rich-click>=1.6",
32
+ # CA bundle for telemetry's HTTPS POST: python.org macOS builds ship none,
33
+ # so without it every send fails verification, silently.
34
+ "certifi>=2023.7.22",
32
35
  ]
33
36
  keywords = [
34
37
  "nu",
@@ -3,7 +3,8 @@
3
3
  ``nuspace verse`` lists it and ``nuspace run --with`` picks a Plane out of
4
4
  it. A package is its entry point's name, and its Planes are
5
5
  ``<package>.<name>``, as a space registers them
6
- (:func:`~nuspace.ext.qualified`).
6
+ (:func:`~nuspace.ext.qualified`). A bare name is a Plane of
7
+ :data:`DEFAULT_PACKAGE`, the one nuspace ships with.
7
8
  """
8
9
 
9
10
  from __future__ import annotations
@@ -24,7 +25,11 @@ if TYPE_CHECKING:
24
25
  from nuspace.ext import Extension, PlaneSpec
25
26
 
26
27
 
27
- __all__ = ["Installed", "installed", "resolve"]
28
+ __all__ = ["DEFAULT_PACKAGE", "Installed", "installed", "resolve"]
29
+
30
+
31
+ DEFAULT_PACKAGE = "nuverse"
32
+ """The package a bare plane name is looked up in: ``jobs`` is ``nuverse.jobs``."""
28
33
 
29
34
 
30
35
  @dataclass(frozen=True)
@@ -61,21 +66,38 @@ def installed(group: str = GROUP) -> list[Installed]:
61
66
  def resolve(spec: str, packages: list[Installed] | None = None) -> PlaneSpec:
62
67
  """``pkg.plane`` as the Plane it names, qualified as a space registers it.
63
68
 
69
+ A bare name, with no dot, is a Plane of :data:`DEFAULT_PACKAGE`.
70
+
64
71
  Raises:
65
- click.ClickException: ``spec`` is not ``pkg.plane``, or no installed
66
- package adds it.
72
+ click.ClickException: ``spec`` is not ``pkg.plane`` or a bare name, or
73
+ no installed package adds it.
67
74
  """
68
75
  packages = installed() if packages is None else packages
69
- pkg, dot, name = spec.partition(".")
76
+ bare = "." not in spec
77
+ full = f"{DEFAULT_PACKAGE}.{spec}" if bare else spec
78
+ pkg, _, name = full.partition(".")
70
79
  hits = [
71
80
  plane
72
81
  for p in packages
73
82
  if p.extension is not None and p.name == pkg
74
83
  for plane in qualified(p.extension, p.name).planes
75
- if plane.name == spec
84
+ if plane.name == full
76
85
  ]
77
- if not (pkg and dot and name and hits):
78
- raise click.ClickException(
79
- f"No plane {spec!r} installed. Name it pkg.plane, see: nuspace verse"
80
- )
81
- return hits[0]
86
+ if pkg and name and hits:
87
+ return hits[0]
88
+ if bare:
89
+ raise click.ClickException(_not_in_default(spec, packages))
90
+ raise click.ClickException(
91
+ f"No plane {spec!r} installed. Name it pkg.plane, see: nuspace verse"
92
+ )
93
+
94
+
95
+ def _not_in_default(spec: str, packages: list[Installed]) -> str:
96
+ """Why bare ``spec`` names no Plane: what :data:`DEFAULT_PACKAGE` has instead, if anything."""
97
+ ext = next((p.extension for p in packages if p.name == DEFAULT_PACKAGE and p.extension), None)
98
+ head = f"No plane {spec!r} in {DEFAULT_PACKAGE}, where a bare name is looked up."
99
+ tail = "Name another package's as pkg.plane, see: nuspace verse"
100
+ if ext is None:
101
+ return f"{head} {DEFAULT_PACKAGE} is not installed or failed to load. {tail}"
102
+ names = ", ".join(plane.name for plane in ext.planes) or "none"
103
+ return f"{head} Its planes: {names}. {tail}"
@@ -1,4 +1,9 @@
1
- """What the commands share: the console, the versions, and how a space is held open."""
1
+ """What the commands share: the console, the versions, which space, and how it is held open.
2
+
3
+ Which space, the way ``git`` finds a repository: PATH when given, else the
4
+ nearest space at or above the current directory, else the home space (root
5
+ ``~``, data in ``~/.nuspace``). ``--tmp`` is a throwaway one instead.
6
+ """
2
7
 
3
8
  from __future__ import annotations
4
9
 
@@ -14,24 +19,22 @@ from rich.text import Text
14
19
  import nu
15
20
  from nuspace import _telemetry
16
21
  from nuspace._branding import BLUE, header
17
- from nuspace.system.kernel import NotASpace
18
- from nuspace.system.usage import Usage
22
+ from nuspace.system.kernel import DATA_DIR, NotASpace, find_space
19
23
 
20
24
 
21
25
  __all__ = [
22
- "STORE_ENV",
23
26
  "announce",
24
27
  "console",
25
28
  "hold",
26
29
  "opened",
27
30
  "path_argument",
31
+ "space_root",
32
+ "tilde",
33
+ "tmp_option",
28
34
  "version_of",
29
35
  ]
30
36
 
31
37
 
32
- #: The environment variable naming the space directory when no path is given.
33
- STORE_ENV = "NUSPACE_STORE"
34
-
35
38
  console = Console()
36
39
 
37
40
  _F = TypeVar("_F", bound=Callable[..., Any])
@@ -46,17 +49,37 @@ def version_of(package: str) -> str:
46
49
 
47
50
 
48
51
  def path_argument(fn: _F) -> _F:
49
- """The space directory a command takes, from :data:`STORE_ENV` when absent."""
50
- return click.argument("path", required=False, envvar=STORE_ENV)(fn)
52
+ """The space's root a command takes, positional and optional: see :func:`space_root`."""
53
+ return click.argument("path", required=False)(fn)
54
+
55
+
56
+ def tmp_option(fn: _F) -> _F:
57
+ """``--tmp``: a throwaway space instead of one found, for the commands that open one."""
58
+ return click.option(
59
+ "--tmp", is_flag=True, help="A throwaway space in a temp dir, gone at close."
60
+ )(fn)
61
+
51
62
 
63
+ def space_root(path: str | None, *, tmp: bool = False) -> str | None:
64
+ """The root of the space a command means, absolute. None with ``tmp``, a throwaway.
52
65
 
53
- def _where(path: str | None) -> str:
54
- """The space directory as a person reads it: absolute, home as ``~``."""
55
- if path is None:
56
- return "throwaway space, gone at close"
66
+ PATH when given, else the nearest space at or above the current
67
+ directory, else home.
68
+ """
69
+ if tmp:
70
+ if path is not None:
71
+ raise click.UsageError("Give PATH or --tmp, not both.")
72
+ return None
73
+ if path is not None:
74
+ return str(Path(path).expanduser().resolve())
75
+ return str(find_space(Path.cwd()) or Path.home())
76
+
77
+
78
+ def tilde(path: str | Path) -> str:
79
+ """``path`` as a person reads it: absolute, home as ``~``."""
57
80
  root = Path(path).expanduser().resolve()
58
81
  try:
59
- return str(Path("~") / root.relative_to(Path.home()))
82
+ return str(Path("~") / root.relative_to(Path.home().resolve()))
60
83
  except ValueError:
61
84
  return str(root)
62
85
 
@@ -68,7 +91,13 @@ def announce(path: str | None, *, headless: bool = False, plane: str | None = No
68
91
  the header leaves both to it.
69
92
  """
70
93
  console.print(
71
- header("sm", version=version_of("nuspace"), where=_where(path)),
94
+ header(
95
+ "sm",
96
+ version=version_of("nuspace"),
97
+ where="throwaway space, gone at close"
98
+ if path is None
99
+ else f"Path: {tilde(Path(path) / DATA_DIR)}",
100
+ ),
72
101
  highlight=False,
73
102
  soft_wrap=True,
74
103
  )
@@ -91,21 +120,15 @@ def opened(preset: Callable[..., nu.Nu], *args: object, **kwargs: object) -> nu.
91
120
  def hold(term: nu.Nu) -> None:
92
121
  """Run a space until interrupted, then let the brackets reap the fleet.
93
122
 
94
- Telemetry starts here (see :mod:`nuspace._telemetry`): on opening a
95
- space, never on import. The first run event and ``space_opened`` are
96
- queued, the space runs with a :class:`~nuspace.system.usage.Usage` bound
97
- so the host tracks what is made and removed in it, and what is still
98
- queued at the end gets a short grace to go.
123
+ What is still queued for telemetry (see :mod:`nuspace._telemetry`) gets
124
+ a short grace to go at the end. The space tells it what happens itself,
125
+ from its open on, the same as when opened from code.
99
126
 
100
127
  Compiled and evaluated without law validation: the space program is ours
101
128
  and tested, and validating its whole tree costs seconds per start.
102
129
  """
103
- _telemetry.first_run()
104
- _telemetry.track("space_opened")
105
130
  try:
106
- nu.eval_in_loop(
107
- nu.compile(nu.Provide(Usage, {"sink": _telemetry.track}, term)), nu.Context()
108
- )
131
+ nu.eval_in_loop(nu.compile(term), nu.Context())
109
132
  except KeyboardInterrupt:
110
133
  console.print("[dim]Stopped[/dim]")
111
134
  finally:
@@ -0,0 +1,60 @@
1
+ """``nuspace init``: make a directory a space, without opening it to run.
2
+
3
+ The store only, through :func:`~nuspace.host.init_space`: ``.nuspace/``
4
+ with its two SQLite files and the system planes, seeded as the first open
5
+ would. Without PATH it is the current directory, said so when that sits
6
+ inside another space. No venv, no packaging: the directory is not set up
7
+ for code of its own yet.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from pathlib import Path
13
+
14
+ import rich_click as click
15
+ from rich.text import Text
16
+
17
+ import nu
18
+ from nuspace._branding import BLUE, PURPLE
19
+ from nuspace.host import init_space
20
+ from nuspace.system.kernel import find_space, is_space
21
+
22
+ from .._meta import console, opened, path_argument, tilde
23
+
24
+
25
+ __all__ = ["init"]
26
+
27
+
28
+ @click.command(
29
+ short_help="Make a directory a space, without running it.",
30
+ help=(
31
+ "Make PATH a space, the current directory without one: its data in .nuspace/,"
32
+ " the system planes seeded, nothing run. No venv or packaging is set up yet."
33
+ ),
34
+ )
35
+ @path_argument
36
+ def init(path: str | None) -> None:
37
+ """Make the space, or say it already is one."""
38
+ root = Path(path or ".").expanduser().resolve()
39
+ if path is None:
40
+ _warn_if_nested(root)
41
+ there = is_space(root)
42
+ term = opened(init_space, str(root))
43
+ nu.eval_in_loop(nu.compile(term), nu.Context())
44
+ what = "Already a space" if there else "Initialized a space"
45
+ console.print(Text.assemble((what, f"bold {PURPLE}"), (" ", ""), (tilde(root), BLUE)))
46
+ again = "nuspace run" if path is None else f"nuspace run {path}"
47
+ console.print(Text.assemble(("open it with: ", "dim"), (again, f"bold {BLUE}")))
48
+
49
+
50
+ def _warn_if_nested(root: Path) -> None:
51
+ """Say so when ``root`` sits inside another space. The home space, everyone's fallback, does not count."""
52
+ outer = find_space(root.parent) if root != root.parent else None
53
+ if outer is not None and outer != Path.home().resolve():
54
+ console.print(
55
+ Text.assemble(
56
+ ("inside the space at ", "yellow"),
57
+ (tilde(outer), BLUE),
58
+ (", making a new one here", "yellow"),
59
+ )
60
+ )
@@ -2,23 +2,28 @@
2
2
 
3
3
  One flag, one :mod:`nuspace.host` preset: :func:`~nuspace.host.serve` by
4
4
  default, :func:`~nuspace.host.headless` with ``--headless``. Runs until
5
- interrupted, then the brackets reap the workers on the way out. PATH is the
6
- space directory, made if missing. With no path the space is a throwaway
7
- directory, gone at close.
5
+ interrupted, then the brackets reap the workers on the way out. Which space
6
+ is :func:`~nuspace._cli._meta.space_root`: PATH, made a space if it is not
7
+ one, else the nearest space up from here, else home. ``--tmp`` is a
8
+ throwaway one, gone at close. The header names the space's root either way.
8
9
 
9
10
  ``--with`` names a Plane an installed package adds, ``pkg.plane`` as
10
- ``nuspace verse`` lists them: it is made in the space once, through
11
- :func:`~nuspace.host.with_plane`, and the tab opens on it.
11
+ ``nuspace verse`` lists them, or a bare name for a nuverse Plane: it is made
12
+ in the space once, through :func:`~nuspace.host.with_plane`, and the tab
13
+ opens on it. The space remembers the plane it made, so the next run with
14
+ the same name reopens it. The header names the Plane: its id is only known
15
+ once the space is open.
12
16
  """
13
17
 
14
18
  from __future__ import annotations
15
19
 
16
20
  import rich_click as click
17
21
 
22
+ from nuspace.ext.registry import registered_name
18
23
  from nuspace.host import presets
19
24
 
20
25
  from .._installed import resolve
21
- from .._meta import STORE_ENV, announce, hold, opened, path_argument
26
+ from .._meta import announce, hold, opened, path_argument, space_root, tmp_option
22
27
 
23
28
 
24
29
  __all__ = ["run"]
@@ -27,17 +32,20 @@ __all__ = ["run"]
27
32
  @click.command(
28
33
  short_help="Open a space, in a browser or headless.",
29
34
  help=(
30
- "Open the space at PATH and serve it in a browser, or headless: kernel and"
31
- f" services, nothing drawn. PATH from ${STORE_ENV}, a throwaway space without one."
35
+ "Open the space at PATH, made if missing, and serve it in a browser, or headless:"
36
+ " kernel and services, nothing drawn. Without PATH, the nearest space up from"
37
+ " here, else the home space at ~."
32
38
  ),
33
39
  )
34
40
  @path_argument
41
+ @tmp_option
35
42
  @click.option(
36
43
  "--with",
37
44
  "plane",
38
45
  metavar="PLANE",
39
46
  help=(
40
- "Make this plane in the space once and open on it: pkg.plane, as nuspace verse lists them."
47
+ "Make this plane in the space once and open on it: pkg.plane, as nuspace verse"
48
+ " lists them, or a bare name for a nuverse plane (jobs is nuverse.jobs)."
41
49
  ),
42
50
  )
43
51
  @click.option(
@@ -47,13 +55,20 @@ __all__ = ["run"]
47
55
  @click.option("-p", "--port", default=8080, show_default=True, help="Port to bind.")
48
56
  @click.option("--no-browser", is_flag=True, help="Do not open a browser tab on the way up.")
49
57
  def run(
50
- path: str | None, plane: str | None, headless: bool, host: str, port: int, no_browser: bool
58
+ path: str | None,
59
+ tmp: bool,
60
+ plane: str | None,
61
+ headless: bool,
62
+ host: str,
63
+ port: int,
64
+ no_browser: bool,
51
65
  ) -> None:
52
66
  """Run until Ctrl+C."""
67
+ path = space_root(path, tmp=tmp)
53
68
  web = {"host": host, "port": port, "open_browser": not no_browser}
54
69
  if plane is not None:
55
70
  spec = resolve(plane)
56
- made = presets.plane_id(spec)
71
+ made = registered_name(spec)
57
72
  if headless:
58
73
  term = opened(presets.with_plane, spec, path, web=False)
59
74
  else:
@@ -6,15 +6,13 @@ you open. ``NUSPACE_TELEMETRY=0`` turns it off whatever the file says.
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
- from pathlib import Path
10
-
11
9
  import rich_click as click
12
10
  from rich.text import Text
13
11
 
14
12
  from nuspace import _telemetry
15
13
  from nuspace._branding import BLUE, PURPLE
16
14
 
17
- from .._meta import console
15
+ from .._meta import console, tilde
18
16
 
19
17
 
20
18
  __all__ = ["telemetry"]
@@ -52,19 +50,11 @@ def _switch(on: bool) -> str:
52
50
  """How to flip it from here."""
53
51
  if on:
54
52
  return "nuspace telemetry off"
55
- if _telemetry.env_override():
56
- return f"unset ${_telemetry.ENV}"
53
+ if (name := _telemetry.env_override()) is not None:
54
+ return f"unset ${name}"
57
55
  return "nuspace telemetry on"
58
56
 
59
57
 
60
- def _where(path: Path) -> str:
61
- """``path`` as a person reads it, home as ``~``."""
62
- try:
63
- return str(Path("~") / path.relative_to(Path.home()))
64
- except ValueError:
65
- return str(path)
66
-
67
-
68
58
  def _print(*lines: Text | str) -> None:
69
59
  for line in lines:
70
60
  console.print(line, highlight=False, soft_wrap=True)
@@ -77,7 +67,7 @@ def _print(*lines: Text | str) -> None:
77
67
  "Anonymous usage telemetry, set once for you across every space. It sends a few usage"
78
68
  " events, never anything from inside your spaces. Status with no command. nuspace is"
79
69
  " still young, so we'd appreciate you keeping it on."
80
- f" ${_telemetry.ENV}=0 turns it off whatever is set here."
70
+ f" ${_telemetry.ENV}=0, $DO_NOT_TRACK or $CI turns it off whatever is set here."
81
71
  ),
82
72
  )
83
73
  @click.pass_context
@@ -92,8 +82,8 @@ def status() -> None:
92
82
  """Print the state and why, what is sent and what never is, the id, the path, how to switch."""
93
83
  on = _telemetry.telemetry_enabled()
94
84
  _print(_state_line(on))
95
- if _telemetry.env_override():
96
- _print(f"[dim]off by ${_telemetry.ENV}, whatever the config says[/dim]")
85
+ if (name := _telemetry.env_override()) is not None:
86
+ _print(f"[dim]off by ${name}, whatever the config says[/dim]")
97
87
  if _telemetry.is_dev_install():
98
88
  _print("[dim]dev install: nothing is sent either way[/dim]")
99
89
  _print(
@@ -103,7 +93,7 @@ def status() -> None:
103
93
  "",
104
94
  _heading("Your settings"),
105
95
  _row("id", _telemetry.distinct_id(), BLUE),
106
- _row("config", _where(_telemetry.config_path()), BLUE),
96
+ _row("config", tilde(_telemetry.config_path()), BLUE),
107
97
  _row("turn " + ("off" if on else "on"), _switch(on), BLUE),
108
98
  "",
109
99
  f"[dim]{_WHY}[/dim]",
@@ -115,8 +105,8 @@ def on() -> None:
115
105
  """Set the flag on."""
116
106
  _telemetry.set_telemetry(True)
117
107
  _print(_state_line(True))
118
- if _telemetry.env_override():
119
- _print(f"[dim]still off while ${_telemetry.ENV} says so[/dim]")
108
+ if (name := _telemetry.env_override()) is not None:
109
+ _print(f"[dim]still off while ${name} says so[/dim]")
120
110
  else:
121
111
  _print("[dim]Thank you, it helps a lot while nuspace is young.[/dim]")
122
112