@dimensionalos/dimcode 0.1.0-next.6

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.
package/LICENSE ADDED
@@ -0,0 +1,17 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ Copyright 2025 Dimensional Inc.
6
+
7
+ Licensed under the Apache License, Version 2.0 (the "License");
8
+ you may not use this file except in compliance with the License.
9
+ You may obtain a copy of the License at
10
+
11
+ http://www.apache.org/licenses/LICENSE-2.0
12
+
13
+ Unless required by applicable law or agreed to in writing, software
14
+ distributed under the License is distributed on an "AS IS" BASIS,
15
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ See the License for the specific language governing permissions and
17
+ limitations under the License.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Mario Zechner
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.
package/README.md ADDED
@@ -0,0 +1,277 @@
1
+ # dimcode
2
+
3
+ Persistent terminal agent for DimOS, built on Pi. The gateway runs independently of robot blueprints. Existing MCP exposes skills; Pi's coding tools use the existing DimOS CLI/Python APIs. Media uses the existing Web SDK only while a renderer needs it.
4
+
5
+ ## Install or update
6
+
7
+ Current prerelease: **`0.1.0-next.6`**. With Node 24 or 26 installed, install it directly from npm:
8
+
9
+ ```sh
10
+ npm install -g --ignore-scripts @dimensionalos/dimcode@next
11
+ dimcode setup
12
+ ```
13
+
14
+ The standalone shell installer shows Pi’s original animated logo before downloading Node or Dimcode. It installs a private Node 24 runtime and creates `~/.local/bin/dimcode`. It checks which executable wins PATH and offers to update your shell profile when another installation takes precedence. The final prompt names an existing config when one is found; otherwise it offers first-time setup. The Node/npm bootstrap needs no sudo. Continuing setup then attempts the DimOS developer installation; its system-package and transport steps can request sudo in your terminal.
15
+
16
+ The standalone script works through `curl … | sh` without an existing Node/npm installation, but the private GitHub repository does not expose a public installer URL yet. Download `install.sh` from a successful Actions run when testing the managed Node bootstrap.
17
+
18
+ ```sh
19
+ dimcode setup # view saved settings and change only what you choose
20
+ dimcode # open the terminal
21
+ dimcode --version # CLI package version
22
+ dimcode doctor # CLI/build path, Node runtime, config path and running gateway
23
+ ```
24
+
25
+ On subsequent releases:
26
+
27
+ ```sh
28
+ dimcode update ./dimensionalos-dimcode-VERSION.tgz
29
+ ```
30
+
31
+ Installation switches the launcher to the selected build and preserves older releases and settings. It does not replace a running process. Once existing sessions are idle, run `dimcode stop`, then `dimcode` to start the new gateway. `doctor` identifies a different or older gateway explicitly. Prerelease versions publish under npm’s `next` tag; stable versions publish under `latest`.
32
+
33
+ With Node **24 or 26** already active, npm is also supported:
34
+
35
+ ```sh
36
+ npm install -g --ignore-scripts @dimensionalos/dimcode@next
37
+ dimcode --version
38
+ dimcode doctor
39
+ ```
40
+
41
+ `npm install -g` replaces the package only in that npm installation’s global prefix. It does not update another Node installation or reorder PATH. `type -a dimcode` lists competing commands; the first wins (run `hash -r` after a PATH change). The Node 22 installed by Pi is sufficient for Pi but incompatible with Dimcode’s WebTransport dependency; use the managed installer above. No uninstall is needed for a normal update through the same installer.
42
+
43
+ Setup reads `~/.dimcode` and displays the saved provider, model, theme and gateway startup choice together using Pi’s native settings list. Continue is selected; opening a row changes that setting through Pi’s native selectors. Existing credentials are reused. For older configurations without a gateway preference, setup reads the existing user service. Keeping “Start at login” does not restart an already configured gateway. A first installation attempts **DimOS developer setup**, then opens native Pi authentication and model selection. Repeating setup reuses the installation report and saved choices. A failed installation opens the setup agent with the recorded failure after authentication.
44
+
45
+ Credentials use a disposable masked field. Pi’s actual installer animation plays once when interactive setup opens; Pi’s animated loader represents waiting states. The bootstrap delegates DimOS installation to its reviewed upstream developer installer. Linux user-service startup remains optional; Windows, Homebrew and WinGet distribution are not part of this prerelease.
46
+
47
+ `dimcode` opens the fullscreen Pi-based terminal: scrollable chat, a multiline editor, live shell output and persistent model/status footer. Running shell cards show up to twelve recent output lines with elapsed time; completed cards collapse to three lines, and `/expand` restores the full tool result. Package names and stages come from actual command output; a command that emits no progress shows a waiting indicator without a fabricated percentage.
48
+
49
+ Shift+Enter or Ctrl+J adds a line; Ctrl-C detaches from the gateway. Older Warp versions send Shift+Enter as plain Enter; update Warp for Kitty keyboard reporting or use Ctrl+J. The footer shows the compatible shortcut.
50
+
51
+ Memory results appear **inside the agent's tool card**, with an overview combining DimOS's timeline, spatial map and selected frames. Click a panel label or enter `/panel 1`, `/panel 2`, etc. to see a larger individual view; `/panel 0` returns to the overview. `/expand` shows the latest tool's full output and source hashes. Use a terminal with image support (such as Kitty) for the SVG/PNG views; other terminals show image metadata.
52
+
53
+ To inspect a saved cloud without a model call, enter these commands **inside dimcode**:
54
+
55
+ ```text
56
+ /inspect /absolute/path/to/cloud.json
57
+ /view
58
+ ```
59
+
60
+ Cloud exports use `{ "points": [[x, y, z]], "frame": "world", "timestamp": 123.45 }`. In the inspector, **←/→** rotates, **+/-** zooms, **g** switches graphics/Braille, **d** shows source/hash, **0** resets the view, and **Esc** returns to chat. An export can supply `selectedIndices` to highlight the exact selection produced by its owning operation. Rotation and zoom change the view only; memory analysis and filtering stay in DimOS.
61
+
62
+ Point-cloud results from `dimcode_render` open the same inspector through `/view`. The terminal verifies the source hash before loading interactive geometry; if the file changed, the original tool preview remains available. `/expand` toggles details for the latest tool. Images use supported terminal graphics; Braille supports point-cloud inspection without image support.
63
+
64
+ A fresh `dimcode` launch opens setup automatically. Repeating `dimcode setup` lets you change the choices; configured providers offer **Use configured credentials** so you do not need to paste a key again. Credentials never enter chat.
65
+
66
+ ```sh
67
+ dimcode setup # repeat interactive setup
68
+ dimcode tui # open the terminal (same as dimcode)
69
+ dimcode --help # all launch commands
70
+ ```
71
+
72
+ ## Developer installation and recovery
73
+
74
+ By default, first-time `dimcode` / `dimcode setup` does the following:
75
+
76
+ 1. Reports platform, available disk and the installation log. Installs managed **uv** into `~/.dimcode/bin` and **Python 3.12** into `~/.dimcode/python`, without activating an unrelated virtual environment.
77
+ 2. Downloads a checksum-verified snapshot of DimOS’s [main installer](https://github.com/dimensionalOS/dimos/blob/4396873e4fa9f769a7b264596439f67b108843d2/scripts/install.sh). Runs developer mode with an explicit `main` branch, cloning **`~/.dimcode/dimos`** and creating its editable **`.venv`**. DimOS owns OS/GPU detection, system dependencies, Git LFS and transport settings. Its current developer recipe runs `uv sync --extra all`; allow disk/time for those dependencies. Real package output streams directly to the terminal and log.
78
+ 3. Verifies the Git checkout, editable distribution metadata, actual import location and CLI help. An upstream success message alone is insufficient. Installation does not launch a blueprint; upstream robot smoke tests are disabled.
79
+ 4. Opens Pi’s provider/model/settings wizard. On success it saves the verified executable/workspace paths. On failure it passes `install.json` and the log to the [`dimensional-install` skill](skills/dimensional-install/SKILL.md), which attempts repair with normal coding tools after login. API keys and sudo passwords never enter the conversation; sudo runs in your local terminal.
80
+
81
+ ```sh
82
+ dimcode install # explicitly attempt or verify local developer setup
83
+ dimcode install --dimos-dir ~/robot/dimos # choose another source checkout
84
+ dimcode install --non-interactive # install/verify only; returns nonzero on failure
85
+ dimcode setup # resume login or agent repair from the saved report
86
+ dimcode setup --skip-dimos # manual/remote setup without automatic local install
87
+ ```
88
+
89
+ Existing directories are verified **without pull, rebase or environment replacement**. An incomplete directory goes to agent repair. An active recorded installer is left in its original terminal. Ctrl-C saves progress and exits; a later setup resumes from the report. A failed automatic attempt is not silently retried on every settings review. An explicit `dimcode install` rechecks it. `DIMCODE_INSTALL_DIMOS=0` disables automatic local installation for new setups; provider-only `setup --provider NAME --key-env VAR` also skips it. Agent repair requires a working model. Unattended installation never launches an agent or marks first-time authentication complete.
90
+
91
+ `install.json` records status, destination, failing stage/command and log path. Successful reports also record verified Python/CLI paths, version and Git commit. `dimcode doctor` displays that report alongside CLI/gateway build identities. Setup completion lists configuration, credentials, sessions and install-log locations. Messaging-platform setup is not offered yet: this release has no Telegram/webhook adapter.
92
+
93
+ ## What is installed / where is the source?
94
+
95
+ This package uses **upstream Pi 0.85.1**, pinned as npm dependencies. It is not a Pi fork or a native binary. `dimcode` is a Node CLI with a Dimensional gateway, terminal frontend and extensions. Pi supplies the agent runtime, provider login, coding tools, message components and tool cards. A fork is unnecessary for these customizations.
96
+
97
+ All source is in this repository's [`src/`](src): [`main.ts`](src/main.ts) launches the CLI; [`install.ts`](src/install.ts) delegates and verifies developer installation; [`setup.ts`](src/setup.ts) handles private authentication/settings; [`skills/dimensional-install`](skills/dimensional-install) guides agent-led installation; [`terminal.ts`](src/terminal.ts) renders chat; [`gateway.ts`](src/gateway.ts) owns persistent sessions; [`media.ts`](src/media.ts) lazily connects the existing Web SDK. [`skills/dimensional`](skills/dimensional) supplies DimOS instructions.
98
+
99
+ ## Authentication and service
100
+
101
+ API keys are entered outside chat and stored by Pi in the user-only auth file. An environment key can be installed with `dimcode setup --provider openai --key-env OPENAI_API_KEY`. ChatGPT subscription login uses `--provider openai-codex --oauth`. Use Anthropic API keys for Claude in dimcode. Refresh/logout use Pi's provider implementation.
102
+
103
+ ```sh
104
+ dimcode service install
105
+ dimcode service status
106
+ dimcode service uninstall
107
+ ```
108
+
109
+ This is a Linux **user** service. Without it, first attach starts a detached gateway. `dimcode stop` stops that gateway; `dimcode gateway` runs it in the foreground for diagnostics, or reports that it is already running. `dimcode --foreground` runs gateway and terminal in one foreground process.
110
+
111
+ Config, Pi settings, credentials and editable context live in **`~/.dimcode`**. `DIMCODE_HOME` overrides that directory. On first launch after upgrading, an existing `~/.config/dimcode` directory (or its XDG equivalent) is moved intact and the old location becomes a compatibility link. Existing processes keep accessing the same files. If both directories already exist, the new home wins and neither is overwritten.
112
+
113
+ ```text
114
+ ~/.dimcode/
115
+ ├── config.json # workspace, DimOS endpoints, gateway startup choice
116
+ ├── settings.json # Pi model and theme preferences
117
+ ├── auth.json # private Pi credentials; never loaded as agent context
118
+ ├── install.json # installation progress and verified source/CLI paths
119
+ ├── install-logs/ # timestamped command output and reviewed installer
120
+ ├── bin/ # managed uv and Python launchers
121
+ ├── python/ # managed Python runtimes
122
+ ├── dimos/ # Git clone; editable environment in dimos/.venv
123
+ ├── AGENTS.md # optional global instructions
124
+ ├── APPEND_SYSTEM.md # optional additions to the built-in instructions
125
+ └── skills/ # custom skills/<name>/SKILL.md
126
+ ```
127
+
128
+ Sessions remain in `$XDG_STATE_HOME/dimcode` and previews in `$XDG_CACHE_HOME/dimcode`; recordings remain owned by DimOS. The runtime socket is user-only. Service removal preserves credentials, sessions and DimOS data. Use `/reload` between turns to reload changed context and skills.
129
+
130
+ ## Connect and work
131
+
132
+ ```sh
133
+ dimcode connect go2 http://127.0.0.1:9990/mcp
134
+ dimcode relay http://127.0.0.1:7780 my-robot
135
+ dimcode workspace /path/to/app # save the default workspace
136
+ dimcode --cwd /path/to/app
137
+ dimcode sessions
138
+ dimcode --session SESSION_ID
139
+ dimcode --session SESSION_ID --view
140
+ dimcode run "inspect the app and explain its blueprint"
141
+ ```
142
+
143
+ Endpoints are explicit; ports are examples, not instance identities. Use `/reload` after changing endpoints. Every advertised skill is registered with its original schema, metadata and remote routing. Tool names are bounded and collision-resistant. There is no static copy of the robot's tools or a parallel lifecycle service.
144
+
145
+ Terminal commands: `/new`, `/sessions`, `/resume ID`, `/models`, `/model PROVIDER MODEL`, `/login PROVIDER [oauth]`, `/logout PROVIDER`, `/abort`, `/steer TEXT`, `/follow TEXT`, `/reload`, `/image PATH`, `/panel N`, `/inspect PATH`, `/view`, `/expand`, `/exit`. Ctrl-C detaches. One terminal owns input; other viewers may observe. Detaching keeps the turn running. Restart restores Pi history and never automatically replays external actions. Very large histories show a bounded recent transcript with an omission notice; the complete agent history remains on disk. Session events carry their source identity so switching sessions cannot mix transcripts.
146
+
147
+ Pi owns context loading, skills, compaction, models and coding-tool behavior. Workspace instructions and configured Pi extensions load normally. Attached terminals support serialized dialogs/notifications; executable extension UI factories belong in the terminal renderer and cannot be sent through a socket.
148
+
149
+ ## Tool rendering
150
+
151
+ DimOS owns reusable visualizations and encodings for agent understanding: geometry, axes, units, timestamps, legends and selections. Dimcode owns terminal styling and interaction: cards, themes, camera/point presentation, resizing, controls and graphics/Braille adaptation. Terminal styling preserves the meaning of colors and labels in supplied visuals.
152
+
153
+ DimOS memory owns video/frame/point-cloud analysis. Evaluate the memory operation once with `materialize()`, then use its existing `Space.to_svg()` / `Plot.to_svg()` and selected-frame exports. Pass those exports together to `dimcode_render`:
154
+
155
+ ```json
156
+ {"kind":"image","title":"Memory · plant search","views":[{"label":"Timeline","path":"timeline.svg"},{"label":"Spatial","path":"space.svg"},{"label":"Frames","path":"frames.png"}]}
157
+ ```
158
+
159
+ The tool rasterizes the original SVG/PNG views for terminal display and model context. Every view reaches the model; the terminal presents a selectable overview. Each export retains its source path and SHA-256. MCP tool results containing multiple images also appear together automatically. No query or filter runs inside the renderer. Existing skills that return only a pose or JSON still require an explicit export; the harness does not invent missing views.
160
+
161
+ Built-in presentation stays constrained to **point clouds and images/SVGs**, with one player for finite sequences of either. `dimcode_render({path:"result.json"})` detects a cloud or frame index; other image paths are rasterized. Numeric plots and graphs use DimOS/Python SVG exports, not a harness plotting engine. Cloud JSON may also supply `colors: [[r,g,b]]` (0–255) and `selectedIndices`; source RGB is preserved and explicit selections are highlighted.
162
+
163
+ For a memory/replay interval, materialize the selection in DimOS and export an index alongside the existing frames:
164
+
165
+ ```json
166
+ {
167
+ "type": "points",
168
+ "timeOrigin": 1766747348.2995782,
169
+ "source": "Go2 memory · lidar · seconds 1–5",
170
+ "frames": [
171
+ {"path":"cloud-0.json","timestamp":1766747349.3433642},
172
+ {"path":"cloud-1.json","timestamp":1766747349.4723642}
173
+ ]
174
+ }
175
+ ```
176
+
177
+ Use `type:"image"` for PNG/JPEG frames or any custom SVG animation. Paths are relative to the index, timestamps are seconds and must increase; optional per-frame `sha256` checks the original exports. Use one recording origin for synchronized camera/cloud windows. This file indexes already exported results; it is not a new query or transport API.
178
+
179
+ Call `dimcode_render({path:"clip.json",title:"Memory · seconds 1–5"})`, or use these commands **inside dimcode** without a model call:
180
+
181
+ ```text
182
+ /inspect /absolute/path/to/clip.json
183
+ ```
184
+
185
+ While preparing a clip, the tool card shows verified source frames as they finish, with a compact progress bar and Pi’s animated loader. Updates are coalesced to approximately 10 Hz; the last frame remains visible while the GIF is encoded. These are actual rendered source frames, not synthetic sensor motion. Cancellation, tool completion and session changes dispose the animation.
186
+
187
+ The latest clip loops automatically, including after reconnect, with no player buttons or timeline. Ask the agent “show a single frame at 2 seconds” to inspect a still; it selects the timestamped result and renders that frame. A single-frame result stays static. Leaving the session or closing the terminal stops playback. Point-cloud clips share one camera, bounds and height scale; mismatched coordinate frames must be aligned in DimOS first.
188
+
189
+ The terminal plays timestamped PNGs. A looping GIF is exported for sharing (GIF timing rounds to centiseconds); the model receives a contact sheet of up to six labeled frames. `dimcode_render({path:"clip.json",frame:17})` returns original zero-based frame 17 for closer inspection, even if preview sampling omitted it. The renderer reads saved files only and never repeats the source query.
190
+
191
+ Previews are bounded to 60 seconds, at most 120 frames and approximately 10 Hz, retaining first/last timestamps and original indexes. Source-frame gaps remain visible in playback. Per-file reads are limited to 32 MiB, a selected clip to 128 MiB/two million points, generated PNGs to 64 MiB and GIFs to 32 MiB. If a limit is reached, export a smaller preview with DimOS. Individual clouds label point display sampling. Original recordings and exports remain unchanged.
192
+
193
+ The agent is instructed to visualize each meaningful sensor/memory operation. When no supported type or existing visualizer applies, it generates a self-contained SVG with inline Python from the evaluated result and displays it through `dimcode_render({kind:"image",path:"result.svg"})`. This supports arbitrary plots, images, simple graphs and labeled proposed overlays. The same rendered image is returned to the model for visual inspection. Rendering failures remain explicit.
194
+
195
+ Live tools select an existing relay/robot/channel. The terminal receives frames directly through the Web SDK and coalesces drawing to 10 Hz. The gateway retains one final snapshot for model context. Closing/cancelling the tool releases consumers; the last consumer closes the connection. MediaPool is generic over decoded SDK slots and accepts existing decoder registries. The initial live image renderer handles JPEG; other channel types use their owning decoder/renderer or saved exports.
196
+
197
+ Graphics use Pi terminal-image support with text fallback. In Warp, terminal images are rendered as complete cell-row slices so partial scrolling crops the image without compressing its aspect ratio. Derived PNGs/GIFs have a bounded 128 MiB cache; evicted previews show an explicit unavailable state and can be regenerated from retained exports; original DimOS recordings stay with DimOS. No recording, raw continuous video, new transport protocol or new DimOS gateway is introduced.
198
+
199
+ ## Source and architecture
200
+
201
+ Dimcode is a standalone repository extracted from `agent/` at DimOS commit
202
+ [`20a9b4b`](https://github.com/dimensionalOS/dimos/commit/20a9b4b552fec15ee91e1c3f085e47dab8627c13).
203
+ Pi 0.85.1 remains an unmodified npm dependency. The currently unpublished DimOS Web SDK is
204
+ bundled from the unchanged, pinned snapshot described in [vendor/README.md](vendor/README.md).
205
+ Normal builds require no DimOS checkout; a pip install, source checkout or remote instance
206
+ can be selected at runtime.
207
+
208
+ - `src/main.ts`, `src/setup.ts`: CLI, private model authentication and agent-led setup.
209
+ - `src/gateway.ts`, `src/session.ts`, `src/protocol.ts`: persistent Pi sessions and local terminal attachment.
210
+ - `src/terminal.ts`: terminal UI; `src/render.ts`, `src/points.ts`, `src/clips.ts`: constrained visual presentation.
211
+ - `src/mcp.ts`, `src/media.ts`: existing MCP tools and lazy Web SDK media subscriptions.
212
+ - `skills/`: DimOS operation and installation instructions for the agent.
213
+
214
+ DimOS continues to own blueprints, robot lifecycle, transport, memory and general-purpose
215
+ visualization. Its existing MCP/CLI fixes remain in the DimOS repository.
216
+
217
+ ## Permissions and privileged commands
218
+
219
+ Pi's Bash tool runs as the gateway's OS user, with that user's filesystem, network, process
220
+ and device access. Neither Pi nor dimcode currently adds a sandbox or per-command approval
221
+ policy. Pi extensions can block or request confirmation for tool calls, but a shell-command
222
+ filter is not an OS security boundary.
223
+
224
+ Bash uses pipes, not an interactive terminal, and cannot collect a sudo password. Passwordless
225
+ sudo can work under the host's sudo policy; password-required commands need a local terminal.
226
+ Keep passwords out of chat, tool arguments and session history. A future local terminal
227
+ handoff could let the user type directly into sudo while exposing only output and status to
228
+ the agent; that handoff is not implemented.
229
+
230
+ DimOS transport autoconfiguration includes system changes such as socket buffers and multicast
231
+ configuration. Its current `confirm()` defaults to yes without a TTY, then invokes sudo.
232
+ `autoconf(check_only=True)` inspects recommendations without applying fixes. Run privileged
233
+ configuration interactively in a local terminal; do not run the gateway as root.
234
+
235
+ ## Contributor development and validation
236
+
237
+ These commands are for working on the source, not the user installation:
238
+
239
+ ```sh
240
+ git clone git@github.com:dimensionalOS/dimcode.git
241
+ cd dimcode
242
+ npm ci
243
+ npm run check
244
+ npm pack
245
+ sh ./install.sh ./dimensionalos-dimcode-0.1.0-next.6.tgz
246
+ ```
247
+
248
+ This builds and tests the exact local tarball through the same managed installer. To run without installing a launcher, use `node dist/main.js setup` then `node dist/main.js`. `npm pack` produces the distributable tarball. `npm-shrinkwrap.json` pins the published dependency tree.
249
+
250
+ The [check workflow](.github/workflows/check.yml) checks Linux/macOS and uploads installable tarballs as private Actions artifacts. After both platforms pass on a push to `main`, it publishes an unpublished package version to `@dimensionalos/dimcode`. Prereleases use the `next` dist-tag; stable versions use `latest`. The workflow skips an already-published version, so documentation-only pushes remain green.
251
+
252
+ Installer options: `DIMCODE_VERSION` selects a version/tag (default `next`), `DIMCODE_INSTALL_DIR` selects private runtime/releases, `DIMCODE_BIN_DIR` selects the launcher directory, and `DIMCODE_SETUP=0` skips the setup offer. Node downloads and npm tarballs are verified before installation. The installer resolves npm’s version metadata directly and checks the package SHA-512, so a newly published release does not depend on npm’s package-index propagation. Run the installer again to update the launcher; older releases remain available to running sessions. Existing gateways continue using their current release until stopped with `dimcode stop` after active turns finish; the next launch starts the selected build.
253
+
254
+ To exercise the reviewed upstream installer in dry-run mode, download the pinned script from the link above and set `DIMCODE_TEST_INSTALLER_SCRIPT=/absolute/install.sh` for `npm test`. This checks its recipe and verification handoff without modifying system packages or starting robots.
255
+
256
+ To run the complete integration suite with an existing DimOS environment and Go2 recording:
257
+
258
+ ```sh
259
+ DIMCODE_TEST_DIMOS=/path/to/dimos \
260
+ DIMCODE_TEST_PYTHON=/path/to/dimos/.venv/bin/python \
261
+ DIMCODE_TEST_DENO=/path/to/deno \
262
+ DIMCODE_TEST_GO2_DB=/path/to/go2_bigoffice.db \
263
+ npm test
264
+ ```
265
+
266
+ The Go2 E2E test starts the standard `unitree-go2` blueprint in **recorded-data replay**, composed with the existing cockpit relay bridge. It starts its own relay and isolates transport discovery. No robot hardware or model API key is required. It verifies:
267
+
268
+ - Multiple distinct, successfully decoded JPEG video frames and timestamped odometry.
269
+ - Full XYZ float32 point clouds, every coordinate finite, source timestamps/frame metadata, and byte-for-byte source SHA-256 matches. A test-only encoder/SDK decoder uses the existing codec registry; it does not mislabel the default XY lidar projection as XYZ.
270
+ - One shared SDK connection, lazy subscriptions, continued cloud reception after video closes, then zero viewers/subscriptions after the last renderer closes.
271
+ - Cleanup of the blueprint and relay, including failure paths.
272
+
273
+ A separate memory test materializes seconds 1–5 once, exports full cloud/camera frames and a native DimOS plot, then checks GIF frame counts, shared time origin and every preview frame’s source hash. Playback unit tests cover looping, cancellation, stale files, RGB preservation and disposal. A scrolling regression checks exact visible pixels at multiple scroll positions and widths.
274
+
275
+ Set `DIMCODE_TEST_REPORT=/absolute/path/report.json` to save measured counts. Without `DIMCODE_TEST_GO2_DB`, the large recording test is explicitly skipped. The Python variable enables MCP-handler tests against that environment. `DIMCODE_TEST_DIMOS` optionally selects a source checkout ahead of installed Python packages; it is required with Deno for relay/Go2 tests. No neighboring checkout is assumed. Use a DimOS revision compatible with the SDK commit in `vendor/dimos/source.json`. Unit/CLI tests also cover onboarding, private credentials, cancellation, `tui`, existing-gateway handling, session ownership, detach/recovery and renderer provenance.
276
+
277
+ A video stream here means consecutive JPEG frames over WebTransport. This does not claim an H.264/WebCodecs decoder, browser UI coverage, or a physical robot test. The harness's initial live terminal renderer displays JPEG; saved point clouds, timed sequences and SVGs render from exported results. Other live types can plug into the generic SDK codec registry.
package/install.sh ADDED
@@ -0,0 +1,300 @@
1
+ #!/bin/sh
2
+ # Bootstrap the harness, then hand off to resumable developer setup.
3
+ set -eu
4
+
5
+ main() {
6
+ package='@dimensionalos/dimcode'
7
+ archive_path=${1:-}
8
+ if [ -n "$archive_path" ]; then
9
+ case "$archive_path" in /*) ;; *) archive_path="$PWD/$archive_path" ;; esac
10
+ [ -f "$archive_path" ] || { echo 'Package tarball does not exist.' >&2; exit 1; }
11
+ fi
12
+ active_path=$(command -v dimcode 2>/dev/null || true)
13
+ version=${DIMCODE_VERSION:-next}
14
+ data=${DIMCODE_INSTALL_DIR:-${XDG_DATA_HOME:-$HOME/.local/share}/dimcode}
15
+ bin=${DIMCODE_BIN_DIR:-$HOME/.local/bin}
16
+ case "$data:$bin" in /*:/*) ;; *) echo 'Install directories must be absolute.' >&2; exit 1 ;; esac
17
+ case "$(uname -s)-$(uname -m)" in
18
+ Linux-x86_64) platform=linux-x64 ;;
19
+ Linux-aarch64|Linux-arm64) platform=linux-arm64 ;;
20
+ Darwin-arm64) platform=darwin-arm64 ;;
21
+ *) echo 'This prerelease supports Linux x64/arm64 and Apple Silicon.' >&2; exit 1 ;;
22
+ esac
23
+ case "$version" in ''|*[!a-zA-Z0-9._-]*) echo 'Invalid DIMCODE_VERSION.' >&2; exit 1 ;; esac
24
+ command -v curl >/dev/null || { echo 'curl is required.' >&2; exit 1; }
25
+ mkdir -p "$data" "$bin"
26
+ staging=$(mktemp -d "$data/.install.XXXXXX")
27
+ trap 'rm -rf "$staging"' EXIT
28
+ trap 'exit 130' INT
29
+ trap 'exit 143' TERM
30
+ pi_logo_animation
31
+ printf '\ndimcode Installer\nInstall or update your Dimensional agent. Existing settings are preserved.\n\n'
32
+ printf 'Detected: %s\n' "$platform"
33
+ if [ -n "$active_path" ]; then printf 'Current shell command: %s\n' "$active_path"; fi
34
+
35
+ node="$data/node/bin/node"
36
+ if ! "$node" -e 'process.exit(+process.versions.node.split(".")[0] === 24 ? 0 : 1)' 2>/dev/null; then
37
+ dist=https://nodejs.org/dist/latest-v24.x
38
+ printf 'Installing a private Node 24 runtime…\n'
39
+ curl -fsSL "$dist/SHASUMS256.txt" -o "$staging/checksums"
40
+ archive=$(awk -v suffix="-$platform.tar.gz" '$2 ~ /^node-v[0-9.]+-/ && substr($2,length($2)-length(suffix)+1)==suffix {print $2; exit}' "$staging/checksums")
41
+ [ -n "$archive" ] || { echo 'No compatible Node archive found.' >&2; exit 1; }
42
+ curl -fsSL "$dist/$archive" -o "$staging/$archive"
43
+ awk -v file="$archive" '$2==file {print}' "$staging/checksums" > "$staging/selected.sha256"
44
+ if command -v sha256sum >/dev/null 2>&1; then
45
+ (cd "$staging" && sha256sum -c selected.sha256)
46
+ elif command -v shasum >/dev/null 2>&1; then
47
+ (cd "$staging" && shasum -a 256 -c selected.sha256)
48
+ else
49
+ echo 'sha256sum or shasum is required to verify Node.' >&2; exit 1
50
+ fi
51
+ tar -xzf "$staging/$archive" -C "$staging"
52
+ if [ -e "$data/node" ] || [ -L "$data/node" ]; then
53
+ echo "Existing runtime at $data/node is unusable; choose another DIMCODE_INSTALL_DIR." >&2
54
+ exit 1
55
+ fi
56
+ mv "$staging/${archive%.tar.gz}" "$data/node"
57
+ fi
58
+ printf '✓ Node %s · %s\n' "$("$node" --version)" "$node"
59
+ PATH="$data/node/bin:$PATH"
60
+ export PATH
61
+ npm="$data/node/bin/npm"
62
+ if [ -n "$archive_path" ]; then
63
+ cp "$archive_path" "$staging/dimcode.tgz"
64
+ tar -xOf "$staging/dimcode.tgz" package/package.json > "$staging/metadata.json"
65
+ else
66
+ curl -fsSL "https://registry.npmjs.org/$package/$version" -o "$staging/metadata.json"
67
+ tarball=$("$node" -p 'JSON.parse(require("node:fs").readFileSync(process.argv[1])).dist.tarball' "$staging/metadata.json")
68
+ case "$tarball" in https://registry.npmjs.org/*) ;; *) echo 'Unexpected package registry.' >&2; exit 1 ;; esac
69
+ curl -fsSL "$tarball" -o "$staging/dimcode.tgz"
70
+ fi
71
+ resolved=$("$node" -p 'const p=JSON.parse(require("node:fs").readFileSync(process.argv[1])); if(p.name!==process.argv[2]) throw new Error("Not a dimcode package"); p.version' "$staging/metadata.json" "$package")
72
+ case "$resolved" in ''|*[!a-zA-Z0-9._-]*) echo 'Invalid package version from registry.' >&2; exit 1 ;; esac
73
+ digest=$("$node" -p 'require("node:crypto").createHash("sha256").update(require("node:fs").readFileSync(process.argv[1])).digest("hex").slice(0,12)' "$staging/dimcode.tgz")
74
+ release="$data/releases/$resolved-$digest"
75
+ entry="$release/node_modules/$package/dist/main.js"
76
+ if [ ! -f "$entry" ]; then
77
+ printf 'Installing %s@%s…\n' "$package" "$resolved"
78
+ if [ -z "$archive_path" ]; then
79
+ "$node" - "$staging/metadata.json" "$staging/dimcode.tgz" "$package" <<'NODE'
80
+ const fs = require('node:fs');
81
+ const crypto = require('node:crypto');
82
+ const metadata = JSON.parse(fs.readFileSync(process.argv[2]));
83
+ const actual = 'sha512-' + crypto.createHash('sha512').update(fs.readFileSync(process.argv[3])).digest('base64');
84
+ if (metadata.name !== process.argv[4] || actual !== metadata.dist.integrity) throw new Error('Package integrity check failed');
85
+ NODE
86
+ fi
87
+ "$npm" install --prefix "$staging/package" --ignore-scripts --min-release-age=0 --omit=dev --no-audit --no-fund "$staging/dimcode.tgz"
88
+ "$node" "$staging/package/node_modules/$package/dist/main.js" --help >/dev/null
89
+ mkdir -p "$data/releases"
90
+ [ ! -e "$release" ] || { echo "Incomplete release at $release; choose another DIMCODE_INSTALL_DIR." >&2; exit 1; }
91
+ mv "$staging/package" "$release"
92
+ fi
93
+ "$node" - "$bin/dimcode" "$node" "$entry" <<'NODE'
94
+ const fs = require('node:fs');
95
+ const [dest, node, entry] = process.argv.slice(2);
96
+ const quote = (value) => "'" + value.replaceAll("'", "'\\''") + "'";
97
+ const temporary = `${dest}.install-${process.pid}`;
98
+ fs.writeFileSync(temporary, `#!/bin/sh\nexec ${quote(node)} ${quote(entry)} "$@"\n`, { mode: 0o755, flag: 'wx' });
99
+ fs.renameSync(temporary, dest);
100
+ NODE
101
+ printf '\nInstalled %s@%s\n' "$package" "$resolved"
102
+ # Like Pi, check which command wins PATH rather than whether the directory exists.
103
+ hash -r 2>/dev/null || true
104
+ active_path=$(command -v dimcode 2>/dev/null || true)
105
+ if [ "$active_path" != "$bin/dimcode" ]; then
106
+ printf 'Your shell resolves dimcode to: %s\nInstalled launcher: %s/dimcode\n' "${active_path:-not found}" "$bin"
107
+ if [ "${DIMCODE_SETUP:-1}" = 1 ] && ( : </dev/tty ) 2>/dev/null; then
108
+ case "$(basename "${SHELL:-sh}")" in
109
+ bash) profile="$HOME/.bashrc" ;;
110
+ zsh) profile="${ZDOTDIR:-$HOME}/.zshrc" ;;
111
+ *) profile="$HOME/.profile" ;;
112
+ esac
113
+ printf 'Put %s first on PATH in %s? [Y/n] ' "$bin" "$profile" >/dev/tty
114
+ IFS= read -r answer </dev/tty || answer=n
115
+ case "$answer" in n|N|no|NO) ;; *)
116
+ "$node" - "$profile" "$bin" <<'NODE'
117
+ const fs = require('node:fs');
118
+ const [profile, bin] = process.argv.slice(2);
119
+ const quote = (value) => "'" + value.replaceAll("'", "'\\''") + "'";
120
+ fs.appendFileSync(profile, `\n# dimcode\nexport PATH=${quote(bin)}:"$PATH"\n`);
121
+ NODE
122
+ ;; esac
123
+ fi
124
+ printf 'Restart your shell, or run: export PATH="%s:$PATH"; hash -r\n' "$bin"
125
+ fi
126
+ printf 'Verify: %s/dimcode --version\nInspect CLI and gateway: %s/dimcode doctor\n' "$bin" "$bin"
127
+ printf 'Existing gateways keep their loaded version. Once idle: dimcode stop, then dimcode.\n'
128
+ if [ "${DIMCODE_SETUP:-1}" = 1 ] && ( : </dev/tty ) 2>/dev/null; then
129
+ config_dir=${DIMCODE_HOME:-$HOME/.dimcode}
130
+ legacy_config=${XDG_CONFIG_HOME:-$HOME/.config}/dimcode
131
+ if [ -z "${DIMCODE_HOME:-}" ] && [ ! -e "$config_dir" ] && [ ! -L "$config_dir" ] && [ -d "$legacy_config" ] && [ ! -L "$legacy_config" ]; then
132
+ config_dir=$legacy_config
133
+ fi
134
+ if [ -f "$config_dir/config.json" ]; then
135
+ printf '\nOpen setup with saved settings from %s? [Y/n] ' "$config_dir" >/dev/tty
136
+ else
137
+ printf '\nOpen first-time setup now? [Y/n] ' >/dev/tty
138
+ fi
139
+ IFS= read -r answer </dev/tty || answer=n
140
+ case "$answer" in n|N|no|NO) ;; *)
141
+ if ! "$bin/dimcode" setup </dev/tty; then
142
+ printf '\nDimcode is installed. Setup is unfinished; saved progress is preserved.\n'
143
+ printf 'Resume: %s/dimcode setup\n' "$bin"
144
+ fi
145
+ ;; esac
146
+ fi
147
+ }
148
+
149
+ # Logo functions below are unchanged from https://pi.dev/install.sh.
150
+ # Retrieved 2026-09-12; full source SHA-256: a3a3604ee550bf72c5da7da3c3014cc361c14ab3b91b1b24f097d9022bd8de5b
151
+ # MIT license: LICENSES/pi-installer.txt
152
+ PI_ESC=$(printf '\033')
153
+ pi_logo_animation() {
154
+ if [ ! -t 1 ] || [ "${TERM:-}" = "dumb" ]; then
155
+ print_static_logo
156
+ return
157
+ fi
158
+
159
+ esc="${PI_ESC}["
160
+ reset="${PI_ESC}[0m"
161
+ hide="${esc}?25l"
162
+ show="${esc}?25h"
163
+ clear="${esc}H"
164
+
165
+ trap 'printf "%s%s\n" "$reset" "$show"; trap - INT TERM; exit 130' INT TERM
166
+ printf '%s%s' "$hide" "${esc}2J${esc}H"
167
+
168
+ for y in 0 1 2 3; do draw_logo_frame "$clear" "$reset" 0 left 2 "$y" 0 0; sleep 0.075; done
169
+ for y in 0 1 2; do draw_logo_frame "$clear" "$reset" 1 top 2 "$y" 0 0; sleep 0.075; done
170
+ for y in 0 1 2 3 4; do draw_logo_frame "$clear" "$reset" 2 right 5 "$y" 0 0; sleep 0.075; done
171
+
172
+ draw_logo_frame "$clear" "$reset" 3 none 0 0 0 0; sleep 0.25
173
+ draw_logo_frame "$clear" "$reset" 3 none 0 0 1 0; sleep 0.08
174
+ draw_logo_frame "$clear" "$reset" 3 none 0 0 0 0; sleep 0.08
175
+ draw_logo_frame "$clear" "$reset" 3 none 0 0 1 0; sleep 0.08
176
+ draw_logo_frame "$clear" "$reset" 4 none 0 0 0 0; sleep 0.10
177
+ draw_logo_frame "$clear" "$reset" 5 none 0 0 0 0; sleep 0.45
178
+ draw_logo_frame "$clear" "$reset" 5 none 0 0 0 1; sleep 0.12
179
+ draw_logo_frame "$clear" "$reset" 5 none 0 0 0 0; sleep 0.12
180
+ draw_logo_frame "$clear" "$reset" 5 none 0 0 0 1; sleep 0.45
181
+
182
+ printf '%s%s\n' "$reset" "$show"
183
+ trap - INT TERM
184
+ }
185
+
186
+ draw_logo_frame() {
187
+ clear="$1"; reset="$2"; phase="$3"; active="$4"; ax="$5"; ay="$6"; flash="$7"; white="$8"
188
+
189
+ left=0
190
+ top=0
191
+
192
+ panel_cell="${reset} "
193
+ cyan_cell="${PI_ESC}[36m██"
194
+ red_cell="${PI_ESC}[31m██"
195
+ green_cell="${PI_ESC}[32m██"
196
+ orange_cell="${PI_ESC}[33m██"
197
+ white_cell="${PI_ESC}[39m██"
198
+ flash_cell="${PI_ESC}[33m██"
199
+
200
+ pad=$(repeat_space "$left")
201
+ clear_cell="$panel_cell"
202
+ frame="$clear"
203
+ i=0
204
+ while [ "$i" -lt "$top" ]; do frame="${frame}\n"; i=$((i + 1)); done
205
+
206
+ for y in 0 1 2 3 4 5 6 7 8; do
207
+ frame="${frame}${pad}"
208
+ for x in 1 2 3 4 5 6 7 8; do
209
+ set_logo_cell_color "$phase" "$active" "$ax" "$ay" "$flash" "$white" "$y" "$x"
210
+ case "$LOGO_COLOR" in
211
+ cyan) cell="$cyan_cell" ;;
212
+ red) cell="$red_cell" ;;
213
+ green) cell="$green_cell" ;;
214
+ orange) cell="$orange_cell" ;;
215
+ white) cell="$white_cell" ;;
216
+ flash) cell="$flash_cell" ;;
217
+ *) cell="$clear_cell" ;;
218
+ esac
219
+ frame="${frame}${cell}"
220
+ done
221
+ frame="${frame}${reset}\n"
222
+ done
223
+ printf '%b' "$frame" 2>/dev/null || true
224
+ }
225
+
226
+ set_logo_cell_color() {
227
+ phase="$1"; active="$2"; ax="$3"; ay="$4"; flash="$5"; white="$6"; y="$7"; x="$8"
228
+
229
+ if [ "$white" = 1 ]; then
230
+ if in_cells "$y" "$x" "3,2 3,3 3,4 4,2 4,4 5,2 5,3 5,5 6,2 6,5"; then LOGO_COLOR=white; else LOGO_COLOR=panel; fi
231
+ return
232
+ fi
233
+ if [ "$flash" = 1 ] && [ "$y" = 6 ] && [ "$x" -ge 1 ] && [ "$x" -le 6 ]; then LOGO_COLOR=flash; return; fi
234
+
235
+ case "$active" in
236
+ left) if in_piece "$y" "$x" "$ay" "$ax" "0,0 1,0 1,1 2,0"; then LOGO_COLOR=red; return; fi ;;
237
+ top) if in_piece "$y" "$x" "$ay" "$ax" "0,0 0,1 0,2 1,2"; then LOGO_COLOR=cyan; return; fi ;;
238
+ right) if in_piece "$y" "$x" "$ay" "$ax" "0,0 1,0 2,0 2,1"; then LOGO_COLOR=green; return; fi ;;
239
+ esac
240
+
241
+ if [ "$phase" = 4 ]; then
242
+ if in_cells "$y" "$x" "2,2 2,3 2,4 3,4"; then LOGO_COLOR=cyan; return; fi
243
+ if in_cells "$y" "$x" "3,2 4,2 4,3 5,2"; then LOGO_COLOR=red; return; fi
244
+ if in_cells "$y" "$x" "4,5 5,5"; then LOGO_COLOR=green; return; fi
245
+ LOGO_COLOR=panel; return
246
+ fi
247
+
248
+ if [ "$phase" -ge 5 ]; then
249
+ if in_cells "$y" "$x" "3,2 3,3 3,4 4,4"; then LOGO_COLOR=cyan; return; fi
250
+ if in_cells "$y" "$x" "4,2 5,2 5,3 6,2"; then LOGO_COLOR=red; return; fi
251
+ if in_cells "$y" "$x" "5,5 6,5"; then LOGO_COLOR=green; return; fi
252
+ LOGO_COLOR=panel; return
253
+ fi
254
+
255
+ if [ "$phase" -le 3 ] && in_cells "$y" "$x" "6,1 6,2 6,3 6,4"; then LOGO_COLOR=orange; return; fi
256
+ if [ "$phase" -ge 2 ] && in_cells "$y" "$x" "2,2 2,3 2,4 3,4"; then LOGO_COLOR=cyan; return; fi
257
+ if [ "$phase" -ge 1 ] && in_cells "$y" "$x" "3,2 4,2 4,3 5,2"; then LOGO_COLOR=red; return; fi
258
+ if [ "$phase" -ge 3 ] && in_cells "$y" "$x" "4,5 5,5 6,5 6,6"; then LOGO_COLOR=green; return; fi
259
+
260
+ LOGO_COLOR=panel
261
+ }
262
+
263
+ in_piece() {
264
+ y="$1"; x="$2"; py="$3"; px="$4"; cells="$5"
265
+ for item in $cells; do
266
+ dy=${item%,*}; dx=${item#*,}
267
+ [ "$y" -eq $((py + dy)) ] && [ "$x" -eq $((px + dx)) ] && return 0
268
+ done
269
+ return 1
270
+ }
271
+
272
+ in_cells() {
273
+ y="$1"; x="$2"; shift 2
274
+ for item in $1; do
275
+ [ "$item" = "$y,$x" ] && return 0
276
+ done
277
+ return 1
278
+ }
279
+
280
+ repeat_space() {
281
+ count="$1"; out=""
282
+ while [ "$count" -gt 0 ]; do out=" $out"; count=$((count - 1)); done
283
+ printf '%s' "$out"
284
+ }
285
+
286
+ print_static_logo() {
287
+ cat <<'EOF'
288
+
289
+ ██████
290
+ ██ ██
291
+ ████ ██
292
+ ██ ██
293
+
294
+ EOF
295
+ }
296
+
297
+ case "${1:-}" in
298
+ --logo) pi_logo_animation ;;
299
+ *) main "$@" ;;
300
+ esac