@openkaiden/opnshll-sdk 0.0.110

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +186 -0
  2. package/dist/client.d.ts +594 -0
  3. package/dist/client.d.ts.map +1 -0
  4. package/dist/client.js +930 -0
  5. package/dist/client.js.map +1 -0
  6. package/dist/errors.d.ts +18 -0
  7. package/dist/errors.d.ts.map +1 -0
  8. package/dist/errors.js +56 -0
  9. package/dist/errors.js.map +1 -0
  10. package/dist/gen/datamodel_pb.d.ts +246 -0
  11. package/dist/gen/datamodel_pb.d.ts.map +1 -0
  12. package/dist/gen/datamodel_pb.js +58 -0
  13. package/dist/gen/datamodel_pb.js.map +1 -0
  14. package/dist/gen/openshell_pb.d.ts +6739 -0
  15. package/dist/gen/openshell_pb.d.ts.map +1 -0
  16. package/dist/gen/openshell_pb.js +1180 -0
  17. package/dist/gen/openshell_pb.js.map +1 -0
  18. package/dist/gen/options_pb.d.ts +58 -0
  19. package/dist/gen/options_pb.d.ts.map +1 -0
  20. package/dist/gen/options_pb.js +24 -0
  21. package/dist/gen/options_pb.js.map +1 -0
  22. package/dist/gen/sandbox_pb.d.ts +1069 -0
  23. package/dist/gen/sandbox_pb.d.ts.map +1 -0
  24. package/dist/gen/sandbox_pb.js +172 -0
  25. package/dist/gen/sandbox_pb.js.map +1 -0
  26. package/dist/index.d.ts +5 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +5 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/raw.d.ts +5 -0
  31. package/dist/raw.d.ts.map +1 -0
  32. package/dist/raw.js +14 -0
  33. package/dist/raw.js.map +1 -0
  34. package/dist/ssh-validate.d.ts +11 -0
  35. package/dist/ssh-validate.d.ts.map +1 -0
  36. package/dist/ssh-validate.js +58 -0
  37. package/dist/ssh-validate.js.map +1 -0
  38. package/dist/transport.d.ts +29 -0
  39. package/dist/transport.d.ts.map +1 -0
  40. package/dist/transport.js +92 -0
  41. package/dist/transport.js.map +1 -0
  42. package/package.json +58 -0
