labgate 0.5.39 → 0.5.42

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 (47) hide show
  1. package/README.md +132 -248
  2. package/dist/cli.js +9 -33
  3. package/dist/cli.js.map +1 -1
  4. package/dist/lib/config.d.ts +19 -3
  5. package/dist/lib/config.js +154 -75
  6. package/dist/lib/config.js.map +1 -1
  7. package/dist/lib/container.d.ts +11 -9
  8. package/dist/lib/container.js +749 -282
  9. package/dist/lib/container.js.map +1 -1
  10. package/dist/lib/dataset-mcp.js +2 -9
  11. package/dist/lib/dataset-mcp.js.map +1 -1
  12. package/dist/lib/display-mcp.d.ts +2 -2
  13. package/dist/lib/display-mcp.js +17 -38
  14. package/dist/lib/display-mcp.js.map +1 -1
  15. package/dist/lib/doctor.js +8 -0
  16. package/dist/lib/doctor.js.map +1 -1
  17. package/dist/lib/explorer-claude.js +36 -1
  18. package/dist/lib/explorer-claude.js.map +1 -1
  19. package/dist/lib/explorer-eval.js +3 -2
  20. package/dist/lib/explorer-eval.js.map +1 -1
  21. package/dist/lib/image-pull-lock.d.ts +18 -0
  22. package/dist/lib/image-pull-lock.js +167 -0
  23. package/dist/lib/image-pull-lock.js.map +1 -0
  24. package/dist/lib/init.js +22 -19
  25. package/dist/lib/init.js.map +1 -1
  26. package/dist/lib/slurm-cli-passthrough.d.ts +12 -2
  27. package/dist/lib/slurm-cli-passthrough.js +401 -143
  28. package/dist/lib/slurm-cli-passthrough.js.map +1 -1
  29. package/dist/lib/startup-stage-lock.d.ts +21 -0
  30. package/dist/lib/startup-stage-lock.js +196 -0
  31. package/dist/lib/startup-stage-lock.js.map +1 -0
  32. package/dist/lib/ui.d.ts +40 -0
  33. package/dist/lib/ui.html +4953 -3366
  34. package/dist/lib/ui.js +1771 -297
  35. package/dist/lib/ui.js.map +1 -1
  36. package/dist/lib/web-terminal-startup-readiness.d.ts +8 -0
  37. package/dist/lib/web-terminal-startup-readiness.js +29 -0
  38. package/dist/lib/web-terminal-startup-readiness.js.map +1 -0
  39. package/dist/lib/web-terminal.d.ts +51 -0
  40. package/dist/lib/web-terminal.js +171 -1
  41. package/dist/lib/web-terminal.js.map +1 -1
  42. package/dist/mcp-bundles/dataset-mcp.bundle.mjs +144 -93
  43. package/dist/mcp-bundles/display-mcp.bundle.mjs +35 -43
  44. package/dist/mcp-bundles/explorer-mcp.bundle.mjs +263 -146
  45. package/dist/mcp-bundles/results-mcp.bundle.mjs +39 -41
  46. package/dist/mcp-bundles/slurm-mcp.bundle.mjs +19 -21
  47. package/package.json +1 -1
package/README.md CHANGED
@@ -1,334 +1,218 @@
1
1
  # LabGate
2
2
 
3
- Policy-controlled sandboxes for AI coding agents. Built for HPC clusters.
3
+ Policy-controlled sandboxes for AI coding agents on HPC.
4
4
 
5
- ## Current Product Focus
5
+ ## Current Focus
6
6
 
