@landstrip/landstrip 0.18.34 → 0.18.36

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
@@ -1,13 +1,9 @@
1
- <!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
2
- <!-- Copyright (C) Jarkko Sakkinen 2026 -->
3
-
4
1
  # landstrip
5
2
 
6
3
  `landstrip` runs commands in an OS-level sandbox using Landlock on Linux,
7
- Seatbelt on macOS, and AppContainer or restricted users on Windows. Policies use
8
- the supported subset of the Anthropic Sandbox Runtime format.
4
+ Seatbelt on macOS, and AppContainer or restricted users on Windows.
9
5
 
10
- ## Installation
6
+ ## Install
11
7
 
12
8
  ```sh
13
9
  npm install --save-dev @landstrip/landstrip
@@ -17,7 +13,7 @@ npx landstrip run -p policy.json -- cargo test
17
13
  The npm package installs a Node.js wrapper and a native binary for the current
18
14
  platform.
19
15
 
20
- ## Quick start
16
+ ## Usage
21
17
 
22
18
  ```sh
23
19
  landstrip run -p policy.json -- cargo test
@@ -26,21 +22,17 @@ landstrip policy resolve -p policy.json
26
22
  landstrip doctor
27
23
  ```
28
24
 
29
- Windows builds also provide `windows install`, `windows status`, and
30
- `windows uninstall`. Full command reference: `landstrip --help` and the
31
- [manual page](man/man1/landstrip.1).
32
-
33
- ### Agent extensions
25
+ Run `landstrip --help` or see [landstrip(1)](man/man1/landstrip.1) for the full
26
+ command reference.
34
27
 
35
- ```sh
36
- pi install npm:pi-landstrip
37
- opencode plugin install opencode-landstrip
38
- ```
28
+ ## Agent extensions
39
29
 
40
- See [pi-landstrip](packages/pi-landstrip/README.md) and
41
- [opencode-landstrip](packages/opencode-landstrip/README.md).
30
+ | Integration | Package | Documentation |
31
+ | ----------- | ---------------------- | -------------------------------------------------------------------- |
32
+ | OpenCode | `opencode-landstrip` | [OpenCode](packages/opencode-landstrip/README.md) |
33
+ | Pi | `pi-landstrip` | [Pi](packages/pi-landstrip/README.md) |
42
34
 
43
- ## Policy sketch
35
+ ## Policy
44
36
 
45
37
  ```json
46
38
  {
@@ -52,49 +44,23 @@ See [pi-landstrip](packages/pi-landstrip/README.md) and
52
44
  },
53
45
  "network": {
54
46
  "allowNetwork": false,
55
- "httpProxyPort": 8080,
56
- "allowLocalBinding": false,
57
- "allowUnixSockets": []
47
+ "allowLocalBinding": false
58
48
  }
59
49
  }
