nuspace 0.2.1__tar.gz → 0.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.
- {nuspace-0.2.1 → nuspace-0.3.0}/.github/workflows/publish-nuspace-ui.yml +5 -5
- {nuspace-0.2.1 → nuspace-0.3.0}/.github/workflows/publish-nuspace.yml +2 -2
- {nuspace-0.2.1 → nuspace-0.3.0}/CHANGELOG.md +15 -2
- {nuspace-0.2.1 → nuspace-0.3.0}/Makefile +1 -1
- {nuspace-0.2.1 → nuspace-0.3.0}/PKG-INFO +4 -4
- nuspace-0.3.0/examples/headless.py +277 -0
- nuspace-0.3.0/examples/web.py +141 -0
- {nuspace-0.2.1 → nuspace-0.3.0}/pyproject.toml +10 -13
- nuspace-0.3.0/src/nuspace/__init__.py +82 -0
- nuspace-0.3.0/src/nuspace/cli/__init__.py +11 -0
- nuspace-0.3.0/src/nuspace/cli/__main__.py +11 -0
- nuspace-0.3.0/src/nuspace/cli/cell.py +175 -0
- nuspace-0.3.0/src/nuspace/cli/main.py +71 -0
- nuspace-0.3.0/src/nuspace/cli/plane.py +175 -0
- nuspace-0.3.0/src/nuspace/cli/space.py +212 -0
- nuspace-0.3.0/src/nuspace/cli/utils.py +97 -0
- nuspace-0.3.0/src/nuspace/drivers/__init__.py +25 -0
- nuspace-0.3.0/src/nuspace/drivers/headless.py +81 -0
- nuspace-0.3.0/src/nuspace/drivers/web.py +234 -0
- nuspace-0.3.0/src/nuspace/exec/__init__.py +51 -0
- nuspace-0.3.0/src/nuspace/exec/cell.py +300 -0
- nuspace-0.3.0/src/nuspace/exec/plane.py +240 -0
- nuspace-0.3.0/src/nuspace/exec/space.py +75 -0
- nuspace-0.3.0/src/nuspace/exec/utils.py +78 -0
- nuspace-0.3.0/src/nuspace/ops/__init__.py +158 -0
- nuspace-0.3.0/src/nuspace/ops/cell.py +494 -0
- nuspace-0.3.0/src/nuspace/ops/chat.py +251 -0
- nuspace-0.3.0/src/nuspace/ops/groups.py +868 -0
- nuspace-0.3.0/src/nuspace/ops/plane.py +349 -0
- nuspace-0.3.0/src/nuspace/ops/space.py +102 -0
- nuspace-0.3.0/src/nuspace/ops/templates.py +162 -0
- nuspace-0.3.0/src/nuspace/ops/utils.py +103 -0
- nuspace-0.3.0/src/nuspace/presets.py +134 -0
- nuspace-0.3.0/src/nuspace/shapes.py +256 -0
- nuspace-0.3.0/src/nuspace/space.py +354 -0
- nuspace-0.3.0/src/nuspace/web/__init__.py +61 -0
- nuspace-0.3.0/src/nuspace/web/route.py +112 -0
- nuspace-0.3.0/src/nuspace/web/session.py +153 -0
- nuspace-0.3.0/src/nuspace/web/shell.py +121 -0
- nuspace-0.3.0/src/nuspace/web/sidebar/__init__.py +44 -0
- nuspace-0.3.0/src/nuspace/web/sidebar/driver.py +232 -0
- nuspace-0.3.0/src/nuspace/web/sidebar/interactions.py +123 -0
- nuspace-0.3.0/src/nuspace/web/sidebar/ref.py +60 -0
- nuspace-0.3.0/src/nuspace/web/utils.py +243 -0
- nuspace-0.3.0/src/nuspace/web/viewer/__init__.py +57 -0
- nuspace-0.3.0/src/nuspace/web/viewer/driver.py +322 -0
- nuspace-0.3.0/src/nuspace/web/viewer/interactions.py +138 -0
- nuspace-0.3.0/src/nuspace/web/viewer/ref.py +179 -0
- nuspace-0.3.0/tests/test_chat.py +252 -0
- nuspace-0.3.0/tests/test_groups.py +153 -0
- {nuspace-0.2.1/src/nuspace/web/ui → nuspace-0.3.0/ts}/package-lock.json +7 -6
- {nuspace-0.2.1/src/nuspace/web/ui → nuspace-0.3.0/ts}/package.json +2 -2
- {nuspace-0.2.1/src/nuspace/web/ui → nuspace-0.3.0/ts}/pyproject.toml +1 -1
- nuspace-0.3.0/ts/src/App.tsx +52 -0
- {nuspace-0.2.1/src/nuspace/web/ui → nuspace-0.3.0/ts}/src/app/connect.ts +38 -8
- nuspace-0.3.0/ts/src/app/ids.ts +26 -0
- nuspace-0.3.0/ts/src/app/router.ts +81 -0
- {nuspace-0.2.1/src/nuspace/web/ui → nuspace-0.3.0/ts}/src/app/surfaces.ts +12 -13
- nuspace-0.3.0/ts/src/components/index.ts +17 -0
- {nuspace-0.2.1/src/nuspace/web/ui → nuspace-0.3.0/ts}/src/design/document.ts +5 -5
- {nuspace-0.2.1/src/nuspace/web/ui → nuspace-0.3.0/ts}/src/design/index.ts +9 -18
- {nuspace-0.2.1/src/nuspace/web/ui → nuspace-0.3.0/ts}/src/design/rail.ts +25 -10
- nuspace-0.3.0/ts/src/design/shell.ts +47 -0
- {nuspace-0.2.1/src/nuspace/web/ui → nuspace-0.3.0/ts}/src/index.css +0 -1
- {nuspace-0.2.1/src/nuspace/web/ui → nuspace-0.3.0/ts}/src/refs/register.ts +14 -16
- nuspace-0.3.0/ts/src/refs/route.ts +48 -0
- {nuspace-0.2.1/src/nuspace/web/ui/src/refs/pages → nuspace-0.3.0/ts/src/refs/sidebar}/Rail.tsx +170 -133
- nuspace-0.3.0/ts/src/refs/sidebar/footer.tsx +61 -0
- nuspace-0.3.0/ts/src/refs/sidebar/header.tsx +15 -0
- nuspace-0.3.0/ts/src/refs/sidebar/ops.ts +40 -0
- {nuspace-0.2.1/src/nuspace/web/ui/src/components/rail → nuspace-0.3.0/ts/src/refs/sidebar}/row-input.tsx +1 -1
- {nuspace-0.2.1/src/nuspace/web/ui/src/components/rail → nuspace-0.3.0/ts/src/refs/sidebar}/row.tsx +16 -3
- nuspace-0.3.0/ts/src/refs/sidebar/sidebar.tsx +60 -0
- nuspace-0.3.0/ts/src/refs/sidebar/state.ts +67 -0
- nuspace-0.3.0/ts/src/refs/sidebar/types.ts +127 -0
- {nuspace-0.2.1/src/nuspace/web/ui/src/refs/pages → nuspace-0.3.0/ts/src/refs/viewer}/Canvas.tsx +73 -55
- {nuspace-0.2.1/src/nuspace/web/ui/src/refs/pages → nuspace-0.3.0/ts/src/refs/viewer}/Program.tsx +2 -2
- {nuspace-0.2.1/src/nuspace/web/ui/src/refs/pages → nuspace-0.3.0/ts/src/refs/viewer}/blocks.ts +10 -16
- {nuspace-0.2.1/src/nuspace/web/ui/src/refs/pages → nuspace-0.3.0/ts/src/refs/viewer}/ops.ts +17 -21
- {nuspace-0.2.1/src/nuspace/web/ui/src/refs/pages → nuspace-0.3.0/ts/src/refs/viewer}/state.ts +19 -35
- nuspace-0.3.0/ts/src/refs/viewer/types.ts +100 -0
- nuspace-0.3.0/ts/src/refs/viewer/viewer.tsx +91 -0
- {nuspace-0.2.1/src/nuspace/web/ui → nuspace-0.3.0/ts}/vitest.config.ts +1 -1
- nuspace-0.2.1/examples/apps.py +0 -113
- nuspace-0.2.1/examples/pages.py +0 -189
- nuspace-0.2.1/examples/web.py +0 -69
- nuspace-0.2.1/src/nuspace/__init__.py +0 -40
- nuspace-0.2.1/src/nuspace/_root.py +0 -34
- nuspace-0.2.1/src/nuspace/agent/__init__.py +0 -47
- nuspace-0.2.1/src/nuspace/agent/model.py +0 -134
- nuspace-0.2.1/src/nuspace/agent/ops.py +0 -223
- nuspace-0.2.1/src/nuspace/agent/prompt.py +0 -248
- nuspace-0.2.1/src/nuspace/agent/runner.py +0 -219
- nuspace-0.2.1/src/nuspace/agent/shapes.py +0 -84
- nuspace-0.2.1/src/nuspace/apps/__init__.py +0 -75
- nuspace-0.2.1/src/nuspace/apps/ops.py +0 -277
- nuspace-0.2.1/src/nuspace/apps/runner.py +0 -257
- nuspace-0.2.1/src/nuspace/apps/shapes.py +0 -51
- nuspace-0.2.1/src/nuspace/chat/__init__.py +0 -21
- nuspace-0.2.1/src/nuspace/chat/ops.py +0 -145
- nuspace-0.2.1/src/nuspace/chat/shapes.py +0 -56
- nuspace-0.2.1/src/nuspace/cli/__init__.py +0 -6
- nuspace-0.2.1/src/nuspace/cli/__main__.py +0 -7
- nuspace-0.2.1/src/nuspace/cli/_meta.py +0 -12
- nuspace-0.2.1/src/nuspace/cli/main.py +0 -35
- nuspace-0.2.1/src/nuspace/core/__init__.py +0 -18
- nuspace-0.2.1/src/nuspace/core/host.py +0 -255
- nuspace-0.2.1/src/nuspace/core/ids.py +0 -30
- nuspace-0.2.1/src/nuspace/core/session.py +0 -127
- nuspace-0.2.1/src/nuspace/core/shapes.py +0 -94
- nuspace-0.2.1/src/nuspace/core/tpl.py +0 -242
- nuspace-0.2.1/src/nuspace/core/ui.py +0 -114
- nuspace-0.2.1/src/nuspace/pages/__init__.py +0 -101
- nuspace-0.2.1/src/nuspace/pages/ops.py +0 -510
- nuspace-0.2.1/src/nuspace/pages/runner.py +0 -474
- nuspace-0.2.1/src/nuspace/pages/shapes.py +0 -112
- nuspace-0.2.1/src/nuspace/web/__init__.py +0 -80
- nuspace-0.2.1/src/nuspace/web/apps/__init__.py +0 -18
- nuspace-0.2.1/src/nuspace/web/apps/driver.py +0 -176
- nuspace-0.2.1/src/nuspace/web/apps/ref.py +0 -114
- nuspace-0.2.1/src/nuspace/web/arms.py +0 -103
- nuspace-0.2.1/src/nuspace/web/chat/__init__.py +0 -19
- nuspace-0.2.1/src/nuspace/web/chat/driver.py +0 -116
- nuspace-0.2.1/src/nuspace/web/chat/ref.py +0 -101
- nuspace-0.2.1/src/nuspace/web/lens/__init__.py +0 -22
- nuspace-0.2.1/src/nuspace/web/lens/driver.py +0 -98
- nuspace-0.2.1/src/nuspace/web/lens/ref.py +0 -74
- nuspace-0.2.1/src/nuspace/web/lens/reflect.py +0 -371
- nuspace-0.2.1/src/nuspace/web/nav.py +0 -75
- nuspace-0.2.1/src/nuspace/web/pages/__init__.py +0 -21
- nuspace-0.2.1/src/nuspace/web/pages/driver.py +0 -286
- nuspace-0.2.1/src/nuspace/web/pages/ref.py +0 -171
- nuspace-0.2.1/src/nuspace/web/pages/session.py +0 -171
- nuspace-0.2.1/src/nuspace/web/serve/__init__.py +0 -33
- nuspace-0.2.1/src/nuspace/web/serve/app.py +0 -148
- nuspace-0.2.1/src/nuspace/web/serve/fabric.py +0 -186
- nuspace-0.2.1/src/nuspace/web/serve/session.py +0 -184
- nuspace-0.2.1/src/nuspace/web/serve/shell.py +0 -119
- nuspace-0.2.1/src/nuspace/web/space.py +0 -234
- nuspace-0.2.1/src/nuspace/web/ui/src/App.tsx +0 -151
- nuspace-0.2.1/src/nuspace/web/ui/src/app/router.ts +0 -123
- nuspace-0.2.1/src/nuspace/web/ui/src/components/index.ts +0 -24
- nuspace-0.2.1/src/nuspace/web/ui/src/components/page-header.tsx +0 -180
- nuspace-0.2.1/src/nuspace/web/ui/src/components/rail/header.tsx +0 -43
- nuspace-0.2.1/src/nuspace/web/ui/src/components/rail/index.ts +0 -24
- nuspace-0.2.1/src/nuspace/web/ui/src/design/apps.ts +0 -89
- nuspace-0.2.1/src/nuspace/web/ui/src/design/chat.ts +0 -120
- nuspace-0.2.1/src/nuspace/web/ui/src/design/lens.css +0 -107
- nuspace-0.2.1/src/nuspace/web/ui/src/design/lens.ts +0 -259
- nuspace-0.2.1/src/nuspace/web/ui/src/design/shell.ts +0 -57
- nuspace-0.2.1/src/nuspace/web/ui/src/refs/apps/Rail.tsx +0 -316
- nuspace-0.2.1/src/nuspace/web/ui/src/refs/apps/Source.tsx +0 -142
- nuspace-0.2.1/src/nuspace/web/ui/src/refs/apps/apps.tsx +0 -226
- nuspace-0.2.1/src/nuspace/web/ui/src/refs/apps/ops.ts +0 -34
- nuspace-0.2.1/src/nuspace/web/ui/src/refs/apps/state.ts +0 -122
- nuspace-0.2.1/src/nuspace/web/ui/src/refs/apps/types.ts +0 -69
- nuspace-0.2.1/src/nuspace/web/ui/src/refs/chat/chat.tsx +0 -299
- nuspace-0.2.1/src/nuspace/web/ui/src/refs/chat/ops.ts +0 -25
- nuspace-0.2.1/src/nuspace/web/ui/src/refs/chat/types.ts +0 -105
- nuspace-0.2.1/src/nuspace/web/ui/src/refs/lens/lens.tsx +0 -623
- nuspace-0.2.1/src/nuspace/web/ui/src/refs/lens/types.ts +0 -90
- nuspace-0.2.1/src/nuspace/web/ui/src/refs/nav/nav.ts +0 -75
- nuspace-0.2.1/src/nuspace/web/ui/src/refs/pages/pages.tsx +0 -159
- nuspace-0.2.1/src/nuspace/web/ui/src/refs/pages/types.ts +0 -211
- nuspace-0.2.1/src/nuspace/web/wire.py +0 -51
- nuspace-0.2.1/tests/nuspace/apps/__init__.py +0 -0
- nuspace-0.2.1/tests/nuspace/apps/conftest.py +0 -147
- nuspace-0.2.1/tests/nuspace/apps/test_changed_key.py +0 -85
- nuspace-0.2.1/tests/nuspace/apps/test_lifecycle.py +0 -210
- nuspace-0.2.1/tests/nuspace/apps/test_ops.py +0 -184
- nuspace-0.2.1/tests/nuspace/pages/__init__.py +0 -0
- nuspace-0.2.1/tests/nuspace/pages/conftest.py +0 -200
- nuspace-0.2.1/tests/nuspace/pages/test_invariant.py +0 -97
- nuspace-0.2.1/tests/nuspace/pages/test_lifecycle.py +0 -228
- nuspace-0.2.1/tests/nuspace/pages/test_ops.py +0 -435
- nuspace-0.2.1/tests/nuspace/test_imports.py +0 -94
- nuspace-0.2.1/tests/nuspace/web/__init__.py +0 -0
- nuspace-0.2.1/tests/nuspace/web/test_apps_driver.py +0 -321
- nuspace-0.2.1/tests/nuspace/web/test_lens_driver.py +0 -336
- nuspace-0.2.1/tests/nuspace/web/test_lens_reflect.py +0 -164
- nuspace-0.2.1/tests/nuspace/web/test_pages_driver.py +0 -341
- nuspace-0.2.1/tests/nuspace/web/test_serve.py +0 -114
- nuspace-0.2.1/tests/nuspace/web/test_supervised.py +0 -424
- {nuspace-0.2.1 → nuspace-0.3.0}/.gitignore +0 -0
- {nuspace-0.2.1 → nuspace-0.3.0}/.pre-commit-config.yaml +0 -0
- {nuspace-0.2.1 → nuspace-0.3.0}/LICENSE.md +0 -0
- {nuspace-0.2.1 → nuspace-0.3.0}/README.md +0 -0
- {nuspace-0.2.1/src/nuspace/web/ui → nuspace-0.3.0/ts}/.gitignore +0 -0
- {nuspace-0.2.1/src/nuspace/web/ui → nuspace-0.3.0/ts}/biome.json +0 -0
- {nuspace-0.2.1/src/nuspace/web/ui → nuspace-0.3.0/ts}/index.html +0 -0
- {nuspace-0.2.1/src/nuspace/web/ui → nuspace-0.3.0/ts}/src/app/local.ts +0 -0
- {nuspace-0.2.1/src/nuspace/web/ui → nuspace-0.3.0/ts}/src/app/theme.ts +0 -0
- {nuspace-0.2.1/src/nuspace/web/ui → nuspace-0.3.0/ts}/src/app/wire.ts +0 -0
- {nuspace-0.2.1/src/nuspace/web/ui → nuspace-0.3.0/ts}/src/components/section-status-dot.tsx +0 -0
- {nuspace-0.2.1/src/nuspace/web/ui → nuspace-0.3.0/ts}/src/design/section-status.ts +0 -0
- {nuspace-0.2.1/src/nuspace/web/ui → nuspace-0.3.0/ts}/src/design/tokens.css +0 -0
- {nuspace-0.2.1/src/nuspace/web/ui → nuspace-0.3.0/ts}/src/main.tsx +0 -0
- {nuspace-0.2.1/src/nuspace/web/ui/src/components/rail → nuspace-0.3.0/ts/src/refs/sidebar}/roving-focus.ts +0 -0
- {nuspace-0.2.1/src/nuspace/web/ui/src/refs/pages → nuspace-0.3.0/ts/src/refs/viewer}/Code.tsx +0 -0
- {nuspace-0.2.1/src/nuspace/web/ui/src/refs/pages → nuspace-0.3.0/ts/src/refs/viewer}/ProseRef.test.tsx +0 -0
- {nuspace-0.2.1/src/nuspace/web/ui/src/refs/pages → nuspace-0.3.0/ts/src/refs/viewer}/ProseRef.tsx +0 -0
- {nuspace-0.2.1/src/nuspace/web/ui/src/refs/pages → nuspace-0.3.0/ts/src/refs/viewer}/Slash.tsx +0 -0
- {nuspace-0.2.1/src/nuspace/web/ui/src/refs/pages → nuspace-0.3.0/ts/src/refs/viewer}/monaco-impl.ts +0 -0
- {nuspace-0.2.1/src/nuspace/web/ui/src/refs/pages → nuspace-0.3.0/ts/src/refs/viewer}/monaco.ts +0 -0
- {nuspace-0.2.1/src/nuspace/web/ui/src/refs/pages → nuspace-0.3.0/ts/src/refs/viewer}/prose.ts +0 -0
- {nuspace-0.2.1/src/nuspace/web/ui → nuspace-0.3.0/ts}/src/vite-env.d.ts +0 -0
- {nuspace-0.2.1/src/nuspace/web/ui → nuspace-0.3.0/ts}/tsconfig.app.json +0 -0
- {nuspace-0.2.1/src/nuspace/web/ui → nuspace-0.3.0/ts}/tsconfig.json +0 -0
- {nuspace-0.2.1/src/nuspace/web/ui → nuspace-0.3.0/ts}/tsconfig.node.json +0 -0
- {nuspace-0.2.1/src/nuspace/web/ui → nuspace-0.3.0/ts}/vite.config.ts +0 -0
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
# Environment name: (leave blank)
|
|
21
21
|
#
|
|
22
22
|
# Release flow:
|
|
23
|
-
# 1. Bump `version` in both `
|
|
24
|
-
# `
|
|
23
|
+
# 1. Bump `version` in both `ts/pyproject.toml` and
|
|
24
|
+
# `ts/package.json` (keep them equal).
|
|
25
25
|
# 2. Commit and push to main.
|
|
26
26
|
# 3. `git tag nuspace-ui@X.Y.Z && git push origin nuspace-ui@X.Y.Z`.
|
|
27
27
|
name: Publish nuspace-ui to PyPI
|
|
@@ -40,7 +40,7 @@ concurrency:
|
|
|
40
40
|
cancel-in-progress: false
|
|
41
41
|
|
|
42
42
|
env:
|
|
43
|
-
UI_DIR:
|
|
43
|
+
UI_DIR: ts
|
|
44
44
|
|
|
45
45
|
jobs:
|
|
46
46
|
publish:
|
|
@@ -53,7 +53,7 @@ jobs:
|
|
|
53
53
|
with:
|
|
54
54
|
node-version: 24
|
|
55
55
|
cache: npm
|
|
56
|
-
cache-dependency-path:
|
|
56
|
+
cache-dependency-path: ts/package-lock.json
|
|
57
57
|
|
|
58
58
|
- uses: actions/setup-python@v5
|
|
59
59
|
with:
|
|
@@ -115,5 +115,5 @@ jobs:
|
|
|
115
115
|
if: startsWith(github.ref, 'refs/tags/nuspace-ui@')
|
|
116
116
|
uses: pypa/gh-action-pypi-publish@release/v1
|
|
117
117
|
with:
|
|
118
|
-
packages-dir:
|
|
118
|
+
packages-dir: ts/wheel-dist
|
|
119
119
|
skip-existing: true
|
|
@@ -15,13 +15,13 @@
|
|
|
15
15
|
#
|
|
16
16
|
# Two distributions ship from this repo, on independent tags:
|
|
17
17
|
# . -> nuspace (the runtime + the `nuspace` command)
|
|
18
|
-
#
|
|
18
|
+
# ts -> nuspace-ui (the compiled vite bundle)
|
|
19
19
|
#
|
|
20
20
|
# They version independently. `nuspace` hard-depends on `nuspace-ui>=X.Y.Z`, so
|
|
21
21
|
# a python release can require a newer bundle, but a bundle-only fix ships on
|
|
22
22
|
# its own tag without republishing this wheel.
|
|
23
23
|
#
|
|
24
|
-
# The wheel deliberately excludes `
|
|
24
|
+
# The wheel deliberately excludes `ts/**` (see the hatch config
|
|
25
25
|
# in pyproject.toml). The compiled assets live in the nuspace-ui wheel and are
|
|
26
26
|
# found at runtime by `import nuspace_ui` -- see `_bundled_static()` in
|
|
27
27
|
# src/nuspace/web/serve/app.py. So this build needs no node toolchain, even
|
|
@@ -2,13 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
## Packages shipped from this repo
|
|
4
4
|
|
|
5
|
-
- **nuspace** (the runtime, the `nuspace` command) — 0.
|
|
6
|
-
- **nuspace-ui** (the compiled web bundle) — 0.
|
|
5
|
+
- **nuspace** (the runtime, the `nuspace` command) — 0.3.0
|
|
6
|
+
- **nuspace-ui** (the compiled web bundle) — 0.3.0
|
|
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.3.0 — 2026-09-21
|
|
13
|
+
|
|
14
|
+
- Make a chat, ask it for something, and watch it work the space
|
|
15
|
+
- See how a job is set to run, and edit what it does in a real editor
|
|
16
|
+
- Keep pages and jobs in separate sidebar sections, and make either in one click
|
|
17
|
+
- Collapse the whole interface to one sidebar and one viewer over any plane
|
|
18
|
+
- A plane now says whether it draws and whether you can edit it
|
|
19
|
+
- Open a space in the browser and write prose and programs into a plane's cells
|
|
20
|
+
- Boot a browser tab with the chrome it holds before anything is drawn
|
|
21
|
+
- Let a cell draw on a browser connection, and let a driver say which planes are up
|
|
22
|
+
- Run a space of planes and cells, each cell with its own restart policy
|
|
23
|
+
- Clear the v0.2 source out of the way for the v0.3 rewrite
|
|
24
|
+
|
|
12
25
|
## 0.2.1 — 2026-09-17
|
|
13
26
|
|
|
14
27
|
- Open a page on quiet air and its own name instead of decoration
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: nuspace
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Nu runtime built on Nu. A self-hosted programmable space that runs Nu apps in a tree of containers and applications.
|
|
5
5
|
Project-URL: Repository, https://github.com/nustackdev/nuspace
|
|
6
6
|
Author-email: Gor Arakelyan <gorarkln@gmail.com>
|
|
@@ -17,9 +17,9 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
17
17
|
Requires-Python: >=3.10
|
|
18
18
|
Requires-Dist: click>=8.0
|
|
19
19
|
Requires-Dist: nuagent>=0.1.3
|
|
20
|
-
Requires-Dist: nucore>=0.5.
|
|
21
|
-
Requires-Dist: nuspace-ui>=0.
|
|
22
|
-
Requires-Dist: nustd[all]>=0.5.
|
|
20
|
+
Requires-Dist: nucore>=0.5.2
|
|
21
|
+
Requires-Dist: nuspace-ui>=0.3.0
|
|
22
|
+
Requires-Dist: nustd[all]>=0.5.2
|
|
23
23
|
Requires-Dist: rich-click>=1.6
|
|
24
24
|
Requires-Dist: rich>=10.7
|
|
25
25
|
Requires-Dist: typing-extensions>=4.7.0
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
"""A Space of two Planes and three Cells, run headless, start to finish in twelve seconds.
|
|
2
|
+
|
|
3
|
+
Everything phase 1 claims, shown rather than described:
|
|
4
|
+
|
|
5
|
+
- a Plane whose ``exec_mode`` is ``async`` holds two Cells in one worker;
|
|
6
|
+
- a Plane whose ``exec_mode`` is ``mp`` gives its one Cell a process to itself;
|
|
7
|
+
- nothing a person wrote runs in the main process, which the pids prove;
|
|
8
|
+
- ``restart: always`` brings a program back after it returns, ``restart: no``
|
|
9
|
+
leaves it stopped;
|
|
10
|
+
- ``reload`` restarts the Cell whose program changed, and only that one.
|
|
11
|
+
|
|
12
|
+
Every Cell runs the same tiny program: count this run, stamp the process it ran
|
|
13
|
+
in, say which version of itself it is. Three numbers, and between them they
|
|
14
|
+
answer every question above.
|
|
15
|
+
|
|
16
|
+
Run it::
|
|
17
|
+
|
|
18
|
+
python examples/headless.py
|
|
19
|
+
|
|
20
|
+
The Space is left on disk, so ``nuspace -s <path> ls`` reads what it did.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import asyncio
|
|
26
|
+
import logging
|
|
27
|
+
import os
|
|
28
|
+
import tempfile
|
|
29
|
+
|
|
30
|
+
import nu
|
|
31
|
+
import nustd.kv
|
|
32
|
+
from nuspace import (
|
|
33
|
+
EXEC_ASYNC,
|
|
34
|
+
EXEC_MP,
|
|
35
|
+
RESTART_ALWAYS,
|
|
36
|
+
RESTART_NO,
|
|
37
|
+
TRIGGER_BOOT,
|
|
38
|
+
Space,
|
|
39
|
+
open_space,
|
|
40
|
+
ops,
|
|
41
|
+
store,
|
|
42
|
+
)
|
|
43
|
+
from nuspace.drivers import run_space
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# Closing the Space drops two sockets under workers that are still holding them,
|
|
47
|
+
# and the transport logs every one of those as an error. Expected, and the only
|
|
48
|
+
# thing this demo has to say is what it printed itself. At module scope so a
|
|
49
|
+
# spawned worker, which imports this file, is quiet too.
|
|
50
|
+
logging.getLogger("invisibles").setLevel(logging.CRITICAL)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
LOOP, BEAT, HOLD = "loop", "beat", "hold"
|
|
54
|
+
SOLO, WORK = "solo", "work"
|
|
55
|
+
|
|
56
|
+
#: How long the Space runs before the first report. Long enough for a Cell that
|
|
57
|
+
#: keeps coming back to have come back a good few times.
|
|
58
|
+
BOOT_SECONDS = 5.0
|
|
59
|
+
|
|
60
|
+
#: How long an edit is given to land before the report after it.
|
|
61
|
+
EDIT_SECONDS = 3.0
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def source(version: int) -> str:
|
|
65
|
+
"""The program every Cell holds. Counts the run, stamps the process, ends.
|
|
66
|
+
|
|
67
|
+
Ending is the point: a program that returns is what ``restart`` is about,
|
|
68
|
+
so the same three lines demonstrate both settings of it.
|
|
69
|
+
"""
|
|
70
|
+
return f'''import os
|
|
71
|
+
|
|
72
|
+
import nu
|
|
73
|
+
import nustd.kv
|
|
74
|
+
from nuspace import Space
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def out(plane, cell):
|
|
78
|
+
"""Count this run, stamp the process it ran in, and end."""
|
|
79
|
+
state = Space.planes[plane].cells[cell].state
|
|
80
|
+
runs = nu.Int(nu.ToInt(state.get_item("runs", nu.Int(0))))
|
|
81
|
+
return nustd.kv.auto_flow_atomic(
|
|
82
|
+
state.set_item("runs", runs + nu.Int(1))
|
|
83
|
+
>> state.set_item("pid", nu.Int(os.getpid()))
|
|
84
|
+
>> state.set_item("version", nu.Int({version})),
|
|
85
|
+
scope=Space,
|
|
86
|
+
)
|
|
87
|
+
'''
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# --- what the Space is -------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def seed():
|
|
94
|
+
"""Two Planes and three Cells, each Cell one commit."""
|
|
95
|
+
return (
|
|
96
|
+
ops.add_plane(plane_id=LOOP, exec_mode=EXEC_ASYNC, trigger=TRIGGER_BOOT)
|
|
97
|
+
>> ops.add_cell(LOOP, source(1), cell_id=BEAT, restart=RESTART_ALWAYS)
|
|
98
|
+
>> ops.add_cell(LOOP, source(1), cell_id=HOLD, restart=RESTART_NO)
|
|
99
|
+
>> ops.add_plane(plane_id=SOLO, exec_mode=EXEC_MP, trigger=TRIGGER_BOOT)
|
|
100
|
+
>> ops.add_cell(SOLO, source(1), cell_id=WORK, restart=RESTART_ALWAYS)
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
# --- what it says while it runs ----------------------------------------------
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def say(text):
|
|
108
|
+
return nu.Print(nu.STDOUT, nu.Str(text), flush=True)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def kept(plane, cell, key):
|
|
112
|
+
"""One number a Cell's program kept, zero where it has not written it yet."""
|
|
113
|
+
state = Space.planes[plane].cells[cell].state
|
|
114
|
+
return nu.Int(nu.ToInt(state.get_item(key, nu.Int(0))))
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def show(plane, cell):
|
|
118
|
+
return nu.Print(
|
|
119
|
+
nu.STDOUT,
|
|
120
|
+
nu.Str(f" {plane}/{cell:<6}"),
|
|
121
|
+
nu.Str(" runs "),
|
|
122
|
+
nu.ToStr(kept(plane, cell, "runs")),
|
|
123
|
+
nu.Str(" version "),
|
|
124
|
+
nu.ToStr(kept(plane, cell, "version")),
|
|
125
|
+
nu.Str(" pid "),
|
|
126
|
+
nu.ToStr(kept(plane, cell, "pid")),
|
|
127
|
+
sep="",
|
|
128
|
+
flush=True,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def report(title):
|
|
133
|
+
return say(title) >> show(LOOP, BEAT) >> show(LOOP, HOLD) >> show(SOLO, WORK)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def timeline():
|
|
137
|
+
"""The whole demo as one branch: watch, edit, watch, edit, watch.
|
|
138
|
+
|
|
139
|
+
It runs beside the runtime in the main process. When it ends, the Race
|
|
140
|
+
around it ends, which is what closes the Space.
|
|
141
|
+
"""
|
|
142
|
+
return (
|
|
143
|
+
nu.DelayedDo(
|
|
144
|
+
nu.Float(BOOT_SECONDS),
|
|
145
|
+
report(f"\nact 1 {BOOT_SECONDS:.0f} seconds in, nobody has touched anything"),
|
|
146
|
+
)
|
|
147
|
+
>> say("\nact 2 editing loop/beat to version 2")
|
|
148
|
+
>> ops.set_prog(LOOP, BEAT, source(2))
|
|
149
|
+
>> nu.DelayedDo(
|
|
150
|
+
nu.Float(EDIT_SECONDS),
|
|
151
|
+
report(" beat is on the new program, hold has not moved"),
|
|
152
|
+
)
|
|
153
|
+
>> say("\nact 3 editing loop/hold, which has been stopped since its one run")
|
|
154
|
+
>> ops.set_prog(LOOP, HOLD, source(2))
|
|
155
|
+
>> nu.DelayedDo(
|
|
156
|
+
nu.Float(EDIT_SECONDS),
|
|
157
|
+
report(" the edit started it again, for exactly one more run"),
|
|
158
|
+
)
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def body():
|
|
163
|
+
"""Seed the Space, then run it against the timeline until the timeline ends."""
|
|
164
|
+
return nustd.kv.auto_flow_atomic(seed(), scope=Space) >> nu.Race(
|
|
165
|
+
run_space(),
|
|
166
|
+
nustd.kv.auto_flow_atomic(timeline(), scope=Space),
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
# --- what it says afterwards -------------------------------------------------
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def read_back(path):
|
|
174
|
+
"""What the three Cells kept, read out of the closed store."""
|
|
175
|
+
value, _ = nu.run_in_loop(
|
|
176
|
+
nu.With(
|
|
177
|
+
store(path),
|
|
178
|
+
body=nustd.kv.Snapshot(
|
|
179
|
+
nu.Dict.of(
|
|
180
|
+
beat=ops.cell_state(LOOP, BEAT),
|
|
181
|
+
hold=ops.cell_state(LOOP, HOLD),
|
|
182
|
+
work=ops.cell_state(SOLO, WORK),
|
|
183
|
+
),
|
|
184
|
+
scope=Space,
|
|
185
|
+
),
|
|
186
|
+
),
|
|
187
|
+
nu.Context(),
|
|
188
|
+
)
|
|
189
|
+
return value
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def verdict(path, main_pid):
|
|
193
|
+
"""Every claim as a check against the final state, so it passes or it does not."""
|
|
194
|
+
state = read_back(path)
|
|
195
|
+
runs = {k: state[k].get("runs", 0) for k in state}
|
|
196
|
+
pids = {k: state[k].get("pid", 0) for k in state}
|
|
197
|
+
version = {k: state[k].get("version", 0) for k in state}
|
|
198
|
+
checks = [
|
|
199
|
+
(
|
|
200
|
+
"restart always",
|
|
201
|
+
runs["beat"] >= 5,
|
|
202
|
+
f"loop/beat returned and came back {runs['beat']} times",
|
|
203
|
+
),
|
|
204
|
+
(
|
|
205
|
+
"restart no",
|
|
206
|
+
runs["hold"] == 2,
|
|
207
|
+
f"loop/hold ran {runs['hold']} times, never once on its own account",
|
|
208
|
+
),
|
|
209
|
+
(
|
|
210
|
+
"reload",
|
|
211
|
+
version["beat"] == 2,
|
|
212
|
+
"loop/beat is running the program it was edited to",
|
|
213
|
+
),
|
|
214
|
+
(
|
|
215
|
+
"reload restarts a stopped Cell",
|
|
216
|
+
version["hold"] == 2 and runs["hold"] == 2,
|
|
217
|
+
"loop/hold was parked, and the edit is what started it again",
|
|
218
|
+
),
|
|
219
|
+
(
|
|
220
|
+
"siblings are left alone",
|
|
221
|
+
runs["hold"] == 2,
|
|
222
|
+
"editing loop/beat left loop/hold on its single boot run",
|
|
223
|
+
),
|
|
224
|
+
(
|
|
225
|
+
"nothing runs in main",
|
|
226
|
+
main_pid not in pids.values(),
|
|
227
|
+
f"main is {main_pid}, no Cell is",
|
|
228
|
+
),
|
|
229
|
+
(
|
|
230
|
+
"async is one process for the Plane",
|
|
231
|
+
pids["beat"] == pids["hold"],
|
|
232
|
+
f"loop/beat and loop/hold both ran in {pids['beat']}",
|
|
233
|
+
),
|
|
234
|
+
(
|
|
235
|
+
"mp is one process for the Cell",
|
|
236
|
+
pids["work"] not in (main_pid, pids["beat"]),
|
|
237
|
+
f"solo/work ran in {pids['work']}, on its own",
|
|
238
|
+
),
|
|
239
|
+
]
|
|
240
|
+
print("\nverdict, read back from the closed store\n")
|
|
241
|
+
for name, passed, detail in checks:
|
|
242
|
+
print(f" {'PASS' if passed else 'FAIL'} {name:<36}{detail}")
|
|
243
|
+
failed = [name for name, passed, _ in checks if not passed]
|
|
244
|
+
print()
|
|
245
|
+
if failed:
|
|
246
|
+
print(f"{len(failed)} check(s) failed: {', '.join(failed)}")
|
|
247
|
+
else:
|
|
248
|
+
print("all good")
|
|
249
|
+
print(f"\nthe Space is still there: nuspace -s {path} ls")
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
# --- driving it --------------------------------------------------------------
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
async def live(path):
|
|
256
|
+
"""The Space open, from boot to the end of the timeline."""
|
|
257
|
+
# max_parallel=1: nothing in the runtime computes, and a larger budget
|
|
258
|
+
# rations each Plane's Race against a semaphore those arms never give back.
|
|
259
|
+
await nu.arun(open_space(body(), path=path), nu.Context(), max_parallel=1)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def main():
|
|
263
|
+
path = tempfile.mkdtemp(prefix="nuspace-demo-")
|
|
264
|
+
print(f"\nnuspace headless demo {path}\n")
|
|
265
|
+
print(" loop async boot beat (restart always) hold (restart no)")
|
|
266
|
+
print(" solo mp boot work (restart always)")
|
|
267
|
+
print(f" main process is {os.getpid()}")
|
|
268
|
+
asyncio.run(live(path))
|
|
269
|
+
# The read back happens after the loop is gone, on a store nothing holds
|
|
270
|
+
# any more, which is the point: this is what a person would find there.
|
|
271
|
+
verdict(path, os.getpid())
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
if __name__ == "__main__":
|
|
275
|
+
# The guard is load bearing: the pool spawns, and a child re-imports
|
|
276
|
+
# whatever __main__ is. Without it, opening a Space forks forever.
|
|
277
|
+
main()
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""A Space in a browser, where opening a Plane is what starts it.
|
|
2
|
+
|
|
3
|
+
Everything phase 2 claims, in a tab rather than in a print:
|
|
4
|
+
|
|
5
|
+
- the sidebar lists every Plane that draws, under one row for the Space;
|
|
6
|
+
- clicking one selects it, and selecting it is what brings its Cells up;
|
|
7
|
+
- a Cell draws under its own node on the page, from the worker it runs in;
|
|
8
|
+
- navigating away takes that worker down, and navigating back brings it up
|
|
9
|
+
again, which the counter says out loud by starting from where it left off;
|
|
10
|
+
- prose is a Cell like any other: it holds a program that draws an editor.
|
|
11
|
+
|
|
12
|
+
Two Planes are seeded the first time and nothing after that, so what you write
|
|
13
|
+
is still there next time.
|
|
14
|
+
|
|
15
|
+
Run it::
|
|
16
|
+
|
|
17
|
+
python examples/web.py
|
|
18
|
+
|
|
19
|
+
A browser opens on http://127.0.0.1:8080. Pick Notes and type. Pick
|
|
20
|
+
Counter and watch it climb, then pick Notes again and come back to it: the
|
|
21
|
+
seconds are not the ones that went by while you were away, because nothing was
|
|
22
|
+
running while nobody was looking.
|
|
23
|
+
|
|
24
|
+
Ctrl+C stops it. The Space is left on disk, so `nuspace -s <path> ls` reads
|
|
25
|
+
what it did.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import logging
|
|
31
|
+
import tempfile
|
|
32
|
+
from pathlib import Path
|
|
33
|
+
|
|
34
|
+
import nu
|
|
35
|
+
import nustd.kv
|
|
36
|
+
from nuspace import EXEC_ASYNC, TRIGGER_NAV, Space, ops, presets, store
|
|
37
|
+
from nuspace.web.viewer import prose_source
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# Closing the Space drops the sockets under workers that are still holding
|
|
41
|
+
# them, and the transport logs every one of those. Expected, and the only thing
|
|
42
|
+
# this demo has to say is what it drew. At module scope so a spawned worker,
|
|
43
|
+
# which imports this file, is quiet too.
|
|
44
|
+
logging.getLogger("invisibles").setLevel(logging.CRITICAL)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
#: Kept between runs, because the point of a page is that what you wrote is
|
|
48
|
+
#: still on it tomorrow.
|
|
49
|
+
STORE = Path(tempfile.gettempdir()) / "nuspace-web-demo"
|
|
50
|
+
|
|
51
|
+
PORT = 8080
|
|
52
|
+
|
|
53
|
+
NOTES, COUNTER = "p_notes", "p_counter"
|
|
54
|
+
TEXT, TICK = "s_text", "s_tick"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
#: A Cell that draws. One stat tile and a loop, and it says nothing about where
|
|
58
|
+
#: the tile lives: the ref is written bare and the host roots it under this
|
|
59
|
+
#: Cell's own node on whatever surface the Plane is drawn on.
|
|
60
|
+
TICKER = '''import nu
|
|
61
|
+
import nustd.kv
|
|
62
|
+
import nustd.ui
|
|
63
|
+
from nuspace import Space
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def out(plane, cell):
|
|
67
|
+
"""Count seconds, in the browser and in this Cell's own state."""
|
|
68
|
+
tile = nustd.ui.StatRef("seconds")
|
|
69
|
+
state = Space.planes[plane].cells[cell].state
|
|
70
|
+
now = nu.Int(nu.ToInt(state.get_item("ticks", nu.Int(0))))
|
|
71
|
+
step = state.set_item("ticks", now + nu.Int(1)) >> tile.set_value(
|
|
72
|
+
nu.ToStr(now + nu.Int(1))
|
|
73
|
+
)
|
|
74
|
+
# A program owns its own atomicity. Nothing brackets it on the way in,
|
|
75
|
+
# because the host cannot see inside a program it evaluates.
|
|
76
|
+
return nustd.kv.auto_flow_atomic(
|
|
77
|
+
tile.set_label(nu.Str("seconds this Plane has been open"))
|
|
78
|
+
>> nu.ForeverDo(nu.DelayedDo(nu.Float(1.0), step)),
|
|
79
|
+
scope=Space,
|
|
80
|
+
)
|
|
81
|
+
'''
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def plane(plane_id, name):
|
|
85
|
+
"""One Plane a tab can navigate to, drawn as a page."""
|
|
86
|
+
return ops.add_plane(
|
|
87
|
+
plane_id=plane_id,
|
|
88
|
+
name=name,
|
|
89
|
+
exec_mode=EXEC_ASYNC,
|
|
90
|
+
# Up because somebody is looking at it, and down again when they stop.
|
|
91
|
+
trigger=TRIGGER_NAV,
|
|
92
|
+
ui=True,
|
|
93
|
+
editable=True,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def seed():
|
|
98
|
+
"""Two Planes, once. A Space that already has one is left alone.
|
|
99
|
+
|
|
100
|
+
Guarded rather than made idempotent: adding a Cell rewrites the row, so
|
|
101
|
+
seeding on every run would put the empty page back over whatever the last
|
|
102
|
+
run wrote on it.
|
|
103
|
+
"""
|
|
104
|
+
made = (
|
|
105
|
+
plane(NOTES, "Notes")
|
|
106
|
+
>> ops.add_cell(NOTES, prose_source(Space), cell_id=TEXT, name=TEXT)
|
|
107
|
+
>> plane(COUNTER, "Counter")
|
|
108
|
+
>> ops.add_cell(COUNTER, TICKER, cell_id=TICK, name=TICK)
|
|
109
|
+
)
|
|
110
|
+
return nu.IfDo(nu.Eq(nu.Len(ops.plane_ids()), nu.Int(0)), made)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def main():
|
|
114
|
+
STORE.mkdir(parents=True, exist_ok=True)
|
|
115
|
+
print(f"\nnuspace web demo {STORE}")
|
|
116
|
+
print(" Notes a page to write on")
|
|
117
|
+
print(" Counter a Cell that draws, and only while you are looking")
|
|
118
|
+
# Seeded against a store this process then closes, because the Space the
|
|
119
|
+
# server opens takes the write lock for as long as it is up.
|
|
120
|
+
nu.run_in_loop(
|
|
121
|
+
nu.With(store(str(STORE)), body=nustd.kv.auto_flow_atomic(seed(), scope=Space)),
|
|
122
|
+
nu.Context(),
|
|
123
|
+
)
|
|
124
|
+
print(f"\n http://127.0.0.1:{PORT}, Ctrl+C to stop\n")
|
|
125
|
+
try:
|
|
126
|
+
# max_parallel=1: nothing in the runtime computes, every branch is an
|
|
127
|
+
# await, and a larger budget rations each arm against a semaphore those
|
|
128
|
+
# arms never give back.
|
|
129
|
+
nu.run_in_loop(
|
|
130
|
+
presets.full(path=str(STORE), port=PORT),
|
|
131
|
+
nu.Context(),
|
|
132
|
+
max_parallel=1,
|
|
133
|
+
)
|
|
134
|
+
except KeyboardInterrupt:
|
|
135
|
+
print("stopped")
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
if __name__ == "__main__":
|
|
139
|
+
# The guard is load bearing: the pool spawns, and a child re-imports
|
|
140
|
+
# whatever __main__ is. Without it, opening a Space forks forever.
|
|
141
|
+
main()
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "nuspace"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
description = "Nu runtime built on Nu. A self-hosted programmable space that runs Nu apps in a tree of containers and applications."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "AGPL-3.0-or-later"
|
|
@@ -16,12 +16,12 @@ dependencies = [
|
|
|
16
16
|
# ui for the browser, proxy (invisibles) for the worker's session, mp_pool
|
|
17
17
|
# for the workers themselves. Picking extras just means discovering the
|
|
18
18
|
# missing one at import time.
|
|
19
|
-
"nucore>=0.5.
|
|
20
|
-
"nustd[all]>=0.5.
|
|
19
|
+
"nucore>=0.5.2",
|
|
20
|
+
"nustd[all]>=0.5.2",
|
|
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.
|
|
24
|
+
"nuspace-ui>=0.3.0",
|
|
25
25
|
"nuagent>=0.1.3",
|
|
26
26
|
"typing-extensions>=4.7.0",
|
|
27
27
|
"click>=8.0",
|
|
@@ -56,21 +56,18 @@ nuspace = "nuspace.cli:main"
|
|
|
56
56
|
|
|
57
57
|
[tool.hatch.build.targets.wheel]
|
|
58
58
|
packages = ["src/nuspace"]
|
|
59
|
-
exclude = [
|
|
60
|
-
"src/nuspace/web/ui/**",
|
|
61
|
-
]
|
|
62
59
|
|
|
63
60
|
[tool.hatch.build.targets.sdist]
|
|
64
61
|
exclude = [
|
|
65
|
-
"
|
|
66
|
-
"
|
|
62
|
+
"ts/node_modules",
|
|
63
|
+
"ts/dist",
|
|
67
64
|
]
|
|
68
65
|
|
|
69
66
|
[tool.uv.sources]
|
|
70
|
-
nuspace-ui = { path = "./
|
|
71
|
-
nucore = { path = "../nu
|
|
72
|
-
nustd = { path = "../nu/
|
|
73
|
-
nucli = { path = "../nu/
|
|
67
|
+
nuspace-ui = { path = "./ts" }
|
|
68
|
+
nucore = { path = "../nu", editable = true }
|
|
69
|
+
nustd = { path = "../nu/pkgs/nustd", editable = true }
|
|
70
|
+
nucli = { path = "../nu/pkgs/nucli", editable = true }
|
|
74
71
|
nuagent = { path = "../nuagent", editable = true }
|
|
75
72
|
|
|
76
73
|
[tool.uv]
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""nuspace: a Nu orchestrator that supports different mediums and behaviors.
|
|
2
|
+
|
|
3
|
+
Everything is inside a **Space**. A Space is a collection of **Planes**, and a
|
|
4
|
+
Plane says how and when its **Cells** run and how they are drawn. A Cell is a
|
|
5
|
+
Nu program, and it is the only thing here that executes.
|
|
6
|
+
|
|
7
|
+
Six modules, and the names are the model::
|
|
8
|
+
|
|
9
|
+
nuspace.shapes the store layout
|
|
10
|
+
nuspace.space opening a Space in a process
|
|
11
|
+
nuspace.ops what a person does to a Space
|
|
12
|
+
nuspace.exec how a Space runs
|
|
13
|
+
nuspace.drivers when and what runs
|
|
14
|
+
nuspace.presets a Space open with some set of drivers in it
|
|
15
|
+
|
|
16
|
+
The store layout and the two entry points are re-exported here, so a program
|
|
17
|
+
that lives in a Cell says ``from nuspace import Space`` and a process that
|
|
18
|
+
opens one says ``open_space(run_space())``. Everything else is reached through
|
|
19
|
+
the module it lives in, because ``ops``, ``exec``, ``drivers`` and ``presets``
|
|
20
|
+
are families rather than functions.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from nuspace import drivers, exec, ops, presets
|
|
24
|
+
from nuspace.drivers import run_space
|
|
25
|
+
from nuspace.shapes import (
|
|
26
|
+
DEFAULT_EDITABLE,
|
|
27
|
+
DEFAULT_EXEC_MODE,
|
|
28
|
+
DEFAULT_RELOAD,
|
|
29
|
+
DEFAULT_RESTART,
|
|
30
|
+
DEFAULT_TRIGGER,
|
|
31
|
+
DEFAULT_UI,
|
|
32
|
+
EXEC_ASYNC,
|
|
33
|
+
EXEC_MODES,
|
|
34
|
+
EXEC_MP,
|
|
35
|
+
RESTART_ALWAYS,
|
|
36
|
+
RESTART_NO,
|
|
37
|
+
RESTART_ON_FAILURE,
|
|
38
|
+
RESTARTS,
|
|
39
|
+
TRIGGER_BOOT,
|
|
40
|
+
TRIGGER_MANUAL,
|
|
41
|
+
TRIGGER_NAV,
|
|
42
|
+
TRIGGERS,
|
|
43
|
+
Cell,
|
|
44
|
+
CellProps,
|
|
45
|
+
Plane,
|
|
46
|
+
PlaneProps,
|
|
47
|
+
Space,
|
|
48
|
+
)
|
|
49
|
+
from nuspace.space import open_space, store
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
__all__ = [
|
|
53
|
+
"DEFAULT_EDITABLE",
|
|
54
|
+
"DEFAULT_EXEC_MODE",
|
|
55
|
+
"DEFAULT_RELOAD",
|
|
56
|
+
"DEFAULT_RESTART",
|
|
57
|
+
"DEFAULT_TRIGGER",
|
|
58
|
+
"DEFAULT_UI",
|
|
59
|
+
"EXEC_ASYNC",
|
|
60
|
+
"EXEC_MODES",
|
|
61
|
+
"EXEC_MP",
|
|
62
|
+
"RESTARTS",
|
|
63
|
+
"RESTART_ALWAYS",
|
|
64
|
+
"RESTART_NO",
|
|
65
|
+
"RESTART_ON_FAILURE",
|
|
66
|
+
"TRIGGERS",
|
|
67
|
+
"TRIGGER_BOOT",
|
|
68
|
+
"TRIGGER_MANUAL",
|
|
69
|
+
"TRIGGER_NAV",
|
|
70
|
+
"Cell",
|
|
71
|
+
"CellProps",
|
|
72
|
+
"Plane",
|
|
73
|
+
"PlaneProps",
|
|
74
|
+
"Space",
|
|
75
|
+
"drivers",
|
|
76
|
+
"exec",
|
|
77
|
+
"open_space",
|
|
78
|
+
"ops",
|
|
79
|
+
"presets",
|
|
80
|
+
"run_space",
|
|
81
|
+
"store",
|
|
82
|
+
]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""The ``nuspace`` command.
|
|
2
|
+
|
|
3
|
+
Seed a Space with ``plane`` and ``cell``, run it headless with ``run``, look at
|
|
4
|
+
it with ``ls``. A Space is a directory one process locks at a time, so the
|
|
5
|
+
commands that write want it to themselves and ``run`` holds it while it is up.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from nuspace.cli.main import cli, main
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
__all__ = ["cli", "main"]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Enables ``python -m nuspace.cli``.
|
|
2
|
+
|
|
3
|
+
The guard is load bearing, not decoration: the worker pool spawns, and a child
|
|
4
|
+
re-imports whatever ``__main__`` is. Without it, opening a Space forks forever.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from nuspace.cli.main import main
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
if __name__ == "__main__":
|
|
11
|
+
main()
|