7
- - Primary workflow: Claude (`labgate claude`)
8
- - Primary runtime: Apptainer on HPC
9
- - macOS runtime: Podman (best-effort fallback path)
10
- - Secondary targets (best-effort): other agents
7
+ - Primary workflow: `labgate claude`
8
+ - Primary runtime: Apptainer
9
+ - HPC default: SLURM tracking enabled by default
10
+ - Secondary path: `labgate codex` (best-effort)
11
+
12
+ Docs: [labgate.dev/docs](https://labgate.dev/docs)
11
13
 
12
14
  ## Install
13
15
 
14
16
  ```bash
15
17
  npm i -g labgate
18
+ labgate init
16
19
  ```
17
20
 
18
- Note: LabGate uses `node-pty` only for the optional sticky footer. On minimal Linux installs, that dependency may fail to build without a compiler toolchain. If it fails, the install still works and LabGate falls back to non-sticky output.
19
-
20
- Note: `tmux` is a host-level dependency for `labgate ui` / web terminals. `npm i -g labgate` does not install `tmux`; install it through your OS or cluster module system.
21
-
22
- LabGate prefers Apptainer for sandbox runtime and supports Podman as a fallback (especially on macOS).
23
-
24
- On UI startup, LabGate ensures a bundled sample dataset (`flowers-iris`) is available at `~/.labgate/datasets/flowers-iris` for first-run testing.
25
-
26
- ## Quick start
27
-
28
- ```bash
29
- labgate init # optional: pre-create ~/.labgate/config.json
30
- labgate claude # launch Claude Code in current dir
31
- labgate codex /projects/my-analysis # launch Codex in a specific dir
32
- ```
33
-
34
- ## What it does
21
+ Notes:
35
22
 
36
- LabGate runs your AI coding agent inside a sandboxed container with:
23
+ - `labgate ui` requires host `tmux`.
24
+ - `node-pty` is optional. If it fails to build on a minimal Linux host, LabGate still works and falls back to non-sticky output.
25
+ - Default config path is `~/.labgate/config.json`.
26
+ - `labgate init` pre-registers a bundled sample dataset at `~/.labgate/datasets/flowers-iris`.
37
27
 
38
- - **Scoped filesystem** — only your working directory and configured paths are visible
39
- - **Credential blocking** — `.ssh`, `.aws`, `.env`, `.gnupg`, and other sensitive paths are hidden by default
40
- - **Network policy** — configurable network modes (`host`, `filtered`, `none`)
41
- - **Command blocking** — high-risk system commands (for example `mount`, `umount`, `mkfs`, `reboot`) are blocked by default
42
- - **Audit logging** — session start/stop and mount configuration logged to `~/.labgate/logs/`
43
- - **Dashboard instructions editor** — view and update per-session `AGENTS.md` / `CLAUDE.md` from the UI
44
- - **Session context injection** — LabGate prepends a temporary sandbox-mapping instruction block during active sessions
45
- - **HPC ready** — first-class Apptainer support for shared clusters
28
+ ## Quick Start
46
29
 
47
- ## Configuration
30
+ Recommended two-node HPC flow:
48
31
 
49
- Edit `~/.labgate/config.json` to customize:
32
+ 1. On the login node, start the dashboard:
50
33
 
51
34
  ```bash
52
- $EDITOR ~/.labgate/config.json
35
+ labgate ui
53
36
  ```
54
37
 
55
- Or start fresh:
38
+ 2. On a compute node, launch Claude inside the sandbox:
56
39
 
57
40
  ```bash
58
- labgate init --force
41
+ srun --pty bash
42
+ cd /path/to/project
43
+ labgate claude
59
44
  ```
60
45
 
61
- Or reset a single setting back to defaults:
46
+ If browser auth is not practical over SSH, pass an API key directly:
62
47
 
63
48
  ```bash
64
- labgate config reset image
49
+ labgate claude --api-key "$ANTHROPIC_API_KEY"
65
50
  ```
66
51
 
67
- ### Key settings
68
-
69
- | Setting | Default | What it does |
70
- |---------|---------|-------------|
71
- | `runtime` | `auto` | `auto`, `apptainer`, or `podman` |
72
- | `image` | `docker.io/library/node:20-bookworm` | Container image |
73
- | `session_timeout_hours` | `8` | Max session length |
74
- | `filesystem.blocked_patterns` | `.ssh, .aws, .env, ...` | Hidden from sandbox |
75
- | `filesystem.extra_paths` | `[]` | Additional mounts |
76
- | `network.mode` | `host` | `none`, `filtered`, or `host` |
77
- | `commands.blacklist` | `mount, umount, mkfs, reboot, shutdown` | Blocked commands |
78
- | `slurm.enabled` | `true` | Enable SLURM CLI passthrough (`sbatch`, `squeue`, etc.) and job tracking |
79
- | `headless.claude_run_with_allowed_permissions` | `true` | In Claude headless mode, auto-allow tool use via `--dangerously-skip-permissions` |
80
- | `headless.continuation_in_other_terminals` | `true` | Show or hide the `Continue in terminal via labgate continue ...` footer hint |
81
- | `headless.git_integration` | `false` | Show or hide Git integration UI (Git DAG sidebar widget and footer branch controls) |
52
+ For local non-SSH, non-SLURM shells, `labgate claude` auto-starts `labgate ui` when the UI is not already running.
82
53
 
83
- ## Commands
54
+ ## What LabGate Does
84
55
 
85
- ```bash
86
- labgate claude [workdir] # launch Claude Code
87
- labgate codex [workdir] # launch Codex
88
- labgate feedback # submit feedback (interactive or piped)
89
- labgate status # list running sessions
90
- labgate stop <id> # stop a session
91
- labgate ui # start dashboard server on localhost:7700 (auth token required, tmux required)
92
- labgate register <activation-key> [--server <url>] # activate + install enterprise license
93
- labgate license # show enterprise license status
94
- labgate license install <key-or-file> [--system|--user|--path] # install enterprise license key
95
- labgate policy init [--institution ... --admin ...] # create policy template
96
- labgate policy validate [file] # validate policy JSON
97
- labgate logs [-n 20] # view recent audit events
98
- labgate logs --follow # stream new audit events
99
- labgate init [--force] # create/reset config
100
- labgate explore create --name <name> --repo <path> --eval "<command>" # create Solution Explorer experiment
101
- labgate explore tick --experiment <id> # run one autopilot tick (cron-friendly)
102
- labgate explore overview --experiment <id> # aggregated status/counts/best/latest
103
- labgate explore run --id <run-id> # run metadata + artifact paths
104
- labgate explore gc --experiment <id> # retention-based prune (dry-run by default)
105
- labgate explore compare --experiment <id> --run <run-id> --to best # score+diff compare
106
- ```
56
+ - Runs Claude or Codex inside a containerized sandbox
57
+ - Mounts the working directory, a persistent sandbox home, configured extra paths, and named datasets
58
+ - Hides common credential and secret paths by default
59
+ - Applies network policy with `host` or `filtered` modes
60
+ - Blocks high-risk commands such as `mount`, `umount`, `mkfs`, `reboot`, and `shutdown`
61
+ - Records audit logs in `~/.labgate/logs/`
62
+ - Tracks SLURM jobs and exposes MCP integrations for supported LabGate subsystems
63
+ - Provides a browser UI and web-terminal control plane with `labgate ui`
64
+ - Ships a bundled `flowers-iris` sample dataset for first-run dataset workflows
65
+ - Lets you edit `AGENTS.md` / `CLAUDE.md` from the UI, with a temporary LabGate sandbox-context block injected for active sessions
107
66
 
108
- ### Solution Explorer (MVP backend)
67
+ `network.mode=none` is rejected for `labgate claude` and `labgate codex`.
109
68
 
110
- LabGate now includes an early **Solution Explorer** backend for reproducible
111
- variant search workflows:
69
+ ## Key Defaults
112
70
 
113
- - Isolated experiment repo clones under `~/.labgate/explorer/repos/`
114
- - Git worktree-per-run execution
115
- - Deterministic eval contract parsing (`score` JSON on last stdout line)
116
- - Per-run artifacts (`eval.json`, logs, diff, summary)
117
- - Cron-safe autopilot tick lock
71
+ | Setting | Default |
72
+ | --- | --- |
73
+ | `runtime` | `auto` |
74
+ | `image` | `docker.io/library/node:20-bookworm` |
75
+ | `session_timeout_hours` | `8` |
76
+ | `network.mode` | `host` |
77
+ | `commands.ensure_commands` | `["git"]` |
78
+ | `slurm.enabled` | `true` |
79
+ | `slurm.mcp_server` | `true` |
80
+ | `audit.enabled` | `true` |
81
+ | `headless.continuation_in_other_terminals` | `true` |
82
+ | `headless.git_integration` | `false` |
118
83
 
119
- Starter template:
84
+ Inspect or change config with:
120
85
 
121
86
  ```bash
122
- templates/tsp-lab/
87
+ labgate config path
88
+ labgate config show
89
+ labgate config get <key>
90
+ labgate config set <key> <value>
91
+ labgate config reset <key>
123
92
  ```
124
93
 
125
- Example flow:
126
-
127
- ```bash
128
- # Create experiment from a local repo
129
- labgate explore create \
130
- --name "TSP baseline" \
131
- --repo /path/to/tsp-lab \
132
- --eval "python3 eval.py" \
133
- --agent-mode stub \
134
- --stub-patch stub-patches/enable_two_opt.patch
135
-
136
- # Trigger one run
137
- labgate explore tick --experiment <experiment-id>
138
-
139
- # Inspect tree and leaderboard
140
- labgate explore tree --experiment <experiment-id> --mode best_path
141
- labgate explore leaderboard --experiment <experiment-id> --top 5
142
- ```
94
+ ## CLI Overview
143
95
 
144
- Cron example (every 5 minutes):
96
+ Core session commands:
145
97
 
146
98
  ```bash
147
- */5 * * * * /usr/bin/env labgate explore tick --experiment <experiment-id>
99
+ labgate init
100
+ labgate claude [workdir]
101
+ labgate codex [workdir]
102
+ labgate ui [--port <number> --listen-address <address> --token <string> | --socket <path>]
103
+ labgate status
104
+ labgate continue [idOrName] [--latest]
105
+ labgate stop <id>
106
+ labgate restart <id> [--dry-run]
107
+ labgate logs [-n|--lines <count>] [--follow]
108
+ labgate feedback [message...]
148
109
  ```
149
110
 
150
- ### Options
111
+ SLURM commands:
151
112
 
152
113
  ```bash
153
- labgate claude --dry-run # print the sandbox command without running
154
- labgate claude --image my-image:tag # use a different container image
155
- labgate claude --no-footer # disable the status footer line
156
- labgate ui # localhost UI on 7700, logs full token URL + short /s/<code> quick link
157
- labgate ui --port 7700 --token Abc123_Xy-9Z # custom quick-link token for /s/<token>
158
- labgate ui --socket ~/.labgate/ui.sock # custom Unix socket path
159
- labgate logs --lines 50 --follow # tail last 50 lines and keep following
114
+ labgate slurm status [--state <state>] [--limit <count>] [--search <query>]
115
+ labgate slurm job <id>
116
+ labgate slurm output <id> [--stderr] [--tail <lines>]
117
+ labgate slurm cancel <id>
118
+ labgate slurm mcp [--db <path>]
160
119
  ```
161
120
 
162
- `labgate claude` auto-starts `labgate ui` when missing in local (non-SSH/non-SLURM) shells.
163
-
164
- ### SLURM inside sandboxes (`sbatch` / `squeue`)
165
-
166
- For Apptainer sessions, LabGate now attempts SLURM CLI passthrough automatically.
167
- If host `sbatch`/`squeue` are available, they are staged into the sandbox, so
168
- `labgate claude` should work without extra config in the common HPC path.
169
-
170
- SLURM tracking and MCP tools are enabled by default (`slurm.enabled=true`).
171
- If native SQLite (`better-sqlite3`) is unavailable on a host, LabGate falls back
172
- to a JSON tracking store automatically.
173
-
174
- Requirements for automatic `sbatch` in sandbox:
175
-
176
- 1. Runtime is Apptainer
177
- 2. The host can resolve SLURM CLI tools when launching LabGate
178
-
179
- If `sbatch` is missing inside the sandbox, run:
121
+ Dataset commands:
180
122
 
181
123
  ```bash
182
- which sbatch # on host, before launching labgate
183
- labgate claude
124
+ labgate dataset list
125
+ labgate dataset init <name>
184
126
  ```
185
127
 
186
- If your cluster uses environment modules, load SLURM first (host shell), then launch LabGate:
128
+ `labgate dataset init` scans an already-registered dataset entry and stores file count and size metadata in config.
187
129
 
188
- ```bash
189
- module load slurm # or your site-specific module name
190
- labgate claude
191
- ```
192
-
193
- Optional (disable SLURM tracking DB + MCP server):
130
+ Solution Explorer commands:
194
131
 
195
132
  ```bash
196
- labgate config set slurm.enabled false
133
+ labgate explore create --name <name> --repo <path> --eval <command> [options]
134
+ labgate explore list [--limit <count>] [--offset <count>]
135
+ labgate explore status <experimentId> [-n|--limit <count>]
136
+ labgate explore pause <experimentId>
137
+ labgate explore resume <experimentId>
138
+ labgate explore tick --experiment <id>
139
+ labgate explore tree --experiment <id> [--mode best_path|full]
140
+ labgate explore leaderboard --experiment <id> [-k|--top <count>]
141
+ labgate explore gc --experiment <id> [--yes]
142
+ labgate explore retention show --experiment <id>
143
+ labgate explore retention set --experiment <id> [retention flags]
144
+ labgate explore compare --experiment <id> --run <runId> [--to best|parent|<runId>] [--diff]
145
+ labgate explore overview --experiment <id>
146
+ labgate explore run --id <runId>
147
+ labgate explore mcp [--db <path>]
197
148
  ```
198
149
 
199
- ## Feedback
150
+ For Claude/Codex sessions, LabGate can also register dataset, cluster, and SLURM MCP servers inside the sandbox when the relevant integrations are enabled.
200
151
 
201
- Submit feedback from the CLI:
152
+ Enterprise commands:
202
153
 
203
154
  ```bash
204
- labgate feedback
205
- echo "This was great" | labgate feedback
206
- labgate feedback "Short feedback message"
155
+ labgate license
156
+ labgate license install <keyOrFile> [--system|--user|--path] [--overwrite]
157
+ labgate register <activationKey> [--server <url>] [--token <token>] [--timeout <ms>] [--system|--user|--path] [--overwrite]
158
+ labgate policy validate [file]
159
+ labgate policy init [--path <path>] [--institution <name>] [--admin <username>] [--runtime <runtime>] [--force]
207
160
  ```
208
161
 
209
- If `LABGATE_FEEDBACK_URL` is set, LabGate will `POST` feedback JSON to that URL.
210
- If `LABGATE_FEEDBACK_TOKEN` is set, it will be sent as a Bearer token.
211
- If no URL is configured, LabGate defaults to `https://labgate.dev/api/feedback`.
212
- If the request fails (or `LABGATE_FEEDBACK_DISABLE=1`), feedback is saved locally at `~/.labgate/feedback.jsonl`.
162
+ For full options, use `labgate <command> --help` or the docs site.
213
163
 
214
- ## Testing
164
+ ## Apptainer and SLURM Notes
215
165
 
216
- Run tests:
166
+ - The primary supported path is login-node `labgate ui` plus compute-node `labgate claude`.
167
+ - LabGate prefers Apptainer on HPC. If you manage runtime explicitly, prefer `apptainer`.
168
+ - SLURM tracking is enabled by default.
169
+ - For Apptainer sessions, LabGate expects host SLURM CLIs such as `sbatch` and `squeue` to be available when the session starts.
170
+ - If your site uses environment modules, load SLURM before launching LabGate:
217
171
 
218
172
  ```bash
219
- npm run setup # install dependencies
220
- npm run verify:quick # build + unit tests
221
- npm run verify # build + unit + integration tests
222
- npm run dev:claude # start UI in background, then launch local labgate claude
223
- npm run test:unit # fast unit tests
224
- npm run test:integration # dashboard integration tests
225
- npm run test:e2e:real # opt-in real runtime OAuth/browser smoke test
226
- npm test # unit + integration
227
- npm run release:check # verify + npm pack --dry-run
173
+ module load slurm
174
+ labgate claude
228
175
  ```
229
176
 
230
- ### Corporate mode smoke test (local or HPC)
177
+ - In SLURM job scripts, use relative paths or real host paths for `#SBATCH --output` and `#SBATCH --error`, not container-only paths such as `/work/...`.
178
+
179
+ Shared SIF cache:
231
180
 
232
181
  ```bash
233
- # 1) Create a signed enterprise key (issuer side)
234
- export LABGATE_LICENSE_SECRET='replace-with-your-signing-secret'
235
- LICENSE_KEY="$(npx tsx scripts/generate-license.ts \
236
- --institution 'Example University' \
237
- --tier pro \
238
- --expires 2099-12-31 2>/dev/null)"
239
-
240
- # 2) Install key on target host (admin side)
241
- labgate license install "$LICENSE_KEY" --path /tmp/labgate/license.key --overwrite
242
- # HPC system-wide install (root):
243
- # sudo labgate license install "$LICENSE_KEY" --system --overwrite
244
-
245
- # Optional online activation (instead of direct install):
246
- # Uses default endpoint: https://labgate.dev/api/license/activate
247
- # labgate register '<activation-key-from-vendor>' --path /tmp/labgate/license.key --overwrite
248
- # Optional custom endpoint:
249
- # export LABGATE_ACTIVATION_URL='https://your-control-plane.example.com/api/license/activate'
250
-
251
- # 3) Bootstrap and validate policy
252
- labgate policy init --path /tmp/labgate/policy.json --admin "$(whoami)" --force
253
- labgate policy validate /tmp/labgate/policy.json
254
-
255
- # 4) Verify forced settings are locked for users
256
- LABGATE_LICENSE_PATH=/tmp/labgate/license.key \
257
- LABGATE_POLICY_PATH=/tmp/labgate/policy.json \
258
- labgate config set runtime auto
259
- # expected: error about admin-locked field
260
-
261
- # 5) Open dashboard and verify UI lock labels ("set by admin")
262
- LABGATE_LICENSE_PATH=/tmp/labgate/license.key \
263
- LABGATE_POLICY_PATH=/tmp/labgate/policy.json \
264
- labgate ui
182
+ labgate config set images_dir /shared/labgate/images
265
183
  ```
266
184
 
267
- Automated enterprise coverage:
185
+ Or with an environment override:
268
186
 
269
187
  ```bash
270
- npx vitest run -c vitest.integration.config.ts src/lib/cli.enterprise.integration.test.ts src/lib/ui.integration.test.ts
188
+ export LABGATE_IMAGES_DIR=/shared/labgate/images
271
189
  ```
272
190
 
273
- CI automation runs `npm run verify` on every push to `main` and on pull requests (`.github/workflows/ci.yml`).
274
- `npm run test:integration` automatically rebuilds `better-sqlite3` first to avoid Node ABI mismatch errors after Node upgrades.
191
+ `LABGATE_IMAGES_DIR` takes precedence over `images_dir`.
275
192
 
276
- Dev launcher options:
193
+ ## Feedback
277
194
 
278
195
  ```bash
279
- LABGATE_UI_PORT=7711 npm run dev:claude -- /path/to/workdir # custom UI port + workdir
280
- LABGATE_KEEP_UI=1 npm run dev:claude -- . # keep UI running after claude exits
196
+ labgate feedback
197
+ labgate feedback "Short feedback message"
198
+ echo "This was great" | labgate feedback
281
199
  ```
282
200
 
283
- Coverage:
284
-
285
- - **`npm run test:unit`** covers config/runtime/container helpers
286
- - **`npm run test:integration`** covers dashboard flow that:
287
- 1. Builds the CLI
288
- 2. Starts `labgate ui` on a temporary localhost port
289
- 3. Launches `labgate claude` and `labgate codex` with a mocked runtime
290
- 4. Verifies sessions appear in `/api/sessions` (dashboard data source)
291
- 5. Stops a session through `POST /api/sessions/stop` and verifies it disappears
292
- 6. Verifies UI port fallback when requested ports are occupied
293
- 7. Verifies `/api/config` accepts valid payloads and rejects invalid payloads without mutating config
294
- 8. Verifies `labgate logs --follow` prints tail output and streams newly appended events
295
- 9. Verifies dashboard activity inference for accessed files and websites from agent transcripts
296
- 10. Verifies `GET/PUT /api/sessions/:id/instructions` with conflict detection for `AGENTS.md` and `CLAUDE.md`
297
- - **`npm run test:e2e:real`** runs a real runtime smoke test for OAuth/browser opening:
298
- 1. Launches real `labgate claude` (no mocked container runtime)
299
- 2. Waits for OAuth URL flow
300
- 3. Verifies host browser-open hook is triggered
301
- 4. Optional override: `LABGATE_REAL_E2E_IMAGE`
302
-
303
- ## How it works
304
-
305
- LabGate builds a sandboxed container from your config:
201
+ Feedback posts to `LABGATE_FEEDBACK_URL` when set, or to `https://labgate.dev/api/feedback` by default. If remote submission fails, LabGate saves feedback locally to `~/.labgate/feedback.jsonl`.
306
202
 
307
- 1. Detects Apptainer first, then Podman (or uses explicit runtime)
308
- 2. Mounts your working directory at `/work`
309
- 3. Mounts persistent sandbox HOME at `/home/sandbox` (for npm cache, agent config)
310
- 4. Overlays blocked paths (`.ssh`, `.aws`, etc.) with empty mounts
311
- 5. Applies network isolation and capability restrictions
312
- 6. Installs the agent (if not cached) and runs it interactively
313
-
314
- On macOS, LabGate syncs your Claude credentials from the system keychain so the agent can authenticate automatically.
315
-
316
- ## Audit logs
317
-
318
- Session events are logged to `~/.labgate/logs/YYYY-MM-DD.jsonl`:
203
+ ## Development
319
204
 
320
205
  ```bash
321
- cat ~/.labgate/logs/2025-02-05.jsonl | jq .
206
+ npm run setup
207
+ npm run verify:quick
208
+ npm run verify
209
+ npm run test:unit
210
+ npm run test:integration
211
+ npm run test:e2e:real
212
+ npm run dev:claude
213
+ npm run release:check
322
214
  ```
323
215
 
324
- ## Roadmap
325
-
326
- - **M0** CLI + sandbox engine + config + audit (this release)
327
- - **M1** Mount allowlists, network filtering, project-level config
328
- - **M2** SLURM proxy (submit/status/cancel from inside sandbox)
329
- - **M3** Web UI for config + audit viewer
330
- - **M4** Institutional mode (/etc/labgate/ policies, admin locks)
331
-
332
216
  ## License
333
217
 
334
- MIT
218
+ License terms: [labgate.dev](https://labgate.dev)
package/dist/cli.js CHANGED
@@ -150,13 +150,7 @@ configCmd
150
150
  process.exit(1);
151
151
  }
152
152
  try {
153
- const rawText = (0, fs_1.readFileSync)(configPath, 'utf-8');
154
- // Strip comments for JSON parsing
155
- const stripped = rawText
156
- .split('\n')
157
- .filter(line => !line.trimStart().startsWith('//'))
158
- .join('\n');
159
- const obj = JSON.parse(stripped);
153
+ const obj = (0, config_js_1.readRawConfigFile)(configPath);
160
154
  // Parse the value (numbers, booleans, JSON arrays/objects)
161
155
  let parsed;
162
156
  if (value === 'true')
@@ -175,7 +169,7 @@ configCmd
175
169
  }
176
170
  setNestedValue(obj, key, parsed);
177
171
  // Re-validate before writing
178
- const testConfig = { ...(0, config_js_1.loadConfig)(), ...obj };
172
+ const testConfig = (0, config_js_1.buildConfigFromRaw)(obj);
179
173
  const errors = (0, config_js_1.validateConfig)(testConfig);
180
174
  if (errors.length > 0) {
181
175
  console.error('Invalid config value:');
@@ -183,8 +177,7 @@ configCmd
183
177
  console.error(` - ${e}`);
184
178
  process.exit(1);
185
179
  }
186
- (0, fs_1.writeFileSync)(configPath, JSON.stringify(obj, null, 2) + '\n', { encoding: 'utf-8', mode: config_js_1.PRIVATE_FILE_MODE });
187
- (0, config_js_1.ensurePrivateFile)(configPath);
180
+ (0, config_js_1.writeRawConfigFile)(obj, configPath);
188
181
  console.log(`Set ${key} = ${JSON.stringify(parsed)}`);
189
182
  }
