pi-landstrip 0.15.7 → 0.15.8
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 +51 -21
- package/package.json +1 -1
package/index.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
// Copyright (C) Jarkko Sakkinen 2026
|
|
3
3
|
|
|
4
|
-
/// <reference path="./landstrip.d.ts" />
|
|
5
|
-
|
|
6
4
|
import type {
|
|
7
5
|
AgentToolResult,
|
|
8
6
|
AgentToolUpdateCallback,
|
|
@@ -70,6 +68,15 @@ interface SandboxConfig {
|
|
|
70
68
|
filesystem: SandboxFilesystemConfig;
|
|
71
69
|
}
|
|
72
70
|
|
|
71
|
+
type SandboxFilesystemConfigFile = Partial<SandboxFilesystemConfig>;
|
|
72
|
+
type SandboxNetworkConfigFile = Partial<SandboxNetworkConfig>;
|
|
73
|
+
|
|
74
|
+
interface SandboxConfigFile {
|
|
75
|
+
enabled?: boolean;
|
|
76
|
+
network?: SandboxNetworkConfigFile;
|
|
77
|
+
filesystem?: SandboxFilesystemConfigFile;
|
|
78
|
+
}
|
|
79
|
+
|
|
73
80
|
type SandboxConfigScope = 'global' | 'project';
|
|
74
81
|
|
|
75
82
|
interface LandstripPolicy {
|
|
@@ -155,8 +162,8 @@ function loadConfig(cwd: string): SandboxConfig {
|
|
|
155
162
|
const projectConfigPath = join(cwd, '.pi', 'sandbox.json');
|
|
156
163
|
const globalConfigPath = join(getAgentDir(), 'sandbox.json');
|
|
157
164
|
|
|
158
|
-
let globalConfig:
|
|
159
|
-
let projectConfig:
|
|
165
|
+
let globalConfig: SandboxConfigFile = {};
|
|
166
|
+
let projectConfig: SandboxConfigFile = {};
|
|
160
167
|
|
|
161
168
|
if (existsSync(globalConfigPath)) {
|
|
162
169
|
try {
|
|
@@ -182,7 +189,7 @@ function mergeArray(base: string[], override?: string[]): string[] {
|
|
|
182
189
|
return [...new Set([...base, ...override])];
|
|
183
190
|
}
|
|
184
191
|
|
|
185
|
-
function deepMerge(base: SandboxConfig, overrides:
|
|
192
|
+
function deepMerge(base: SandboxConfig, overrides: SandboxConfigFile): SandboxConfig {
|
|
186
193
|
const network = overrides.network;
|
|
187
194
|
const filesystem = overrides.filesystem;
|
|
188
195
|
|
|
@@ -212,7 +219,7 @@ function getConfigPaths(cwd: string): { globalPath: string; projectPath: string
|
|
|
212
219
|
};
|
|
213
220
|
}
|
|
214
221
|
|
|
215
|
-
function readOrEmptyConfig(configPath: string):
|
|
222
|
+
function readOrEmptyConfig(configPath: string): SandboxConfigFile {
|
|
216
223
|
if (!existsSync(configPath)) return {};
|
|
217
224
|
try {
|
|
218
225
|
return JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
@@ -221,7 +228,7 @@ function readOrEmptyConfig(configPath: string): Partial<SandboxConfig> {
|
|
|
221
228
|
}
|
|
222
229
|
}
|
|
223
230
|
|
|
224
|
-
function writeConfigFile(configPath: string, config:
|
|
231
|
+
function writeConfigFile(configPath: string, config: SandboxConfigFile): void {
|
|
225
232
|
mkdirSync(dirname(configPath), { recursive: true });
|
|
226
233
|
writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
227
234
|
}
|
|
@@ -255,7 +262,7 @@ async function addDomainToConfig(configPath: string, domain: string): Promise<vo
|
|
|
255
262
|
...config.network,
|
|
256
263
|
allowedDomains: [...existing, domain],
|
|
257
264
|
deniedDomains: config.network?.deniedDomains ?? [],
|
|
258
|
-
}
|
|
265
|
+
};
|
|
259
266
|
writeConfigFile(configPath, config);
|
|
260
267
|
});
|
|
261
268
|
}
|
|
@@ -269,7 +276,7 @@ async function addReadPathToConfig(configPath: string, pathToAdd: string): Promi
|
|
|
269
276
|
config.filesystem = {
|
|
270
277
|
...config.filesystem,
|
|
271
278
|
allowRead: [...existing, pathToAdd],
|
|
272
|
-
}
|
|
279
|
+
};
|
|
273
280
|
writeConfigFile(configPath, config);
|
|
274
281
|
});
|
|
275
282
|
}
|
|
@@ -283,7 +290,7 @@ async function addWritePathToConfig(configPath: string, pathToAdd: string): Prom
|
|
|
283
290
|
config.filesystem = {
|
|
284
291
|
...config.filesystem,
|
|
285
292
|
allowWrite: [...existing, pathToAdd],
|
|
286
|
-
}
|
|
293
|
+
};
|
|
287
294
|
writeConfigFile(configPath, config);
|
|
288
295
|
});
|
|
289
296
|
}
|
|
@@ -816,21 +823,25 @@ function proxyEnv(env: NodeJS.ProcessEnv | undefined, port: number): NodeJS.Proc
|
|
|
816
823
|
};
|
|
817
824
|
}
|
|
818
825
|
|
|
826
|
+
function parseProxyPort(value: string | undefined, defaultPort: number): number | null {
|
|
827
|
+
const rawPort = value ?? String(defaultPort);
|
|
828
|
+
if (!/^\d+$/.test(rawPort)) return null;
|
|
829
|
+
|
|
830
|
+
const port = Number(rawPort);
|
|
831
|
+
return port >= 1 && port <= 65535 ? port : null;
|
|
832
|
+
}
|
|
833
|
+
|
|
819
834
|
function splitHostPort(target: string, defaultPort: number): { host: string; port: number } | null {
|
|
820
|
-
const bracketMatch = target.match(/^\[([^\]]+)\](?::(
|
|
835
|
+
const bracketMatch = target.match(/^\[([^\]]+)\](?::(.*))?$/);
|
|
821
836
|
if (bracketMatch) {
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
port: bracketMatch[2] ? Number(bracketMatch[2]) : defaultPort,
|
|
825
|
-
};
|
|
837
|
+
const port = parseProxyPort(bracketMatch[2], defaultPort);
|
|
838
|
+
return port === null ? null : { host: bracketMatch[1], port };
|
|
826
839
|
}
|
|
827
840
|
|
|
828
841
|
const lastColon = target.lastIndexOf(':');
|
|
829
842
|
if (lastColon > -1 && target.indexOf(':') === lastColon) {
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
port: Number(target.slice(lastColon + 1)),
|
|
833
|
-
};
|
|
843
|
+
const port = parseProxyPort(target.slice(lastColon + 1), defaultPort);
|
|
844
|
+
return port === null ? null : { host: target.slice(0, lastColon), port };
|
|
834
845
|
}
|
|
835
846
|
|
|
836
847
|
return { host: target, port: defaultPort };
|
|
@@ -853,20 +864,28 @@ function pipeSockets(client: Socket, upstream: Socket, initialData?: Buffer): vo
|
|
|
853
864
|
|
|
854
865
|
type LandstripBashTool = ReturnType<typeof createBashToolDefinition>;
|
|
855
866
|
|
|
867
|
+
/** Options for creating a landstrip sandbox integration. */
|
|
856
868
|
export interface LandstripIntegrationOptions {
|
|
869
|
+
/** Register a sandboxed bash tool when the integration is registered. */
|
|
857
870
|
registerBashTool?: boolean;
|
|
871
|
+
/** Working directory used when registering the default bash tool. */
|
|
858
872
|
cwd?: string;
|
|
859
873
|
}
|
|
860
874
|
|
|
875
|
+
/** Landstrip sandbox integration hooks for Pi. */
|
|
861
876
|
export interface LandstripIntegration {
|
|
877
|
+
/** Create a bash tool definition that runs commands through landstrip when enabled. */
|
|
862
878
|
createBashTool(cwd: string, ctx?: ExtensionContext): LandstripBashTool;
|
|
879
|
+
/** Register the integration's tools, events, flags, and commands with Pi. */
|
|
863
880
|
register(pi: ExtensionAPI): void;
|
|
864
881
|
}
|
|
865
882
|
|
|
883
|
+
/** Register the landstrip extension with Pi. */
|
|
866
884
|
export default function (pi: ExtensionAPI) {
|
|
867
885
|
createLandstripIntegration().register(pi);
|
|
868
886
|
}
|
|
869
887
|
|
|
888
|
+
/** Create a landstrip integration for registration or custom embedding. */
|
|
870
889
|
export function createLandstripIntegration(
|
|
871
890
|
options: LandstripIntegrationOptions = {},
|
|
872
891
|
): LandstripIntegration {
|
|
@@ -1037,7 +1056,13 @@ export function createLandstripIntegration(
|
|
|
1037
1056
|
denyProxyRequest(client, '400 Bad Request');
|
|
1038
1057
|
return;
|
|
1039
1058
|
}
|
|
1040
|
-
|
|
1059
|
+
|
|
1060
|
+
try {
|
|
1061
|
+
url = new URL(`http://${host}${rawTarget}`);
|
|
1062
|
+
} catch {
|
|
1063
|
+
denyProxyRequest(client, '400 Bad Request');
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1041
1066
|
}
|
|
1042
1067
|
|
|
1043
1068
|
if (!(await ensureDomainAllowed(ctx, url.hostname, cwd))) {
|
|
@@ -1045,7 +1070,12 @@ export function createLandstripIntegration(
|
|
|
1045
1070
|
return;
|
|
1046
1071
|
}
|
|
1047
1072
|
|
|
1048
|
-
const
|
|
1073
|
+
const defaultPort = url.protocol === 'https:' ? 443 : 80;
|
|
1074
|
+
const port = parseProxyPort(url.port || undefined, defaultPort);
|
|
1075
|
+
if (port === null) {
|
|
1076
|
+
denyProxyRequest(client, '400 Bad Request');
|
|
1077
|
+
return;
|
|
1078
|
+
}
|
|
1049
1079
|
const path = `${url.pathname}${url.search}` || '/';
|
|
1050
1080
|
lines[0] = `${method} ${path} ${version}`;
|
|
1051
1081
|
|