codebay 0.9.1 → 0.10.0
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/.mochi/manifest.json +213 -146
- package/.mochi/svelte-client/HydratableIsland-aebn1fa1.js +1 -0
- package/.mochi/svelte-client/_hydrate-AppShell_2rtnxn6pgp2rh-rx6arrv5.js +2 -0
- package/.mochi/svelte-client/{_hydrate-AvatarsView_257s9yq1wwr4f-92hepzby.js → _hydrate-AvatarsView_257s9yq1wwr4f-jpjdmgpe.js} +1 -1
- package/.mochi/svelte-client/_hydrate-InstanceView_2ma45l9bybfhl-c9y47dat.js +1 -0
- package/.mochi/svelte-client/_hydrate-SettingsView_8hmarwmf8onq-d61feq89.js +38 -0
- package/.mochi/svelte-client/{chunk-2j95sv83.js → chunk-0fjx3z3r.js} +1 -1
- package/.mochi/svelte-client/chunk-8xvr02wh.js +2 -0
- package/.mochi/svelte-client/{chunk-m497q88s.js → chunk-9143wy3j.js} +2 -2
- package/.mochi/svelte-client/{chunk-v65n8th6.js → chunk-bn731rda.js} +1 -1
- package/.mochi/svelte-client/{chunk-a2psjmrm.js → chunk-j72r8pkk.js} +2 -2
- package/.mochi/svelte-client/chunk-k37wj1jm.js +2 -0
- package/.mochi/svelte-client/{chunk-mpv3qx28.js → chunk-p20tkpde.js} +1 -1
- package/.mochi/svelte-client/chunk-pc0fm37c.js +2 -0
- package/.mochi/svelte-client/{chunk-6rbs0mhx.js → chunk-t0ap7q89.js} +1 -1
- package/.mochi/svelte-client/{chunk-rs41by6w.js → chunk-wvtkwfsh.js} +1 -1
- package/.mochi/svelte-compile/{App-7vjcp38v.server.js → App-59cdj86f.server.js} +167 -103
- package/.mochi/svelte-compile/{Avatars-e2cfc95t.server.js → Avatars-fmagfyj2.server.js} +1 -1
- package/.mochi/svelte-compile/{Instance-h78n2dsv.server.js → Instance-s1gqwzmx.server.js} +16 -10
- package/.mochi/svelte-compile/{Settings-tav2asn7.server.js → Settings-k447mt0x.server.js} +540 -88
- package/.mochi/svelte-compile/{chunk-brjv9pyd.js → chunk-3t7v4hk0.js} +1 -7
- package/.mochi/svelte-compile/chunk-gkjgybb2.js +15 -0
- package/.mochi/svelte-compile/{chunk-m8ygpyhr.js → chunk-th2dbqvc.js} +20 -32
- package/.mochi/svelte-css/Button-oharop2xz6am.css +1 -0
- package/.mochi/svelte-css/IdeBar-3bgr3m5z0lpeg.css +1 -0
- package/.mochi/svelte-css/{InstanceView-3vzow3ttxzlh3.css → InstanceView-1pd7j16bd2b16.css} +1 -1
- package/.mochi/svelte-css/SettingsView-n3oy75uonzp2.css +1 -0
- package/.mochi/svelte-css/{TopBar-2q87li6yt8ids.css → TopBar-2y76zxwehrfos.css} +1 -1
- package/migrations/0007_instance_avatar.sql +1 -0
- package/package.json +2 -2
- package/src/avatars/diamond.ts +5 -6
- package/src/avatars/index.ts +1 -16
- package/src/avatars/pick.server.ts +25 -0
- package/src/avatars/whale.ts +7 -8
- package/src/components/AppShell.svelte +29 -4
- package/src/components/Avatar.svelte +8 -8
- package/src/components/Button.svelte +22 -1
- package/src/components/DashboardView.svelte +14 -2
- package/src/components/FolderBrowser.svelte +6 -2
- package/src/components/IdeBar.svelte +56 -13
- package/src/components/IdeLoader.svelte +15 -3
- package/src/components/InstanceCard.svelte +6 -1
- package/src/components/InstanceView.svelte +33 -15
- package/src/components/SettingsView.svelte +548 -1
- package/src/components/TopBar.svelte +37 -10
- package/src/components/UiShowcase.svelte +5 -10
- package/src/container-injections/claude-aliases.ts +1 -1
- package/src/container-injections/claude-code-custom.ts +2 -1
- package/src/container-injections/claude-code-ide-extension.ts +5 -0
- package/src/container-injections/claude-code-models.ts +2 -1
- package/src/container-injections/claude-code-update.ts +62 -1
- package/src/container-injections/claude-effort-level.ts +32 -0
- package/src/container-injections/claude-permission-mode.ts +40 -0
- package/src/container-injections/custom-env-vars.ts +82 -0
- package/src/container-injections/host-env-vars.ts +2 -1
- package/src/lib/config.server.ts +3 -0
- package/src/lib/db.server.ts +8 -3
- package/src/lib/devcontainer.server.ts +129 -42
- package/src/lib/health.server.ts +55 -11
- package/src/lib/injections.server.ts +45 -38
- package/src/lib/instances.server.ts +181 -19
- package/src/lib/log-capture.server.ts +262 -0
- package/src/lib/ports.server.ts +31 -0
- package/src/lib/proxy.server.ts +2 -2
- package/src/pages/Settings.svelte +22 -1
- package/src/routes.ts +98 -2
- package/src/types.ts +36 -0
- package/.mochi/svelte-client/HydratableIsland-k6q8esj4.js +0 -1
- package/.mochi/svelte-client/_hydrate-AppShell_2rtnxn6pgp2rh-sg96rp17.js +0 -2
- package/.mochi/svelte-client/_hydrate-InstanceView_2ma45l9bybfhl-p3f649gk.js +0 -1
- package/.mochi/svelte-client/_hydrate-SettingsView_8hmarwmf8onq-dzmgmv78.js +0 -29
- package/.mochi/svelte-client/chunk-7wkvn2vx.js +0 -2
- package/.mochi/svelte-client/chunk-m4fzsd5b.js +0 -2
- package/.mochi/svelte-css/Button-2vtbrzb5kgu25.css +0 -1
- package/.mochi/svelte-css/IdeBar-10xodudcx57qr.css +0 -1
- package/.mochi/svelte-css/SettingsView-2qfauz68axro1.css +0 -1
- package/src/container-injections/claude-skip-permissions.ts +0 -28
|
@@ -15,8 +15,13 @@
|
|
|
15
15
|
import UserCog from '@lucide/svelte/icons/user-cog';
|
|
16
16
|
import Cpu from '@lucide/svelte/icons/cpu';
|
|
17
17
|
import Variable from '@lucide/svelte/icons/variable';
|
|
18
|
+
import KeySquare from '@lucide/svelte/icons/key-square';
|
|
18
19
|
import Plus from '@lucide/svelte/icons/plus';
|
|
19
20
|
import X from '@lucide/svelte/icons/x';
|
|
21
|
+
import SlidersHorizontal from '@lucide/svelte/icons/sliders-horizontal';
|
|
22
|
+
import ListOrdered from '@lucide/svelte/icons/list-ordered';
|
|
23
|
+
import Boxes from '@lucide/svelte/icons/boxes';
|
|
24
|
+
import Puzzle from '@lucide/svelte/icons/puzzle';
|
|
20
25
|
import { Toaster } from 'svelte-french-toast';
|
|
21
26
|
import { flushSync } from 'svelte';
|
|
22
27
|
import { enhance } from 'mochi-framework';
|
|
@@ -27,9 +32,17 @@
|
|
|
27
32
|
import CoinButton from './CoinButton.svelte';
|
|
28
33
|
import Avatar from './Avatar.svelte';
|
|
29
34
|
import { avatars, findAvatar, type AvatarArt } from '../avatars/index.ts';
|
|
30
|
-
import
|
|
35
|
+
import {
|
|
36
|
+
CLAUDE_PERMISSION_MODES,
|
|
37
|
+
CLAUDE_EFFORT_LEVELS,
|
|
38
|
+
type ClaudePermissionMode,
|
|
39
|
+
type ClaudeEffortLevel,
|
|
40
|
+
type InstanceMode
|
|
41
|
+
} from '../types.ts';
|
|
31
42
|
import Terminal from '@lucide/svelte/icons/terminal';
|
|
32
43
|
import LayoutTemplate from '@lucide/svelte/icons/layout-template';
|
|
44
|
+
import ShieldCheck from '@lucide/svelte/icons/shield-check';
|
|
45
|
+
import Gauge from '@lucide/svelte/icons/gauge';
|
|
33
46
|
import { installPopupBackTrap } from '../lib/popup-nav.ts';
|
|
34
47
|
|
|
35
48
|
/** Every settings form action fails with the same `{ error }` shape. */
|
|
@@ -38,6 +51,8 @@
|
|
|
38
51
|
let {
|
|
39
52
|
pet,
|
|
40
53
|
defaultMode,
|
|
54
|
+
claudePermissionMode,
|
|
55
|
+
claudeEffortLevel,
|
|
41
56
|
defaultImage,
|
|
42
57
|
builtinImage,
|
|
43
58
|
disableBuildCache,
|
|
@@ -67,10 +82,17 @@
|
|
|
67
82
|
hostEnvVarsEnabled,
|
|
68
83
|
hostEnvVarNames,
|
|
69
84
|
hostEnvVarPresence,
|
|
85
|
+
customEnvVarsEnabled,
|
|
86
|
+
customEnvVarNames,
|
|
87
|
+
advancedSerialInjections,
|
|
88
|
+
advancedNoBuildkit,
|
|
89
|
+
advancedBlockingExtInstall,
|
|
70
90
|
version
|
|
71
91
|
}: {
|
|
72
92
|
pet?: AvatarArt;
|
|
73
93
|
defaultMode: InstanceMode;
|
|
94
|
+
claudePermissionMode: ClaudePermissionMode;
|
|
95
|
+
claudeEffortLevel: ClaudeEffortLevel;
|
|
74
96
|
defaultImage: string;
|
|
75
97
|
builtinImage: string;
|
|
76
98
|
disableBuildCache: boolean;
|
|
@@ -100,6 +122,11 @@
|
|
|
100
122
|
hostEnvVarsEnabled: boolean;
|
|
101
123
|
hostEnvVarNames: string[];
|
|
102
124
|
hostEnvVarPresence: Record<string, boolean>;
|
|
125
|
+
customEnvVarsEnabled: boolean;
|
|
126
|
+
customEnvVarNames: string[];
|
|
127
|
+
advancedSerialInjections: boolean;
|
|
128
|
+
advancedNoBuildkit: boolean;
|
|
129
|
+
advancedBlockingExtInstall: boolean;
|
|
103
130
|
version: string;
|
|
104
131
|
} = $props();
|
|
105
132
|
|
|
@@ -200,6 +227,63 @@
|
|
|
200
227
|
modeFormEl?.requestSubmit();
|
|
201
228
|
}
|
|
202
229
|
|
|
230
|
+
// svelte-ignore state_referenced_locally
|
|
231
|
+
let permissionChoice = $state<ClaudePermissionMode>(claudePermissionMode);
|
|
232
|
+
let savingPermission = $state(false);
|
|
233
|
+
let permissionError = $state<string | null>(null);
|
|
234
|
+
let permissionFormEl: HTMLFormElement | undefined;
|
|
235
|
+
|
|
236
|
+
const permissionModeOpts = saveOpts<{ mode: ClaudePermissionMode }>({
|
|
237
|
+
setSaving: (v) => (savingPermission = v),
|
|
238
|
+
setError: (v) => (permissionError = v),
|
|
239
|
+
setMsg: () => {},
|
|
240
|
+
onSuccess: (data) => {
|
|
241
|
+
if (data?.mode) permissionChoice = data.mode;
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
const PERMISSION_LABELS: Record<ClaudePermissionMode, string> = {
|
|
246
|
+
default: 'Default',
|
|
247
|
+
manual: 'Manual',
|
|
248
|
+
auto: 'Auto',
|
|
249
|
+
plan: 'Plan'
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
function choosePermissionMode(next: ClaudePermissionMode) {
|
|
253
|
+
if (next === permissionChoice) return;
|
|
254
|
+
flushSync(() => (permissionChoice = next));
|
|
255
|
+
permissionFormEl?.requestSubmit();
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// svelte-ignore state_referenced_locally
|
|
259
|
+
let effortChoice = $state<ClaudeEffortLevel>(claudeEffortLevel);
|
|
260
|
+
let savingEffort = $state(false);
|
|
261
|
+
let effortError = $state<string | null>(null);
|
|
262
|
+
let effortFormEl: HTMLFormElement | undefined;
|
|
263
|
+
|
|
264
|
+
const effortLevelOpts = saveOpts<{ level: ClaudeEffortLevel }>({
|
|
265
|
+
setSaving: (v) => (savingEffort = v),
|
|
266
|
+
setError: (v) => (effortError = v),
|
|
267
|
+
setMsg: () => {},
|
|
268
|
+
onSuccess: (data) => {
|
|
269
|
+
if (data?.level) effortChoice = data.level;
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
const EFFORT_LABELS: Record<ClaudeEffortLevel, string> = {
|
|
274
|
+
low: 'Low',
|
|
275
|
+
medium: 'Medium',
|
|
276
|
+
high: 'High',
|
|
277
|
+
xhigh: 'X-High',
|
|
278
|
+
max: 'Max'
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
function chooseEffort(next: ClaudeEffortLevel) {
|
|
282
|
+
if (next === effortChoice) return;
|
|
283
|
+
flushSync(() => (effortChoice = next));
|
|
284
|
+
effortFormEl?.requestSubmit();
|
|
285
|
+
}
|
|
286
|
+
|
|
203
287
|
// svelte-ignore state_referenced_locally
|
|
204
288
|
let image = $state(defaultImage);
|
|
205
289
|
let savingImage = $state(false);
|
|
@@ -327,6 +411,49 @@
|
|
|
327
411
|
}
|
|
328
412
|
});
|
|
329
413
|
|
|
414
|
+
// DB-backed advanced escape hatches, so they initialize from the props.
|
|
415
|
+
// svelte-ignore state_referenced_locally
|
|
416
|
+
let serialInjections = $state(advancedSerialInjections);
|
|
417
|
+
let savingSerial = $state(false);
|
|
418
|
+
let serialError = $state<string | null>(null);
|
|
419
|
+
const serialOpts = toggleOpts({
|
|
420
|
+
set: (v) => (serialInjections = v),
|
|
421
|
+
setSaving: (v) => (savingSerial = v),
|
|
422
|
+
setError: (v) => (serialError = v)
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
// svelte-ignore state_referenced_locally
|
|
426
|
+
let noBuildkit = $state(advancedNoBuildkit);
|
|
427
|
+
let savingBuildkit = $state(false);
|
|
428
|
+
let buildkitError = $state<string | null>(null);
|
|
429
|
+
const buildkitOpts = toggleOpts({
|
|
430
|
+
set: (v) => (noBuildkit = v),
|
|
431
|
+
setSaving: (v) => (savingBuildkit = v),
|
|
432
|
+
setError: (v) => (buildkitError = v)
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
// svelte-ignore state_referenced_locally
|
|
436
|
+
let blockingExtInstall = $state(advancedBlockingExtInstall);
|
|
437
|
+
let savingBlocking = $state(false);
|
|
438
|
+
let blockingError = $state<string | null>(null);
|
|
439
|
+
const blockingOpts = toggleOpts({
|
|
440
|
+
set: (v) => (blockingExtInstall = v),
|
|
441
|
+
setSaving: (v) => (savingBlocking = v),
|
|
442
|
+
setError: (v) => (blockingError = v)
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
let clearingVer = $state(false);
|
|
446
|
+
let verMsg = $state<string | null>(null);
|
|
447
|
+
let verError = $state<string | null>(null);
|
|
448
|
+
const clearVersionOpts = saveOpts({
|
|
449
|
+
setSaving: (v) => (clearingVer = v),
|
|
450
|
+
setError: (v) => (verError = v),
|
|
451
|
+
setMsg: (v) => (verMsg = v),
|
|
452
|
+
onSuccess: () => {
|
|
453
|
+
verMsg = 'Cleared — the next instance boot re-checks the registry.';
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
|
|
330
457
|
// Token values never reach the client; only whether each is set, for the placeholder.
|
|
331
458
|
// svelte-ignore state_referenced_locally
|
|
332
459
|
let manualTokens = $state(manualTokensEnabled);
|
|
@@ -575,6 +702,75 @@
|
|
|
575
702
|
});
|
|
576
703
|
}
|
|
577
704
|
|
|
705
|
+
// svelte-ignore state_referenced_locally
|
|
706
|
+
let customEnvEnabled = $state(customEnvVarsEnabled);
|
|
707
|
+
let savingCustomEnvToggle = $state(false);
|
|
708
|
+
let customEnvToggleError = $state<string | null>(null);
|
|
709
|
+
|
|
710
|
+
const customEnvVarsToggleOpts = toggleOpts({
|
|
711
|
+
set: (v) => (customEnvEnabled = v),
|
|
712
|
+
setSaving: (v) => (savingCustomEnvToggle = v),
|
|
713
|
+
setError: (v) => (customEnvToggleError = v)
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
// Only names round-trip; the secret values live server-side and are shown here as a placeholder.
|
|
717
|
+
// svelte-ignore state_referenced_locally
|
|
718
|
+
let customEnvNames = $state([...customEnvVarNames]);
|
|
719
|
+
let newCustomEnvName = $state('');
|
|
720
|
+
let newCustomEnvValue = $state('');
|
|
721
|
+
let savingCustomEnv = $state(false);
|
|
722
|
+
let customEnvMsg = $state<string | null>(null);
|
|
723
|
+
let customEnvError = $state<string | null>(null);
|
|
724
|
+
|
|
725
|
+
/**
|
|
726
|
+
* Add/update one var: name + secret value are submitted alone (the client never holds the other
|
|
727
|
+
* secrets to resubmit), so re-adding an existing name upserts its value server-side.
|
|
728
|
+
*/
|
|
729
|
+
const addCustomEnvOpts: MochiEnhanceOptions<{ names: string[] }, ActionFailure> = {
|
|
730
|
+
onPending: (v) => (savingCustomEnv = v),
|
|
731
|
+
submit: ({ formData, cancel }) => {
|
|
732
|
+
customEnvError = null;
|
|
733
|
+
customEnvMsg = null;
|
|
734
|
+
const name = String(formData.get('name') ?? '').trim();
|
|
735
|
+
const value = String(formData.get('value') ?? '');
|
|
736
|
+
if (!name || !value) {
|
|
737
|
+
customEnvError = !name ? 'Name is required.' : 'Value is required.';
|
|
738
|
+
cancel();
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
|
|
742
|
+
customEnvError = `Invalid variable name: ${name}`;
|
|
743
|
+
cancel();
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
formData.set('name', name);
|
|
747
|
+
return ({ result }) => {
|
|
748
|
+
if (result.type === 'success') {
|
|
749
|
+
customEnvNames = result.data?.names ?? customEnvNames;
|
|
750
|
+
newCustomEnvName = '';
|
|
751
|
+
newCustomEnvValue = '';
|
|
752
|
+
customEnvMsg = 'Saved.';
|
|
753
|
+
} else if (result.type === 'failure') {
|
|
754
|
+
customEnvError = result.data?.error ?? 'Request failed';
|
|
755
|
+
} else if (result.type === 'error') {
|
|
756
|
+
customEnvError = 'Network error. Try again.';
|
|
757
|
+
}
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
|
|
762
|
+
/** Remove form (one per row): posts just this var's name. */
|
|
763
|
+
function removeCustomEnvOpts(name: string) {
|
|
764
|
+
return saveOpts<{ names: string[] }>({
|
|
765
|
+
setSaving: (v) => (savingCustomEnv = v),
|
|
766
|
+
setError: (v) => (customEnvError = v),
|
|
767
|
+
setMsg: (v) => (customEnvMsg = v),
|
|
768
|
+
onSuccess: (data) => {
|
|
769
|
+
customEnvNames = data?.names ?? customEnvNames.filter((n) => n !== name);
|
|
770
|
+
}
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
|
|
578
774
|
function toggleSound(on: boolean) {
|
|
579
775
|
sound = on;
|
|
580
776
|
setSoundEnabled(on);
|
|
@@ -683,6 +879,87 @@
|
|
|
683
879
|
{/if}
|
|
684
880
|
</section>
|
|
685
881
|
|
|
882
|
+
<section class="card">
|
|
883
|
+
<form
|
|
884
|
+
class="row"
|
|
885
|
+
method="POST"
|
|
886
|
+
action="?/claudePermissionMode"
|
|
887
|
+
bind:this={permissionFormEl}
|
|
888
|
+
{@attach enhance(permissionModeOpts)}
|
|
889
|
+
>
|
|
890
|
+
<div class="label">
|
|
891
|
+
<ShieldCheck size={20} />
|
|
892
|
+
<div class="text">
|
|
893
|
+
<div class="name">Claude permission mode</div>
|
|
894
|
+
<div class="desc">
|
|
895
|
+
The mode Claude Code starts in inside new instances. <strong>Default</strong> runs
|
|
896
|
+
<code>--dangerously-skip-permissions</code>, which never prompts; the other three pass
|
|
897
|
+
<code>--permission-mode</code> instead, because the skip flag overrides them. Applies on
|
|
898
|
+
create and rebuild, not to already-running instances.
|
|
899
|
+
</div>
|
|
900
|
+
</div>
|
|
901
|
+
</div>
|
|
902
|
+
<input type="hidden" name="mode" value={permissionChoice} />
|
|
903
|
+
<div class="mode-toggle" role="group" aria-label="Claude permission mode">
|
|
904
|
+
{#each CLAUDE_PERMISSION_MODES as option (option)}
|
|
905
|
+
<button
|
|
906
|
+
type="button"
|
|
907
|
+
class="mode-btn"
|
|
908
|
+
class:active={permissionChoice === option}
|
|
909
|
+
aria-pressed={permissionChoice === option}
|
|
910
|
+
disabled={savingPermission}
|
|
911
|
+
onclick={() => choosePermissionMode(option)}
|
|
912
|
+
>
|
|
913
|
+
{PERMISSION_LABELS[option]}
|
|
914
|
+
</button>
|
|
915
|
+
{/each}
|
|
916
|
+
</div>
|
|
917
|
+
</form>
|
|
918
|
+
{#if permissionError}
|
|
919
|
+
<div class="sub"><div class="msg error">{permissionError}</div></div>
|
|
920
|
+
{/if}
|
|
921
|
+
</section>
|
|
922
|
+
|
|
923
|
+
<section class="card">
|
|
924
|
+
<form
|
|
925
|
+
class="row"
|
|
926
|
+
method="POST"
|
|
927
|
+
action="?/claudeEffortLevel"
|
|
928
|
+
bind:this={effortFormEl}
|
|
929
|
+
{@attach enhance(effortLevelOpts)}
|
|
930
|
+
>
|
|
931
|
+
<div class="label">
|
|
932
|
+
<Gauge size={20} />
|
|
933
|
+
<div class="text">
|
|
934
|
+
<div class="name">Claude effort level</div>
|
|
935
|
+
<div class="desc">
|
|
936
|
+
The default reasoning effort Claude Code starts new sessions at inside instances,
|
|
937
|
+
written to <code>~/.claude/settings.json</code>. Applies on create and rebuild, not to
|
|
938
|
+
already-running instances.
|
|
939
|
+
</div>
|
|
940
|
+
</div>
|
|
941
|
+
</div>
|
|
942
|
+
<input type="hidden" name="level" value={effortChoice} />
|
|
943
|
+
<div class="mode-toggle" role="group" aria-label="Claude effort level">
|
|
944
|
+
{#each CLAUDE_EFFORT_LEVELS as option (option)}
|
|
945
|
+
<button
|
|
946
|
+
type="button"
|
|
947
|
+
class="mode-btn"
|
|
948
|
+
class:active={effortChoice === option}
|
|
949
|
+
aria-pressed={effortChoice === option}
|
|
950
|
+
disabled={savingEffort}
|
|
951
|
+
onclick={() => chooseEffort(option)}
|
|
952
|
+
>
|
|
953
|
+
{EFFORT_LABELS[option]}
|
|
954
|
+
</button>
|
|
955
|
+
{/each}
|
|
956
|
+
</div>
|
|
957
|
+
</form>
|
|
958
|
+
{#if effortError}
|
|
959
|
+
<div class="sub"><div class="msg error">{effortError}</div></div>
|
|
960
|
+
{/if}
|
|
961
|
+
</section>
|
|
962
|
+
|
|
686
963
|
<section class="card">
|
|
687
964
|
<form
|
|
688
965
|
class="row image-row"
|
|
@@ -1561,6 +1838,110 @@
|
|
|
1561
1838
|
{/if}
|
|
1562
1839
|
</section>
|
|
1563
1840
|
|
|
1841
|
+
<section class="card">
|
|
1842
|
+
<form
|
|
1843
|
+
class="row"
|
|
1844
|
+
method="POST"
|
|
1845
|
+
action="?/customEnvVarsToggle"
|
|
1846
|
+
{@attach enhance(customEnvVarsToggleOpts)}
|
|
1847
|
+
>
|
|
1848
|
+
<div class="label">
|
|
1849
|
+
<KeySquare size={20} />
|
|
1850
|
+
<div class="text">
|
|
1851
|
+
<div class="name">Custom environment variables</div>
|
|
1852
|
+
<div class="desc">
|
|
1853
|
+
Inject your own <code>NAME=value</code> pairs into every new container as
|
|
1854
|
+
<code>containerEnv</code>, available to all processes. Values are secret — stored
|
|
1855
|
+
locally, masked in the build log, and never shown here after saving. Re-add a name to
|
|
1856
|
+
update its value.
|
|
1857
|
+
</div>
|
|
1858
|
+
</div>
|
|
1859
|
+
</div>
|
|
1860
|
+
<label class="switch">
|
|
1861
|
+
<input
|
|
1862
|
+
type="checkbox"
|
|
1863
|
+
name="enabled"
|
|
1864
|
+
checked={customEnvEnabled}
|
|
1865
|
+
disabled={savingCustomEnvToggle}
|
|
1866
|
+
onchange={(e) => {
|
|
1867
|
+
customEnvEnabled = e.currentTarget.checked;
|
|
1868
|
+
e.currentTarget.form?.requestSubmit();
|
|
1869
|
+
}}
|
|
1870
|
+
/>
|
|
1871
|
+
<span class="track"><span class="thumb"></span></span>
|
|
1872
|
+
</label>
|
|
1873
|
+
</form>
|
|
1874
|
+
{#if customEnvToggleError}
|
|
1875
|
+
<div class="sub"><div class="msg error">{customEnvToggleError}</div></div>
|
|
1876
|
+
{/if}
|
|
1877
|
+
|
|
1878
|
+
{#if customEnvEnabled}
|
|
1879
|
+
<div class="row divided var-row">
|
|
1880
|
+
<div class="var-list">
|
|
1881
|
+
{#each customEnvNames as name (name)}
|
|
1882
|
+
<form
|
|
1883
|
+
class="var-item"
|
|
1884
|
+
method="POST"
|
|
1885
|
+
action="?/removeCustomEnvVar"
|
|
1886
|
+
{@attach enhance(removeCustomEnvOpts(name))}
|
|
1887
|
+
>
|
|
1888
|
+
<input type="hidden" name="name" value={name} />
|
|
1889
|
+
<span class="var-name">{name}</span>
|
|
1890
|
+
<span class="var-status present" title="Set — injected into every new container">
|
|
1891
|
+
•••• saved
|
|
1892
|
+
</span>
|
|
1893
|
+
<button
|
|
1894
|
+
type="submit"
|
|
1895
|
+
class="var-remove"
|
|
1896
|
+
disabled={savingCustomEnv}
|
|
1897
|
+
aria-label={`Remove ${name}`}
|
|
1898
|
+
>
|
|
1899
|
+
<X size={13} />
|
|
1900
|
+
</button>
|
|
1901
|
+
</form>
|
|
1902
|
+
{:else}
|
|
1903
|
+
<div class="var-empty">No variables added yet.</div>
|
|
1904
|
+
{/each}
|
|
1905
|
+
</div>
|
|
1906
|
+
<form
|
|
1907
|
+
class="var-add"
|
|
1908
|
+
method="POST"
|
|
1909
|
+
action="?/setCustomEnvVar"
|
|
1910
|
+
{@attach enhance(addCustomEnvOpts)}
|
|
1911
|
+
>
|
|
1912
|
+
<input
|
|
1913
|
+
type="text"
|
|
1914
|
+
name="name"
|
|
1915
|
+
class="image-input"
|
|
1916
|
+
bind:value={newCustomEnvName}
|
|
1917
|
+
spellcheck="false"
|
|
1918
|
+
autocapitalize="off"
|
|
1919
|
+
autocorrect="off"
|
|
1920
|
+
autocomplete="off"
|
|
1921
|
+
placeholder="MY_SECRET"
|
|
1922
|
+
/>
|
|
1923
|
+
<input
|
|
1924
|
+
type="password"
|
|
1925
|
+
name="value"
|
|
1926
|
+
class="image-input"
|
|
1927
|
+
bind:value={newCustomEnvValue}
|
|
1928
|
+
spellcheck="false"
|
|
1929
|
+
autocapitalize="off"
|
|
1930
|
+
autocorrect="off"
|
|
1931
|
+
autocomplete="off"
|
|
1932
|
+
placeholder="value"
|
|
1933
|
+
/>
|
|
1934
|
+
<Button type="submit" icon={Plus} disabled={savingCustomEnv}>Add</Button>
|
|
1935
|
+
</form>
|
|
1936
|
+
{#if customEnvError}
|
|
1937
|
+
<div class="msg error">{customEnvError}</div>
|
|
1938
|
+
{:else if customEnvMsg}
|
|
1939
|
+
<div class="msg ok">{customEnvMsg}</div>
|
|
1940
|
+
{/if}
|
|
1941
|
+
</div>
|
|
1942
|
+
{/if}
|
|
1943
|
+
</section>
|
|
1944
|
+
|
|
1564
1945
|
<section class="card">
|
|
1565
1946
|
<div class="row">
|
|
1566
1947
|
<div class="label">
|
|
@@ -1643,6 +2024,144 @@
|
|
|
1643
2024
|
{/if}
|
|
1644
2025
|
</section>
|
|
1645
2026
|
|
|
2027
|
+
<section class="card">
|
|
2028
|
+
<details class="advanced">
|
|
2029
|
+
<summary>
|
|
2030
|
+
<SlidersHorizontal size={20} />
|
|
2031
|
+
<span class="name">Advanced</span>
|
|
2032
|
+
<span class="hint">escape hatches — defaults are right for almost everyone</span>
|
|
2033
|
+
</summary>
|
|
2034
|
+
|
|
2035
|
+
<form
|
|
2036
|
+
class="row divided"
|
|
2037
|
+
method="POST"
|
|
2038
|
+
action="?/serialInjectionsToggle"
|
|
2039
|
+
{@attach enhance(serialOpts)}
|
|
2040
|
+
>
|
|
2041
|
+
<div class="label">
|
|
2042
|
+
<ListOrdered size={20} />
|
|
2043
|
+
<div class="text">
|
|
2044
|
+
<div class="name">Serial boot injections</div>
|
|
2045
|
+
<div class="desc">
|
|
2046
|
+
Run boot injections one at a time instead of in parallel stages. Slower boot; useful
|
|
2047
|
+
when diagnosing injection interference.
|
|
2048
|
+
</div>
|
|
2049
|
+
</div>
|
|
2050
|
+
</div>
|
|
2051
|
+
<label class="switch">
|
|
2052
|
+
<input
|
|
2053
|
+
type="checkbox"
|
|
2054
|
+
name="enabled"
|
|
2055
|
+
checked={serialInjections}
|
|
2056
|
+
disabled={savingSerial}
|
|
2057
|
+
onchange={(e) => {
|
|
2058
|
+
serialInjections = e.currentTarget.checked;
|
|
2059
|
+
e.currentTarget.form?.requestSubmit();
|
|
2060
|
+
}}
|
|
2061
|
+
/>
|
|
2062
|
+
<span class="track"><span class="thumb"></span></span>
|
|
2063
|
+
</label>
|
|
2064
|
+
</form>
|
|
2065
|
+
{#if serialError}
|
|
2066
|
+
<div class="sub"><div class="msg error">{serialError}</div></div>
|
|
2067
|
+
{/if}
|
|
2068
|
+
|
|
2069
|
+
<form
|
|
2070
|
+
class="row divided"
|
|
2071
|
+
method="POST"
|
|
2072
|
+
action="?/noBuildkitToggle"
|
|
2073
|
+
{@attach enhance(buildkitOpts)}
|
|
2074
|
+
>
|
|
2075
|
+
<div class="label">
|
|
2076
|
+
<Boxes size={20} />
|
|
2077
|
+
<div class="text">
|
|
2078
|
+
<div class="name">Don't force BuildKit</div>
|
|
2079
|
+
<div class="desc">
|
|
2080
|
+
Skip setting <code>DOCKER_BUILDKIT=1</code> for container builds and let the daemon's
|
|
2081
|
+
default builder decide. For daemons where forcing BuildKit breaks builds.
|
|
2082
|
+
</div>
|
|
2083
|
+
</div>
|
|
2084
|
+
</div>
|
|
2085
|
+
<label class="switch">
|
|
2086
|
+
<input
|
|
2087
|
+
type="checkbox"
|
|
2088
|
+
name="enabled"
|
|
2089
|
+
checked={noBuildkit}
|
|
2090
|
+
disabled={savingBuildkit}
|
|
2091
|
+
onchange={(e) => {
|
|
2092
|
+
noBuildkit = e.currentTarget.checked;
|
|
2093
|
+
e.currentTarget.form?.requestSubmit();
|
|
2094
|
+
}}
|
|
2095
|
+
/>
|
|
2096
|
+
<span class="track"><span class="thumb"></span></span>
|
|
2097
|
+
</label>
|
|
2098
|
+
</form>
|
|
2099
|
+
{#if buildkitError}
|
|
2100
|
+
<div class="sub"><div class="msg error">{buildkitError}</div></div>
|
|
2101
|
+
{/if}
|
|
2102
|
+
|
|
2103
|
+
<form
|
|
2104
|
+
class="row divided"
|
|
2105
|
+
method="POST"
|
|
2106
|
+
action="?/blockingExtInstallToggle"
|
|
2107
|
+
{@attach enhance(blockingOpts)}
|
|
2108
|
+
>
|
|
2109
|
+
<div class="label">
|
|
2110
|
+
<Puzzle size={20} />
|
|
2111
|
+
<div class="text">
|
|
2112
|
+
<div class="name">Blocking IDE extension install</div>
|
|
2113
|
+
<div class="desc">
|
|
2114
|
+
Install the Claude Code IDE extension before code-server starts, so the first window
|
|
2115
|
+
always has it active. Slower first boot.
|
|
2116
|
+
</div>
|
|
2117
|
+
</div>
|
|
2118
|
+
</div>
|
|
2119
|
+
<label class="switch">
|
|
2120
|
+
<input
|
|
2121
|
+
type="checkbox"
|
|
2122
|
+
name="enabled"
|
|
2123
|
+
checked={blockingExtInstall}
|
|
2124
|
+
disabled={savingBlocking}
|
|
2125
|
+
onchange={(e) => {
|
|
2126
|
+
blockingExtInstall = e.currentTarget.checked;
|
|
2127
|
+
e.currentTarget.form?.requestSubmit();
|
|
2128
|
+
}}
|
|
2129
|
+
/>
|
|
2130
|
+
<span class="track"><span class="thumb"></span></span>
|
|
2131
|
+
</label>
|
|
2132
|
+
</form>
|
|
2133
|
+
{#if blockingError}
|
|
2134
|
+
<div class="sub"><div class="msg error">{blockingError}</div></div>
|
|
2135
|
+
{/if}
|
|
2136
|
+
|
|
2137
|
+
<form
|
|
2138
|
+
class="row divided"
|
|
2139
|
+
method="POST"
|
|
2140
|
+
action="?/clearVersionCache"
|
|
2141
|
+
{@attach enhance(clearVersionOpts)}
|
|
2142
|
+
>
|
|
2143
|
+
<div class="label">
|
|
2144
|
+
<Trash2 size={20} />
|
|
2145
|
+
<div class="text">
|
|
2146
|
+
<div class="name">Clear claude-code version cache</div>
|
|
2147
|
+
<div class="desc">
|
|
2148
|
+
Forget the cached latest-version check; the next instance boot asks the npm registry
|
|
2149
|
+
again.
|
|
2150
|
+
</div>
|
|
2151
|
+
{#if verError}
|
|
2152
|
+
<div class="msg error">{verError}</div>
|
|
2153
|
+
{:else if verMsg}
|
|
2154
|
+
<div class="msg ok">{verMsg}</div>
|
|
2155
|
+
{/if}
|
|
2156
|
+
</div>
|
|
2157
|
+
</div>
|
|
2158
|
+
<Button type="submit" icon={Trash2} disabled={clearingVer}>
|
|
2159
|
+
{clearingVer ? 'Clearing…' : 'Clear cache'}
|
|
2160
|
+
</Button>
|
|
2161
|
+
</form>
|
|
2162
|
+
</details>
|
|
2163
|
+
</section>
|
|
2164
|
+
|
|
1646
2165
|
<section class="card danger-card">
|
|
1647
2166
|
<form class="row" method="POST" action="?/shutdown" {@attach enhance(shutdownOpts)}>
|
|
1648
2167
|
<div class="label">
|
|
@@ -1711,6 +2230,34 @@
|
|
|
1711
2230
|
.danger-card {
|
|
1712
2231
|
border-color: var(--danger);
|
|
1713
2232
|
}
|
|
2233
|
+
details.advanced > summary {
|
|
2234
|
+
display: flex;
|
|
2235
|
+
align-items: center;
|
|
2236
|
+
gap: 10px;
|
|
2237
|
+
padding: 14px 16px;
|
|
2238
|
+
cursor: pointer;
|
|
2239
|
+
list-style: none;
|
|
2240
|
+
font-family: var(--font-mono);
|
|
2241
|
+
}
|
|
2242
|
+
details.advanced > summary::-webkit-details-marker {
|
|
2243
|
+
display: none;
|
|
2244
|
+
}
|
|
2245
|
+
details.advanced > summary .name {
|
|
2246
|
+
font-size: 13px;
|
|
2247
|
+
}
|
|
2248
|
+
details.advanced > summary .hint {
|
|
2249
|
+
color: var(--ink-soft);
|
|
2250
|
+
font-size: 12px;
|
|
2251
|
+
}
|
|
2252
|
+
/* The chevron affordance a hidden native marker would otherwise provide. */
|
|
2253
|
+
details.advanced > summary::after {
|
|
2254
|
+
content: '▸';
|
|
2255
|
+
margin-left: auto;
|
|
2256
|
+
color: var(--ink-soft);
|
|
2257
|
+
}
|
|
2258
|
+
details.advanced[open] > summary::after {
|
|
2259
|
+
content: '▾';
|
|
2260
|
+
}
|
|
1714
2261
|
.disabled-card {
|
|
1715
2262
|
opacity: 0.55;
|
|
1716
2263
|
pointer-events: none;
|