pi-landstrip 0.3.1 → 0.3.3

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/index.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  // Copyright (C) Jarkko Sakkinen 2026
3
3
 
4
+ /// <reference path="./landstrip.d.ts" />
5
+
4
6
  import type {
5
7
  AgentToolResult,
6
8
  AgentToolUpdateCallback,
@@ -45,6 +47,7 @@ interface SandboxFilesystemConfig {
45
47
  }
46
48
 
47
49
  interface SandboxNetworkConfig {
50
+ allowNetwork: boolean;
48
51
  allowLocalBinding: boolean;
49
52
  allowAllUnixSockets: boolean;
50
53
  allowUnixSockets: string[];
@@ -60,10 +63,11 @@ interface SandboxConfig {
60
63
 
61
64
  interface LandstripPolicy {
62
65
  network: {
66
+ allowNetwork: boolean;
63
67
  allowLocalBinding: boolean;
64
68
  allowAllUnixSockets: boolean;
65
69
  allowUnixSockets: string[];
66
- httpProxyPort: number;
70
+ httpProxyPort?: number;
67
71
  };
68
72
  filesystem: SandboxFilesystemConfig;
69
73
  }
@@ -76,12 +80,13 @@ interface LandstripErrorResponse {
76
80
  message: string;
77
81
  }
78
82
 
79
- const LANDSTRIP_VERSION = [0, 9, 5] as const;
83
+ const LANDSTRIP_VERSION = [0, 9, 7] as const;
80
84
  const SUPPORTED_PLATFORMS = new Set<NodeJS.Platform>(['linux', 'darwin', 'win32']);
81
85
 
82
86
  const DEFAULT_CONFIG: SandboxConfig = {
83
87
  enabled: true,
84
88
  network: {
89
+ allowNetwork: false,
85
90
  allowLocalBinding: false,
86
91
  allowAllUnixSockets: false,
87
92
  allowUnixSockets: [],
@@ -593,16 +598,33 @@ function pipeSockets(client: Socket, upstream: Socket, initialData?: Buffer): vo
593
598
  upstream.pipe(client);
594
599
  }
595
600
 
601
+ type LandstripBashTool = ReturnType<typeof createBashToolDefinition>;
602
+
603
+ export interface LandstripIntegrationOptions {
604
+ registerBashTool?: boolean;
605
+ cwd?: string;
606
+ }
607
+
608
+ export interface LandstripIntegration {
609
+ createBashTool(cwd: string, ctx?: ExtensionContext): LandstripBashTool;
610
+ register(pi: ExtensionAPI): void;
611
+ }
612
+
596
613
  export default function (pi: ExtensionAPI) {
597
- pi.registerFlag('no-sandbox', {
598
- description: 'Disable landstrip sandboxing for bash commands',
599
- type: 'boolean',
600
- default: false,
601
- });
614
+ createLandstripIntegration().register(pi);
615
+ }
616
+
617
+ export function createLandstripIntegration(
618
+ options: LandstripIntegrationOptions = {},
619
+ ): LandstripIntegration {
620
+ const shouldRegisterBashTool = options.registerBashTool ?? true;
621
+ const localCwd = options.cwd ?? process.cwd();
602
622
 
603
- const localCwd = process.cwd();
604
- const userShellPath = SettingsManager.create(localCwd).getShellPath();
605
- const localBash = createBashToolDefinition(localCwd, { shellPath: userShellPath });
623
+ function createPlainBashTool(cwd: string): LandstripBashTool {
624
+ return createBashToolDefinition(cwd, {
625
+ shellPath: SettingsManager.create(cwd).getShellPath(),
626
+ });
627
+ }
606
628
 
607
629
  let sandboxEnabled = false;
608
630
  let sandboxReady = false;
@@ -675,15 +697,16 @@ export default function (pi: ExtensionAPI) {
675
697
  return true;
676
698
  }
677
699
 
678
- function buildLandstripPolicy(cwd: string, proxyPort: number): LandstripPolicy {
700
+ function buildLandstripPolicy(cwd: string, proxyPort: number | null): LandstripPolicy {
679
701
  const config = loadConfig(cwd);
680
702
 
681
703
  return {
682
704
  network: {
705
+ allowNetwork: config.network.allowNetwork,
683
706
  allowLocalBinding: config.network.allowLocalBinding,
684
707
  allowAllUnixSockets: config.network.allowAllUnixSockets,
685
708
  allowUnixSockets: config.network.allowUnixSockets,
686
- httpProxyPort: proxyPort,
709
+ ...(proxyPort !== null ? { httpProxyPort: proxyPort } : {}),
687
710
  },
688
711
  filesystem: {
689
712
  denyRead: config.filesystem.denyRead,
@@ -694,7 +717,7 @@ export default function (pi: ExtensionAPI) {
694
717
  };
695
718
  }
696
719
 
697
- function writePolicyFile(cwd: string, proxyPort: number): { dir: string; path: string } {
720
+ function writePolicyFile(cwd: string, proxyPort: number | null): { dir: string; path: string } {
698
721
  const dir = mkdtempSync(join(tmpdir(), 'pi-landstrip-'));
699
722
  const path = join(dir, 'policy.json');
700
723
  writeFileSync(
@@ -838,9 +861,12 @@ export default function (pi: ExtensionAPI) {
838
861
  async exec(command, cwd, { onData, signal, timeout, env }) {
839
862
  if (!existsSync(cwd)) throw new Error(`Working directory does not exist: ${cwd}`);
840
863
 
841
- const { shell, args } = getShellConfig(userShellPath);
842
- const proxy = await startProxy(ctx, cwd);
843
- const policy = writePolicyFile(cwd, proxy.port);
864
+ const { shell, args } = getShellConfig(SettingsManager.create(cwd).getShellPath());
865
+ const config = loadConfig(cwd);
866
+ const allowNetwork = config.network.allowNetwork;
867
+ const proxy = allowNetwork ? null : await startProxy(ctx, cwd);
868
+ const proxyPort = proxy ? proxy.port : null;
869
+ const policy = writePolicyFile(cwd, proxyPort);
844
870
  const landstripArgs = ['-p', policy.path, shell, ...args, command];
845
871
 
846
872
  return new Promise((resolvePromise, reject) => {
@@ -853,13 +879,13 @@ export default function (pi: ExtensionAPI) {
853
879
  cleaned = true;
854
880
  if (timeoutHandle) clearTimeout(timeoutHandle);
855
881
  signal?.removeEventListener('abort', onAbort);
856
- void proxy.stop();
882
+ void proxy?.stop();
857
883
  rmSync(policy.dir, { recursive: true, force: true });
858
884
  };
859
885
 
860
886
  const child = spawn(binaryPath(), landstripArgs, {
861
887
  cwd,
862
- env: proxyEnv(env, proxy.port),
888
+ env: allowNetwork ? { ...process.env, ...env } : proxyEnv(env, proxy!.port),
863
889
  detached: true,
864
890
  stdio: ['ignore', 'pipe', 'pipe'],
865
891
  });
@@ -919,11 +945,11 @@ export default function (pi: ExtensionAPI) {
919
945
  ctx: ExtensionContext,
920
946
  ): Promise<AgentToolResult<BashToolDetails | undefined>> {
921
947
  let landstripStderr = '';
922
- const sandboxedBash = createBashToolDefinition(localCwd, {
948
+ const sandboxedBash = createBashToolDefinition(ctx.cwd, {
923
949
  operations: createLandstripBashOps(ctx, (data) => {
924
950
  landstripStderr += data.toString('utf8');
925
951
  }),
926
- shellPath: userShellPath,
952
+ shellPath: SettingsManager.create(ctx.cwd).getShellPath(),
927
953
  });
928
954
 
929
955
  const run = () => sandboxedBash.execute(id, params, signal, onUpdate, ctx);
@@ -986,6 +1012,10 @@ export default function (pi: ExtensionAPI) {
986
1012
  }
987
1013
 
988
1014
  function warnIfAllDomainsAllowed(ctx: ExtensionContext, config: SandboxConfig): void {
1015
+ if (config.network.allowNetwork) {
1016
+ ctx.ui.notify('Network sandbox is disabled because network.allowNetwork is true.', 'warning');
1017
+ return;
1018
+ }
989
1019
  if (!allowsAllDomains(config.network.allowedDomains)) return;
990
1020
  ctx.ui.notify(
991
1021
  'Network sandbox allows all domains because network.allowedDomains contains "*".',
@@ -994,9 +1024,11 @@ export default function (pi: ExtensionAPI) {
994
1024
  }
995
1025
 
996
1026
  function enableStatus(ctx: ExtensionContext, config: SandboxConfig): void {
997
- const networkLabel = allowsAllDomains(config.network.allowedDomains)
998
- ? 'all domains'
999
- : `${config.network.allowedDomains.length} domains`;
1027
+ const networkLabel = config.network.allowNetwork
1028
+ ? 'unrestricted'
1029
+ : allowsAllDomains(config.network.allowedDomains)
1030
+ ? 'all domains'
1031
+ : `${config.network.allowedDomains.length} domains`;
1000
1032
  ctx.ui.setStatus(
1001
1033
  'sandbox',
1002
1034
  ctx.ui.theme.fg(
@@ -1030,7 +1062,7 @@ export default function (pi: ExtensionAPI) {
1030
1062
  if (!hasMinimumVersion(version, LANDSTRIP_VERSION)) {
1031
1063
  sandboxEnabled = false;
1032
1064
  sandboxReady = false;
1033
- ctx.ui.notify(`landstrip 0.9.5 or newer is required; found: ${version}`, 'error');
1065
+ ctx.ui.notify(`landstrip 0.9.7 or newer is required; found: ${version}`, 'error');
1034
1066
  return false;
1035
1067
  }
1036
1068
 
@@ -1041,178 +1073,207 @@ export default function (pi: ExtensionAPI) {
1041
1073
  return true;
1042
1074
  }
1043
1075
 
1044
- pi.registerTool({
1045
- ...localBash,
1046
- label: 'bash (landstrip)',
1047
- async execute(id, params, signal, onUpdate, ctx) {
1048
- if (!sandboxEnabled || !sandboxReady)
1049
- return localBash.execute(id, params, signal, onUpdate, ctx);
1076
+ function createBashTool(cwd: string, ctx?: ExtensionContext): LandstripBashTool {
1077
+ const localBash = createPlainBashTool(cwd);
1050
1078
 
1051
- return runBashWithOptionalRetry(id, params, signal, onUpdate, ctx);
1052
- },
1053
- });
1054
-
1055
- pi.on('user_bash', async (event, ctx) => {
1056
- if (!sandboxEnabled || !sandboxReady) return;
1057
- if (!loadConfig(ctx.cwd).enabled) return;
1058
-
1059
- const blockedDomain = await preflightCommandDomains(event.command, ctx);
1060
- if (blockedDomain) {
1061
- return {
1062
- result: {
1063
- output: `Blocked: "${blockedDomain}" is not allowed by the sandbox. Use /sandbox to review your config.`,
1064
- exitCode: 1,
1065
- cancelled: false,
1066
- truncated: false,
1067
- },
1068
- };
1069
- }
1070
-
1071
- return { operations: createLandstripBashOps(ctx) };
1072
- });
1079
+ return {
1080
+ ...localBash,
1081
+ label: 'bash (landstrip)',
1082
+ async execute(id, params, signal, onUpdate, callCtx) {
1083
+ const effectiveCtx = callCtx ?? ctx;
1084
+ if (!sandboxEnabled || !sandboxReady || !effectiveCtx)
1085
+ return localBash.execute(id, params, signal, onUpdate, effectiveCtx);
1086
+
1087
+ return runBashWithOptionalRetry(id, params, signal, onUpdate, effectiveCtx);
1088
+ },
1089
+ };
1090
+ }
1073
1091
 
1074
- pi.on('tool_call', async (event, ctx) => {
1075
- if (!sandboxEnabled) return;
1092
+ function register(pi: ExtensionAPI): void {
1093
+ const maybePi = pi as ExtensionAPI & {
1094
+ getFlag?: (name: string) => unknown;
1095
+ registerCommand?: ExtensionAPI['registerCommand'];
1096
+ registerFlag?: ExtensionAPI['registerFlag'];
1097
+ };
1076
1098
 
1077
- const config = loadConfig(ctx.cwd);
1078
- if (!config.enabled) return;
1099
+ maybePi.registerFlag?.('no-sandbox', {
1100
+ description: 'Disable landstrip sandboxing for bash commands',
1101
+ type: 'boolean',
1102
+ default: false,
1103
+ });
1079
1104
 
1080
- const { globalPath, projectPath } = getConfigPaths(ctx.cwd);
1105
+ if (shouldRegisterBashTool) pi.registerTool(createBashTool(localCwd));
1081
1106
 
1082
- if (sandboxReady && isToolCallEventType('bash', event)) {
1083
- const blockedDomain = await preflightCommandDomains(event.input.command, ctx);
1084
- if (blockedDomain) {
1085
- return {
1086
- block: true,
1087
- reason: `Network access to "${blockedDomain}" is blocked by the sandbox.`,
1088
- };
1089
- }
1090
- }
1107
+ pi.on('user_bash', async (event, ctx) => {
1108
+ if (!sandboxEnabled || !sandboxReady) return;
1109
+ const config = loadConfig(ctx.cwd);
1110
+ if (!config.enabled) return;
1091
1111
 
1092
- if (isToolCallEventType('read', event)) {
1093
- const filePath = canonicalizePath(event.input.path);
1094
- if (!matchesPattern(filePath, getEffectiveAllowRead(ctx.cwd))) {
1095
- const choice = await promptReadBlock(ctx, filePath);
1096
- if (choice === 'abort') {
1112
+ if (!config.network.allowNetwork) {
1113
+ const blockedDomain = await preflightCommandDomains(event.command, ctx);
1114
+ if (blockedDomain) {
1097
1115
  return {
1098
- block: true,
1099
- reason: `Sandbox: read access denied for "${filePath}"`,
1116
+ result: {
1117
+ output: `Blocked: "${blockedDomain}" is not allowed by the sandbox. Use /sandbox to review your config.`,
1118
+ exitCode: 1,
1119
+ cancelled: false,
1120
+ truncated: false,
1121
+ },
1100
1122
  };
1101
1123
  }
1102
- await applyReadChoice(choice, filePath, ctx.cwd);
1103
1124
  }
1104
- }
1105
1125
 
1106
- if (isToolCallEventType('write', event) || isToolCallEventType('edit', event)) {
1107
- const filePath = canonicalizePath((event.input as { path: string }).path);
1126
+ return { operations: createLandstripBashOps(ctx) };
1127
+ });
1128
+
1129
+ pi.on('tool_call', async (event, ctx) => {
1130
+ if (!sandboxEnabled) return;
1131
+
1132
+ const config = loadConfig(ctx.cwd);
1133
+ if (!config.enabled) return;
1134
+
1135
+ const { globalPath, projectPath } = getConfigPaths(ctx.cwd);
1108
1136
 
1109
- if (matchesPattern(filePath, config.filesystem.denyWrite)) {
1110
- return {
1111
- block: true,
1112
- reason:
1113
- `Sandbox: write access denied for "${filePath}" (in denyWrite). ` +
1114
- `To change this, edit denyWrite in:\n ${projectPath}\n ${globalPath}`,
1115
- };
1137
+ if (sandboxReady && isToolCallEventType('bash', event)) {
1138
+ if (!config.network.allowNetwork) {
1139
+ const blockedDomain = await preflightCommandDomains(event.input.command, ctx);
1140
+ if (blockedDomain) {
1141
+ return {
1142
+ block: true,
1143
+ reason: `Network access to "${blockedDomain}" is blocked by the sandbox.`,
1144
+ };
1145
+ }
1146
+ }
1116
1147
  }
1117
1148
 
1118
- if (shouldPromptForWrite(filePath, getEffectiveAllowWrite(ctx.cwd), matchesPattern)) {
1119
- const choice = await promptWriteBlock(ctx, filePath);
1120
- if (choice === 'abort') {
1121
- return {
1122
- block: true,
1123
- reason: `Sandbox: write access denied for "${filePath}" (not in allowWrite)`,
1124
- };
1149
+ if (isToolCallEventType('read', event)) {
1150
+ const filePath = canonicalizePath(event.input.path);
1151
+ if (!matchesPattern(filePath, getEffectiveAllowRead(ctx.cwd))) {
1152
+ const choice = await promptReadBlock(ctx, filePath);
1153
+ if (choice === 'abort') {
1154
+ return {
1155
+ block: true,
1156
+ reason: `Sandbox: read access denied for "${filePath}"`,
1157
+ };
1158
+ }
1159
+ await applyReadChoice(choice, filePath, ctx.cwd);
1125
1160
  }
1126
- await applyWriteChoice(choice, filePath, ctx.cwd);
1127
1161
  }
1128
- }
1129
- });
1130
1162
 
1131
- pi.on('session_start', async (_event, ctx) => {
1132
- const noSandbox = pi.getFlag('no-sandbox') as boolean;
1163
+ if (isToolCallEventType('write', event) || isToolCallEventType('edit', event)) {
1164
+ const filePath = canonicalizePath((event.input as { path: string }).path);
1133
1165
 
1134
- if (noSandbox) {
1135
- sandboxEnabled = false;
1136
- sandboxReady = false;
1137
- ctx.ui.notify('Sandbox disabled via --no-sandbox', 'warning');
1138
- return;
1139
- }
1166
+ if (matchesPattern(filePath, config.filesystem.denyWrite)) {
1167
+ return {
1168
+ block: true,
1169
+ reason:
1170
+ `Sandbox: write access denied for "${filePath}" (in denyWrite). ` +
1171
+ `To change this, edit denyWrite in:\n ${projectPath}\n ${globalPath}`,
1172
+ };
1173
+ }
1140
1174
 
1141
- const config = loadConfig(ctx.cwd);
1142
- if (!config.enabled) {
1143
- sandboxEnabled = false;
1144
- sandboxReady = false;
1145
- ctx.ui.notify('Sandbox disabled via config', 'info');
1146
- return;
1147
- }
1175
+ if (shouldPromptForWrite(filePath, getEffectiveAllowWrite(ctx.cwd), matchesPattern)) {
1176
+ const choice = await promptWriteBlock(ctx, filePath);
1177
+ if (choice === 'abort') {
1178
+ return {
1179
+ block: true,
1180
+ reason: `Sandbox: write access denied for "${filePath}" (not in allowWrite)`,
1181
+ };
1182
+ }
1183
+ await applyWriteChoice(choice, filePath, ctx.cwd);
1184
+ }
1185
+ }
1186
+ });
1148
1187
 
1149
- enableSandbox(ctx);
1150
- });
1188
+ pi.on('session_start', async (_event, ctx) => {
1189
+ const noSandbox = maybePi.getFlag?.('no-sandbox') as boolean;
1151
1190
 
1152
- pi.registerCommand('sandbox-enable', {
1153
- description: 'Enable the landstrip sandbox for this session',
1154
- handler: async (_args, ctx) => {
1155
- if (sandboxEnabled) {
1156
- ctx.ui.notify('Sandbox is already enabled', 'info');
1191
+ if (noSandbox) {
1192
+ sandboxEnabled = false;
1193
+ sandboxReady = false;
1194
+ ctx.ui.notify('Sandbox disabled via --no-sandbox', 'warning');
1157
1195
  return;
1158
1196
  }
1159
1197
 
1160
- if (enableSandbox(ctx)) ctx.ui.notify('Sandbox enabled', 'info');
1161
- },
1162
- });
1163
-
1164
- pi.registerCommand('sandbox-disable', {
1165
- description: 'Disable the landstrip sandbox for this session',
1166
- handler: async (_args, ctx) => {
1167
- if (!sandboxEnabled) {
1168
- ctx.ui.notify('Sandbox is already disabled', 'info');
1198
+ const config = loadConfig(ctx.cwd);
1199
+ if (!config.enabled) {
1200
+ sandboxEnabled = false;
1201
+ sandboxReady = false;
1202
+ ctx.ui.notify('Sandbox disabled via config', 'info');
1169
1203
  return;
1170
1204
  }
1171
1205
 
1172
- sandboxEnabled = false;
1173
- sandboxReady = false;
1174
- ctx.ui.setStatus('sandbox', '');
1175
- ctx.ui.notify('Sandbox disabled', 'info');
1176
- },
1177
- });
1206
+ enableSandbox(ctx);
1207
+ });
1178
1208
 
1179
- pi.registerCommand('sandbox', {
1180
- description: 'Show sandbox configuration',
1181
- handler: async (_args, ctx) => {
1182
- if (!sandboxEnabled) {
1183
- ctx.ui.notify('Sandbox is disabled', 'info');
1184
- return;
1185
- }
1209
+ maybePi.registerCommand?.('sandbox-enable', {
1210
+ description: 'Enable the landstrip sandbox for this session',
1211
+ handler: async (_args, ctx) => {
1212
+ if (sandboxEnabled) {
1213
+ ctx.ui.notify('Sandbox is already enabled', 'info');
1214
+ return;
1215
+ }
1186
1216
 
1187
- const config = loadConfig(ctx.cwd);
1188
- const { globalPath, projectPath } = getConfigPaths(ctx.cwd);
1189
- const lines = [
1190
- 'Sandbox Configuration',
1191
- ` Project config: ${projectPath}`,
1192
- ` Global config: ${globalPath}`,
1193
- ` landstrip: ${binaryPath()}`,
1194
- '',
1195
- 'Network (bash through HTTP proxy):',
1196
- ` Allowed domains: ${config.network.allowedDomains.join(', ') || '(none)'}`,
1197
- ` Denied domains: ${config.network.deniedDomains.join(', ') || '(none)'}`,
1198
- ...(sessionAllowedDomains.length > 0
1199
- ? [` Session allowed: ${sessionAllowedDomains.join(', ')}`]
1200
- : []),
1201
- '',
1202
- 'Filesystem (bash + read/write/edit tools):',
1203
- ` Deny Read: ${config.filesystem.denyRead.join(', ') || '(none)'}`,
1204
- ` Allow Read: ${config.filesystem.allowRead.join(', ') || '(none)'}`,
1205
- ` Allow Write: ${config.filesystem.allowWrite.join(', ') || '(none)'}`,
1206
- ` Deny Write: ${config.filesystem.denyWrite.join(', ') || '(none)'}`,
1207
- ...(sessionAllowedReadPaths.length > 0
1208
- ? [` Session read: ${sessionAllowedReadPaths.join(', ')}`]
1209
- : []),
1210
- ...(sessionAllowedWritePaths.length > 0
1211
- ? [` Session write: ${sessionAllowedWritePaths.join(', ')}`]
1212
- : []),
1213
- ];
1214
-
1215
- ctx.ui.notify(lines.join('\n'), 'info');
1216
- },
1217
- });
1217
+ if (enableSandbox(ctx)) ctx.ui.notify('Sandbox enabled', 'info');
1218
+ },
1219
+ });
1220
+
1221
+ maybePi.registerCommand?.('sandbox-disable', {
1222
+ description: 'Disable the landstrip sandbox for this session',
1223
+ handler: async (_args, ctx) => {
1224
+ if (!sandboxEnabled) {
1225
+ ctx.ui.notify('Sandbox is already disabled', 'info');
1226
+ return;
1227
+ }
1228
+
1229
+ sandboxEnabled = false;
1230
+ sandboxReady = false;
1231
+ ctx.ui.setStatus('sandbox', '');
1232
+ ctx.ui.notify('Sandbox disabled', 'info');
1233
+ },
1234
+ });
1235
+
1236
+ maybePi.registerCommand?.('sandbox', {
1237
+ description: 'Show sandbox configuration',
1238
+ handler: async (_args, ctx) => {
1239
+ if (!sandboxEnabled) {
1240
+ ctx.ui.notify('Sandbox is disabled', 'info');
1241
+ return;
1242
+ }
1243
+
1244
+ const config = loadConfig(ctx.cwd);
1245
+ const { globalPath, projectPath } = getConfigPaths(ctx.cwd);
1246
+ const lines = [
1247
+ 'Sandbox Configuration',
1248
+ ` Project config: ${projectPath}`,
1249
+ ` Global config: ${globalPath}`,
1250
+ ` landstrip: ${binaryPath()}`,
1251
+ '',
1252
+ `Network (bash ${config.network.allowNetwork ? 'unrestricted' : 'through HTTP proxy'}):`,
1253
+ ` Allow network: ${config.network.allowNetwork}`,
1254
+ ` Allowed domains: ${config.network.allowedDomains.join(', ') || '(none)'}`,
1255
+ ` Denied domains: ${config.network.deniedDomains.join(', ') || '(none)'}`,
1256
+ ...(sessionAllowedDomains.length > 0
1257
+ ? [` Session allowed: ${sessionAllowedDomains.join(', ')}`]
1258
+ : []),
1259
+ '',
1260
+ 'Filesystem (bash + read/write/edit tools):',
1261
+ ` Deny Read: ${config.filesystem.denyRead.join(', ') || '(none)'}`,
1262
+ ` Allow Read: ${config.filesystem.allowRead.join(', ') || '(none)'}`,
1263
+ ` Allow Write: ${config.filesystem.allowWrite.join(', ') || '(none)'}`,
1264
+ ` Deny Write: ${config.filesystem.denyWrite.join(', ') || '(none)'}`,
1265
+ ...(sessionAllowedReadPaths.length > 0
1266
+ ? [` Session read: ${sessionAllowedReadPaths.join(', ')}`]
1267
+ : []),
1268
+ ...(sessionAllowedWritePaths.length > 0
1269
+ ? [` Session write: ${sessionAllowedWritePaths.join(', ')}`]
1270
+ : []),
1271
+ ];
1272
+
1273
+ ctx.ui.notify(lines.join('\n'), 'info');
1274
+ },
1275
+ });
1276
+ }
1277
+
1278
+ return { createBashTool, register };
1218
1279
  }
package/landstrip.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ declare module '@jarkkojs/landstrip' {
2
+ function binaryPath(): string;
3
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-landstrip",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "Landlock-based sandboxing for pi with interactive permission prompts",
5
5
  "keywords": [
6
6
  "landstrip",
@@ -15,6 +15,7 @@
15
15
  },
16
16
  "files": [
17
17
  "index.ts",
18
+ "landstrip.d.ts",
18
19
  "README.md",
19
20
  "sandbox.json"
20
21
  ],
@@ -30,7 +31,7 @@
30
31
  },
31
32
  "dependencies": {
32
33
  "@earendil-works/pi-tui": "^0.78.0",
33
- "@jarkkojs/landstrip": "^0.9.5"
34
+ "@jarkkojs/landstrip": "^0.9.7"
34
35
  },
35
36
  "devDependencies": {
36
37
  "@earendil-works/pi-coding-agent": "^0.78.0",
package/sandbox.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "enabled": true,
3
3
  "network": {
4
+ "allowNetwork": false,
4
5
  "allowLocalBinding": true,
5
6
  "allowAllUnixSockets": false,
6
7
  "allowUnixSockets": [],