opencode-landstrip 0.3.12 → 0.14.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/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # opencode-landstrip
2
2
 
3
3
  Landlock-based sandboxing for [opencode](https://opencode.ai/) using
4
- [`landstrip`](https://github.com/jarkkojs/landstrip).
4
+ [`landstrip`](https://github.com/landstrip/landstrip).
5
5
 
6
6
  ## Install
7
7
 
@@ -26,9 +26,11 @@ manually:
26
26
 
27
27
  `opencode plugin install opencode-landstrip` configures both entrypoints.
28
28
 
29
- This installs `opencode-landstrip` and its `@jarkkojs/landstrip` dependency, which
29
+ This installs `opencode-landstrip` and its `@landstrip/landstrip` dependency, which
30
30
  includes platform-specific native binaries for Linux, macOS, and Windows.
31
31
 
32
+ Requires OpenCode `1.17.7` or newer.
33
+
32
34
  On unsupported platforms the plugin loads but leaves sandboxing disabled.
33
35
 
34
36
  ## Configure
@@ -48,12 +50,16 @@ access is off unless domains are allowed, reads are limited to the project,
48
50
  `~/.gitconfig`, and `/dev/null`, and writes are limited to the project and
49
51
  `/dev/null`.
50
52
 
51
- Run `/sandbox` in the TUI to inspect the active sandbox configuration.
53
+ Run `/sandbox` in the TUI to inspect the active sandbox configuration. A compact
54
+ status badge in the prompt area shows whether the sandbox is active and whether
55
+ network is proxied or open.
52
56
 
53
- When OpenCode asks for a sandboxed permission, the TUI plugin adds choices to
54
- allow once, allow for the session, persist for the project, persist globally, or
55
- reject. Project approvals are written to `.opencode/sandbox.json`; global
56
- approvals are written to `~/.config/opencode/sandbox.json`.
57
+ When OpenCode asks for a sandboxed permission, the TUI plugin plays the host's
58
+ permission sound and desktop notification, then opens a single dialog with
59
+ choices to allow once, allow for the session, persist for the project, persist
60
+ globally, or reject. The dialog shows the exact path or domain being approved.
61
+ Project approvals are written to `.opencode/sandbox.json`; global approvals are
62
+ written to `~/.config/opencode/sandbox.json`.
57
63
 
58
64
  OpenCode's current plugin API allows wrapping AI `bash` tool calls, but does not
59
65
  allow a plugin to replace manually typed shell-mode commands with a landstrip
@@ -76,5 +82,5 @@ Set `enabled` to `false` in `sandbox.json`, or pass plugin options:
76
82
  `opencode-landstrip` is licensed under `MIT`. See [LICENSE](LICENSE) for more
77
83
  information.
78
84
 
79
- The bundled `@jarkkojs/landstrip` package is licensed separately as
85
+ The bundled `@landstrip/landstrip` package is licensed separately as
80
86
  `Apache-2.0 AND LGPL-2.1-or-later`.
package/index.ts CHANGED
@@ -3,43 +3,23 @@
3
3
 
4
4
  import type { Hooks, Plugin, PluginInput, PluginOptions } from '@opencode-ai/plugin';
5
5
 
6
- import { binaryPath } from '@jarkkojs/landstrip';
7
-
8
6
  import { spawnSync } from 'node:child_process';
9
- import {
10
- existsSync,
11
- mkdtempSync,
12
- readFileSync,
13
- realpathSync,
14
- rmSync,
15
- writeFileSync,
16
- } from 'node:fs';
7
+ import { existsSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
17
8
  import { type AddressInfo, connect as connectNet, createServer, type Socket } from 'node:net';
18
9
  import { homedir, tmpdir } from 'node:os';
19
10
  import { basename, dirname, isAbsolute, join, resolve } from 'node:path';
20
11
  import { URL } from 'node:url';
21
12
 
22
- interface SandboxFilesystemConfig {
23
- denyRead: string[];
24
- allowRead: string[];
25
- allowWrite: string[];
26
- denyWrite: string[];
27
- }
28
-
29
- interface SandboxNetworkConfig {
30
- allowNetwork: boolean;
31
- allowLocalBinding: boolean;
32
- allowAllUnixSockets: boolean;
33
- allowUnixSockets: string[];
34
- allowedDomains: string[];
35
- deniedDomains: string[];
36
- }
37
-
38
- interface SandboxConfig {
39
- enabled: boolean;
40
- network: SandboxNetworkConfig;
41
- filesystem: SandboxFilesystemConfig;
42
- }
13
+ import {
14
+ type SandboxConfig,
15
+ type SandboxFilesystemConfig,
16
+ extractDomainsFromCommand,
17
+ getConfigPaths,
18
+ isRecord,
19
+ landstripBinaryPath,
20
+ loadConfig,
21
+ normalizeOptions,
22
+ } from './shared.js';
43
23
 
44
24
  interface LandstripPolicy {
45
25
  network: {
@@ -52,20 +32,12 @@ interface LandstripPolicy {
52
32
  filesystem: SandboxFilesystemConfig;
53
33
  }
54
34
 
55
- interface LandstripErrorResponse {
56
- reason: 'Other' | 'AccessDenied' | 'LaunchFailed' | 'SetupFailed' | 'Usage';
57
- file?: string;
58
- operation?: 'read' | 'write';
59
- program?: string;
60
- type?: 'filesystem' | 'network' | 'platform' | 'launch' | 'encoding';
61
- source?: string;
62
- }
63
-
64
- interface SandboxConfigOverrides {
65
- enabled?: boolean;
66
- network?: Partial<SandboxNetworkConfig>;
67
- filesystem?: Partial<SandboxFilesystemConfig>;
68
- }
35
+ type LandstripTrap =
36
+ | { kind: 'Filesystem'; operation: 'read' | 'write'; path: string; mechanism: string }
37
+ | { kind: 'Network'; operation: string; target: string; mechanism: string }
38
+ | { kind: 'Launch'; program: string; message: string }
39
+ | { kind: 'Usage'; message: string }
40
+ | { kind: 'Internal'; fields: Record<string, string> };
69
41
 
70
42
  interface BashSandboxState {
71
43
  originalCommand: string;
@@ -86,189 +58,13 @@ interface SandboxPermissionDecision {
86
58
 
87
59
  type ToastVariant = 'info' | 'success' | 'warning' | 'error';
88
60
 
89
- const LANDSTRIP_VERSION = [0, 11, 9] as const;
61
+ const LANDSTRIP_VERSION = [0, 14, 0] as const;
90
62
  const REQUIRED_LANDSTRIP_VERSION = LANDSTRIP_VERSION.join('.');
91
- const LANDSTRIP_ERROR_REASONS = new Set<LandstripErrorResponse['reason']>([
92
- 'Other',
93
- 'AccessDenied',
94
- 'LaunchFailed',
95
- 'SetupFailed',
96
- 'Usage',
97
- ]);
98
- const LANDSTRIP_OPERATIONS = new Set<NonNullable<LandstripErrorResponse['operation']>>([
99
- 'read',
100
- 'write',
101
- ]);
102
- const LANDSTRIP_ERROR_TYPES = new Set<NonNullable<LandstripErrorResponse['type']>>([
103
- 'filesystem',
104
- 'network',
105
- 'platform',
106
- 'launch',
107
- 'encoding',
108
- ]);
63
+ const LANDSTRIP_OPERATIONS = new Set<'read' | 'write'>(['read', 'write']);
109
64
  const SUPPORTED_PLATFORMS = new Set<NodeJS.Platform>(['linux', 'darwin', 'win32']);
110
- const LANDSTRIP_PACKAGE_NAMES = new Set([
111
- '@jarkkojs/landstrip',
112
- '@jarkkojs/landstrip-darwin-arm64',
113
- '@jarkkojs/landstrip-darwin-x64',
114
- '@jarkkojs/landstrip-linux-x64',
115
- '@jarkkojs/landstrip-win32-x64',
116
- ]);
117
-
118
- function isLandstripErrorReason(value: string): value is LandstripErrorResponse['reason'] {
119
- return LANDSTRIP_ERROR_REASONS.has(value as LandstripErrorResponse['reason']);
120
- }
121
-
122
- function isLandstripOperation(
123
- value: string,
124
- ): value is NonNullable<LandstripErrorResponse['operation']> {
125
- return LANDSTRIP_OPERATIONS.has(value as NonNullable<LandstripErrorResponse['operation']>);
126
- }
127
-
128
- function isLandstripErrorType(value: string): value is NonNullable<LandstripErrorResponse['type']> {
129
- return LANDSTRIP_ERROR_TYPES.has(value as NonNullable<LandstripErrorResponse['type']>);
130
- }
131
-
132
- const DEFAULT_CONFIG: SandboxConfig = {
133
- enabled: true,
134
- network: {
135
- allowNetwork: false,
136
- allowLocalBinding: false,
137
- allowAllUnixSockets: false,
138
- allowUnixSockets: [],
139
- allowedDomains: [],
140
- deniedDomains: [],
141
- },
142
- filesystem: {
143
- denyRead: ['/Users', '/home'],
144
- allowRead: ['.', '~/.gitconfig', '/dev/null'],
145
- allowWrite: ['.', '/dev/null'],
146
- denyWrite: ['**/.env', '**/.env.*', '**/*.pem', '**/*.key'],
147
- },
148
- };
149
-
150
- function isRecord(value: unknown): value is Record<string, unknown> {
151
- return typeof value === 'object' && value !== null && !Array.isArray(value);
152
- }
153
-
154
- function stringArray(value: unknown): string[] | undefined {
155
- if (!Array.isArray(value)) return undefined;
156
- return value.every((item) => typeof item === 'string') ? [...value] : undefined;
157
- }
158
-
159
- function normalizeNetworkConfig(value: unknown): Partial<SandboxNetworkConfig> | undefined {
160
- if (!isRecord(value)) return undefined;
161
-
162
- const config: Partial<SandboxNetworkConfig> = {};
163
- if (typeof value.allowNetwork === 'boolean') config.allowNetwork = value.allowNetwork;
164
- if (typeof value.allowLocalBinding === 'boolean')
165
- config.allowLocalBinding = value.allowLocalBinding;
166
- if (typeof value.allowAllUnixSockets === 'boolean')
167
- config.allowAllUnixSockets = value.allowAllUnixSockets;
168
-
169
- const allowUnixSockets = stringArray(value.allowUnixSockets);
170
- if (allowUnixSockets) config.allowUnixSockets = allowUnixSockets;
171
65
 
172
- const allowedDomains = stringArray(value.allowedDomains);
173
- if (allowedDomains) config.allowedDomains = allowedDomains;
174
-
175
- const deniedDomains = stringArray(value.deniedDomains);
176
- if (deniedDomains) config.deniedDomains = deniedDomains;
177
-
178
- return config;
179
- }
180
-
181
- function normalizeFilesystemConfig(value: unknown): Partial<SandboxFilesystemConfig> | undefined {
182
- if (!isRecord(value)) return undefined;
183
-
184
- const config: Partial<SandboxFilesystemConfig> = {};
185
- const denyRead = stringArray(value.denyRead);
186
- if (denyRead) config.denyRead = denyRead;
187
-
188
- const allowRead = stringArray(value.allowRead);
189
- if (allowRead) config.allowRead = allowRead;
190
-
191
- const allowWrite = stringArray(value.allowWrite);
192
- if (allowWrite) config.allowWrite = allowWrite;
193
-
194
- const denyWrite = stringArray(value.denyWrite);
195
- if (denyWrite) config.denyWrite = denyWrite;
196
-
197
- return config;
198
- }
199
-
200
- function normalizeConfig(value: unknown): SandboxConfigOverrides {
201
- if (!isRecord(value)) return {};
202
-
203
- const config: SandboxConfigOverrides = {};
204
- if (typeof value.enabled === 'boolean') config.enabled = value.enabled;
205
-
206
- const network = normalizeNetworkConfig(value.network);
207
- if (network) config.network = network;
208
-
209
- const filesystem = normalizeFilesystemConfig(value.filesystem);
210
- if (filesystem) config.filesystem = filesystem;
211
-
212
- return config;
213
- }
214
-
215
- function normalizeOptions(options: PluginOptions | undefined): SandboxConfigOverrides {
216
- if (!isRecord(options)) return {};
217
- return normalizeConfig(isRecord(options.config) ? options.config : options);
218
- }
219
-
220
- function mergeArray(base: string[], override?: string[]): string[] {
221
- if (!override) return base;
222
- return [...new Set([...base, ...override])];
223
- }
224
-
225
- function deepMerge(base: SandboxConfig, overrides: SandboxConfigOverrides): SandboxConfig {
226
- const network = overrides.network;
227
- const filesystem = overrides.filesystem;
228
-
229
- return {
230
- enabled: overrides.enabled ?? base.enabled,
231
- network: {
232
- allowNetwork: network?.allowNetwork ?? base.network.allowNetwork,
233
- allowLocalBinding: network?.allowLocalBinding ?? base.network.allowLocalBinding,
234
- allowAllUnixSockets: network?.allowAllUnixSockets ?? base.network.allowAllUnixSockets,
235
- allowUnixSockets: mergeArray(base.network.allowUnixSockets, network?.allowUnixSockets),
236
- allowedDomains: mergeArray(base.network.allowedDomains, network?.allowedDomains),
237
- deniedDomains: mergeArray(base.network.deniedDomains, network?.deniedDomains),
238
- },
239
- filesystem: {
240
- denyRead: mergeArray(base.filesystem.denyRead, filesystem?.denyRead),
241
- allowRead: mergeArray(base.filesystem.allowRead, filesystem?.allowRead),
242
- allowWrite: mergeArray(base.filesystem.allowWrite, filesystem?.allowWrite),
243
- denyWrite: mergeArray(base.filesystem.denyWrite, filesystem?.denyWrite),
244
- },
245
- };
246
- }
247
-
248
- function getConfigPaths(baseDirectory: string): { globalPath: string; projectPath: string } {
249
- return {
250
- globalPath: join(homedir(), '.config', 'opencode', 'sandbox.json'),
251
- projectPath: join(baseDirectory, '.opencode', 'sandbox.json'),
252
- };
253
- }
254
-
255
- function readConfigFile(configPath: string): SandboxConfigOverrides {
256
- if (!existsSync(configPath)) return {};
257
-
258
- try {
259
- return normalizeConfig(JSON.parse(readFileSync(configPath, 'utf-8')));
260
- } catch (error) {
261
- console.error(`Warning: Could not parse ${configPath}: ${error}`);
262
- return {};
263
- }
264
- }
265
-
266
- function loadConfig(baseDirectory: string, optionOverrides: SandboxConfigOverrides): SandboxConfig {
267
- const { globalPath, projectPath } = getConfigPaths(baseDirectory);
268
- return deepMerge(
269
- deepMerge(deepMerge(DEFAULT_CONFIG, readConfigFile(globalPath)), readConfigFile(projectPath)),
270
- optionOverrides,
271
- );
66
+ function isLandstripOperation(value: unknown): value is 'read' | 'write' {
67
+ return typeof value === 'string' && LANDSTRIP_OPERATIONS.has(value as 'read' | 'write');
272
68
  }
273
69
 
274
70
  function expandPath(filePath: string, baseDirectory: string): string {
@@ -281,33 +77,6 @@ function configuredShellPath(config: unknown): string | undefined {
281
77
  return typeof config.shell === 'string' ? config.shell : undefined;
282
78
  }
283
79
 
284
- function landstripBinaryPath(): string {
285
- const filePath = realpathSync.native(binaryPath());
286
- let probe = dirname(filePath);
287
-
288
- while (true) {
289
- const manifestPath = join(probe, 'package.json');
290
- if (existsSync(manifestPath)) {
291
- try {
292
- const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8')) as unknown;
293
- if (isRecord(manifest) && LANDSTRIP_PACKAGE_NAMES.has(String(manifest.name))) {
294
- return filePath;
295
- }
296
- } catch {
297
- // malformed package.json — continue walking to parent
298
- }
299
- }
300
-
301
- const parent = dirname(probe);
302
- if (parent === probe) break;
303
- probe = parent;
304
- }
305
-
306
- throw new Error(
307
- `Refusing to use landstrip binary outside official @jarkkojs/landstrip packages: ${filePath}`,
308
- );
309
- }
310
-
311
80
  function canonicalizePath(filePath: string, baseDirectory: string): string {
312
81
  const abs = expandPath(filePath, baseDirectory);
313
82
 
@@ -378,18 +147,6 @@ function shouldPromptForWrite(path: string, allowWrite: string[], baseDirectory:
378
147
  return allowWrite.length === 0 || !matchesPattern(path, allowWrite, baseDirectory);
379
148
  }
380
149
 
381
- function extractDomainsFromCommand(command: string): string[] {
382
- const urlRegex = /https?:\/\/([^\s/:?#'"]+)(?::\d+)?(?:[/?#]|\s|$)/g;
383
- const domains = new Set<string>();
384
- let match: RegExpExecArray | null;
385
-
386
- while ((match = urlRegex.exec(command)) !== null) {
387
- domains.add(match[1]);
388
- }
389
-
390
- return [...domains];
391
- }
392
-
393
150
  function domainMatchesPattern(domain: string, pattern: string): boolean {
394
151
  const normalizedDomain = domain.toLowerCase();
395
152
  const normalizedPattern = pattern.toLowerCase();
@@ -456,13 +213,16 @@ function extractBlockedPath(
456
213
  );
457
214
  if (match) return normalizeBlockedPath(match[1], baseDirectory);
458
215
 
459
- // Landstrip structured error format with file field
216
+ // Landstrip structured trap format carrying a denied path
460
217
  const landstripErrors = parseLandstripErrors(output);
461
- for (const error of landstripErrors) {
462
- if (error.file) return normalizeBlockedPath(error.file, baseDirectory);
218
+ for (const trap of landstripErrors) {
219
+ if (trap.kind === 'Filesystem') return normalizeBlockedPath(trap.path, baseDirectory);
220
+ if (trap.kind === 'Internal' && trap.fields.file) {
221
+ return normalizeBlockedPath(trap.fields.file, baseDirectory);
222
+ }
463
223
  }
464
224
 
465
- // If landstrip reported an error but without a file field, try to
225
+ // If landstrip reported a trap but without a path, try to
466
226
  // extract the blocked path from the command itself
467
227
  if (landstripErrors.length > 0 && command) {
468
228
  for (const candidate of extractCandidatePaths(command)) {
@@ -610,63 +370,94 @@ function hasMinimumVersion(version: string, minimum: readonly [number, number, n
610
370
  return true;
611
371
  }
612
372
 
613
- function parseLandstripErrors(output: string): LandstripErrorResponse[] {
614
- const errors: LandstripErrorResponse[] = [];
615
-
616
- for (const block of output.trim().split(/\n\n+/)) {
617
- const fields: Record<string, string> = {};
373
+ function decodeLandstripTrap(value: unknown): LandstripTrap | null {
374
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) return null;
375
+ const entries = Object.entries(value as Record<string, unknown>);
376
+ if (entries.length !== 1) return null;
377
+ const [kind, payload] = entries[0];
618
378
 
619
- for (const line of block.split('\n')) {
620
- const colonIndex = line.indexOf(':');
621
- if (colonIndex === -1) continue;
622
- const key = line.slice(0, colonIndex).trim();
623
- const value = line.slice(colonIndex + 1).trim();
624
- if (key.length > 0 && value.length > 0) fields[key] = value;
379
+ switch (kind) {
380
+ case 'Filesystem': {
381
+ if (!Array.isArray(payload)) return null;
382
+ const [operation, path, mechanism] = payload;
383
+ if (!isLandstripOperation(operation) || typeof path !== 'string') return null;
384
+ return { kind, operation, path, mechanism: typeof mechanism === 'string' ? mechanism : '' };
625
385
  }
626
-
627
- if (fields.reason && isLandstripErrorReason(fields.reason)) {
628
- const error: LandstripErrorResponse = {
629
- reason: fields.reason,
630
- };
631
-
632
- if (fields.file) error.file = fields.file;
633
- if (fields.operation && isLandstripOperation(fields.operation)) {
634
- error.operation = fields.operation;
386
+ case 'Network': {
387
+ if (!Array.isArray(payload)) return null;
388
+ const [operation, target, mechanism] = payload;
389
+ if (typeof operation !== 'string' || typeof target !== 'string') return null;
390
+ return { kind, operation, target, mechanism: typeof mechanism === 'string' ? mechanism : '' };
391
+ }
392
+ case 'Launch': {
393
+ if (!Array.isArray(payload)) return null;
394
+ const [program, message] = payload;
395
+ if (typeof program !== 'string') return null;
396
+ return { kind, program, message: typeof message === 'string' ? message : '' };
397
+ }
398
+ case 'Usage': {
399
+ if (typeof payload !== 'string') return null;
400
+ return { kind, message: payload };
401
+ }
402
+ case 'Internal': {
403
+ if (typeof payload !== 'object' || payload === null || Array.isArray(payload)) return null;
404
+ const fields: Record<string, string> = {};
405
+ for (const [key, val] of Object.entries(payload as Record<string, unknown>)) {
406
+ fields[key] = typeof val === 'string' ? val : JSON.stringify(val);
635
407
  }
636
- if (fields.program) error.program = fields.program;
637
- if (fields.source) error.source = fields.source;
408
+ return { kind, fields };
409
+ }
410
+ default:
411
+ return null;
412
+ }
413
+ }
414
+
415
+ function parseLandstripErrors(output: string): LandstripTrap[] {
416
+ const traps: LandstripTrap[] = [];
638
417
 
639
- if (fields.type && isLandstripErrorType(fields.type)) error.type = fields.type;
418
+ for (const line of output.split('\n')) {
419
+ const trimmed = line.trim();
420
+ if (trimmed.length === 0 || trimmed[0] !== '{') continue;
640
421
 
641
- errors.push(error);
422
+ let parsed: unknown;
423
+ try {
424
+ parsed = JSON.parse(trimmed);
425
+ } catch {
426
+ continue;
642
427
  }
428
+
429
+ const trap = decodeLandstripTrap(parsed);
430
+ if (trap) traps.push(trap);
643
431
  }
644
432
 
645
- return errors;
433
+ return traps;
434
+ }
435
+
436
+ function formatLandstripTrap(trap: LandstripTrap): string {
437
+ switch (trap.kind) {
438
+ case 'Filesystem':
439
+ return `landstrip: filesystem ${trap.operation} denied (${trap.path})${
440
+ trap.mechanism ? ` [${trap.mechanism}]` : ''
441
+ }`;
442
+ case 'Network':
443
+ return `landstrip: network ${trap.operation} denied (${trap.target})${
444
+ trap.mechanism ? ` [${trap.mechanism}]` : ''
445
+ }`;
446
+ case 'Launch':
447
+ return `landstrip: launch failed (${trap.program})${trap.message ? `: ${trap.message}` : ''}`;
448
+ case 'Usage':
449
+ return `landstrip: usage error: ${trap.message}`;
450
+ case 'Internal': {
451
+ const detail = Object.entries(trap.fields)
452
+ .map(([key, val]) => `${key}: ${val}`)
453
+ .join(', ');
454
+ return `landstrip: internal error${detail ? ` (${detail})` : ''}`;
455
+ }
456
+ }
646
457
  }
647
458
 
648
- function formatLandstripErrors(errors: LandstripErrorResponse[]): string {
649
- return errors
650
- .map((err) => {
651
- const parts: string[] = [`landstrip: ${err.reason}`];
652
-
653
- if (err.file) {
654
- parts.push(` (${err.file})`);
655
- }
656
- if (err.operation) {
657
- parts.push(` ${err.operation}`);
658
- }
659
- if (err.program) {
660
- parts.push(` ${err.program}`);
661
- }
662
- if (err.type) {
663
- parts.push(`:${err.type}`);
664
- }
665
- if (err.source) parts.push(`: ${err.source}`);
666
-
667
- return parts.join('');
668
- })
669
- .join('\n');
459
+ function formatLandstripErrors(traps: LandstripTrap[]): string {
460
+ return traps.map(formatLandstripTrap).join('\n');
670
461
  }
671
462
 
672
463
  function splitHostPort(target: string, defaultPort: number): { host: string; port: number } | null {
@@ -1162,7 +953,7 @@ const plugin: Plugin = async ({ client, directory }: PluginInput, options?: Plug
1162
953
  if (!version) {
1163
954
  landstripCheck = {
1164
955
  ok: false,
1165
- reason: `landstrip was not found. Reinstall with: npm install @jarkkojs/landstrip`,
956
+ reason: `landstrip was not found. Reinstall with: npm install @landstrip/landstrip`,
1166
957
  };
1167
958
  return landstripCheck;
1168
959
  }
package/landstrip.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- declare module '@jarkkojs/landstrip' {
1
+ declare module '@landstrip/landstrip' {
2
2
  function binaryPath(): string;
3
3
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-landstrip",
3
- "version": "0.3.12",
3
+ "version": "0.14.0",
4
4
  "description": "Landlock-based sandboxing for opencode with landstrip",
5
5
  "keywords": [
6
6
  "landlock",
@@ -11,11 +11,12 @@
11
11
  "license": "MIT",
12
12
  "repository": {
13
13
  "type": "git",
14
- "url": "git+https://github.com/jarkkojs/opencode-landstrip.git"
14
+ "url": "git+https://github.com/landstrip/opencode-landstrip.git"
15
15
  },
16
16
  "files": [
17
17
  "index.ts",
18
18
  "tui.ts",
19
+ "shared.ts",
19
20
  "landstrip.d.ts",
20
21
  "README.md",
21
22
  "sandbox.json"
@@ -37,8 +38,8 @@
37
38
  }
38
39
  },
39
40
  "scripts": {
40
- "fmt": "oxfmt index.ts tui.ts package.json tsconfig.json sandbox.json .oxfmtrc.json README.md test/*.test.mjs",
41
- "lint": "oxlint index.ts tui.ts",
41
+ "fmt": "oxfmt index.ts tui.ts shared.ts package.json tsconfig.json sandbox.json .oxfmtrc.json README.md test/*.test.mjs",
42
+ "lint": "oxlint index.ts tui.ts shared.ts",
42
43
  "check": "tsc --noEmit",
43
44
  "test": "node --test test/*.test.mjs",
44
45
  "all": "npm run fmt && npm run lint && npm run check && npm test",
@@ -48,10 +49,10 @@
48
49
  "ci:test": "npm test"
49
50
  },
50
51
  "dependencies": {
51
- "@jarkkojs/landstrip": "^0.11.11"
52
+ "@landstrip/landstrip": "^0.14.5"
52
53
  },
53
54
  "devDependencies": {
54
- "@opencode-ai/plugin": "^1.17.6",
55
+ "@opencode-ai/plugin": "^1.17.7",
55
56
  "@opentui/core": ">=0.3.4",
56
57
  "@opentui/keymap": ">=0.3.4",
57
58
  "@opentui/solid": ">=0.3.4",
@@ -61,7 +62,7 @@
61
62
  "typescript": "^5.8.2"
62
63
  },
63
64
  "peerDependencies": {
64
- "@opencode-ai/plugin": "^1.17.6"
65
+ "@opencode-ai/plugin": "^1.17.7"
65
66
  },
66
67
  "peerDependenciesMeta": {
67
68
  "@opencode-ai/plugin": {
@@ -69,6 +70,6 @@
69
70
  }
70
71
  },
71
72
  "engines": {
72
- "opencode": ">=1.17.6"
73
+ "opencode": ">=1.17.7"
73
74
  }
74
75
  }