pi-landstrip 0.11.8 → 0.11.10
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 +5 -6
- package/index.ts +109 -120
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,21 +23,20 @@ Project config takes precedence.
|
|
|
23
23
|
|
|
24
24
|
See [`sandbox.json`](./sandbox.json) for a starter config.
|
|
25
25
|
|
|
26
|
-
Use
|
|
26
|
+
Use sandbox config to toggle sandboxing:
|
|
27
27
|
|
|
28
28
|
```json
|
|
29
29
|
{
|
|
30
|
-
"
|
|
31
|
-
"enabled": false
|
|
32
|
-
}
|
|
30
|
+
"enabled": false
|
|
33
31
|
}
|
|
34
32
|
```
|
|
35
33
|
|
|
36
|
-
Project
|
|
34
|
+
Project config overrides global config.
|
|
35
|
+
The `/sandbox` UI updates the project config when present, otherwise the global config.
|
|
37
36
|
|
|
38
37
|
## Usage
|
|
39
38
|
|
|
40
|
-
Use `/sandbox` inside Pi to show the active config.
|
|
39
|
+
Use `/sandbox` inside Pi to show the active config and toggle sandboxing.
|
|
41
40
|
|
|
42
41
|
## License
|
|
43
42
|
|
package/index.ts
CHANGED
|
@@ -62,11 +62,7 @@ interface SandboxConfig {
|
|
|
62
62
|
filesystem: SandboxFilesystemConfig;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
landstrip?: {
|
|
67
|
-
enabled?: boolean;
|
|
68
|
-
};
|
|
69
|
-
}
|
|
65
|
+
type SandboxConfigScope = 'global' | 'project';
|
|
70
66
|
|
|
71
67
|
interface LandstripPolicy {
|
|
72
68
|
network: {
|
|
@@ -190,14 +186,6 @@ function loadConfig(cwd: string): SandboxConfig {
|
|
|
190
186
|
return deepMerge(deepMerge(DEFAULT_CONFIG, globalConfig), projectConfig);
|
|
191
187
|
}
|
|
192
188
|
|
|
193
|
-
function isSandboxEnabledInPiSettings(cwd: string): boolean {
|
|
194
|
-
const settings = SettingsManager.create(cwd);
|
|
195
|
-
const globalSettings = settings.getGlobalSettings() as PiLandstripSettings;
|
|
196
|
-
const projectSettings = settings.getProjectSettings() as PiLandstripSettings;
|
|
197
|
-
|
|
198
|
-
return projectSettings.landstrip?.enabled ?? globalSettings.landstrip?.enabled ?? true;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
189
|
function mergeArray(base: string[], override?: string[]): string[] {
|
|
202
190
|
if (!override) return base;
|
|
203
191
|
return [...new Set([...base, ...override])];
|
|
@@ -247,6 +235,25 @@ function writeConfigFile(configPath: string, config: Partial<SandboxConfig>): vo
|
|
|
247
235
|
writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
248
236
|
}
|
|
249
237
|
|
|
238
|
+
function getSandboxConfigWriteTarget(cwd: string): { scope: SandboxConfigScope; path: string } {
|
|
239
|
+
const { globalPath, projectPath } = getConfigPaths(cwd);
|
|
240
|
+
const projectConfig = readOrEmptyConfig(projectPath);
|
|
241
|
+
|
|
242
|
+
if (projectConfig.enabled !== undefined) return { scope: 'project', path: projectPath };
|
|
243
|
+
return { scope: 'global', path: globalPath };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async function setSandboxConfigEnabled(cwd: string, enabled: boolean): Promise<SandboxConfigScope> {
|
|
247
|
+
const { scope, path } = getSandboxConfigWriteTarget(cwd);
|
|
248
|
+
await withFileMutationQueue(path, async () => {
|
|
249
|
+
const config = readOrEmptyConfig(path);
|
|
250
|
+
config.enabled = enabled;
|
|
251
|
+
writeConfigFile(path, config);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
return scope;
|
|
255
|
+
}
|
|
256
|
+
|
|
250
257
|
async function addDomainToConfig(configPath: string, domain: string): Promise<void> {
|
|
251
258
|
await withFileMutationQueue(configPath, async () => {
|
|
252
259
|
const config = readOrEmptyConfig(configPath);
|
|
@@ -1457,11 +1464,6 @@ export function createLandstripIntegration(
|
|
|
1457
1464
|
return false;
|
|
1458
1465
|
}
|
|
1459
1466
|
|
|
1460
|
-
if (!isSandboxEnabledInPiSettings(ctx.cwd)) {
|
|
1461
|
-
disableSandbox(ctx);
|
|
1462
|
-
return false;
|
|
1463
|
-
}
|
|
1464
|
-
|
|
1465
1467
|
const config = loadConfig(ctx.cwd);
|
|
1466
1468
|
if (!config.enabled) {
|
|
1467
1469
|
disableSandbox(ctx);
|
|
@@ -1592,12 +1594,6 @@ export function createLandstripIntegration(
|
|
|
1592
1594
|
return;
|
|
1593
1595
|
}
|
|
1594
1596
|
|
|
1595
|
-
if (!isSandboxEnabledInPiSettings(ctx.cwd)) {
|
|
1596
|
-
disableSandbox(ctx);
|
|
1597
|
-
notify(ctx, 'Sandbox disabled via pi settings', 'info');
|
|
1598
|
-
return;
|
|
1599
|
-
}
|
|
1600
|
-
|
|
1601
1597
|
const config = loadConfig(ctx.cwd);
|
|
1602
1598
|
if (!config.enabled) {
|
|
1603
1599
|
disableSandbox(ctx);
|
|
@@ -1610,12 +1606,8 @@ export function createLandstripIntegration(
|
|
|
1610
1606
|
maybePi.registerCommand?.('sandbox', {
|
|
1611
1607
|
description: 'Show sandbox configuration',
|
|
1612
1608
|
handler: async (_args, ctx) => {
|
|
1613
|
-
|
|
1614
|
-
notify(ctx, 'Sandbox is disabled', 'info');
|
|
1615
|
-
return;
|
|
1616
|
-
}
|
|
1609
|
+
let config = loadConfig(ctx.cwd);
|
|
1617
1610
|
|
|
1618
|
-
const config = loadConfig(ctx.cwd);
|
|
1619
1611
|
const { globalPath, projectPath } = getConfigPaths(ctx.cwd);
|
|
1620
1612
|
|
|
1621
1613
|
if (!ctx.hasUI) return;
|
|
@@ -1627,6 +1619,13 @@ export function createLandstripIntegration(
|
|
|
1627
1619
|
const text = (s: string) => theme.fg('text', s);
|
|
1628
1620
|
const borderFg = (s: string) => theme.fg('border', s);
|
|
1629
1621
|
|
|
1622
|
+
function sandboxStatus(): { color: 'success' | 'warning'; label: string } {
|
|
1623
|
+
if (noSandboxFlag) return { color: 'warning', label: 'Disabled (--no-sandbox)' };
|
|
1624
|
+
if (!config.enabled) return { color: 'warning', label: 'Disabled' };
|
|
1625
|
+
if (!sandboxEnabled || !sandboxReady) return { color: 'warning', label: 'Inactive' };
|
|
1626
|
+
return { color: 'success', label: 'Active' };
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1630
1629
|
function boolVal(v: boolean): string {
|
|
1631
1630
|
return v ? theme.fg('warning', 'yes') : theme.fg('success', 'no');
|
|
1632
1631
|
}
|
|
@@ -1639,120 +1638,110 @@ export function createLandstripIntegration(
|
|
|
1639
1638
|
|
|
1640
1639
|
return {
|
|
1641
1640
|
render(width: number): string[] {
|
|
1642
|
-
const innerW = width - 4;
|
|
1641
|
+
const innerW = Math.max(1, width - 4);
|
|
1643
1642
|
const border = borderFg('│');
|
|
1644
|
-
const row = (
|
|
1643
|
+
const row = (content: string) => makeRow(content, innerW, border);
|
|
1645
1644
|
const lines: string[] = [];
|
|
1645
|
+
const status = sandboxStatus();
|
|
1646
|
+
const toggleValue = config.enabled
|
|
1647
|
+
? theme.fg('success', 'enabled')
|
|
1648
|
+
: theme.fg('warning', 'disabled');
|
|
1649
|
+
|
|
1650
|
+
function topBorder(titleText: string): string {
|
|
1651
|
+
const title = accent(` ${titleText} `);
|
|
1652
|
+
const fill = borderFg('─'.repeat(Math.max(0, width - 4 - visibleWidth(title))));
|
|
1653
|
+
return `${borderFg('╭─')}${title}${fill}${borderFg('─╮')}`;
|
|
1654
|
+
}
|
|
1646
1655
|
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1656
|
+
function section(titleText: string, detail?: string): void {
|
|
1657
|
+
lines.push(row(''));
|
|
1658
|
+
lines.push(row(`${accent(titleText)}${detail ? dim(` · ${detail}`) : ''}`));
|
|
1659
|
+
}
|
|
1651
1660
|
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
lines.push(
|
|
1656
|
-
row(
|
|
1657
|
-
` ${statusDot} ${text('Active')} ${dim('·')} ${muted('landstrip:')} ${pathSnippet}`,
|
|
1658
|
-
),
|
|
1659
|
-
);
|
|
1661
|
+
function item(label: string, value: string): void {
|
|
1662
|
+
lines.push(row(` ${dim('•')} ${muted(label.padEnd(13))} ${value}`));
|
|
1663
|
+
}
|
|
1660
1664
|
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
+
function listValue(values: string[], maxWidth: number): string {
|
|
1666
|
+
const value = values.join(', ') || 'none';
|
|
1667
|
+
return text(truncateToWidth(value, Math.max(10, maxWidth)));
|
|
1668
|
+
}
|
|
1665
1669
|
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
const
|
|
1670
|
-
lines.push(row(` ${accent('Network')}${dim(netMode)}`));
|
|
1671
|
-
lines.push(
|
|
1672
|
-
row(
|
|
1673
|
-
` ${dim('•')} ${muted('Allow network:')} ${boolVal(config.network.allowNetwork)}`,
|
|
1674
|
-
),
|
|
1675
|
-
);
|
|
1676
|
-
const domainsStr = config.network.allowedDomains.join(', ') || '(none)';
|
|
1677
|
-
lines.push(
|
|
1678
|
-
row(
|
|
1679
|
-
` ${dim('•')} ${muted('Allowed:')} ${text(truncateToWidth(domainsStr, Math.max(10, innerW - 15)))}`,
|
|
1680
|
-
),
|
|
1681
|
-
);
|
|
1682
|
-
const denyStr = config.network.deniedDomains.join(', ') || '(none)';
|
|
1670
|
+
lines.push(topBorder('Sandbox'));
|
|
1671
|
+
|
|
1672
|
+
const statusDot = theme.fg(status.color, '●');
|
|
1673
|
+
const pathSnippet = text(truncateToWidth(binaryPath(), Math.max(20, innerW - 28)));
|
|
1683
1674
|
lines.push(
|
|
1684
1675
|
row(
|
|
1685
|
-
|
|
1676
|
+
`${statusDot} ${text(status.label)} ${dim('·')} persisted ${toggleValue} ${dim('·')} ${muted('landstrip')} ${pathSnippet}`,
|
|
1686
1677
|
),
|
|
1687
1678
|
);
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1679
|
+
|
|
1680
|
+
section('Config');
|
|
1681
|
+
item('project', text(projectPath));
|
|
1682
|
+
item('global', text(globalPath));
|
|
1683
|
+
|
|
1684
|
+
const netMode = config.network.allowNetwork ? 'unrestricted' : 'proxied';
|
|
1685
|
+
section('Network', netMode);
|
|
1686
|
+
item('allow network', boolVal(config.network.allowNetwork));
|
|
1687
|
+
item('allowed', listValue(config.network.allowedDomains, innerW - 17));
|
|
1688
|
+
item('denied', listValue(config.network.deniedDomains, innerW - 17));
|
|
1689
|
+
if (sessionAllowedDomains.length > 0)
|
|
1690
|
+
item('session', theme.fg('accent', sessionAllowedDomains.join(', ')));
|
|
1691
|
+
|
|
1692
|
+
section('Filesystem');
|
|
1693
|
+
item('deny read', listValue(config.filesystem.denyRead, innerW - 17));
|
|
1694
|
+
item('allow read', listValue(config.filesystem.allowRead, innerW - 17));
|
|
1695
|
+
item('allow write', listValue(config.filesystem.allowWrite, innerW - 17));
|
|
1696
|
+
item('deny write', listValue(config.filesystem.denyWrite, innerW - 17));
|
|
1697
|
+
|
|
1698
|
+
if (sessionAllowedReadPaths.length > 0 || sessionAllowedWritePaths.length > 0) {
|
|
1699
|
+
section('Session grants');
|
|
1700
|
+
if (sessionAllowedReadPaths.length > 0)
|
|
1701
|
+
item('read', theme.fg('accent', sessionAllowedReadPaths.join(', ')));
|
|
1702
|
+
if (sessionAllowedWritePaths.length > 0)
|
|
1703
|
+
item('write', theme.fg('accent', sessionAllowedWritePaths.join(', ')));
|
|
1694
1704
|
}
|
|
1695
1705
|
|
|
1696
|
-
// Filesystem section
|
|
1697
1706
|
lines.push(row(''));
|
|
1698
|
-
lines.push(row(`${'─'.repeat(innerW)}`));
|
|
1699
|
-
lines.push(row(` ${accent('Filesystem')}`));
|
|
1700
|
-
const denyReadStr = config.filesystem.denyRead.join(', ') || '(none)';
|
|
1701
1707
|
lines.push(
|
|
1702
1708
|
row(
|
|
1703
|
-
|
|
1709
|
+
`${dim('t')} ${muted('toggle persisted setting')} ${dim('esc')} ${muted('close')}`,
|
|
1704
1710
|
),
|
|
1705
1711
|
);
|
|
1706
|
-
const allowReadStr = config.filesystem.allowRead.join(', ') || '(none)';
|
|
1707
1712
|
lines.push(
|
|
1708
|
-
|
|
1709
|
-
` ${dim('•')} ${muted('Allow read:')} ${text(truncateToWidth(allowReadStr, Math.max(10, innerW - 17)))}`,
|
|
1710
|
-
),
|
|
1711
|
-
);
|
|
1712
|
-
const allowWriteStr = config.filesystem.allowWrite.join(', ') || '(none)';
|
|
1713
|
-
lines.push(
|
|
1714
|
-
row(
|
|
1715
|
-
` ${dim('•')} ${muted('Allow write:')} ${text(truncateToWidth(allowWriteStr, Math.max(10, innerW - 18)))}`,
|
|
1716
|
-
),
|
|
1717
|
-
);
|
|
1718
|
-
const denyWriteStr = config.filesystem.denyWrite.join(', ') || '(none)';
|
|
1719
|
-
lines.push(
|
|
1720
|
-
row(
|
|
1721
|
-
` ${dim('•')} ${muted('Deny write:')} ${text(truncateToWidth(denyWriteStr, Math.max(10, innerW - 17)))}`,
|
|
1722
|
-
),
|
|
1713
|
+
`${borderFg('╰')}${borderFg('─'.repeat(Math.max(0, width - 2)))}${borderFg('╯')}`,
|
|
1723
1714
|
);
|
|
1724
1715
|
|
|
1725
|
-
// Session allowances
|
|
1726
|
-
if (sessionAllowedReadPaths.length > 0 || sessionAllowedWritePaths.length > 0) {
|
|
1727
|
-
lines.push(row(''));
|
|
1728
|
-
if (sessionAllowedReadPaths.length > 0) {
|
|
1729
|
-
lines.push(
|
|
1730
|
-
row(
|
|
1731
|
-
` ${dim('•')} ${muted('Session read:')} ${theme.fg('accent', sessionAllowedReadPaths.join(', '))}`,
|
|
1732
|
-
),
|
|
1733
|
-
);
|
|
1734
|
-
}
|
|
1735
|
-
if (sessionAllowedWritePaths.length > 0) {
|
|
1736
|
-
lines.push(
|
|
1737
|
-
row(
|
|
1738
|
-
` ${dim('•')} ${muted('Session write:')} ${theme.fg('accent', sessionAllowedWritePaths.join(', '))}`,
|
|
1739
|
-
),
|
|
1740
|
-
);
|
|
1741
|
-
}
|
|
1742
|
-
}
|
|
1743
|
-
|
|
1744
|
-
// Footer
|
|
1745
|
-
lines.push(row(''));
|
|
1746
|
-
lines.push(row(` ${dim('esc')} ${muted('or any key to close')}`));
|
|
1747
|
-
|
|
1748
|
-
// Bottom border
|
|
1749
|
-
lines.push(`${borderFg('╰')}${borderFg('─'.repeat(width - 2))}${borderFg('╯')}`);
|
|
1750
|
-
|
|
1751
1716
|
return lines;
|
|
1752
1717
|
},
|
|
1753
1718
|
|
|
1754
|
-
handleInput(): void {
|
|
1755
|
-
|
|
1719
|
+
handleInput(data: string): void {
|
|
1720
|
+
if (data !== 't' && data !== 'T') {
|
|
1721
|
+
done(undefined);
|
|
1722
|
+
return;
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
void (async () => {
|
|
1726
|
+
const enabled = !config.enabled;
|
|
1727
|
+
const scope = await setSandboxConfigEnabled(ctx.cwd, enabled);
|
|
1728
|
+
config = loadConfig(ctx.cwd);
|
|
1729
|
+
|
|
1730
|
+
if (!enabled) {
|
|
1731
|
+
disableSandbox(ctx);
|
|
1732
|
+
notify(ctx, `Sandbox disabled in ${scope} config`, 'info');
|
|
1733
|
+
} else if (noSandboxFlag) {
|
|
1734
|
+
notify(ctx, 'Sandbox remains disabled via --no-sandbox', 'warning');
|
|
1735
|
+
} else if (!config.enabled) {
|
|
1736
|
+
notify(ctx, 'Sandbox remains disabled via config', 'info');
|
|
1737
|
+
} else if (enableSandbox(ctx)) {
|
|
1738
|
+
notify(ctx, `Sandbox enabled in ${scope} config`, 'info');
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
tui.requestRender();
|
|
1742
|
+
})().catch((error: unknown) => {
|
|
1743
|
+
notify(ctx, `Could not update config: ${error}`, 'error');
|
|
1744
|
+
});
|
|
1756
1745
|
},
|
|
1757
1746
|
|
|
1758
1747
|
invalidate(): void {},
|