opencode-landstrip 0.3.10 → 0.3.12
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 +19 -7
- package/index.ts +464 -158
- package/landstrip.d.ts +3 -0
- package/package.json +9 -8
- package/sandbox.json +7 -47
- package/tui.ts +265 -43
package/landstrip.d.ts
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-landstrip",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.12",
|
|
4
4
|
"description": "Landlock-based sandboxing for opencode with landstrip",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"landlock",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"files": [
|
|
17
17
|
"index.ts",
|
|
18
18
|
"tui.ts",
|
|
19
|
+
"landstrip.d.ts",
|
|
19
20
|
"README.md",
|
|
20
21
|
"sandbox.json"
|
|
21
22
|
],
|
|
@@ -47,20 +48,20 @@
|
|
|
47
48
|
"ci:test": "npm test"
|
|
48
49
|
},
|
|
49
50
|
"dependencies": {
|
|
50
|
-
"@jarkkojs/landstrip": "^0.11.
|
|
51
|
+
"@jarkkojs/landstrip": "^0.11.11"
|
|
51
52
|
},
|
|
52
53
|
"devDependencies": {
|
|
53
|
-
"@opencode-ai/plugin": "^1.
|
|
54
|
-
"@opentui/core": ">=0.3.
|
|
55
|
-
"@opentui/keymap": ">=0.3.
|
|
56
|
-
"@opentui/solid": ">=0.3.
|
|
54
|
+
"@opencode-ai/plugin": "^1.17.6",
|
|
55
|
+
"@opentui/core": ">=0.3.4",
|
|
56
|
+
"@opentui/keymap": ">=0.3.4",
|
|
57
|
+
"@opentui/solid": ">=0.3.4",
|
|
57
58
|
"@types/node": "^24.0.0",
|
|
58
59
|
"oxfmt": "^0.53.0",
|
|
59
60
|
"oxlint": "^1.68.0",
|
|
60
61
|
"typescript": "^5.8.2"
|
|
61
62
|
},
|
|
62
63
|
"peerDependencies": {
|
|
63
|
-
"@opencode-ai/plugin": "^1.
|
|
64
|
+
"@opencode-ai/plugin": "^1.17.6"
|
|
64
65
|
},
|
|
65
66
|
"peerDependenciesMeta": {
|
|
66
67
|
"@opencode-ai/plugin": {
|
|
@@ -68,6 +69,6 @@
|
|
|
68
69
|
}
|
|
69
70
|
},
|
|
70
71
|
"engines": {
|
|
71
|
-
"opencode": ">=1.
|
|
72
|
+
"opencode": ">=1.17.6"
|
|
72
73
|
}
|
|
73
74
|
}
|
package/sandbox.json
CHANGED
|
@@ -1,57 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"enabled": true,
|
|
3
3
|
"network": {
|
|
4
|
-
"
|
|
4
|
+
"allowNetwork": false,
|
|
5
|
+
"allowLocalBinding": false,
|
|
5
6
|
"allowAllUnixSockets": false,
|
|
6
7
|
"allowUnixSockets": [],
|
|
7
|
-
"allowedDomains": [
|
|
8
|
-
"github.com",
|
|
9
|
-
"*.github.com",
|
|
10
|
-
"api.github.com",
|
|
11
|
-
"raw.githubusercontent.com",
|
|
12
|
-
"objects.githubusercontent.com",
|
|
13
|
-
"codeload.github.com",
|
|
14
|
-
"registry.npmjs.org",
|
|
15
|
-
"npmjs.org",
|
|
16
|
-
"*.npmjs.org",
|
|
17
|
-
"nodejs.org",
|
|
18
|
-
"*.nodejs.org",
|
|
19
|
-
"crates.io",
|
|
20
|
-
"*.crates.io",
|
|
21
|
-
"static.crates.io"
|
|
22
|
-
],
|
|
8
|
+
"allowedDomains": [],
|
|
23
9
|
"deniedDomains": []
|
|
24
10
|
},
|
|
25
11
|
"filesystem": {
|
|
26
|
-
"denyRead": ["/home"],
|
|
27
|
-
"allowRead": [
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
"/var/tmp",
|
|
31
|
-
"/dev/null",
|
|
32
|
-
"~/.config/opencode",
|
|
33
|
-
"~/.config/git",
|
|
34
|
-
"~/.gitconfig",
|
|
35
|
-
"~/.local",
|
|
36
|
-
"~/.cargo",
|
|
37
|
-
"~/.rustup",
|
|
38
|
-
"~/.npm",
|
|
39
|
-
"~/.cache",
|
|
40
|
-
"~/.bun",
|
|
41
|
-
"~/.node-gyp"
|
|
42
|
-
],
|
|
43
|
-
"allowWrite": [
|
|
44
|
-
".",
|
|
45
|
-
"/tmp",
|
|
46
|
-
"/var/tmp",
|
|
47
|
-
"/dev/null",
|
|
48
|
-
"~/.cargo",
|
|
49
|
-
"~/.rustup",
|
|
50
|
-
"~/.npm",
|
|
51
|
-
"~/.cache",
|
|
52
|
-
"~/.bun",
|
|
53
|
-
"~/.node-gyp"
|
|
54
|
-
],
|
|
55
|
-
"denyWrite": [".env", ".env.*", "*.pem", "*.key"]
|
|
12
|
+
"denyRead": ["/Users", "/home"],
|
|
13
|
+
"allowRead": [".", "~/.gitconfig", "/dev/null"],
|
|
14
|
+
"allowWrite": [".", "/dev/null"],
|
|
15
|
+
"denyWrite": ["**/.env", "**/.env.*", "**/*.pem", "**/*.key"]
|
|
56
16
|
}
|
|
57
17
|
}
|
package/tui.ts
CHANGED
|
@@ -5,9 +5,9 @@ import type { TuiPlugin } from '@opencode-ai/plugin/tui';
|
|
|
5
5
|
|
|
6
6
|
import { binaryPath } from '@jarkkojs/landstrip';
|
|
7
7
|
|
|
8
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
8
|
+
import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';
|
|
9
9
|
import { homedir } from 'node:os';
|
|
10
|
-
import { join } from 'node:path';
|
|
10
|
+
import { dirname, join } from 'node:path';
|
|
11
11
|
|
|
12
12
|
interface SandboxFilesystemConfig {
|
|
13
13
|
denyRead: string[];
|
|
@@ -44,38 +44,23 @@ const DEFAULT_CONFIG: SandboxConfig = {
|
|
|
44
44
|
allowLocalBinding: false,
|
|
45
45
|
allowAllUnixSockets: false,
|
|
46
46
|
allowUnixSockets: [],
|
|
47
|
-
allowedDomains: [
|
|
48
|
-
'npmjs.org',
|
|
49
|
-
'*.npmjs.org',
|
|
50
|
-
'registry.npmjs.org',
|
|
51
|
-
'registry.yarnpkg.com',
|
|
52
|
-
'pypi.org',
|
|
53
|
-
'*.pypi.org',
|
|
54
|
-
'github.com',
|
|
55
|
-
'*.github.com',
|
|
56
|
-
'api.github.com',
|
|
57
|
-
'raw.githubusercontent.com',
|
|
58
|
-
'crates.io',
|
|
59
|
-
'*.crates.io',
|
|
60
|
-
'static.crates.io',
|
|
61
|
-
],
|
|
47
|
+
allowedDomains: [],
|
|
62
48
|
deniedDomains: [],
|
|
63
49
|
},
|
|
64
50
|
filesystem: {
|
|
65
51
|
denyRead: ['/Users', '/home'],
|
|
66
|
-
allowRead: [
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
'~/.config/opencode',
|
|
70
|
-
'~/.config/git',
|
|
71
|
-
'~/.gitconfig',
|
|
72
|
-
'~/.local',
|
|
73
|
-
'~/.cargo',
|
|
74
|
-
],
|
|
75
|
-
allowWrite: ['.', '/tmp', '/dev/null'],
|
|
76
|
-
denyWrite: ['.env', '.env.*', '*.pem', '*.key'],
|
|
52
|
+
allowRead: ['.', '~/.gitconfig', '/dev/null'],
|
|
53
|
+
allowWrite: ['.', '/dev/null'],
|
|
54
|
+
denyWrite: ['**/.env', '**/.env.*', '**/*.pem', '**/*.key'],
|
|
77
55
|
},
|
|
78
56
|
};
|
|
57
|
+
const LANDSTRIP_PACKAGE_NAMES = new Set([
|
|
58
|
+
'@jarkkojs/landstrip',
|
|
59
|
+
'@jarkkojs/landstrip-darwin-arm64',
|
|
60
|
+
'@jarkkojs/landstrip-darwin-x64',
|
|
61
|
+
'@jarkkojs/landstrip-linux-x64',
|
|
62
|
+
'@jarkkojs/landstrip-win32-x64',
|
|
63
|
+
]);
|
|
79
64
|
|
|
80
65
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
81
66
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
@@ -147,16 +132,30 @@ function normalizeOptions(options: unknown): SandboxConfigOverrides {
|
|
|
147
132
|
return normalizeConfig(isRecord(options.config) ? options.config : options);
|
|
148
133
|
}
|
|
149
134
|
|
|
135
|
+
function mergeArray(base: string[], override?: string[]): string[] {
|
|
136
|
+
if (!override) return base;
|
|
137
|
+
return [...new Set([...base, ...override])];
|
|
138
|
+
}
|
|
139
|
+
|
|
150
140
|
function deepMerge(base: SandboxConfig, overrides: SandboxConfigOverrides): SandboxConfig {
|
|
141
|
+
const network = overrides.network;
|
|
142
|
+
const filesystem = overrides.filesystem;
|
|
143
|
+
|
|
151
144
|
return {
|
|
152
145
|
enabled: overrides.enabled ?? base.enabled,
|
|
153
146
|
network: {
|
|
154
|
-
|
|
155
|
-
|
|
147
|
+
allowNetwork: network?.allowNetwork ?? base.network.allowNetwork,
|
|
148
|
+
allowLocalBinding: network?.allowLocalBinding ?? base.network.allowLocalBinding,
|
|
149
|
+
allowAllUnixSockets: network?.allowAllUnixSockets ?? base.network.allowAllUnixSockets,
|
|
150
|
+
allowUnixSockets: mergeArray(base.network.allowUnixSockets, network?.allowUnixSockets),
|
|
151
|
+
allowedDomains: mergeArray(base.network.allowedDomains, network?.allowedDomains),
|
|
152
|
+
deniedDomains: mergeArray(base.network.deniedDomains, network?.deniedDomains),
|
|
156
153
|
},
|
|
157
154
|
filesystem: {
|
|
158
|
-
|
|
159
|
-
|
|
155
|
+
denyRead: mergeArray(base.filesystem.denyRead, filesystem?.denyRead),
|
|
156
|
+
allowRead: mergeArray(base.filesystem.allowRead, filesystem?.allowRead),
|
|
157
|
+
allowWrite: mergeArray(base.filesystem.allowWrite, filesystem?.allowWrite),
|
|
158
|
+
denyWrite: mergeArray(base.filesystem.denyWrite, filesystem?.denyWrite),
|
|
160
159
|
},
|
|
161
160
|
};
|
|
162
161
|
}
|
|
@@ -168,20 +167,61 @@ function getConfigPaths(baseDirectory: string): { globalPath: string; projectPat
|
|
|
168
167
|
};
|
|
169
168
|
}
|
|
170
169
|
|
|
171
|
-
function readConfigFile(configPath: string): SandboxConfigOverrides {
|
|
170
|
+
function readConfigFile(configPath: string): SandboxConfigOverrides | null {
|
|
172
171
|
if (!existsSync(configPath)) return {};
|
|
173
172
|
|
|
174
173
|
try {
|
|
175
174
|
return normalizeConfig(JSON.parse(readFileSync(configPath, 'utf-8')));
|
|
176
175
|
} catch {
|
|
177
|
-
return
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function landstripBinaryPath(): string {
|
|
181
|
+
const filePath = realpathSync.native(binaryPath());
|
|
182
|
+
let probe = dirname(filePath);
|
|
183
|
+
|
|
184
|
+
while (true) {
|
|
185
|
+
const manifestPath = join(probe, 'package.json');
|
|
186
|
+
if (existsSync(manifestPath)) {
|
|
187
|
+
try {
|
|
188
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8')) as unknown;
|
|
189
|
+
if (isRecord(manifest) && LANDSTRIP_PACKAGE_NAMES.has(String(manifest.name))) {
|
|
190
|
+
return filePath;
|
|
191
|
+
}
|
|
192
|
+
} catch {
|
|
193
|
+
// malformed package.json — continue walking to parent
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const parent = dirname(probe);
|
|
198
|
+
if (parent === probe) break;
|
|
199
|
+
probe = parent;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
throw new Error(
|
|
203
|
+
`Refusing to use landstrip binary outside official @jarkkojs/landstrip packages: ${filePath}`,
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function writeConfigFile(configPath: string, update: SandboxConfigOverrides): void {
|
|
208
|
+
const current = readConfigFile(configPath);
|
|
209
|
+
if (current === null) {
|
|
210
|
+
throw new Error(`Config file ${configPath} is corrupted; refusing to overwrite`);
|
|
178
211
|
}
|
|
212
|
+
|
|
213
|
+
const next = deepMerge(deepMerge(DEFAULT_CONFIG, current), update);
|
|
214
|
+
|
|
215
|
+
mkdirSync(dirname(configPath), { recursive: true });
|
|
216
|
+
writeFileSync(configPath, JSON.stringify(next, null, 2) + '\n');
|
|
179
217
|
}
|
|
180
218
|
|
|
181
219
|
function loadConfig(baseDirectory: string, optionOverrides: SandboxConfigOverrides): SandboxConfig {
|
|
182
220
|
const { globalPath, projectPath } = getConfigPaths(baseDirectory);
|
|
221
|
+
const globalConfig = readConfigFile(globalPath);
|
|
222
|
+
const projectConfig = readConfigFile(projectPath);
|
|
183
223
|
return deepMerge(
|
|
184
|
-
deepMerge(deepMerge(DEFAULT_CONFIG,
|
|
224
|
+
deepMerge(deepMerge(DEFAULT_CONFIG, globalConfig ?? {}), projectConfig ?? {}),
|
|
185
225
|
optionOverrides,
|
|
186
226
|
);
|
|
187
227
|
}
|
|
@@ -201,7 +241,7 @@ function sandboxSummary(baseDirectory: string, optionOverrides: SandboxConfigOve
|
|
|
201
241
|
|
|
202
242
|
return [
|
|
203
243
|
`Status: ${config.enabled ? 'active' : 'disabled by config'}`,
|
|
204
|
-
`landstrip: ${
|
|
244
|
+
`landstrip package binary: ${landstripBinaryPath()}`,
|
|
205
245
|
'',
|
|
206
246
|
'Config files',
|
|
207
247
|
configPathLine('project', projectPath),
|
|
@@ -223,7 +263,150 @@ function sandboxSummary(baseDirectory: string, optionOverrides: SandboxConfigOve
|
|
|
223
263
|
].join('\n');
|
|
224
264
|
}
|
|
225
265
|
|
|
266
|
+
type PermissionChoice = 'once' | 'session' | 'project' | 'global' | 'reject';
|
|
267
|
+
|
|
268
|
+
function permissionType(permission: Record<string, unknown>, fallback = ''): string {
|
|
269
|
+
if (typeof permission.permission === 'string') return permission.permission;
|
|
270
|
+
if (typeof permission.action === 'string') return permission.action;
|
|
271
|
+
if (typeof permission.type === 'string') return permission.type;
|
|
272
|
+
return fallback;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function permissionPattern(permission: Record<string, unknown>): string | undefined {
|
|
276
|
+
const patterns = permission.patterns;
|
|
277
|
+
if (Array.isArray(patterns))
|
|
278
|
+
return patterns.find((item): item is string => typeof item === 'string');
|
|
279
|
+
|
|
280
|
+
const pattern = permission.pattern;
|
|
281
|
+
if (typeof pattern === 'string') return pattern;
|
|
282
|
+
if (Array.isArray(pattern))
|
|
283
|
+
return pattern.find((item): item is string => typeof item === 'string');
|
|
284
|
+
|
|
285
|
+
return undefined;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function domainsFromCommand(command: string): string[] {
|
|
289
|
+
const domains = new Set<string>();
|
|
290
|
+
const urlRegex = /https?:\/\/([^\s/:?#'"]+)(?::\d+)?(?:[/?#]|\s|$)/g;
|
|
291
|
+
let match: RegExpExecArray | null;
|
|
292
|
+
|
|
293
|
+
while ((match = urlRegex.exec(command)) !== null) domains.add(match[1]);
|
|
294
|
+
|
|
295
|
+
return [...domains];
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function updateForPermission(permission: Record<string, unknown>): SandboxConfigOverrides | null {
|
|
299
|
+
const metadata = isRecord(permission.metadata) ? permission.metadata : {};
|
|
300
|
+
const type = permissionType(permission);
|
|
301
|
+
const pattern = permissionPattern(permission);
|
|
302
|
+
|
|
303
|
+
if (type === 'bash') {
|
|
304
|
+
const command = typeof metadata.command === 'string' ? metadata.command : pattern;
|
|
305
|
+
const domains = typeof command === 'string' ? domainsFromCommand(command) : [];
|
|
306
|
+
return domains.length > 0 ? { network: { allowedDomains: domains } } : null;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (type === 'read' || type === 'glob' || type === 'grep' || type === 'list') {
|
|
310
|
+
const filePath = typeof metadata.filepath === 'string' ? metadata.filepath : pattern;
|
|
311
|
+
return filePath ? { filesystem: { allowRead: [filePath] } } : null;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (type === 'edit' || type === 'write' || type === 'apply_patch') {
|
|
315
|
+
const filePath = typeof metadata.filepath === 'string' ? metadata.filepath : pattern;
|
|
316
|
+
return filePath ? { filesystem: { allowWrite: [filePath] } } : null;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function permissionLabel(permission: Record<string, unknown>): string {
|
|
323
|
+
const type = permissionType(permission, 'permission');
|
|
324
|
+
const title = typeof permission.title === 'string' ? permission.title : type;
|
|
325
|
+
const pattern = permissionPattern(permission);
|
|
326
|
+
return pattern ? `${title}: ${pattern}` : title;
|
|
327
|
+
}
|
|
328
|
+
|
|
226
329
|
const tui: TuiPlugin = async (api, options) => {
|
|
330
|
+
const handledPermissions = new Set<string>();
|
|
331
|
+
|
|
332
|
+
async function replyPermission(
|
|
333
|
+
permission: Record<string, unknown>,
|
|
334
|
+
choice: PermissionChoice,
|
|
335
|
+
): Promise<void> {
|
|
336
|
+
const id = typeof permission.id === 'string' ? permission.id : undefined;
|
|
337
|
+
if (!id || typeof permission.sessionID !== 'string') return;
|
|
338
|
+
|
|
339
|
+
const directory = api.state.path.directory || process.cwd();
|
|
340
|
+
const { globalPath, projectPath } = getConfigPaths(directory);
|
|
341
|
+
|
|
342
|
+
try {
|
|
343
|
+
if (choice === 'project' || choice === 'global') {
|
|
344
|
+
const update = updateForPermission(permission);
|
|
345
|
+
if (update) writeConfigFile(choice === 'project' ? projectPath : globalPath, update);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
await api.client.permission.reply({
|
|
349
|
+
requestID: id,
|
|
350
|
+
reply: choice === 'reject' ? 'reject' : choice === 'once' ? 'once' : 'always',
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
api.ui.toast({
|
|
354
|
+
title: 'Sandbox',
|
|
355
|
+
message: choice === 'reject' ? 'Permission rejected' : `Permission allowed for ${choice}`,
|
|
356
|
+
variant: choice === 'reject' ? 'warning' : 'success',
|
|
357
|
+
});
|
|
358
|
+
} catch {
|
|
359
|
+
api.ui.toast({
|
|
360
|
+
title: 'Sandbox',
|
|
361
|
+
message: 'Permission was already handled or could not be updated',
|
|
362
|
+
variant: 'warning',
|
|
363
|
+
});
|
|
364
|
+
} finally {
|
|
365
|
+
api.ui.dialog.clear();
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function showPermission(permission: Record<string, unknown>): void {
|
|
370
|
+
const id = typeof permission.id === 'string' ? permission.id : undefined;
|
|
371
|
+
if (!id || handledPermissions.has(id)) return;
|
|
372
|
+
handledPermissions.add(id);
|
|
373
|
+
|
|
374
|
+
api.ui.dialog.replace(
|
|
375
|
+
() =>
|
|
376
|
+
api.ui.DialogSelect<PermissionChoice>({
|
|
377
|
+
title: 'Sandbox Permission',
|
|
378
|
+
placeholder: permissionLabel(permission),
|
|
379
|
+
options: [
|
|
380
|
+
{ title: 'Allow once', value: 'once', description: 'Approve only this request' },
|
|
381
|
+
{
|
|
382
|
+
title: 'Allow for session',
|
|
383
|
+
value: 'session',
|
|
384
|
+
description: 'Use OpenCode session approval for matching requests',
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
title: 'Allow for project',
|
|
388
|
+
value: 'project',
|
|
389
|
+
description: 'Persist to .opencode/sandbox.json and approve this session',
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
title: 'Allow globally',
|
|
393
|
+
value: 'global',
|
|
394
|
+
description: 'Persist to ~/.config/opencode/sandbox.json and approve this session',
|
|
395
|
+
},
|
|
396
|
+
{ title: 'Reject', value: 'reject', description: 'Deny this request' },
|
|
397
|
+
],
|
|
398
|
+
onSelect: (option) => {
|
|
399
|
+
void replyPermission(permission, option.value);
|
|
400
|
+
},
|
|
401
|
+
}),
|
|
402
|
+
() => api.ui.dialog.clear(),
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
api.event.on('permission.asked', (event) => {
|
|
407
|
+
showPermission(event.properties as Record<string, unknown>);
|
|
408
|
+
});
|
|
409
|
+
|
|
227
410
|
const showSandbox = () => {
|
|
228
411
|
const directory = api.state.path.directory || process.cwd();
|
|
229
412
|
const message = sandboxSummary(directory, normalizeOptions(options));
|
|
@@ -239,32 +422,71 @@ const tui: TuiPlugin = async (api, options) => {
|
|
|
239
422
|
);
|
|
240
423
|
};
|
|
241
424
|
|
|
425
|
+
const executeServerCommand = async (command: string): Promise<boolean> => {
|
|
426
|
+
await api.client.tui.executeCommand({ command });
|
|
427
|
+
return true;
|
|
428
|
+
};
|
|
429
|
+
|
|
242
430
|
api.keymap.registerLayer({
|
|
243
431
|
commands: [
|
|
244
432
|
{
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
desc: 'Show landstrip sandbox status and rules',
|
|
249
|
-
description: 'Show landstrip sandbox status and rules',
|
|
433
|
+
name: 'sandbox',
|
|
434
|
+
title: 'Sandbox',
|
|
435
|
+
description: 'Show sandbox configuration',
|
|
250
436
|
category: 'Sandbox',
|
|
251
437
|
suggested: true,
|
|
252
|
-
|
|
438
|
+
slash: { name: 'sandbox' },
|
|
253
439
|
run: showSandbox,
|
|
254
440
|
},
|
|
441
|
+
{
|
|
442
|
+
name: 'sandbox-disable',
|
|
443
|
+
title: 'Disable sandbox',
|
|
444
|
+
description: 'Disable sandbox for this session',
|
|
445
|
+
category: 'Sandbox',
|
|
446
|
+
suggested: true,
|
|
447
|
+
slash: { name: 'sandbox-disable' },
|
|
448
|
+
run: () => executeServerCommand('sandbox-disable'),
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
name: 'sandbox-enable',
|
|
452
|
+
title: 'Enable sandbox',
|
|
453
|
+
description: 'Re-enable sandbox for this session',
|
|
454
|
+
category: 'Sandbox',
|
|
455
|
+
suggested: true,
|
|
456
|
+
slash: { name: 'sandbox-enable' },
|
|
457
|
+
run: () => executeServerCommand('sandbox-enable'),
|
|
458
|
+
},
|
|
255
459
|
],
|
|
256
460
|
});
|
|
257
461
|
|
|
258
462
|
api.command?.register(() => [
|
|
259
463
|
{
|
|
260
464
|
title: 'Sandbox',
|
|
261
|
-
value: '
|
|
465
|
+
value: 'sandbox',
|
|
262
466
|
description: 'Show sandbox configuration',
|
|
263
467
|
category: 'Sandbox',
|
|
264
468
|
suggested: true,
|
|
265
469
|
slash: { name: 'sandbox' },
|
|
266
470
|
onSelect: showSandbox,
|
|
267
471
|
},
|
|
472
|
+
{
|
|
473
|
+
title: 'Disable sandbox',
|
|
474
|
+
value: 'sandbox-disable',
|
|
475
|
+
description: 'Disable sandbox for this session',
|
|
476
|
+
category: 'Sandbox',
|
|
477
|
+
suggested: true,
|
|
478
|
+
slash: { name: 'sandbox-disable' },
|
|
479
|
+
onSelect: () => executeServerCommand('sandbox-disable'),
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
title: 'Enable sandbox',
|
|
483
|
+
value: 'sandbox-enable',
|
|
484
|
+
description: 'Re-enable sandbox for this session',
|
|
485
|
+
category: 'Sandbox',
|
|
486
|
+
suggested: true,
|
|
487
|
+
slash: { name: 'sandbox-enable' },
|
|
488
|
+
onSelect: () => executeServerCommand('sandbox-enable'),
|
|
489
|
+
},
|
|
268
490
|
]);
|
|
269
491
|
};
|
|
270
492
|
|