package/README.md ADDED
@@ -0,0 +1,186 @@
1
+ # @nvidia/openshell-sdk
2
+
3
+ TypeScript client for the OpenShell gateway — thin, idiomatic bindings generated from the OpenShell protobufs.
4
+
5
+ Distributed via GitHub Packages. A public npm release under the same name follows once the npm org is in place; the install specifier and API are unchanged across that move.
6
+
7
+ Use the SDK and gateway from the same OpenShell release when possible. The raw
8
+ types and RPC descriptors are generated from the protobuf definitions in that
9
+ release; curated methods remain compatible while those RPC contracts remain
10
+ compatible.
11
+
12
+ ## Install
13
+
14
+ Published to GitHub Packages, so point the `@nvidia` scope at it with a project `.npmrc`:
15
+
16
+ ```shell
17
+ @nvidia:registry=https://npm.pkg.github.com
18
+ ```
19
+
20
+ Authenticate with a GitHub token that has `read:packages`, then:
21
+
22
+ ```shell
23
+ npm install @nvidia/openshell-sdk
24
+ ```
25
+
26
+ ## Usage
27
+
28
+ ```ts
29
+ import { OpenShellClient } from '@nvidia/openshell-sdk'
30
+
31
+ const client = await OpenShellClient.connect({
32
+ gateway: 'https://gateway.example.com',
33
+ oidcToken: process.env.OPENSHELL_TOKEN,
34
+ })
35
+
36
+ const sandbox = await client.sandbox.create({
37
+ image: 'ghcr.io/nvidia/openshell-community/sandboxes/python:latest',
38
+ })
39
+ await client.sandbox.waitReady(sandbox.name, 120)
40
+
41
+ const result = await client.sandbox.exec(sandbox.name, ['/bin/sh', '-c', 'echo hello'])
42
+ console.log(result.stdout.toString())
43
+
44
+ await client.sandbox.delete(sandbox.name)
45
+ ```
46
+
47
+ `connect()` constructs a lazy client; call `health()` when startup must verify
48
+ gateway reachability. Authentication material is static for the client's
49
+ lifetime, so create a new client after refreshing an OIDC or edge token. The
50
+ root client has no explicit close method because Connect does not retain a
51
+ dedicated session. Close operation-scoped streams and forward handles instead.
52
+
53
+ Express the create-time safety boundary with `policy`. Sandbox-scoped `setPolicy`
54
+ cannot introduce static policy fields later, so set filesystem, landlock,
55
+ process, and initial network policy at creation. For proto spec fields the
56
+ curated shape does not surface, `rawSpec` is an escape hatch that shallow-
57
+ overrides the assembled spec at the top level (any field it sets wins):
58
+
59
+ ```ts
60
+ await client.sandbox.create({
61
+ image,
62
+ policy: { version: 1, networkPolicies: {} },
63
+ rawSpec: { logLevel: 'debug', template: { runtimeClassName: 'gvisor' } },
64
+ })
65
+ ```
66
+
67
+ ### Scoped clients
68
+
69
+ `client.sandbox` is a `SandboxClient`. If you only need sandboxes, connect one
70
+ directly — same API, one less hop:
71
+
72
+ ```ts
73
+ import { SandboxClient } from '@nvidia/openshell-sdk'
74
+
75
+ const sandbox = await SandboxClient.connect({ gateway, oidcToken })
76
+ await sandbox.create({ image })
77
+ ```
78
+
79
+ ## Streaming and interactive exec
80
+
81
+ `execStream` yields stdout/stderr chunks as they arrive, so long or chatty commands surface output incrementally instead of buffering until exit. The stream ends with a terminal `{ type: 'exit', exitCode }` event, yielded in-band so a failing command cannot look successful under `for await`. Discriminate it with `'type' in event`. If the gateway closes the stream without an exit event, `execStream` throws. `exec` drains `execStream` internally, so its buffered `ExecResult` is unchanged.
82
+
83
+ ```ts
84
+ for await (const event of client.sandbox.execStream(name, ['pytest', '-q'])) {
85
+ if ('type' in event) console.log(`exit ${event.exitCode}`)
86
+ else process[event.stream].write(event.data) // 'stdout' | 'stderr'
87
+ }
88
+ ```
89
+
90
+ `execInteractive` is the TTY + stdin transport primitive. Drive it by consuming `output`, which yields the same chunk/exit events; `done` resolves with the exit code once the stream reaches its exit event and rejects if it ends without one. It ships raw bytes only; raw mode, signal forwarding, and SIGWINCH stay with the caller.
91
+
92
+ ```ts
93
+ const session = await client.sandbox.execInteractive(name, ['bash'])
94
+ session.write(Buffer.from('echo hi\n'))
95
+ session.resize(120, 40)
96
+ for await (const event of session.output) {
97
+ if (!('type' in event)) process.stdout.write(event.data)
98
+ }
99
+ const code = await session.done
100
+ ```
101
+
102
+ ## Port forwarding
103
+
104
+ `forward` binds a local TCP listener and tunnels each accepted connection into the sandbox for the lifetime of the Node process. Call `close()` on teardown.
105
+
106
+ ```ts
107
+ const fwd = await client.sandbox.forward(name, {
108
+ targetPort: 8000,
109
+ onConnectionError: (error) => console.error(error),
110
+ })
111
+ // ... reach the sandbox service at 127.0.0.1:fwd.localPort ...
112
+ await fwd.close()
113
+ ```
114
+
115
+ `close()` is idempotent. It cancels active forwarding RPCs, destroys accepted
116
+ sockets, and waits for their cleanup.
117
+
118
+ ## SSH sessions, providers, config and policy
119
+
120
+ ```ts
121
+ const ssh = await client.sandbox.createSshSession(name)
122
+ await client.sandbox.revokeSshSession(ssh.token)
123
+
124
+ await client.sandbox.attachProvider(name, 'claude')
125
+ await client.sandbox.listProviders(name)
126
+ await client.sandbox.detachProvider(name, 'claude')
127
+
128
+ const config = await client.sandbox.getConfig(name)
129
+ config.policy!.networkPolicies['web'] = { name: 'web', endpoints: [], binaries: [] }
130
+ await client.sandbox.setPolicy(name, config.policy!, { wait: true })
131
+ await client.sandbox.setSetting(name, 'feature.enabled', { value: { case: 'boolValue', value: true } })
132
+ ```
133
+
134
+ Sandbox-scoped `setPolicy` may only change `networkPolicies`; static fields (`filesystem`, `landlock`, `process`) must match the create-time policy. Sandbox-scoped setting deletes are rejected by the gateway, so only upsert (`setSetting`) is exposed here.
135
+
136
+ ## Surface and roadmap
137
+
138
+ The SDK's goal is agent parity: anything the OpenShell gateway can do should be reachable from typed code, not only the CLI. The API is organized as scoped sub-clients over one shared connection, mirroring the CLI's verbs.
139
+
140
+ - `client.sandbox` (`SandboxClient`) is available today: sandbox lifecycle, exec, forward, SSH, sandbox-scoped providers, config, and policy.
141
+ - `client.gateway` (`GatewayClient`) is planned: gateway-scoped config and settings, health, and cluster status.
142
+ - `client.providers` (`ProviderClient`) is planned: gateway-scoped provider CRUD and profiles.
143
+
144
+ `health()` lives at the root today and will move under `client.gateway` (with a root alias) when that lands.
145
+
146
+ Curated methods are added deliberately, so some gateway RPCs are not yet wrapped in a typed helper. Rather than ship methods that exist but throw, the SDK omits what it has not curated and gives you the raw escape hatch below to reach the full gateway surface today. Omission means "not yet ergonomic," never "impossible."
147
+
148
+ ### Advanced: raw escape hatch
149
+
150
+ `client.raw` is a generated client for every gateway RPC, including surface the curated sub-clients do not wrap yet (gateway config, provider CRUD, policy status, watch, logs, and the full observed `Sandbox`). `client.transport` is the shared connection, so extra clients reuse one socket. Generated request and response types live at `@nvidia/openshell-sdk/raw`.
151
+
152
+ ```ts
153
+ import { OpenShellClient } from '@nvidia/openshell-sdk'
154
+ import type { GetGatewayConfigResponse } from '@nvidia/openshell-sdk/raw'
155
+
156
+ const client = await OpenShellClient.connect({ gateway, oidcToken })
157
+
158
+ // Reach RPCs the curated surface does not wrap yet:
159
+ const cfg: GetGatewayConfigResponse = await client.raw.getGatewayConfig({})
160
+ const status = await client.raw.getSandboxPolicyStatus({ name: 'my-sandbox', version: 0, global: false })
161
+ ```
162
+
163
+ The raw layer returns the generated wire messages verbatim, preserving proto distinctions (an omitted optional versus an explicitly empty map) that the curated types may smooth over. As curated sub-clients land, prefer them; `raw` stays as the always-available floor.
164
+
165
+ ## Boundaries
166
+
167
+ The SDK ships primitives, not the CLI's terminal experience. Some things are intentionally out of scope:
168
+
169
+ - **Interactive `connect()` / PTY ownership.** `execInteractive`, `createSshSession`, and `forward` are the transport primitives; raw mode, OpenSSH `ProxyCommand`, and terminal glue stay in the CLI.
170
+ - **`upload()` / `download()`.** There is no file-transfer RPC — the CLI does tar-over-SSH. For small payloads, `exec`/`execStream` with `stdin` covers it. A first-class gateway file-transfer RPC is a follow-up.
171
+ - **Detached / background forwards.** An in-process forward cannot outlive its caller; `forward` is process-lifetime only.
172
+
173
+ ## Development
174
+
175
+ The version field is a `0.0.0` placeholder; CI stamps the real version from the git release tag at publish time, matching the Rust and Python packages.
176
+
177
+ ```shell
178
+ mise run sdk:ts:proto # generate stubs from proto/ with buf
179
+ mise run sdk:ts:format # Biome: format + safe fixes (writes)
180
+ mise run sdk:ts:lint # Biome: lint + format check (read-only)
181
+ mise run sdk:ts:typecheck # tsc --noEmit
182
+ mise run sdk:ts:test # Vitest unit tests with an 80% line-coverage gate
183
+ mise run sdk:ts:build # emit dist/
184
+ ```
185
+
186
+ Formatting and linting are handled by [Biome](https://biomejs.dev) (`biome.json`): 2-space indent, single quotes, semicolons, 120-column width. Generated `src/gen/` is excluded. `sdk:ts:lint` runs in CI as part of `sdk:ts:ci`.