60
50
  ```
61
51
 
62
- Pass policies with `-p, --policy <FILE>` (repeatable). Semantics, platform
63
- limits, traps, and exit status are in the manual page.
52
+ Pass policies with `-p, --policy <FILE>`. Later policies merge over earlier
53
+ ones. Platform behavior and limits are documented in the manual.
64
54
 
65
55
  ## Development
66
56
 
67
- ### Package
68
-
69
- Artifacts land in `npm/*/bin` and `artifacts/`. Staging writes platform tarballs
70
- and matching `<file>.sha256` sidecars under `artifacts/`.
71
-
72
- Non-host targets use [`cargo-zigbuild`](https://github.com/rust-cross/cargo-zigbuild)
73
- (Zig as linker). Requires `cargo-zigbuild`, `zig`, and the matching `rustup`
74
- targets (`x86_64-unknown-linux-musl`, `aarch64-unknown-linux-musl`,
75
- `x86_64-pc-windows-gnu`, `aarch64-pc-windows-gnullvm`, `aarch64-apple-darwin`,
76
- `x86_64-apple-darwin`). Host-native triples use plain `cargo`. Darwin links
77
- `libsandbox`; from Linux set `SDKROOT` to a macOS SDK (Apple SDK; not
78
- redistributed).
79
-
80
- ```sh
81
- make package
82
- make package PLATFORMS='linux-x64 win32-x64'
83
- PACKAGE_STRICT=1 make package
84
- ```
85
-
86
- `make publish` uploads each GitHub release tarball with its `<file>.sha256`
87
- sidecar. Verify a download with:
88
-
89
57
  ```sh
90
- sha256sum -c landstrip-<version>-<platform>.tar.gz.sha256
58
+ make ci
91
59
  ```
92
60
 
93
- ## License
61
+ ## Licensing
94
62
 
95
- `landstrip` is free software under the GNU Lesser General Public License
96
- version 2.1 or later (LGPL-2.1+). See [LICENSE-LGPL-2.1](LICENSE-LGPL-2.1).
63
+ `landstrip` uses [LGPL-2.1+](LICENSE-LGPL-2.1).
97
64
 
98
- The JavaScript npm wrapper is under the Apache License 2.0. See
99
- [LICENSE-APACHE-2.0](LICENSE-APACHE-2.0). Corresponding source for each native
100
- package is available from the matching repository tag.
65
+ The JavaScript wrapper and agent extensions use
66
+ [Apache 2.0](LICENSE-APACHE-2.0).
package/lib/proxy.d.ts ADDED
@@ -0,0 +1,24 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+
3
+ export interface ProxyPortRange {
4
+ low: number;
5
+ high: number;
6
+ }
7
+
8
+ export interface FilterProxyOptions {
9
+ /** Gate for every proxied domain; may prompt interactively. */
10
+ isDomainAllowed(domain: string): boolean | Promise<boolean>;
11
+ /** Exact `Proxy-Authorization` header value required from clients. */
12
+ authorization?: string;
13
+ /** Inclusive listen port range; defaults to an ephemeral port. */
14
+ portRange?: ProxyPortRange;
15
+ }
16
+
17
+ export interface FilterProxyHandle {
18
+ port: number;
19
+ stop(): Promise<void>;
20
+ }
21
+
22
+ export function isPublicProxyAddress(address: string, family?: number): boolean;
23
+
24
+ export function startFilterProxy(options: FilterProxyOptions): Promise<FilterProxyHandle>;
package/lib/proxy.js ADDED
@@ -0,0 +1,304 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ 'use strict';
3
+
4
+ const { lookup } = require('node:dns/promises');
5
+ const { BlockList, connect, createServer, isIP } = require('node:net');
6
+
7
+ const prohibitedProxyAddresses = new BlockList();
8
+
9
+ for (const [network, prefix] of [
10
+ ['0.0.0.0', 8],
11
+ ['10.0.0.0', 8],
12
+ ['100.64.0.0', 10],
13
+ ['127.0.0.0', 8],
14
+ ['169.254.0.0', 16],
15
+ ['172.16.0.0', 12],
16
+ ['192.0.0.0', 24],
17
+ ['192.168.0.0', 16],
18
+ ['198.18.0.0', 15],
19
+ ['224.0.0.0', 4],
20
+ ['240.0.0.0', 4],
21
+ ]) {
22
+ prohibitedProxyAddresses.addSubnet(network, prefix, 'ipv4');
23
+ }
24
+ for (const [network, prefix] of [
25
+ ['::', 128],
26
+ ['::1', 128],
27
+ ['fc00::', 7],
28
+ ['fe80::', 10],
29
+ ['ff00::', 8],
30
+ ]) {
31
+ prohibitedProxyAddresses.addSubnet(network, prefix, 'ipv6');
32
+ }
33
+
34
+ function parseProxyPort(value, defaultPort) {
35
+ const rawPort = value ?? String(defaultPort);
36
+ if (!/^\d+$/.test(rawPort)) return null;
37
+
38
+ const port = Number(rawPort);
39
+ return port >= 1 && port <= 65535 ? port : null;
40
+ }
41
+
42
+ function splitHostPort(target, defaultPort) {
43
+ const bracketMatch = target.match(/^\[([^\]]+)\](?::(.*))?$/);
44
+ const host = bracketMatch?.[1];
45
+ if (host) {
46
+ const port = parseProxyPort(bracketMatch[2], defaultPort);
47
+ return port === null ? null : { host, port };
48
+ }
49
+
50
+ const lastColon = target.lastIndexOf(':');
51
+ if (lastColon > -1 && target.indexOf(':') === lastColon) {
52
+ const port = parseProxyPort(target.slice(lastColon + 1), defaultPort);
53
+ return port === null ? null : { host: target.slice(0, lastColon), port };
54
+ }
55
+
56
+ return { host: target, port: defaultPort };
57
+ }
58
+
59
+ function denyProxyRequest(client, status = '403 Forbidden') {
60
+ client.write(`HTTP/1.1 ${status}\r\nContent-Length: 0\r\n\r\n`);
61
+ client.end();
62
+ }
63
+
64
+ function isPublicProxyAddress(address, family = isIP(address)) {
65
+ if (family === 4) return !prohibitedProxyAddresses.check(address, 'ipv4');
66
+ if (family === 6) return !prohibitedProxyAddresses.check(address, 'ipv6');
67
+ return false;
68
+ }
69
+
70
+ async function resolveProxyEndpoint(host) {
71
+ const literalFamily = isIP(host);
72
+ if (literalFamily === 4 || literalFamily === 6) {
73
+ if (!isPublicProxyAddress(host, literalFamily)) {
74
+ throw new Error(`Proxy destination is not public: ${host}`);
75
+ }
76
+ return { address: host, family: literalFamily };
77
+ }
78
+
79
+ const addresses = await lookup(host, { all: true, verbatim: true });
80
+ if (
81
+ addresses.length === 0 ||
82
+ addresses.some(({ address, family }) => !isPublicProxyAddress(address, family))
83
+ ) {
84
+ throw new Error(`Proxy destination resolves to a non-public address: ${host}`);
85
+ }
86
+
87
+ const endpoint = addresses[0];
88
+ if (endpoint.family !== 4 && endpoint.family !== 6) {
89
+ throw new Error(`Proxy could not resolve destination: ${host}`);
90
+ }
91
+ return { address: endpoint.address, family: endpoint.family };
92
+ }
93
+
94
+ function pipeSockets(client, upstream, initialData) {
95
+ upstream.on('error', () => client.destroy());
96
+ client.on('error', () => upstream.destroy());
97
+
98
+ if (initialData?.length) upstream.write(initialData);
99
+
100
+ client.pipe(upstream);
101
+ upstream.pipe(client);
102
+ }
103
+
104
+ function startFilterProxy(options) {
105
+ const { isDomainAllowed, authorization, portRange } = options;
106
+ const sockets = new Set();
107
+
108
+ // Track the upstream socket so stop() tears it down, and abandon a still-
109
+ // connecting upstream when its client goes away — otherwise a connect to a
110
+ // black-holed host lingers in SYN-retry (~2 min), leaking an fd per request.
111
+ function trackUpstream(upstream, client, settled) {
112
+ sockets.add(upstream);
113
+ upstream.once('close', () => sockets.delete(upstream));
114
+ client.once('close', () => {
115
+ if (!settled()) upstream.destroy();
116
+ });
117
+ }
118
+
119
+ async function handleConnect(client, target, rest) {
120
+ const endpoint = splitHostPort(target, 443);
121
+ if (!endpoint) {
122
+ denyProxyRequest(client, '400 Bad Request');
123
+ return;
124
+ }
125
+
126
+ if (!(await isDomainAllowed(endpoint.host))) {
127
+ denyProxyRequest(client);
128
+ return;
129
+ }
130
+
131
+ const resolved = await resolveProxyEndpoint(endpoint.host);
132
+ let settled = false;
133
+ const upstream = connect(
134
+ { host: resolved.address, port: endpoint.port, family: resolved.family },
135
+ () => {
136
+ settled = true;
137
+ client.write('HTTP/1.1 200 Connection Established\r\n\r\n');
138
+ pipeSockets(client, upstream, rest);
139
+ },
140
+ );
141
+ trackUpstream(upstream, client, () => settled);
142
+ upstream.once('error', () => {
143
+ if (settled) return;
144
+ settled = true;
145
+ denyProxyRequest(client, '502 Bad Gateway');
146
+ });
147
+ }
148
+
149
+ async function handleHttp(client, headerText, rest) {
150
+ const lines = headerText.split(/\r?\n/);
151
+ const requestLine = lines[0];
152
+ if (!requestLine) {
153
+ denyProxyRequest(client, '400 Bad Request');
154
+ return;
155
+ }
156
+
157
+ const [method, rawTarget, version] = requestLine.split(' ');
158
+ if (!method || !rawTarget || !version) {
159
+ denyProxyRequest(client, '400 Bad Request');
160
+ return;
161
+ }
162
+
163
+ let url;
164
+ try {
165
+ url = new URL(rawTarget);
166
+ } catch {
167
+ const host = lines
168
+ .find((line) => line.toLowerCase().startsWith('host:'))
169
+ ?.slice(5)
170
+ .trim();
171
+ if (!host) {
172
+ denyProxyRequest(client, '400 Bad Request');
173
+ return;
174
+ }
175
+
176
+ try {
177
+ url = new URL(`http://${host}${rawTarget}`);
178
+ } catch {
179
+ denyProxyRequest(client, '400 Bad Request');
180
+ return;
181
+ }
182
+ }
183
+
184
+ if (!(await isDomainAllowed(url.hostname))) {
185
+ denyProxyRequest(client);
186
+ return;
187
+ }
188
+
189
+ const port = parseProxyPort(url.port || undefined, url.protocol === 'https:' ? 443 : 80);
190
+ if (port === null) {
191
+ denyProxyRequest(client, '400 Bad Request');
192
+ return;
193
+ }
194
+
195
+ const path = `${url.pathname}${url.search}` || '/';
196
+ lines[0] = `${method} ${path} ${version}`;
197
+
198
+ const rewrittenHeader = lines
199
+ .filter(
200
+ (line) =>
201
+ !line.toLowerCase().startsWith('proxy-connection:') &&
202
+ !line.toLowerCase().startsWith('proxy-authorization:'),
203
+ )
204
+ .map((line) => (line.toLowerCase().startsWith('host:') ? `Host: ${url.host}` : line))
205
+ .join('\r\n');
206
+ const resolved = await resolveProxyEndpoint(url.hostname);
207
+ let settled = false;
208
+ const upstream = connect({ host: resolved.address, port, family: resolved.family }, () => {
209
+ settled = true;
210
+ upstream.write(`${rewrittenHeader}\r\n\r\n`);
211
+ pipeSockets(client, upstream, rest);
212
+ });
213
+ trackUpstream(upstream, client, () => settled);
214
+ upstream.once('error', () => {
215
+ if (settled) return;
216
+ settled = true;
217
+ denyProxyRequest(client, '502 Bad Gateway');
218
+ });
219
+ }
220
+
221
+ function handleClient(client) {
222
+ sockets.add(client);
223
+ client.on('close', () => sockets.delete(client));
224
+ client.on('error', () => sockets.delete(client));
225
+
226
+ let buffered = Buffer.alloc(0);
227
+
228
+ client.on('data', (chunk) => {
229
+ buffered = Buffer.concat([buffered, chunk]);
230
+ const headerEnd = buffered.indexOf('\r\n\r\n');
231
+ if (headerEnd === -1) {
232
+ if (buffered.length > 65536) {
233
+ client.removeAllListeners('data');
234
+ client.pause();
235
+ denyProxyRequest(client, '431 Request Header Fields Too Large');
236
+ }
237
+ return;
238
+ }
239
+
240
+ client.pause();
241
+ client.removeAllListeners('data');
242
+
243
+ const header = buffered.subarray(0, headerEnd).toString('utf-8');
244
+ const rest = buffered.subarray(headerEnd + 4);
245
+ if (authorization) {
246
+ const supplied = header
247
+ .split(/\r?\n/)
248
+ .find((line) => line.toLowerCase().startsWith('proxy-authorization:'))
249
+ ?.slice('proxy-authorization:'.length)
250
+ .trim();
251
+ if (supplied !== authorization) {
252
+ denyProxyRequest(client, '407 Proxy Authentication Required');
253
+ return;
254
+ }
255
+ }
256
+ const [method, target] = header.split(/\r?\n/, 1)[0].split(' ');
257
+
258
+ const task =
259
+ method?.toUpperCase() === 'CONNECT' && target
260
+ ? handleConnect(client, target, rest)
261
+ : handleHttp(client, header, rest);
262
+ task.catch(() => denyProxyRequest(client, '502 Bad Gateway'));
263
+ });
264
+ }
265
+
266
+ const server = createServer(handleClient);
267
+ let stopped = false;
268
+
269
+ return new Promise((resolve, reject) => {
270
+ const listen = (port) => {
271
+ server.once('error', (error) => {
272
+ if (error.code === 'EADDRINUSE' && portRange && port < portRange.high) {
273
+ listen(port + 1);
274
+ return;
275
+ }
276
+ reject(error);
277
+ });
278
+ server.listen(port, '127.0.0.1', () => {
279
+ server.removeAllListeners('error');
280
+ const address = server.address();
281
+
282
+ resolve({
283
+ port: address.port,
284
+ stop: () =>
285
+ new Promise((done) => {
286
+ if (stopped) {
287
+ done();
288
+ return;
289
+ }
290
+ stopped = true;
291
+ for (const socket of sockets) socket.destroy();
292
+ server.close(() => done());
293
+ }),
294
+ });
295
+ });
296
+ };
297
+ listen(portRange ? portRange.low : 0);
298
+ });
299
+ }
300
+
301
+ module.exports = {
302
+ isPublicProxyAddress,
303
+ startFilterProxy,
304
+ };
package/lib/shared.d.ts CHANGED
@@ -13,6 +13,16 @@ export function isRecord(value: unknown): value is Record<string, unknown>;
13
13
 