190
183
  catch (err) {
@@ -208,16 +201,13 @@ configCmd
208
201
  process.exit(1);
209
202
  }
210
203
  try {
211
- const rawText = (0, fs_1.readFileSync)(configPath, 'utf-8');
212
- const stripped = stripJsonComments(rawText).trim();
213
- const obj = stripped ? JSON.parse(stripped) : {};
204
+ const obj = (0, config_js_1.readRawConfigFile)(configPath);
214
205
  const deleted = deleteNestedValue(obj, key);
215
206
  if (!deleted) {
216
207
  console.log(`No user override for ${key} (already using defaults / policy).`);
217
208
  return;
218
209
  }
219
- (0, fs_1.writeFileSync)(configPath, JSON.stringify(obj, null, 2) + '\n', { encoding: 'utf-8', mode: config_js_1.PRIVATE_FILE_MODE });
220
- (0, config_js_1.ensurePrivateFile)(configPath);
210
+ (0, config_js_1.writeRawConfigFile)(obj, configPath);
221
211
  console.log(`Reset ${key} (unset from user config).`);
222
212
  }
223
213
  catch (err) {
@@ -716,15 +706,7 @@ datasetCmd
716
706
  .argument('<name>', 'Dataset name to initialize')
717
707
  .action((name) => {
718
708
  const configPath = (0, config_js_1.getConfigPath)();
719
- const rawText = (0, fs_1.existsSync)(configPath) ? (0, fs_1.readFileSync)(configPath, 'utf-8') : '{}';
720
- const stripped = rawText.split('\n').filter(l => !l.trimStart().startsWith('//')).join('\n');
721
- let obj;
722
- try {
723
- obj = JSON.parse(stripped);
724
- }
725
- catch {
726
- obj = {};
727
- }
709
+ const obj = (0, config_js_1.readRawConfigFile)(configPath);
728
710
  const datasets = (obj.datasets || []);
729
711
  const idx = datasets.findIndex((d) => d?.name && String(d.name).toLowerCase() === name.toLowerCase());
730
712
  if (idx < 0) {
@@ -791,8 +773,7 @@ datasetCmd
791
773
  };
792
774
  ds.stats = stats;
793
775
  obj.datasets = datasets;
794
- (0, fs_1.writeFileSync)(configPath, JSON.stringify(obj, null, 2) + '\n', { encoding: 'utf-8', mode: config_js_1.PRIVATE_FILE_MODE });
795
- (0, config_js_1.ensurePrivateFile)(configPath);
776
+ (0, config_js_1.writeRawConfigFile)(obj, configPath);
796
777
  console.log(`\nDataset "${name}" initialized:`);
797
778
  console.log(` Files: ${stats.file_count}`);
798
779
  console.log(` Size: ${stats.total_size_formatted}`);
@@ -1426,7 +1407,7 @@ policyCmd
1426
1407
  process.exit(1);
1427
1408
  }
1428
1409
  const rawText = (0, fs_1.readFileSync)(policyPath, 'utf-8');
1429
- const stripped = stripJsonComments(rawText);
1410
+ const stripped = (0, config_js_1.stripJsonComments)(rawText);
1430
1411
  const parsed = JSON.parse(stripped);
1431
1412
  const errors = validatePolicy(parsed);
1432
1413
  if (errors.length > 0) {
@@ -1565,12 +1546,6 @@ function deleteNestedValue(obj, key) {
1565
1546
  }
1566
1547
  return true;
1567
1548
  }
1568
- function stripJsonComments(rawText) {
1569
- return rawText
1570
- .split('\n')
1571
- .filter(line => !line.trimStart().startsWith('//'))
1572
- .join('\n');
1573
- }
1574
1549
  function collectStringOption(value, previous) {
1575
1550
  previous.push(value);
1576
1551
  return previous;
@@ -1874,6 +1849,7 @@ async function runAgent(agent, workdir, opts) {
1874
1849
  ['Blacklist', config.commands.blacklist.join(', ')],
1875
1850
  ['Mounts', `${config.filesystem.extra_paths.length} extra`],
1876
1851
  ['Home', `${config_js_1.LABGATE_DIR}/ai-home`],
1852
+ ['SIF cache', (0, config_js_1.getImagesDir)()],
1877
1853
  ];
1878
1854
  if (effective.enterprise) {
1879
1855
  rows.push(['Enterprise', `${effective.institution ?? 'enabled'}`]);