opencode-landstrip 0.18.34 → 0.18.36
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 +23 -70
- package/index.ts +56 -467
- package/package.json +2 -2
- package/shared.ts +100 -37
- package/tui.ts +290 -154
- package/LICENSE +0 -201
package/tui.ts
CHANGED
|
@@ -12,7 +12,7 @@ import type {
|
|
|
12
12
|
TuiSlotPlugin,
|
|
13
13
|
} from '@opencode-ai/plugin/tui';
|
|
14
14
|
import { RGBA } from '@opentui/core';
|
|
15
|
-
import { useTerminalDimensions } from '@opentui/solid';
|
|
15
|
+
import { Portal, useTerminalDimensions } from '@opentui/solid';
|
|
16
16
|
import { Fragment, jsx, jsxs } from '@opentui/solid/jsx-runtime';
|
|
17
17
|
import { createSignal, onCleanup, onMount } from 'solid-js';
|
|
18
18
|
|
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
removeDiscoveryFile,
|
|
28
28
|
sessionAllows,
|
|
29
29
|
sessionAllowancesFor,
|
|
30
|
-
|
|
30
|
+
sandboxConfigTarget,
|
|
31
31
|
sessionScopeFor,
|
|
32
32
|
rootSessionIDFor,
|
|
33
33
|
setSandboxConfigEnabled,
|
|
@@ -118,26 +118,21 @@ function formatPath(input: string, base: string): string {
|
|
|
118
118
|
return absolute;
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
function selectedForeground(
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
if (theme.background.a === 0) {
|
|
126
|
-
const { r, g, b } = theme.warning;
|
|
127
|
-
const luminance = 0.299 * r + 0.587 * g + 0.114 * b;
|
|
128
|
-
return luminance > 0.5 ? RGBA.fromInts(0, 0, 0) : RGBA.fromInts(255, 255, 255);
|
|
129
|
-
}
|
|
130
|
-
return theme.background;
|
|
121
|
+
function selectedForeground(background: RGBA): RGBA {
|
|
122
|
+
const luminance = 0.2126 * background.r + 0.7152 * background.g + 0.0722 * background.b;
|
|
123
|
+
return luminance > 0.5 ? RGBA.fromInts(0, 0, 0) : RGBA.fromInts(255, 255, 255);
|
|
131
124
|
}
|
|
132
125
|
|
|
133
126
|
const tui: TuiPlugin = async (api, options, meta) => {
|
|
134
127
|
const optionOverrides = normalizeOptions(options);
|
|
128
|
+
const enabledManagedByOptions = optionOverrides.enabled !== undefined;
|
|
135
129
|
|
|
136
130
|
function LandstripPermissionPrompt(rawProps: Record<string, unknown>) {
|
|
137
131
|
const props = rawProps as unknown as PermissionPromptProps<string>;
|
|
138
132
|
const theme = api.theme.current;
|
|
139
133
|
const dimensions = useTerminalDimensions();
|
|
140
134
|
const [selected, setSelected] = createSignal(0);
|
|
135
|
+
const [expanded, setExpanded] = createSignal(false);
|
|
141
136
|
onMount(props.onShow);
|
|
142
137
|
|
|
143
138
|
function move(direction: number): void {
|
|
@@ -152,12 +147,16 @@ const tui: TuiPlugin = async (api, options, meta) => {
|
|
|
152
147
|
const unregister = api.keymap.registerLayer({
|
|
153
148
|
priority: 1000,
|
|
154
149
|
bindings: [
|
|
155
|
-
{ key: 'left', cmd: () => move(-1) },
|
|
156
|
-
{ key: '
|
|
157
|
-
{ key: '
|
|
158
|
-
{ key: '
|
|
159
|
-
{
|
|
160
|
-
|
|
150
|
+
{ key: 'left', desc: 'Previous permission option', group: 'Sandbox', cmd: () => move(-1) },
|
|
151
|
+
{ key: 'right', desc: 'Next permission option', group: 'Sandbox', cmd: () => move(1) },
|
|
152
|
+
{ key: 'return', desc: 'Select permission option', group: 'Sandbox', cmd: submit },
|
|
153
|
+
{ key: 'escape', desc: 'Reject permission', group: 'Sandbox', cmd: props.onCancel },
|
|
154
|
+
{
|
|
155
|
+
key: 'ctrl+f',
|
|
156
|
+
desc: 'Toggle permission prompt fullscreen',
|
|
157
|
+
group: 'Sandbox',
|
|
158
|
+
cmd: () => setExpanded((value) => !value),
|
|
159
|
+
},
|
|
161
160
|
],
|
|
162
161
|
});
|
|
163
162
|
onCleanup(unregister);
|
|
@@ -177,13 +176,126 @@ const tui: TuiPlugin = async (api, options, meta) => {
|
|
|
177
176
|
},
|
|
178
177
|
children: jsx('text', {
|
|
179
178
|
get fg() {
|
|
180
|
-
return selected() === index ? selectedForeground(theme) : theme.textMuted;
|
|
179
|
+
return selected() === index ? selectedForeground(theme.warning) : theme.textMuted;
|
|
181
180
|
},
|
|
182
181
|
children: option.label,
|
|
183
182
|
}),
|
|
184
183
|
}),
|
|
185
184
|
);
|
|
186
185
|
|
|
186
|
+
const children = [
|
|
187
|
+
jsxs('box', {
|
|
188
|
+
gap: 1,
|
|
189
|
+
paddingLeft: 1,
|
|
190
|
+
paddingRight: 3,
|
|
191
|
+
paddingTop: 1,
|
|
192
|
+
paddingBottom: 1,
|
|
193
|
+
flexGrow: 1,
|
|
194
|
+
children: [
|
|
195
|
+
jsxs('box', {
|
|
196
|
+
flexDirection: 'column',
|
|
197
|
+
gap: 0,
|
|
198
|
+
paddingLeft: 1,
|
|
199
|
+
flexShrink: 0,
|
|
200
|
+
children: [
|
|
201
|
+
jsxs('box', {
|
|
202
|
+
flexDirection: 'row',
|
|
203
|
+
gap: 1,
|
|
204
|
+
flexShrink: 0,
|
|
205
|
+
children: [
|
|
206
|
+
jsx('text', { fg: theme.warning, children: '△' }),
|
|
207
|
+
jsx('text', { fg: theme.text, children: 'Permission required' }),
|
|
208
|
+
],
|
|
209
|
+
}),
|
|
210
|
+
jsxs('box', {
|
|
211
|
+
flexDirection: 'row',
|
|
212
|
+
gap: 1,
|
|
213
|
+
paddingLeft: 2,
|
|
214
|
+
flexShrink: 0,
|
|
215
|
+
children: [
|
|
216
|
+
jsx('text', { fg: theme.textMuted, flexShrink: 0, children: props.icon }),
|
|
217
|
+
jsx('text', { fg: theme.text, wrapMode: 'word', children: props.title }),
|
|
218
|
+
],
|
|
219
|
+
}),
|
|
220
|
+
],
|
|
221
|
+
}),
|
|
222
|
+
],
|
|
223
|
+
}),
|
|
224
|
+
jsxs('box', {
|
|
225
|
+
get flexDirection() {
|
|
226
|
+
return dimensions().width < 80 ? 'column' : 'row';
|
|
227
|
+
},
|
|
228
|
+
flexShrink: 0,
|
|
229
|
+
gap: 1,
|
|
230
|
+
paddingTop: 1,
|
|
231
|
+
paddingLeft: 2,
|
|
232
|
+
paddingRight: 3,
|
|
233
|
+
paddingBottom: 1,
|
|
234
|
+
backgroundColor: theme.backgroundElement,
|
|
235
|
+
get justifyContent() {
|
|
236
|
+
return dimensions().width < 80 ? 'flex-start' : 'space-between';
|
|
237
|
+
},
|
|
238
|
+
get alignItems() {
|
|
239
|
+
return dimensions().width < 80 ? 'flex-start' : 'center';
|
|
240
|
+
},
|
|
241
|
+
children: [
|
|
242
|
+
jsx('box', {
|
|
243
|
+
flexDirection: 'row',
|
|
244
|
+
gap: 1,
|
|
245
|
+
flexShrink: 0,
|
|
246
|
+
children: optionButtons,
|
|
247
|
+
}),
|
|
248
|
+
jsxs('box', {
|
|
249
|
+
flexDirection: 'row',
|
|
250
|
+
gap: 2,
|
|
251
|
+
flexShrink: 0,
|
|
252
|
+
children: [
|
|
253
|
+
jsxs('text', {
|
|
254
|
+
fg: theme.text,
|
|
255
|
+
children: [
|
|
256
|
+
'ctrl+f ',
|
|
257
|
+
jsx('span', {
|
|
258
|
+
style: { fg: theme.textMuted },
|
|
259
|
+
children: expanded() ? 'minimize' : 'fullscreen',
|
|
260
|
+
}),
|
|
261
|
+
],
|
|
262
|
+
}),
|
|
263
|
+
jsxs('text', {
|
|
264
|
+
fg: theme.text,
|
|
265
|
+
children: [
|
|
266
|
+
'⇆ ',
|
|
267
|
+
jsx('span', { style: { fg: theme.textMuted }, children: 'select' }),
|
|
268
|
+
],
|
|
269
|
+
}),
|
|
270
|
+
jsxs('text', {
|
|
271
|
+
fg: theme.text,
|
|
272
|
+
children: [
|
|
273
|
+
'enter ',
|
|
274
|
+
jsx('span', { style: { fg: theme.textMuted }, children: 'confirm' }),
|
|
275
|
+
],
|
|
276
|
+
}),
|
|
277
|
+
],
|
|
278
|
+
}),
|
|
279
|
+
],
|
|
280
|
+
}),
|
|
281
|
+
];
|
|
282
|
+
|
|
283
|
+
if (expanded()) {
|
|
284
|
+
return Portal({
|
|
285
|
+
children: jsxs('box', {
|
|
286
|
+
top: dimensions().height * -1 + 1,
|
|
287
|
+
bottom: 1,
|
|
288
|
+
left: 2,
|
|
289
|
+
right: 2,
|
|
290
|
+
position: 'absolute',
|
|
291
|
+
border: ['left'],
|
|
292
|
+
borderColor: theme.warning,
|
|
293
|
+
customBorderChars: promptBorderChars,
|
|
294
|
+
backgroundColor: theme.backgroundPanel,
|
|
295
|
+
children,
|
|
296
|
+
}),
|
|
297
|
+
});
|
|
298
|
+
}
|
|
187
299
|
return jsxs('box', {
|
|
188
300
|
top: 0,
|
|
189
301
|
maxHeight: 15,
|
|
@@ -195,92 +307,7 @@ const tui: TuiPlugin = async (api, options, meta) => {
|
|
|
195
307
|
borderColor: theme.warning,
|
|
196
308
|
customBorderChars: promptBorderChars,
|
|
197
309
|
backgroundColor: theme.backgroundPanel,
|
|
198
|
-
children
|
|
199
|
-
jsxs('box', {
|
|
200
|
-
gap: 1,
|
|
201
|
-
paddingLeft: 1,
|
|
202
|
-
paddingRight: 3,
|
|
203
|
-
paddingTop: 1,
|
|
204
|
-
paddingBottom: 1,
|
|
205
|
-
flexGrow: 1,
|
|
206
|
-
children: [
|
|
207
|
-
jsxs('box', {
|
|
208
|
-
flexDirection: 'column',
|
|
209
|
-
gap: 0,
|
|
210
|
-
paddingLeft: 1,
|
|
211
|
-
flexShrink: 0,
|
|
212
|
-
children: [
|
|
213
|
-
jsxs('box', {
|
|
214
|
-
flexDirection: 'row',
|
|
215
|
-
gap: 1,
|
|
216
|
-
flexShrink: 0,
|
|
217
|
-
children: [
|
|
218
|
-
jsx('text', { fg: theme.warning, children: '△' }),
|
|
219
|
-
jsx('text', { fg: theme.text, children: 'Permission required' }),
|
|
220
|
-
],
|
|
221
|
-
}),
|
|
222
|
-
jsxs('box', {
|
|
223
|
-
flexDirection: 'row',
|
|
224
|
-
gap: 1,
|
|
225
|
-
paddingLeft: 2,
|
|
226
|
-
flexShrink: 0,
|
|
227
|
-
children: [
|
|
228
|
-
jsx('text', { fg: theme.textMuted, flexShrink: 0, children: props.icon }),
|
|
229
|
-
jsx('text', { fg: theme.text, wrapMode: 'word', children: props.title }),
|
|
230
|
-
],
|
|
231
|
-
}),
|
|
232
|
-
],
|
|
233
|
-
}),
|
|
234
|
-
],
|
|
235
|
-
}),
|
|
236
|
-
jsxs('box', {
|
|
237
|
-
get flexDirection() {
|
|
238
|
-
return dimensions().width < 80 ? 'column' : 'row';
|
|
239
|
-
},
|
|
240
|
-
flexShrink: 0,
|
|
241
|
-
gap: 1,
|
|
242
|
-
paddingTop: 1,
|
|
243
|
-
paddingLeft: 2,
|
|
244
|
-
paddingRight: 3,
|
|
245
|
-
paddingBottom: 1,
|
|
246
|
-
backgroundColor: theme.backgroundElement,
|
|
247
|
-
get justifyContent() {
|
|
248
|
-
return dimensions().width < 80 ? 'flex-start' : 'space-between';
|
|
249
|
-
},
|
|
250
|
-
get alignItems() {
|
|
251
|
-
return dimensions().width < 80 ? 'flex-start' : 'center';
|
|
252
|
-
},
|
|
253
|
-
children: [
|
|
254
|
-
jsx('box', {
|
|
255
|
-
flexDirection: 'row',
|
|
256
|
-
gap: 1,
|
|
257
|
-
flexShrink: 0,
|
|
258
|
-
children: optionButtons,
|
|
259
|
-
}),
|
|
260
|
-
jsxs('box', {
|
|
261
|
-
flexDirection: 'row',
|
|
262
|
-
gap: 2,
|
|
263
|
-
flexShrink: 0,
|
|
264
|
-
children: [
|
|
265
|
-
jsxs('text', {
|
|
266
|
-
fg: theme.text,
|
|
267
|
-
children: [
|
|
268
|
-
'⇆ ',
|
|
269
|
-
jsx('span', { style: { fg: theme.textMuted }, children: 'select' }),
|
|
270
|
-
],
|
|
271
|
-
}),
|
|
272
|
-
jsxs('text', {
|
|
273
|
-
fg: theme.text,
|
|
274
|
-
children: [
|
|
275
|
-
'enter ',
|
|
276
|
-
jsx('span', { style: { fg: theme.textMuted }, children: 'confirm' }),
|
|
277
|
-
],
|
|
278
|
-
}),
|
|
279
|
-
],
|
|
280
|
-
}),
|
|
281
|
-
],
|
|
282
|
-
}),
|
|
283
|
-
],
|
|
310
|
+
children,
|
|
284
311
|
});
|
|
285
312
|
}
|
|
286
313
|
|
|
@@ -662,68 +689,176 @@ const tui: TuiPlugin = async (api, options, meta) => {
|
|
|
662
689
|
refreshPermissionPresentation,
|
|
663
690
|
);
|
|
664
691
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
692
|
+
const [landstripOpen, setLandstripOpen] = createSignal(false);
|
|
693
|
+
const [confirmingDisable, setConfirmingDisable] = createSignal(false);
|
|
694
|
+
const [sandboxRevision, setSandboxRevision] = createSignal(0);
|
|
695
|
+
let unregisterLandstripKeys: (() => void) | undefined;
|
|
696
|
+
let popLandstripMode: (() => void) | undefined;
|
|
697
|
+
|
|
698
|
+
const closeLandstrip = () => {
|
|
699
|
+
unregisterLandstripKeys?.();
|
|
700
|
+
popLandstripMode?.();
|
|
701
|
+
unregisterLandstripKeys = undefined;
|
|
702
|
+
popLandstripMode = undefined;
|
|
703
|
+
setConfirmingDisable(false);
|
|
704
|
+
setLandstripOpen(false);
|
|
705
|
+
};
|
|
706
|
+
const toggleLandstrip = () => {
|
|
707
|
+
if (enabledManagedByOptions) {
|
|
708
|
+
api.ui.toast({
|
|
709
|
+
title: 'Landstrip',
|
|
710
|
+
message: 'Sandbox state is managed by plugin options',
|
|
711
|
+
variant: 'warning',
|
|
712
|
+
});
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
|
|
669
716
|
const directory = api.state.path.directory || process.cwd();
|
|
670
717
|
const config = loadConfig(directory, optionOverrides);
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
api.ui.
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
message: `Sandbox ${next ? 'enabled' : 'disabled'} (${scope} config)`,
|
|
689
|
-
variant: next ? 'success' : 'warning',
|
|
690
|
-
});
|
|
691
|
-
},
|
|
692
|
-
}),
|
|
693
|
-
);
|
|
718
|
+
if (config.enabled && !confirmingDisable()) {
|
|
719
|
+
setConfirmingDisable(true);
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
setConfirmingDisable(false);
|
|
723
|
+
const enabled = !config.enabled;
|
|
724
|
+
const scope = setSandboxConfigEnabled(directory, enabled, optionOverrides);
|
|
725
|
+
refreshSandboxStatus?.();
|
|
726
|
+
api.ui.toast({
|
|
727
|
+
title: 'Landstrip',
|
|
728
|
+
message: `Sandbox ${enabled ? 'enabled' : 'disabled'} (${scope} config)`,
|
|
729
|
+
variant: enabled ? 'success' : 'warning',
|
|
730
|
+
});
|
|
731
|
+
};
|
|
732
|
+
const cancelLandstrip = () => {
|
|
733
|
+
if (confirmingDisable()) setConfirmingDisable(false);
|
|
734
|
+
else closeLandstrip();
|
|
694
735
|
};
|
|
695
736
|
|
|
737
|
+
function LandstripPane() {
|
|
738
|
+
const theme = api.theme.current;
|
|
739
|
+
const dimensions = useTerminalDimensions();
|
|
740
|
+
|
|
741
|
+
const row = (label: string, value: string) =>
|
|
742
|
+
jsxs('box', {
|
|
743
|
+
flexDirection: 'row',
|
|
744
|
+
paddingLeft: 2,
|
|
745
|
+
paddingRight: 2,
|
|
746
|
+
children: [
|
|
747
|
+
jsx('text', { fg: theme.textMuted, width: 22, children: label }),
|
|
748
|
+
jsx('text', { fg: theme.text, flexGrow: 1, wrapMode: 'word', children: value }),
|
|
749
|
+
],
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
return Portal({
|
|
753
|
+
get children() {
|
|
754
|
+
if (!landstripOpen()) return null;
|
|
755
|
+
sandboxRevision();
|
|
756
|
+
const directory = api.state.path.directory || process.cwd();
|
|
757
|
+
const config = loadConfig(directory, optionOverrides);
|
|
758
|
+
const target = sandboxConfigTarget(directory);
|
|
759
|
+
const values = (items: readonly string[]) => items.join(', ') || 'none';
|
|
760
|
+
const status = enabledManagedByOptions
|
|
761
|
+
? config.enabled
|
|
762
|
+
? 'Active (plugin options)'
|
|
763
|
+
: 'Disabled by plugin options'
|
|
764
|
+
: config.enabled
|
|
765
|
+
? 'Active'
|
|
766
|
+
: 'Disabled by configuration';
|
|
767
|
+
return jsxs('box', {
|
|
768
|
+
position: 'absolute',
|
|
769
|
+
zIndex: 4000,
|
|
770
|
+
bottom: 0,
|
|
771
|
+
left: 0,
|
|
772
|
+
right: 0,
|
|
773
|
+
get height() {
|
|
774
|
+
return Math.min(22, Math.max(12, dimensions().height - 2));
|
|
775
|
+
},
|
|
776
|
+
flexDirection: 'column',
|
|
777
|
+
border: ['top'],
|
|
778
|
+
borderColor: theme.primary,
|
|
779
|
+
backgroundColor: theme.backgroundPanel,
|
|
780
|
+
children: [
|
|
781
|
+
jsxs('box', {
|
|
782
|
+
flexDirection: 'row',
|
|
783
|
+
paddingLeft: 2,
|
|
784
|
+
paddingRight: 2,
|
|
785
|
+
paddingTop: 1,
|
|
786
|
+
paddingBottom: 1,
|
|
787
|
+
justifyContent: 'space-between',
|
|
788
|
+
children: [
|
|
789
|
+
jsx('text', { fg: theme.primary, children: 'Landstrip' }),
|
|
790
|
+
jsx('text', {
|
|
791
|
+
fg: config.enabled ? theme.success : theme.warning,
|
|
792
|
+
children: status,
|
|
793
|
+
}),
|
|
794
|
+
],
|
|
795
|
+
}),
|
|
796
|
+
row('Network', config.network.allowNetwork ? 'Unrestricted' : 'Proxied'),
|
|
797
|
+
row('Allowed Domains', values(config.network.allowedDomains)),
|
|
798
|
+
row('Denied Domains', values(config.network.deniedDomains)),
|
|
799
|
+
row('Allowed Reads', values(config.filesystem.allowRead)),
|
|
800
|
+
row('Denied Reads', values(config.filesystem.denyRead)),
|
|
801
|
+
row('Allowed Writes', values(config.filesystem.allowWrite)),
|
|
802
|
+
row('Denied Writes', values(config.filesystem.denyWrite)),
|
|
803
|
+
row(
|
|
804
|
+
'Configuration Scope',
|
|
805
|
+
enabledManagedByOptions
|
|
806
|
+
? 'Plugin options'
|
|
807
|
+
: target.scope === 'project'
|
|
808
|
+
? 'Project'
|
|
809
|
+
: 'Global',
|
|
810
|
+
),
|
|
811
|
+
row('Configuration File', formatPath(target.path, directory)),
|
|
812
|
+
jsx('box', { flexGrow: 1 }),
|
|
813
|
+
jsx('text', {
|
|
814
|
+
fg: confirmingDisable() ? theme.error : theme.textMuted,
|
|
815
|
+
marginLeft: 2,
|
|
816
|
+
marginRight: 2,
|
|
817
|
+
marginBottom: 1,
|
|
818
|
+
children: confirmingDisable()
|
|
819
|
+
? 'Disable the sandbox? Commands will run without OS isolation. Enter confirm · Esc cancel'
|
|
820
|
+
: enabledManagedByOptions
|
|
821
|
+
? 'Sandbox state is managed by plugin options · Esc close'
|
|
822
|
+
: `Enter ${config.enabled ? 'disable' : 'enable'} · Esc close`,
|
|
823
|
+
}),
|
|
824
|
+
],
|
|
825
|
+
});
|
|
826
|
+
},
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
const showLandstrip = () => {
|
|
831
|
+
if (landstripOpen()) return;
|
|
832
|
+
setLandstripOpen(true);
|
|
833
|
+
popLandstripMode = api.mode.push('modal');
|
|
834
|
+
unregisterLandstripKeys = api.keymap.registerLayer({
|
|
835
|
+
priority: 1000,
|
|
836
|
+
bindings: [
|
|
837
|
+
{ key: 'return', desc: 'Toggle sandbox', group: 'Landstrip', cmd: toggleLandstrip },
|
|
838
|
+
{ key: 'escape', desc: 'Close Landstrip', group: 'Landstrip', cmd: cancelLandstrip },
|
|
839
|
+
],
|
|
840
|
+
});
|
|
841
|
+
};
|
|
842
|
+
api.lifecycle.onDispose(closeLandstrip);
|
|
843
|
+
|
|
696
844
|
api.keymap.registerLayer({
|
|
697
845
|
commands: [
|
|
698
846
|
{
|
|
699
847
|
namespace: 'palette',
|
|
700
|
-
name: '
|
|
701
|
-
title: '
|
|
702
|
-
desc: '
|
|
703
|
-
category: '
|
|
848
|
+
name: 'landstrip',
|
|
849
|
+
title: 'Landstrip',
|
|
850
|
+
desc: 'Manage the Landstrip sandbox',
|
|
851
|
+
category: 'Landstrip',
|
|
704
852
|
suggested: true,
|
|
705
|
-
slash: { name: '
|
|
706
|
-
slashName: '
|
|
707
|
-
run:
|
|
853
|
+
slash: { name: 'landstrip' },
|
|
854
|
+
slashName: 'landstrip',
|
|
855
|
+
run: showLandstrip,
|
|
708
856
|
},
|
|
709
857
|
],
|
|
710
858
|
});
|
|
711
859
|
|
|
712
|
-
api.command?.register(() => [
|
|
713
|
-
{
|
|
714
|
-
title: 'Sandbox',
|
|
715
|
-
value: 'sandbox',
|
|
716
|
-
description: 'Inspect and toggle the sandbox',
|
|
717
|
-
category: 'Sandbox',
|
|
718
|
-
suggested: true,
|
|
719
|
-
slash: { name: 'sandbox' },
|
|
720
|
-
onSelect: showSandbox,
|
|
721
|
-
},
|
|
722
|
-
]);
|
|
723
|
-
|
|
724
860
|
// Persistent status badge in the prompt area.
|
|
725
861
|
try {
|
|
726
|
-
const [sandboxRevision, setSandboxRevision] = createSignal(0);
|
|
727
862
|
const statusBadge = (ctx: TuiSlotContext) => {
|
|
728
863
|
sandboxRevision();
|
|
729
864
|
const directory = api.state.path.directory || process.cwd();
|
|
@@ -741,6 +876,7 @@ const tui: TuiPlugin = async (api, options, meta) => {
|
|
|
741
876
|
|
|
742
877
|
const statusSlot: TuiSlotPlugin = {
|
|
743
878
|
slots: {
|
|
879
|
+
app: () => jsx(LandstripPane, {}),
|
|
744
880
|
home_prompt_right: (ctx) => statusBadge(ctx),
|
|
745
881
|
session_prompt: (_ctx, props) => renderSessionPrompt(props),
|
|
746
882
|
session_prompt_right: (ctx) => statusBadge(ctx),
|
|
@@ -749,7 +885,7 @@ const tui: TuiPlugin = async (api, options, meta) => {
|
|
|
749
885
|
api.slots.register(statusSlot);
|
|
750
886
|
refreshSandboxStatus = () => setSandboxRevision((revision) => revision + 1);
|
|
751
887
|
} catch {
|
|
752
|
-
// Solid runtime unavailable on this host — skip the status badge.
|
|
888
|
+
// Solid runtime unavailable on this host — skip the pane and status badge.
|
|
753
889
|
}
|
|
754
890
|
|
|
755
891
|
// First-run onboarding: a single quiet pointer to the default-strict policy
|
|
@@ -759,7 +895,7 @@ const tui: TuiPlugin = async (api, options, meta) => {
|
|
|
759
895
|
api.kv.set('onboarded', true);
|
|
760
896
|
api.ui.toast({
|
|
761
897
|
title: 'Sandbox active',
|
|
762
|
-
message: 'Landstrip sandbox is on. Run /
|
|
898
|
+
message: 'Landstrip sandbox is on. Run /landstrip to inspect it.',
|
|
763
899
|
variant: 'info',
|
|
764
900
|
duration: 8000,
|
|
765
901
|
});
|