@sealant/sdk 0.17.0 → 0.18.1

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/README.md CHANGED
@@ -124,6 +124,31 @@ const workspace = await sealant.workspaces.create({
124
124
  rootless daemon at launch. The workspace receives `DOCKER_HOST`; Sealant never mounts the host
125
125
  Docker socket. GitHub credentials provide both `GH_TOKEN` and `GITHUB_TOKEN` to the workspace.
126
126
 
127
+ ## Dotfiles and shell
128
+
129
+ Bring your own environment: a login shell and dotfiles applied before the workspace accepts work.
130
+
131
+ ```ts
132
+ const workspace = await sealant.workspaces.create({
133
+ repository: "github.com/acme/billing-service",
134
+ harness: codex(),
135
+ shell: "zsh",
136
+ dotfiles: {
137
+ // A repo the platform clones and applies (chezmoi/stow layouts detected, else copied):
138
+ repository: { url: "github.com/acme/dotfiles" },
139
+ // And/or caller-resolved archives — a checkout cloned host-side with your own ssh identity,
140
+ // or a scanned selection of home files, sent as gzipped tars (max 4, ~4MB decoded each):
141
+ archives: [{ data: tarGzBase64, manager: "copy", bootstrap: false }],
142
+ },
143
+ });
144
+ ```
145
+
146
+ `shell` installs the shell package and switches the login shell, so `.zshrc`/`.fishrc` actually take
147
+ effect. The `repository` applies first, then each archive in order, so local selections override
148
+ repo files. A failing apply fails the launch loudly rather than handing the agent a half-prepared
149
+ home. Managed OS families only — with `baseImage`, `dotfiles` and non-bash `shell` are rejected
150
+ client-side with the platform's reasoning.
151
+
127
152
  ## Custom base images
128
153
 
129
154
  Instead of a managed OS family, a workspace image can be built from any image reference you already
@@ -41,6 +41,17 @@ export const buildCreateWorkspaceRequest = (options, config) => {
41
41
  if (options.os !== undefined && options.baseImage !== undefined) {
42
42
  throw new SealantError("workspaces.create accepts either `os` (a managed OS family) or `baseImage` (a custom base image reference), not both.", { code: "invalid_create_options" });
43
43
  }
44
+ if (options.baseImage !== undefined && options.dotfiles !== undefined) {
45
+ throw new SealantError("`dotfiles` is not supported with `baseImage`: custom bases guarantee only a POSIX shell, so the dotfiles managers are not provisioned.", { code: "invalid_create_options" });
46
+ }
47
+ if (options.baseImage !== undefined && options.shell !== undefined && options.shell !== "bash") {
48
+ throw new SealantError("`shell` is not supported with `baseImage`: custom bases run /bin/sh and the login shell cannot be switched.", { code: "invalid_create_options" });
49
+ }
50
+ if (options.dotfiles !== undefined &&
51
+ options.dotfiles.repository === undefined &&
52
+ (options.dotfiles.archives === undefined || options.dotfiles.archives.length === 0)) {
53
+ throw new SealantError("`dotfiles` requires a `repository`, at least one entry in `archives`, or both.", { code: "invalid_create_options" });
54
+ }
44
55
  const sourceName = options.repository ?? options.source?.path ?? "workspace";
45
56
  const tail = sourceName
46
57
  .split("/")
@@ -72,6 +83,8 @@ export const buildCreateWorkspaceRequest = (options, config) => {
72
83
  ...(toolingPackages.length === 0 ? {} : { packages: toolingPackages }),
73
84
  ...(dockerService ? { services: { docker: { enabled: true } } } : {}),
74
85
  };
86
+ const dotfilesRepository = options.dotfiles?.repository;
87
+ const dotfilesArchives = options.dotfiles?.archives ?? [];
75
88
  const spec = {
76
89
  version: "1",
77
90
  sources: {
@@ -84,6 +97,21 @@ export const buildCreateWorkspaceRequest = (options, config) => {
84
97
  ...(options.ref === undefined ? {} : { ref: options.ref }),
85
98
  }
86
99
  : { kind: "mount", hostPath: options.source?.path },
100
+ ...(dotfilesRepository === undefined
101
+ ? {}
102
+ : {
103
+ inputs: [
104
+ {
105
+ id: "dotfiles",
106
+ kind: "git",
107
+ purpose: "dotfiles",
108
+ provider: "generic",
109
+ url: toGitUrl(dotfilesRepository.url),
110
+ // Omitted ref = the remote's default branch — never assumed to be `main`.
111
+ ...(dotfilesRepository.ref === undefined ? {} : { ref: dotfilesRepository.ref }),
112
+ },
113
+ ],
114
+ }),
87
115
  ...(mounts.length === 0
88
116
  ? {}
89
117
  : {
@@ -96,7 +124,35 @@ export const buildCreateWorkspaceRequest = (options, config) => {
96
124
  }),
97
125
  },
98
126
  harness: { id: options.harness.id },
99
- customization: { enableSealantd: true },
127
+ customization: {
128
+ enableSealantd: true,
129
+ ...(options.shell === undefined ? {} : { defaultShell: options.shell }),
130
+ // The repository path's knobs live at customization level; archives carry their own.
131
+ ...(dotfilesRepository?.manager === undefined
132
+ ? {}
133
+ : { dotfilesManager: dotfilesRepository.manager }),
134
+ ...(dotfilesRepository?.bootstrap === undefined
135
+ ? {}
136
+ : { dotfilesBootstrap: dotfilesRepository.bootstrap }),
137
+ ...(dotfilesRepository?.bootstrapCommand === undefined
138
+ ? {}
139
+ : { dotfilesBootstrapCommand: dotfilesRepository.bootstrapCommand }),
140
+ },
141
+ ...(dotfilesArchives.length === 0
142
+ ? {}
143
+ : {
144
+ runtime: {
145
+ dotfilesArchives: dotfilesArchives.map((archive) => ({
146
+ data: archive.data,
147
+ ...(archive.manager === undefined ? {} : { manager: archive.manager }),
148
+ ...(archive.target === undefined ? {} : { target: archive.target }),
149
+ ...(archive.bootstrap === undefined ? {} : { bootstrap: archive.bootstrap }),
150
+ ...(archive.bootstrapCommand === undefined
151
+ ? {}
152
+ : { bootstrapCommand: archive.bootstrapCommand }),
153
+ })),
154
+ },
155
+ }),
100
156
  target: {
101
157
  os: options.baseImage !== undefined
102
158
  ? { family: "custom", mode: "require", baseImage: options.baseImage }
package/dist/types.d.ts CHANGED
@@ -123,6 +123,53 @@ export interface WorkspaceExtraMount {
123
123
  /** Defaults to `true`. Pass `false` deliberately — writes to extra mounts are unrecorded. */
124
124
  readonly readOnly?: boolean;
125
125
  }
126
+ /** How a dotfiles tree is applied inside the workspace. */
127
+ export type WorkspaceDotfilesManager = "auto" | "chezmoi" | "stow" | "copy";
128
+ /**
129
+ * A dotfiles repository the platform clones and applies before the workspace accepts work. Public
130
+ * or GitHub-App-reachable repos work as-is; for a repo only the caller's own ssh identity can
131
+ * reach, resolve it host-side and send the checkout as an archive instead (see
132
+ * `WorkspaceDotfilesArchive`).
133
+ */
134
+ export interface WorkspaceDotfilesRepository {
135
+ /** Clone URL (or `"github.com/acme/dotfiles"` shorthand). */
136
+ readonly url: string;
137
+ /** Git ref. Omitted = the remote's default branch — never assumed to be `main`. */
138
+ readonly ref?: string;
139
+ /** Defaults to `"auto"`: chezmoi/stow layouts are detected, everything else is copied. */
140
+ readonly manager?: WorkspaceDotfilesManager;
141
+ /** Run the repo's bootstrap command after applying (skipped when absent). Defaults to true. */
142
+ readonly bootstrap?: boolean;
143
+ /** Bootstrap command, relative to the checkout. Defaults to `./install.sh`. */
144
+ readonly bootstrapCommand?: string;
145
+ }
146
+ /**
147
+ * A caller-resolved dotfiles tree: a gzipped tar the daemon extracts and applies at boot through
148
+ * the same manager dispatch as a cloned repo. This is the shape for dotfiles resolved host-side —
149
+ * a checkout cloned with the caller's own ssh identity, or a scanned selection of home files —
150
+ * so no URL or credential ever has to reach the workspace. Max 4 archives, ~4MB decoded each.
151
+ */
152
+ export interface WorkspaceDotfilesArchive {
153
+ /** base64 of a `.tar.gz` whose contents apply relative to the target. */
154
+ readonly data: string;
155
+ /** Defaults to `"auto"`. Scanned home files usually want `"copy"`. */
156
+ readonly manager?: WorkspaceDotfilesManager;
157
+ /** Where the tree lands: `"home"` (default) or `"config"` (`$HOME/.config`, copy manager only). */
158
+ readonly target?: "home" | "config";
159
+ /** Run `./install.sh` (or `bootstrapCommand`) after applying when present. Defaults to true. */
160
+ readonly bootstrap?: boolean;
161
+ readonly bootstrapCommand?: string;
162
+ }
163
+ /**
164
+ * Dotfiles for the workspace: a repository the platform clones, caller-resolved archives, or both
165
+ * (the repository applies first, archives after — in order — so local selections override repo
166
+ * files). Applied before the workspace reports ready; a failing apply fails the launch loudly.
167
+ * Not supported with `baseImage` (custom bases guarantee only a POSIX shell).
168
+ */
169
+ export interface WorkspaceDotfilesOptions {
170
+ readonly repository?: WorkspaceDotfilesRepository;
171
+ readonly archives?: readonly WorkspaceDotfilesArchive[];
172
+ }
126
173
  /** Runtime-managed services attached only to this workspace. */
127
174
  export interface WorkspaceServicesOptions {
128
175
  /**
@@ -160,6 +207,14 @@ export interface CreateOptions {
160
207
  readonly baseImage?: string;
161
208
  /** Extra OS packages to install in the workspace. */
162
209
  readonly packages?: readonly string[];
210
+ /**
211
+ * Login shell for the workspace user (`"bash"` default). The shell package is installed and the
212
+ * login shell switched, so dotfiles like `.zshrc` actually take effect. Managed OS families
213
+ * only — custom bases guarantee just a POSIX shell.
214
+ */
215
+ readonly shell?: "bash" | "zsh" | "fish";
216
+ /** Dotfiles applied before the workspace accepts work (see `WorkspaceDotfilesOptions`). */
217
+ readonly dotfiles?: WorkspaceDotfilesOptions;
163
218
  /** Runtime-managed services that need more than installing an OS package. */
164
219
  readonly services?: WorkspaceServicesOptions;
165
220
  /** When true (default), resolve only once the workspace runtime is live. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sealant/sdk",
3
- "version": "0.17.0",
3
+ "version": "0.18.1",
4
4
  "description": "The fluent public SDK for Sealant — create a workspace, run a harness, replay the record.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -26,7 +26,7 @@
26
26
  "access": "public"
27
27
  },
28
28
  "dependencies": {
29
- "@sealant/api-contracts": "^0.17.0"
29
+ "@sealant/api-contracts": "^0.18.1"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@effect/vitest": "4.0.0-beta.85",