@nexus-aethra/dsh-box-sandbox 0.1.0
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 +21 -0
- package/README.md +88 -0
- package/cordis.patch.yml +11 -0
- package/dist/index.d.mts +47 -0
- package/dist/index.mjs +2231 -0
- package/dist/rpc.d.mts +139 -0
- package/dist/rpc.mjs +186 -0
- package/package.json +62 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nexus Aethra
|
|
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,88 @@
|
|
|
1
|
+
# @nexus-aethra/dsh-box-sandbox
|
|
2
|
+
|
|
3
|
+
Host-side bridge from a DSH session to the DSH Box daemon.
|
|
4
|
+
|
|
5
|
+
## Why it exists
|
|
6
|
+
|
|
7
|
+
Developing a DSH plugin *inside* DSH is a crash story: a bad edit takes
|
|
8
|
+
down the host that is running it. DSH Box stays outside that loop, so the
|
|
9
|
+
development sandbox is the one process the agent is allowed to break. This
|
|
10
|
+
plugin makes that sandbox reachable over a typed transport instead of by
|
|
11
|
+
shelling out to the `dshbox` CLI and scraping stdout.
|
|
12
|
+
|
|
13
|
+
## Wire contract
|
|
14
|
+
|
|
15
|
+
Verified against `src-tauri/crates/dshboxd/src/main.rs`. The details below
|
|
16
|
+
are the ones that are easy to get wrong:
|
|
17
|
+
|
|
18
|
+
- **`POST /rpc` is matched exactly.** `path != "//rpc"` is a 404, so the
|
|
19
|
+
path must carry no query string.
|
|
20
|
+
- **The token travels in the JSON body**, not in an `Authorization`
|
|
21
|
+
header. `box-client` also sends `Bearer`, but the daemon never reads it.
|
|
22
|
+
- **Params are flattened** into the top-level request object alongside
|
|
23
|
+
`token` and `method` — there is no nested `params` field.
|
|
24
|
+
- **Replies**: `{ ok: true, result }` for sync methods,
|
|
25
|
+
`{ ok: true, task }` for async ones, `{ ok: false, error }` otherwise.
|
|
26
|
+
The daemon closes the connection after each frame.
|
|
27
|
+
- **`GET /events?token=...`** is the SSE stream; that endpoint takes the
|
|
28
|
+
token from the query string.
|
|
29
|
+
|
|
30
|
+
## Discovery
|
|
31
|
+
|
|
32
|
+
The daemon binds a **dynamic** loopback port and mints a token per launch,
|
|
33
|
+
then publishes both atomically:
|
|
34
|
+
|
|
35
|
+
<config-dir>/server/discovery.json
|
|
36
|
+
|
|
37
|
+
where `<config-dir>` is `DSHBOX_CONFIG_DIR` if set, otherwise `~/.dsh-box` —
|
|
38
|
+
the same precedence `box_foundation::config_path` applies on the Rust side.
|
|
39
|
+
The record is `{ token, pid, startedAt, port }` and is removed on shutdown.
|
|
40
|
+
|
|
41
|
+
Ports are never hardcoded. The record is re-read on every call, and a
|
|
42
|
+
transport failure is retried exactly once against a fresh read, so a daemon
|
|
43
|
+
restart (new port, new token) is transparent to callers.
|
|
44
|
+
|
|
45
|
+
## Usage
|
|
46
|
+
|
|
47
|
+
import { getRpc, status } from '@nexus-aethra/dsh-box-sandbox'
|
|
48
|
+
|
|
49
|
+
const pong = await getRpc().ping()
|
|
50
|
+
const containers = await getRpc().call('list_containers')
|
|
51
|
+
|
|
52
|
+
// Probe without throwing — for prompt surfaces and diagnostics.
|
|
53
|
+
const s = await status()
|
|
54
|
+
if (s.problem) console.log('dshbox unavailable:', s.problem)
|
|
55
|
+
|
|
56
|
+
`status()` never throws: it returns the discovery path, the endpoint when
|
|
57
|
+
reachable, and a `problem` string otherwise. A failed mount is never
|
|
58
|
+
fatal — a missing daemon is a normal first-run state, not a load error.
|
|
59
|
+
|
|
60
|
+
## Config
|
|
61
|
+
|
|
62
|
+
- insert:
|
|
63
|
+
- id: dsh-box-sandbox
|
|
64
|
+
name: '@nexus-aethra/dsh-box-sandbox'
|
|
65
|
+
config: {}
|
|
66
|
+
|
|
67
|
+
| Field | Default | Meaning |
|
|
68
|
+
|---|---|---|
|
|
69
|
+
| `configDir` | `''` | Empty means resolve like the daemon does. |
|
|
70
|
+
| `timeoutMs` | `30000` | Per-attempt request timeout. |
|
|
71
|
+
|
|
72
|
+
## Security
|
|
73
|
+
|
|
74
|
+
The discovery token is full control of the daemon: create and delete
|
|
75
|
+
containers, run builds, install plugins. Anything that can read the record
|
|
76
|
+
can drive dshbox. That is the intent here, but it is a deliberate trust
|
|
77
|
+
boundary — do not mount the host profile directory into an untrusted
|
|
78
|
+
container.
|
|
79
|
+
|
|
80
|
+
## Development
|
|
81
|
+
|
|
82
|
+
pnpm install
|
|
83
|
+
pnpm typecheck
|
|
84
|
+
pnpm build
|
|
85
|
+
node smoke.mjs # 10 checks against a running DSH Box
|
|
86
|
+
|
|
87
|
+
`smoke.mjs` needs a live daemon: it reads the real discovery record, pings
|
|
88
|
+
it, forces a stale-port failure, and verifies recovery.
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# @nexus-aethra/dsh-box-sandbox: mounts the host-side development sandbox.
|
|
2
|
+
#
|
|
3
|
+
# Every field is optional. With no config the client honours
|
|
4
|
+
# DSHBOX_CONFIG_DIR and falls back to ~/.dsh-box, which is where DSH Box
|
|
5
|
+
# publishes its discovery record. Overriding configDir is only needed to
|
|
6
|
+
# point at a non-default profile.
|
|
7
|
+
|
|
8
|
+
- insert:
|
|
9
|
+
- id: dsh-box-sandbox
|
|
10
|
+
name: '@nexus-aethra/dsh-box-sandbox'
|
|
11
|
+
config: {}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Discovery, DshboxRpc, DshboxUnavailableError, discoveryPath, getRpc, resetRpc } from "./rpc.mjs";
|
|
2
|
+
import { Context } from "@deepseek-ai/cordis";
|
|
3
|
+
//#region src/index.d.ts
|
|
4
|
+
/** Cordis plugin name used by loader diagnostics and the patch overlay. */
|
|
5
|
+
declare const name = "dsh-box-sandbox";
|
|
6
|
+
/**
|
|
7
|
+
* Services this plugin depends on. Empty for now: the client is pure
|
|
8
|
+
* transport and needs no host service. Tools built on top of it will
|
|
9
|
+
* declare their own.
|
|
10
|
+
*/
|
|
11
|
+
declare const inject: string[];
|
|
12
|
+
/**
|
|
13
|
+
* Plugin config. Both fields carry defaults, so the mount works with an
|
|
14
|
+
* empty config block; an empty configDir means "resolve it the way the
|
|
15
|
+
* daemon does" rather than "the current directory".
|
|
16
|
+
*/
|
|
17
|
+
interface Config {
|
|
18
|
+
/**
|
|
19
|
+
* Directory holding server/discovery.json. The default empty string
|
|
20
|
+
* means: honour DSHBOX_CONFIG_DIR, then fall back to ~/.dsh-box.
|
|
21
|
+
*/
|
|
22
|
+
configDir: string;
|
|
23
|
+
/** Per-attempt request timeout in milliseconds. */
|
|
24
|
+
timeoutMs: number;
|
|
25
|
+
}
|
|
26
|
+
/** Snapshot of reachability, for diagnostics and prompt surfaces. */
|
|
27
|
+
interface Status {
|
|
28
|
+
/** Path the discovery record was looked for at. */
|
|
29
|
+
discoveryPath: string;
|
|
30
|
+
/** Endpoint details when the daemon is up, else null. */
|
|
31
|
+
endpoint: Discovery | null;
|
|
32
|
+
/** Populated when endpoint is null. */
|
|
33
|
+
problem: string | null;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Probe the daemon without throwing.
|
|
37
|
+
*
|
|
38
|
+
* Returns the discovery record when the daemon answers, or the reason it
|
|
39
|
+
* could not be reached. A ping failure is reported rather than raised so
|
|
40
|
+
* that a status surface can render "dshbox is not running" without every
|
|
41
|
+
* caller having to catch.
|
|
42
|
+
*/
|
|
43
|
+
declare function status(config?: Partial<Config>): Promise<Status>;
|
|
44
|
+
/** Register the plugin against a freshly mounted Cordis context. */
|
|
45
|
+
declare function apply(ctx: Context, config?: Partial<Config>): void;
|
|
46
|
+
//#endregion
|
|
47
|
+
export { Config, type Discovery, DshboxRpc, DshboxUnavailableError, Status, apply, discoveryPath, getRpc, inject, name, resetRpc, status };
|