14
14
  export function expandHomePath(path: string): string;
15
15
 
16
+ export function expandPath(filePath: string, baseDirectory: string): string;
17
+
18
+ export function canonicalizePath(filePath: string, baseDirectory: string): string;
19
+
20
+ export function canonicalizeGlobPattern(pattern: string, baseDirectory: string): string;
21
+
22
+ export function normalizePathSeparators(path: string): string;
23
+
24
+ export function globToRegExp(globPattern: string): RegExp;
25
+
16
26
  export function pathUnderDirectory(filePath: string, dir: string): boolean;
17
27
 
18
28
  export function sessionAllows(prefixes: Set<string>, filePath: string): boolean;
package/lib/shared.js CHANGED
@@ -1,9 +1,9 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  'use strict';
3
3
 
4
- const { realpathSync } = require('node:fs');
4
+ const { lstatSync, readlinkSync, realpathSync } = require('node:fs');
5
5
  const { homedir } = require('node:os');
6
- const { dirname, isAbsolute, join, relative, sep } = require('node:path');
6
+ const { basename, dirname, isAbsolute, join, relative, resolve, sep } = require('node:path');
7
7
 
8
8
  function isRecord(value) {
9
9
  return typeof value === 'object' && value !== null && !Array.isArray(value);
@@ -191,6 +191,81 @@ function formatLandstripTraps(traps) {
191
191
  return traps.map(formatLandstripTrap).join('\n');
192
192
  }
193
193
 
194
+ // Relative entries (notably ".") resolve against the caller-supplied base
195
+ // directory — the command's working directory that landstrip itself uses as
196
+ // its policy base — never the host process's own cwd.
197
+ function expandPath(filePath, baseDirectory) {
198
+ return resolve(baseDirectory, expandHomePath(filePath));
199
+ }
200
+
201
+ function canonicalizePath(filePath, baseDirectory, seen = new Set()) {
202
+ const abs = expandPath(filePath, baseDirectory);
203
+ const missing = [];
204
+ let existing = abs;
205
+
206
+ for (;;) {
207
+ try {
208
+ const stat = lstatSync(existing);
209
+ if (stat.isSymbolicLink()) {
210
+ if (seen.has(existing)) return abs;
211
+ seen.add(existing);
212
+ const target = resolve(dirname(existing), readlinkSync(existing), ...missing);
213
+ return canonicalizePath(target, baseDirectory, seen);
214
+ }
215
+ break;
216
+ } catch {
217
+ const parent = dirname(existing);
218
+ if (parent === existing) return abs;
219
+ missing.unshift(basename(existing));
220
+ existing = parent;
221
+ }
222
+ }
223
+
224
+ try {
225
+ return resolve(realpathSync.native(existing), ...missing);
226
+ } catch {
227
+ return abs;
228
+ }
229
+ }
230
+
231
+ function canonicalizeGlobPattern(pattern, baseDirectory) {
232
+ const expanded = expandPath(pattern, baseDirectory);
233
+ const wildcard = expanded.indexOf('*');
234
+ if (wildcard < 0) return canonicalizePath(expanded, baseDirectory);
235
+
236
+ const prefix = expanded.slice(0, wildcard);
237
+ const base = prefix.endsWith(sep) ? prefix : dirname(prefix);
238
+ const suffixOffset = base.endsWith(sep) ? base.length - 1 : base.length;
239
+ const canonicalBase = canonicalizePath(base, baseDirectory);
240
+ const suffix = expanded.slice(suffixOffset);
241
+ return canonicalBase.endsWith(sep) && suffix.startsWith(sep)
242
+ ? `${canonicalBase}${suffix.slice(1)}`
243
+ : `${canonicalBase}${suffix}`;
244
+ }
245
+
246
+ function normalizePathSeparators(path) {
247
+ return process.platform === 'win32' ? path.replaceAll('\\', '/') : path;
248
+ }
249
+
250
+ const globRegExpCache = new Map();
251
+
252
+ // Translates an absolute glob pattern to a regular expression using standard
253
+ // path semantics: `**` crosses directory boundaries (and `**/` may match zero
254
+ // segments), while a single `*` is confined to one path segment.
255
+ function globToRegExp(globPattern) {
256
+ const cached = globRegExpCache.get(globPattern);
257
+ if (cached) return cached;
258
+
259
+ const escaped = globPattern
260
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&')
261
+ .replace(/\*\*\/|\*\*|\*/g, (token) =>
262
+ token === '**/' ? '(?:.*/)?' : token === '**' ? '.*' : '[^/]*',
263
+ );
264
+ const result = new RegExp(`^${escaped}$`);
265
+ globRegExpCache.set(globPattern, result);
266
+ return result;
267
+ }
268
+
194
269
  // The broker matches an answer to its query by the exact decimal `query_id`
195
270
  // string the trap carried. A numeric id fails its deserializer, the line is
196
271
  // dropped, and the child's syscall stays suspended.
@@ -201,6 +276,11 @@ function controlResponseLine(queryId, action) {
201
276
  module.exports = {
202
277
  isRecord,
203
278
  expandHomePath,
279
+ expandPath,
280
+ canonicalizePath,
281
+ canonicalizeGlobPattern,
282
+ normalizePathSeparators,
283
+ globToRegExp,
204
284
  pathUnderDirectory,
205
285
  sessionAllows,
206
286
  sessionScopeFor,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@landstrip/landstrip",
3
- "version": "0.18.34",
3
+ "version": "0.18.36",
4
4
  "description": "Sandbox runner using Landlock, Seatbelt, and AppContainer",
5
5
  "license": "Apache-2.0 AND LGPL-2.1-or-later",
6
6
  "homepage": "https://github.com/landstrip/landstrip#readme",
@@ -21,6 +21,10 @@
21
21
  "./shared": {
22
22
  "types": "./lib/shared.d.ts",
23
23
  "default": "./lib/shared.js"
24
+ },
25
+ "./proxy": {
26
+ "types": "./lib/proxy.d.ts",
27
+ "default": "./lib/proxy.js"
24
28
  }
25
29
  },
26
30
  "bin": {
@@ -34,24 +38,17 @@
34
38
  "LICENSE-LGPL-2.1"
35
39
  ],
36
40
  "optionalDependencies": {
37
- "@landstrip/landstrip-darwin-arm64": "0.18.34",
38
- "@landstrip/landstrip-darwin-x64": "0.18.34",
39
- "@landstrip/landstrip-linux-x64": "0.18.34",
40
- "@landstrip/landstrip-linux-arm64": "0.18.34",
41
- "@landstrip/landstrip-win32-x64": "0.18.34",
42
- "@landstrip/landstrip-win32-arm64": "0.18.34"
41
+ "@landstrip/landstrip-darwin-arm64": "0.18.36",
42
+ "@landstrip/landstrip-darwin-x64": "0.18.36",
43
+ "@landstrip/landstrip-linux-x64": "0.18.36",
44
+ "@landstrip/landstrip-linux-arm64": "0.18.36",
45
+ "@landstrip/landstrip-win32-x64": "0.18.36",
46
+ "@landstrip/landstrip-win32-arm64": "0.18.36"
43
47
  },
44
48
  "publishConfig": {
45
49
  "access": "public"
46
50
  },
47
51
  "engines": {
48
52
  "node": ">=18"
49
- },
50
- "workspaces": [
51
- "packages/*"
52
- ],
53
- "scripts": {
54
- "ci:extensions": "bash scripts/test-extensions.sh",
55
- "ci:extensions:local": "bash scripts/test-extensions.sh --local-root"
56
53
  }
57
54
  }