@brimveyn/aimux 1.21.0 → 1.22.2
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 +27 -25
- package/package.json +2 -2
- package/src/app-runtime/auto-commit-driver.ts +22 -27
- package/src/app-runtime/backend-attach-runtime.ts +43 -36
- package/src/app-runtime/backend-runtime-events.ts +107 -88
- package/src/app-runtime/editor-actions.ts +201 -0
- package/src/app-runtime/git-actions.ts +197 -0
- package/src/app-runtime/measure-ref.ts +19 -0
- package/src/app-runtime/navigation-actions.ts +213 -0
- package/src/app-runtime/project-actions.ts +188 -0
- package/src/app-runtime/prompt-injection.ts +123 -0
- package/src/app-runtime/selection.ts +29 -0
- package/src/app-runtime/settings-actions.ts +156 -0
- package/src/app-runtime/setup-actions.ts +237 -0
- package/src/app-runtime/side-effect-context.ts +33 -0
- package/src/app-runtime/side-effects.ts +302 -1541
- package/src/app-runtime/snippet-actions.ts +3 -2
- package/src/app-runtime/tab-actions.ts +218 -0
- package/src/app-runtime/tab-runtime-timeouts.ts +1 -1
- package/src/app-runtime/use-auto-commit-driver.ts +14 -13
- package/src/app-runtime/use-backend-runtime.ts +12 -11
- package/src/app-runtime/use-directory-search.ts +5 -4
- package/src/app-runtime/use-mouse-handlers.ts +2 -1
- package/src/app-runtime/{use-workspace-autosave.ts → use-project-autosave.ts} +4 -4
- package/src/app-runtime/use-renderer-bindings.ts +2 -1
- package/src/app-runtime/use-setup-runner.ts +96 -0
- package/src/app-runtime/use-terminal-resize.ts +18 -14
- package/src/app-runtime/workspace-actions.ts +366 -0
- package/src/app-runtime/workspace-activity.ts +138 -0
- package/src/app-runtime/workspace-naming.ts +90 -0
- package/src/app.tsx +93 -48
- package/src/assets/sounds/bell.wav +0 -0
- package/src/assets/sounds/ding.wav +0 -0
- package/src/assets/sounds/plane.wav +0 -0
- package/src/cli/client/daemon-client.ts +2 -2
- package/src/cli/client/project-resolver.ts +112 -0
- package/src/cli/commands/project/close.ts +32 -0
- package/src/cli/commands/project/create.ts +93 -0
- package/src/cli/commands/project/list.ts +25 -0
- package/src/cli/commands/project/show.ts +32 -0
- package/src/cli/commands/{workspace → project}/switch.ts +19 -19
- package/src/cli/commands/tab/await.ts +2 -2
- package/src/cli/commands/tab/close.ts +3 -3
- package/src/cli/commands/tab/create.ts +74 -73
- package/src/cli/commands/tab/focus.ts +3 -3
- package/src/cli/commands/tab/list.ts +6 -6
- package/src/cli/commands/tab/run.ts +2 -2
- package/src/cli/commands/tab/send.ts +2 -2
- package/src/cli/commands/tab/snapshot.ts +2 -2
- package/src/cli/commands/tab/tail.ts +2 -2
- package/src/cli/commands/tab/wait.ts +2 -2
- package/src/cli/commands/worker/await.ts +3 -3
- package/src/cli/commands/worker/doctor.ts +30 -30
- package/src/cli/commands/worker/list.ts +15 -15
- package/src/cli/commands/worker/prompt.ts +3 -3
- package/src/cli/commands/worker/run.ts +23 -23
- package/src/cli/commands/worker/shared.ts +53 -59
- package/src/cli/commands/worker/stop.ts +29 -29
- package/src/cli/commands/worker/submit.ts +3 -3
- package/src/cli/commands/{worktree → workspace}/create-core.ts +26 -26
- package/src/cli/commands/workspace/create.ts +30 -68
- package/src/cli/commands/workspace/list.ts +52 -9
- package/src/cli/commands/workspace/remove.ts +81 -0
- package/src/cli/completion/plan.ts +2 -2
- package/src/cli/completion/sources.ts +18 -18
- package/src/cli/context.ts +13 -13
- package/src/cli/flags.ts +30 -10
- package/src/cli/index.ts +26 -16
- package/src/cli/output.ts +1 -1
- package/src/cli/registry.ts +12 -12
- package/src/config/loader.ts +16 -5
- package/src/config.ts +70 -112
- package/src/daemon/catalog-writer.ts +67 -67
- package/src/daemon/daemon.ts +205 -201
- package/src/daemon/session-manager.ts +39 -39
- package/src/daemon/session-registry.ts +14 -14
- package/src/git/divergence.ts +33 -2
- package/src/git/git-poller.ts +1 -1
- package/src/git/git-status.ts +2 -2
- package/src/git/{move-worktree.ts → move-workspace.ts} +5 -5
- package/src/git/pr-status.ts +1 -1
- package/src/git/repo-discovery.ts +1 -1
- package/src/git/use-repo-discovery.ts +1 -1
- package/src/git/workspace-branch-poller.ts +54 -0
- package/src/git/workspace-divergence-poller.ts +65 -0
- package/src/git/worktree.ts +29 -0
- package/src/index.tsx +4 -4
- package/src/input/keymap/help-entries.ts +7 -5
- package/src/input/modes/bridge.ts +13 -12
- package/src/input/modes/handlers/shared.ts +1 -1
- package/src/input/modes/transitions.ts +34 -21
- package/src/input/modes/types.ts +48 -31
- package/src/ipc/manager-protocol.ts +44 -44
- package/src/ipc/protocol.ts +192 -166
- package/src/platform/play-sound.ts +184 -0
- package/src/platform/project-search.ts +8 -8
- package/src/platform/worktree-paths.ts +8 -6
- package/src/pty/assistant-question-extractor.ts +1 -1
- package/src/pty/assistant-status-detection-loop.ts +79 -55
- package/src/pty/assistant-status-detector.ts +40 -15
- package/src/pty/command-registry.ts +81 -1
- package/src/restart-terminal-manager.ts +1 -1
- package/src/services/ai-usage/provider.ts +9 -2
- package/src/services/ai-usage/spawn.ts +19 -6
- package/src/session-backend/bootstrap.ts +4 -4
- package/src/session-backend/local-session-backend.ts +78 -75
- package/src/session-backend/remote-session-backend.ts +64 -52
- package/src/session-backend/types.ts +43 -32
- package/src/settings/live.ts +90 -0
- package/src/settings/search.ts +43 -0
- package/src/settings/sections/about.ts +49 -0
- package/src/settings/sections/appearance.ts +54 -0
- package/src/settings/sections/automation.ts +105 -0
- package/src/settings/sections/commands.ts +72 -0
- package/src/settings/sections/editor.ts +56 -0
- package/src/settings/sections/experimental.ts +57 -0
- package/src/settings/sections/git.ts +112 -0
- package/src/settings/sections/index.ts +95 -0
- package/src/settings/sections/integrations.ts +23 -0
- package/src/settings/sections/layout.ts +72 -0
- package/src/settings/sections/notifications.ts +99 -0
- package/src/settings/sections/setup.ts +54 -0
- package/src/settings/sections/status-bar.ts +63 -0
- package/src/settings/settings-store.ts +207 -0
- package/src/settings/types.ts +108 -0
- package/src/snippets/run-shell-var.ts +48 -22
- package/src/state/actions.ts +342 -0
- package/src/state/app-store.ts +2 -1
- package/src/state/bars.ts +1 -1
- package/src/state/dispatch-ref.ts +1 -1
- package/src/state/layout-resize.ts +2 -2
- package/src/state/project-catalog.ts +275 -0
- package/src/state/project-data.ts +143 -0
- package/src/state/{session-persistence.ts → project-persistence.ts} +74 -68
- package/src/state/project-save.ts +46 -0
- package/src/state/project-workspaces.ts +392 -0
- package/src/state/reducers/auto-commit-state.ts +11 -10
- package/src/state/reducers/git-commit-modal-state.ts +122 -0
- package/src/state/reducers/git-mode-state.ts +1 -1
- package/src/state/reducers/git-panel-state.ts +10 -6
- package/src/state/reducers/modal-state.ts +290 -681
- package/src/state/reducers/multi-repo-state.ts +3 -6
- package/src/state/reducers/project-state.ts +312 -0
- package/src/state/reducers/settings-state.ts +80 -0
- package/src/state/reducers/tab-state.ts +107 -60
- package/src/state/reducers/ui-state.ts +4 -3
- package/src/state/selectors.ts +49 -37
- package/src/state/store.ts +57 -24
- package/src/state/types.ts +244 -405
- package/src/state/validation.ts +14 -12
- package/src/terminal-manager/manager-client.ts +32 -32
- package/src/terminal-manager/terminal-manager.ts +24 -24
- package/src/ui/components/git/git-panel.tsx +2 -2
- package/src/ui/components/git/git-view.tsx +12 -12
- package/src/ui/components/git/image-diff/image-diff-view.tsx +1 -1
- package/src/ui/components/git/pane/git-pane-widget.tsx +7 -7
- package/src/ui/components/layout/sidebar/project-list.tsx +444 -0
- package/src/ui/components/layout/sidebar/tab-item.tsx +27 -12
- package/src/ui/components/layout/sidebar/use-sidebar-auto-scroll.ts +1 -1
- package/src/ui/components/layout/sidebar/workspace-row.tsx +253 -0
- package/src/ui/components/layout/status-bar.tsx +6 -2
- package/src/ui/components/layout/terminal-pane.tsx +34 -29
- package/src/ui/components/layout/top-tab-bar.tsx +46 -36
- package/src/ui/components/modals/git/git-commit-modal.tsx +8 -8
- package/src/ui/components/modals/{sessions/create-session-modal.tsx → projects/create-project-modal.tsx} +12 -12
- package/src/ui/components/modals/{sessions/session-name-modal.tsx → projects/project-name-modal.tsx} +2 -2
- package/src/ui/components/modals/{sessions/session-picker-modal.tsx → projects/project-picker-modal.tsx} +33 -35
- package/src/ui/components/modals/settings/settings-search-modal.tsx +70 -0
- package/src/ui/components/modals/shared/form.tsx +34 -22
- package/src/ui/components/modals/shared/picker.tsx +10 -10
- package/src/ui/components/modals/shared/{worktree-delete-confirm.tsx → workspace-delete-confirm.tsx} +9 -9
- package/src/ui/components/modals/tabs/new-tab-modal.tsx +30 -324
- package/src/ui/components/modals/workspace/create-workspace-modal.tsx +101 -0
- package/src/ui/components/modals/{worktree/worktree-move-confirm-modal.tsx → workspace/workspace-move-confirm-modal.tsx} +5 -5
- package/src/ui/components/modals/{worktree/worktree-move-modal.tsx → workspace/workspace-move-modal.tsx} +29 -27
- package/src/ui/components/primitives/input-field.tsx +7 -3
- package/src/ui/components/primitives/surface.tsx +3 -0
- package/src/ui/components/settings/row-value.tsx +74 -0
- package/src/ui/components/settings/settings-row.tsx +78 -0
- package/src/ui/components/settings/settings-view.tsx +169 -0
- package/src/ui/components/setup/setup-widget.tsx +152 -0
- package/src/ui/flash/build-labels.ts +27 -27
- package/src/ui/hooks/use-activity-sprite.ts +73 -0
- package/src/ui/hooks/use-scroll-active-into-view.ts +26 -0
- package/src/ui/{session-ordering.ts → project-ordering.ts} +4 -4
- package/src/ui/root.tsx +139 -121
- package/src/ui/status-bar-model.ts +48 -34
- package/src/ui/terminal-graphics/kitty.ts +28 -2
- package/src/ui/terminal-graphics/sprites/done@330/0.png +0 -0
- package/src/ui/terminal-graphics/sprites/done@330/1.png +0 -0
- package/src/ui/terminal-graphics/sprites/idle@1000/0.png +0 -0
- package/src/ui/terminal-graphics/sprites/idle@1000/1.png +0 -0
- package/src/ui/terminal-graphics/sprites/waiting@170/0.png +0 -0
- package/src/ui/terminal-graphics/sprites/waiting@170/1.png +0 -0
- package/src/ui/terminal-graphics/sprites/working@150/0.png +0 -0
- package/src/ui/terminal-graphics/sprites/working@150/1.png +0 -0
- package/src/ui/terminal-graphics/sprites/working@150/2.png +0 -0
- package/src/ui/terminal-graphics/sprites/working@150/3.png +0 -0
- package/src/ui/terminal-graphics/sprites.ts +246 -0
- package/src/ui/truncate.ts +7 -0
- package/src/ui/widgets/registry.tsx +6 -3
- package/src/ui/widgets/widget-context-menu.ts +26 -10
- package/src/update.ts +2 -2
- package/src/app-runtime/session-actions.ts +0 -187
- package/src/cli/client/workspace-resolver.ts +0 -110
- package/src/cli/commands/workspace/close.ts +0 -32
- package/src/cli/commands/workspace/show.ts +0 -32
- package/src/cli/commands/worktree/create.ts +0 -55
- package/src/cli/commands/worktree/list.ts +0 -68
- package/src/cli/commands/worktree/remove.ts +0 -81
- package/src/git/worktree-branch-poller.ts +0 -54
- package/src/git/worktree-divergence-poller.ts +0 -59
- package/src/state/reducers/session-state.ts +0 -262
- package/src/state/session-catalog.ts +0 -143
- package/src/state/session-worktrees.ts +0 -260
- package/src/state/workspace-save.ts +0 -43
- package/src/ui/components/layout/sidebar/use-top-tab-bar-auto-scroll.ts +0 -30
- package/src/ui/components/layout/sidebar/workspace-list.tsx +0 -418
- package/src/ui/components/layout/sidebar/worktree-row.tsx +0 -149
- /package/src/ui/{components/git/image-diff → terminal-graphics}/dimensions.ts +0 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { setAutoCommitEnabled } from '@brimveyn/aimux-config'
|
|
2
|
+
|
|
3
|
+
import type { SettingSection } from '../types'
|
|
4
|
+
|
|
5
|
+
/** Ids `src/settings/live.ts` reads back — spelled once, here, next to the rows. */
|
|
6
|
+
export const AUTO_COMMIT_ENABLED = 'autoCommit.enabled'
|
|
7
|
+
export const AUTO_COMMIT_TIMEOUT = 'autoCommit.timeoutMs'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Auto-commit applies live: the driver reads its config through a ref that this
|
|
11
|
+
* store feeds, and the keymap action reads the module flag `apply` sets.
|
|
12
|
+
*
|
|
13
|
+
* Auto-rename does not. Its config is handed to the session backend at bootstrap,
|
|
14
|
+
* before this screen exists, so a change reaches it on the next launch — which is
|
|
15
|
+
* what `restart` puts on the row.
|
|
16
|
+
*/
|
|
17
|
+
export const AUTOMATION_SECTION: SettingSection = {
|
|
18
|
+
id: 'automation',
|
|
19
|
+
label: 'Automation',
|
|
20
|
+
rows: [
|
|
21
|
+
{
|
|
22
|
+
apply: (value) => setAutoCommitEnabled(value === true),
|
|
23
|
+
description: 'Offer a generated commit message when you commit from git mode.',
|
|
24
|
+
fallback: false,
|
|
25
|
+
fromConfig: (config) => config.autoCommit?.enabled,
|
|
26
|
+
id: AUTO_COMMIT_ENABLED,
|
|
27
|
+
kind: 'toggle',
|
|
28
|
+
label: 'Auto-commit',
|
|
29
|
+
storage: 'settings',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
description: 'How long the assistant gets to produce one, in milliseconds.',
|
|
33
|
+
fallback: 60_000,
|
|
34
|
+
fromConfig: (config) => config.autoCommit?.timeoutMs,
|
|
35
|
+
id: AUTO_COMMIT_TIMEOUT,
|
|
36
|
+
kind: 'number',
|
|
37
|
+
label: 'Auto-commit timeout',
|
|
38
|
+
max: 300_000,
|
|
39
|
+
min: 10_000,
|
|
40
|
+
step: 10_000,
|
|
41
|
+
storage: 'settings',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
description: 'Name a tab from the first real prompt you send it.',
|
|
45
|
+
fallback: true,
|
|
46
|
+
fromConfig: (config) => config.autoRename?.enabled,
|
|
47
|
+
id: 'autoRename.enabled',
|
|
48
|
+
kind: 'toggle',
|
|
49
|
+
label: 'Auto-rename tabs',
|
|
50
|
+
restart: true,
|
|
51
|
+
storage: 'settings',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
description: 'Quiet period after a prompt before a title is generated, in milliseconds.',
|
|
55
|
+
fallback: 2_500,
|
|
56
|
+
fromConfig: (config) => config.autoRename?.settleMs,
|
|
57
|
+
id: 'autoRename.settleMs',
|
|
58
|
+
kind: 'number',
|
|
59
|
+
label: 'Auto-rename settle',
|
|
60
|
+
max: 10_000,
|
|
61
|
+
min: 0,
|
|
62
|
+
restart: true,
|
|
63
|
+
step: 500,
|
|
64
|
+
storage: 'settings',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
description: 'Tries before falling back to a title derived from the prompt itself.',
|
|
68
|
+
fallback: 3,
|
|
69
|
+
fromConfig: (config) => config.autoRename?.maxAttempts,
|
|
70
|
+
id: 'autoRename.maxAttempts',
|
|
71
|
+
kind: 'number',
|
|
72
|
+
label: 'Auto-rename attempts',
|
|
73
|
+
max: 10,
|
|
74
|
+
min: 1,
|
|
75
|
+
restart: true,
|
|
76
|
+
step: 1,
|
|
77
|
+
storage: 'settings',
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
description: 'Shorter prompts are menu answers, not something to name a tab after.',
|
|
81
|
+
fallback: 3,
|
|
82
|
+
fromConfig: (config) => config.autoRename?.minPromptWords,
|
|
83
|
+
id: 'autoRename.minPromptWords',
|
|
84
|
+
kind: 'number',
|
|
85
|
+
label: 'Auto-rename minimum words',
|
|
86
|
+
max: 20,
|
|
87
|
+
min: 1,
|
|
88
|
+
restart: true,
|
|
89
|
+
step: 1,
|
|
90
|
+
storage: 'settings',
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
fallback: 15_000,
|
|
94
|
+
fromConfig: (config) => config.autoRename?.timeoutMs,
|
|
95
|
+
id: 'autoRename.timeoutMs',
|
|
96
|
+
kind: 'number',
|
|
97
|
+
label: 'Auto-rename timeout',
|
|
98
|
+
max: 60_000,
|
|
99
|
+
min: 5_000,
|
|
100
|
+
restart: true,
|
|
101
|
+
step: 5_000,
|
|
102
|
+
storage: 'settings',
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { SettingRow, SettingSection } from '../types'
|
|
2
|
+
|
|
3
|
+
import { ASSISTANT_OPTIONS } from '../../pty/command-registry'
|
|
4
|
+
import { dispatchGlobal } from '../../state/dispatch-ref'
|
|
5
|
+
|
|
6
|
+
export const SNIPPET_TRIGGER_CHAR = 'snippets.triggerChar'
|
|
7
|
+
export const AUTO_COMMIT_MODEL_PREFIX = 'autoCommit.models.'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* What each assistant is actually launched with. The value lives in `AppState`
|
|
11
|
+
* (and is persisted with the rest of it), so these rows delegate — and an empty
|
|
12
|
+
* value removes the override rather than launching an empty command.
|
|
13
|
+
*/
|
|
14
|
+
function customCommandRow(option: (typeof ASSISTANT_OPTIONS)[number]): SettingRow {
|
|
15
|
+
return {
|
|
16
|
+
description: `Default: ${option.command}`,
|
|
17
|
+
id: `customCommands.${option.id}`,
|
|
18
|
+
kind: 'text',
|
|
19
|
+
label: option.label,
|
|
20
|
+
placeholder: option.command,
|
|
21
|
+
read: (ctx) => ctx.state.customCommands[option.id] ?? '',
|
|
22
|
+
storage: 'app',
|
|
23
|
+
write: (value, ctx) => {
|
|
24
|
+
const next = { ...ctx.state.customCommands }
|
|
25
|
+
if (value === '') delete next[option.id]
|
|
26
|
+
else next[option.id] = String(value)
|
|
27
|
+
dispatchGlobal({ customCommands: next, type: 'set-custom-commands' })
|
|
28
|
+
},
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** The assistants whose auto-commit model the config ships a default for. */
|
|
33
|
+
const MODEL_ASSISTANTS = ['claude', 'codex'] as const
|
|
34
|
+
|
|
35
|
+
function autoCommitModelRow(assistant: (typeof MODEL_ASSISTANTS)[number]): SettingRow {
|
|
36
|
+
return {
|
|
37
|
+
description: `Model that writes the commit message when ${assistant} is the active tab.`,
|
|
38
|
+
fallback: '',
|
|
39
|
+
fromConfig: (config) => config.autoCommit?.models?.[assistant],
|
|
40
|
+
id: `${AUTO_COMMIT_MODEL_PREFIX}${assistant}`,
|
|
41
|
+
kind: 'text',
|
|
42
|
+
label: `Auto-commit model (${assistant})`,
|
|
43
|
+
placeholder: 'the built-in default',
|
|
44
|
+
storage: 'settings',
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const COMMANDS_SECTION: SettingSection = {
|
|
49
|
+
id: 'commands',
|
|
50
|
+
label: 'Commands',
|
|
51
|
+
rows: [
|
|
52
|
+
{
|
|
53
|
+
description: 'Type this, then a trigger, to expand a snippet. One character.',
|
|
54
|
+
fallback: ':',
|
|
55
|
+
fromConfig: (config) => config.snippetTriggerChar,
|
|
56
|
+
id: SNIPPET_TRIGGER_CHAR,
|
|
57
|
+
kind: 'text',
|
|
58
|
+
label: 'Snippet trigger',
|
|
59
|
+
storage: 'settings',
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
description: 'The picker owns them: create, edit, delete, and see the config-pinned ones.',
|
|
63
|
+
id: 'snippets.manage',
|
|
64
|
+
kind: 'action',
|
|
65
|
+
label: 'Snippets',
|
|
66
|
+
run: () => dispatchGlobal({ returnTo: 'settings', type: 'open-snippet-picker' }),
|
|
67
|
+
value: (ctx) => `${String(ctx.state.snippets.length)} ›`,
|
|
68
|
+
},
|
|
69
|
+
...MODEL_ASSISTANTS.map(autoCommitModelRow),
|
|
70
|
+
...ASSISTANT_OPTIONS.map(customCommandRow),
|
|
71
|
+
],
|
|
72
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { getExternalEditorConfig, setExternalEditorConfig } from '@brimveyn/aimux-config'
|
|
2
|
+
|
|
3
|
+
import type { SettingSection } from '../types'
|
|
4
|
+
|
|
5
|
+
/** `auto` is the absence of a value, which is not something a row can hold. */
|
|
6
|
+
const AUTO = 'auto'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* `args` and `terminal` are absent: both are argv lists, and a list is not a row.
|
|
10
|
+
* They stay in `aimux.config.ts`, where they are the only two settings here that
|
|
11
|
+
* really need to be.
|
|
12
|
+
*/
|
|
13
|
+
export const EDITOR_SECTION: SettingSection = {
|
|
14
|
+
id: 'editor',
|
|
15
|
+
label: 'Editor',
|
|
16
|
+
rows: [
|
|
17
|
+
{
|
|
18
|
+
apply: (value) => {
|
|
19
|
+
const command = String(value)
|
|
20
|
+
setExternalEditorConfig({
|
|
21
|
+
...getExternalEditorConfig(),
|
|
22
|
+
command: command === '' ? undefined : command,
|
|
23
|
+
})
|
|
24
|
+
},
|
|
25
|
+
description: 'Overrides $VISUAL and $EDITOR. Empty falls back to them.',
|
|
26
|
+
fallback: '',
|
|
27
|
+
fromConfig: (config) => config.externalEditor?.command,
|
|
28
|
+
id: 'externalEditor.command',
|
|
29
|
+
kind: 'text',
|
|
30
|
+
label: 'Command',
|
|
31
|
+
placeholder: '$VISUAL / $EDITOR',
|
|
32
|
+
storage: 'settings',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
apply: (value) => {
|
|
36
|
+
setExternalEditorConfig({
|
|
37
|
+
...getExternalEditorConfig(),
|
|
38
|
+
kind: value === 'gui' || value === 'tui' ? value : undefined,
|
|
39
|
+
})
|
|
40
|
+
},
|
|
41
|
+
description:
|
|
42
|
+
'gui spawns detached, tui hands over the terminal. auto decides from the command.',
|
|
43
|
+
fallback: AUTO,
|
|
44
|
+
fromConfig: (config) => config.externalEditor?.kind,
|
|
45
|
+
id: 'externalEditor.kind',
|
|
46
|
+
kind: 'select',
|
|
47
|
+
label: 'Window',
|
|
48
|
+
options: [
|
|
49
|
+
{ label: AUTO, value: AUTO },
|
|
50
|
+
{ label: 'gui', value: 'gui' },
|
|
51
|
+
{ label: 'tui', value: 'tui' },
|
|
52
|
+
],
|
|
53
|
+
storage: 'settings',
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { SettingSection } from '../types'
|
|
2
|
+
|
|
3
|
+
import { userSpriteDir } from '../../ui/terminal-graphics/sprites'
|
|
4
|
+
|
|
5
|
+
export const HARMONIZE_CLAUDE_THEME = 'theme.beta.harmonizeClaudeTheme'
|
|
6
|
+
export const ACTIVITY_SPRITES = 'theme.beta.experimentalActivitySprites'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The `theme.beta.*` block of the config, under the label its own docstrings use.
|
|
10
|
+
* Not grouped with the integrations even though both of these touch Claude Code:
|
|
11
|
+
* what they have in common is that they are unfinished, which is the thing a user
|
|
12
|
+
* needs to know before turning one on.
|
|
13
|
+
*/
|
|
14
|
+
export const EXPERIMENTAL_SECTION: SettingSection = {
|
|
15
|
+
description: 'Unfinished. These can change behaviour, or go away, in any release.',
|
|
16
|
+
id: 'experimental',
|
|
17
|
+
label: 'Experimental',
|
|
18
|
+
rows: [
|
|
19
|
+
{
|
|
20
|
+
description: 'Write the active aimux theme into ~/.claude and select it there.',
|
|
21
|
+
fallback: false,
|
|
22
|
+
fromConfig: (config) => config.theme?.beta?.harmonizeClaudeTheme,
|
|
23
|
+
id: HARMONIZE_CLAUDE_THEME,
|
|
24
|
+
kind: 'toggle',
|
|
25
|
+
label: 'Harmonize the Claude Code theme',
|
|
26
|
+
storage: 'settings',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
description: "Turn off Claude's own highlighting and re-colour diffs from the aimux theme.",
|
|
30
|
+
fallback: false,
|
|
31
|
+
fromConfig: (config) => config.theme?.beta?.experimentalSyntaxHighlight,
|
|
32
|
+
id: 'theme.beta.experimentalSyntaxHighlight',
|
|
33
|
+
kind: 'toggle',
|
|
34
|
+
label: 'Syntax highlight from the theme',
|
|
35
|
+
restart: true,
|
|
36
|
+
storage: 'settings',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
description: 'A little animation per agent state, instead of the spinner and the dots.',
|
|
40
|
+
fallback: false,
|
|
41
|
+
fromConfig: (config) => config.theme?.beta?.experimentalActivitySprites,
|
|
42
|
+
id: ACTIVITY_SPRITES,
|
|
43
|
+
kind: 'toggle',
|
|
44
|
+
label: 'Activity sprites (Kitty or Ghostty, not tmux)',
|
|
45
|
+
storage: 'settings',
|
|
46
|
+
},
|
|
47
|
+
// The row above is useless without knowing where to put anything, and a path
|
|
48
|
+
// that has to be looked up in the docs is a path nobody uses.
|
|
49
|
+
{
|
|
50
|
+
description: 'Drop a `<state>.gif` here — states: idle, working, waiting, done.',
|
|
51
|
+
id: 'experimental.spriteFolder',
|
|
52
|
+
kind: 'info',
|
|
53
|
+
label: 'Sprite folder',
|
|
54
|
+
value: () => userSpriteDir(),
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { getMultiRepoConfig, setMultiRepoConfig } from '@brimveyn/aimux-config'
|
|
2
|
+
|
|
3
|
+
import type { GitFileListMode } from '../../state/types'
|
|
4
|
+
import type { SettingSection, SettingValue } from '../types'
|
|
5
|
+
|
|
6
|
+
import { dispatchGlobal, runSideEffectGlobal } from '../../state/dispatch-ref'
|
|
7
|
+
|
|
8
|
+
function isFileListMode(value: SettingValue): value is GitFileListMode {
|
|
9
|
+
return value === 'tree' || value === 'flat'
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The file list and diff preferences already live in `AppState` and have their own
|
|
14
|
+
* persistence, so those rows delegate. The two with no toggle of their own —
|
|
15
|
+
* prefetch radius and the diff counter — are stored here and pushed into
|
|
16
|
+
* `AppState` on change; multi-repo is a module singleton, like auto-commit.
|
|
17
|
+
*/
|
|
18
|
+
export const GIT_SECTION: SettingSection = {
|
|
19
|
+
id: 'git',
|
|
20
|
+
label: 'Git',
|
|
21
|
+
rows: [
|
|
22
|
+
{
|
|
23
|
+
description: 'Show changed files as a tree, or as one flat list of paths.',
|
|
24
|
+
id: 'git.fileListMode',
|
|
25
|
+
kind: 'select',
|
|
26
|
+
label: 'File list',
|
|
27
|
+
options: [
|
|
28
|
+
{ label: 'tree', value: 'tree' },
|
|
29
|
+
{ label: 'flat', value: 'flat' },
|
|
30
|
+
],
|
|
31
|
+
read: (ctx) => ctx.state.gitPane.fileListMode,
|
|
32
|
+
storage: 'app',
|
|
33
|
+
// Flips rather than sets, because flipping is what also reconciles the git
|
|
34
|
+
// view's selected entry with the new shape of the list. Safe because
|
|
35
|
+
// `writeRow` never hands a row the value it already holds — with two
|
|
36
|
+
// options, flipping and setting agree. A third would need the action to
|
|
37
|
+
// take a target first; `settings-schema.test.ts` holds that line.
|
|
38
|
+
write: (value) => {
|
|
39
|
+
if (!isFileListMode(value)) return
|
|
40
|
+
dispatchGlobal({ type: 'git-mode-toggle-file-list-mode' })
|
|
41
|
+
runSideEffectGlobal({ mode: value, type: 'persist-git-file-list-mode' })
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
description: 'Collapse folder chains with a single child into one line.',
|
|
46
|
+
id: 'git.treeCompaction',
|
|
47
|
+
kind: 'toggle',
|
|
48
|
+
label: 'Compact the tree',
|
|
49
|
+
read: (ctx) => ctx.state.gitPane.treeCompaction,
|
|
50
|
+
storage: 'app',
|
|
51
|
+
write: (value) => {
|
|
52
|
+
dispatchGlobal({ type: 'git-mode-toggle-tree-compaction' })
|
|
53
|
+
runSideEffectGlobal({ enabled: value === true, type: 'persist-git-tree-compaction' })
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
apply: (value) =>
|
|
58
|
+
dispatchGlobal({ patch: { diffCount: { enabled: value === true } }, type: 'set-git-pane' }),
|
|
59
|
+
description: 'Added and removed line counts next to each file.',
|
|
60
|
+
fallback: true,
|
|
61
|
+
fromConfig: (config) => config.gitPane?.diffCount?.enabled,
|
|
62
|
+
id: 'git.diffCount',
|
|
63
|
+
kind: 'toggle',
|
|
64
|
+
label: 'Diff counts',
|
|
65
|
+
storage: 'settings',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
apply: (value) => {
|
|
69
|
+
if (typeof value === 'number') {
|
|
70
|
+
dispatchGlobal({ patch: { prefetchRadius: value }, type: 'set-git-pane' })
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
description: 'Load this many diffs either side of the cursor ahead of time. 0 disables it.',
|
|
74
|
+
fallback: 5,
|
|
75
|
+
fromConfig: (config) => config.gitPane?.prefetchRadius,
|
|
76
|
+
id: 'git.prefetchRadius',
|
|
77
|
+
kind: 'number',
|
|
78
|
+
label: 'Diff prefetch',
|
|
79
|
+
max: 50,
|
|
80
|
+
min: 0,
|
|
81
|
+
step: 1,
|
|
82
|
+
storage: 'settings',
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
apply: (value) => setMultiRepoConfig({ ...getMultiRepoConfig(), enabled: value === true }),
|
|
86
|
+
description: 'Aggregate the status of git repos nested under the project.',
|
|
87
|
+
fallback: true,
|
|
88
|
+
fromConfig: (config) => config.multiRepo?.enabled,
|
|
89
|
+
id: 'multiRepo.enabled',
|
|
90
|
+
kind: 'toggle',
|
|
91
|
+
label: 'Multi-repo',
|
|
92
|
+
storage: 'settings',
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
apply: (value) => {
|
|
96
|
+
if (typeof value === 'number') {
|
|
97
|
+
setMultiRepoConfig({ ...getMultiRepoConfig(), maxDepth: value })
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
description: 'How far below the project to look for them. 1 = its direct children.',
|
|
101
|
+
fallback: 1,
|
|
102
|
+
fromConfig: (config) => config.multiRepo?.maxDepth,
|
|
103
|
+
id: 'multiRepo.maxDepth',
|
|
104
|
+
kind: 'number',
|
|
105
|
+
label: 'Multi-repo depth',
|
|
106
|
+
max: 5,
|
|
107
|
+
min: 1,
|
|
108
|
+
step: 1,
|
|
109
|
+
storage: 'settings',
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { ProjectRecord } from '../../state/types'
|
|
2
|
+
import type { SettingRow, SettingSection } from '../types'
|
|
3
|
+
|
|
4
|
+
import { ABOUT_SECTION } from './about'
|
|
5
|
+
import { APPEARANCE_SECTION } from './appearance'
|
|
6
|
+
import { AUTOMATION_SECTION } from './automation'
|
|
7
|
+
import { COMMANDS_SECTION } from './commands'
|
|
8
|
+
import { EDITOR_SECTION } from './editor'
|
|
9
|
+
import { EXPERIMENTAL_SECTION } from './experimental'
|
|
10
|
+
import { GIT_SECTION } from './git'
|
|
11
|
+
import { INTEGRATIONS_SECTION } from './integrations'
|
|
12
|
+
import { LAYOUT_SECTION } from './layout'
|
|
13
|
+
import { NOTIFICATIONS_SECTION } from './notifications'
|
|
14
|
+
import { SETUP_SECTION } from './setup'
|
|
15
|
+
import { STATUS_BAR_SECTION } from './status-bar'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Every section, in the order the screen lists them. Adding a setting is one
|
|
19
|
+
* entry in one of these files — no component, no reducer branch, no keybind.
|
|
20
|
+
*/
|
|
21
|
+
export const SETTING_SECTIONS: readonly SettingSection[] = [
|
|
22
|
+
APPEARANCE_SECTION,
|
|
23
|
+
LAYOUT_SECTION,
|
|
24
|
+
AUTOMATION_SECTION,
|
|
25
|
+
NOTIFICATIONS_SECTION,
|
|
26
|
+
SETUP_SECTION,
|
|
27
|
+
GIT_SECTION,
|
|
28
|
+
COMMANDS_SECTION,
|
|
29
|
+
EDITOR_SECTION,
|
|
30
|
+
STATUS_BAR_SECTION,
|
|
31
|
+
INTEGRATIONS_SECTION,
|
|
32
|
+
EXPERIMENTAL_SECTION,
|
|
33
|
+
ABOUT_SECTION,
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The rows that exist regardless of state, which is what hydration resolves. A
|
|
38
|
+
* section that builds its rows from state is a view over something that has its
|
|
39
|
+
* own home — a file, a project record — so it has nothing to hydrate.
|
|
40
|
+
*/
|
|
41
|
+
export const ALL_SETTING_ROWS: readonly SettingRow[] = SETTING_SECTIONS.flatMap((section) =>
|
|
42
|
+
Array.isArray(section.rows) ? section.rows : []
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
export const DEFAULT_SECTION_ID = SETTING_SECTIONS[0]?.id ?? 'about'
|
|
46
|
+
|
|
47
|
+
export function getSection(sectionId: string): SettingSection | undefined {
|
|
48
|
+
return SETTING_SECTIONS.find((section) => section.id === sectionId)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function sectionRows(
|
|
52
|
+
section: SettingSection,
|
|
53
|
+
projects: readonly ProjectRecord[]
|
|
54
|
+
): readonly SettingRow[] {
|
|
55
|
+
return Array.isArray(section.rows) ? section.rows : section.rows(projects)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* How many rows a section has, without building them. The reducer clamps the
|
|
60
|
+
* cursor with this, which is why it exists: building a Setup row reads a script
|
|
61
|
+
* off disk, and a reducer has no business doing that.
|
|
62
|
+
*/
|
|
63
|
+
export function sectionRowCount(
|
|
64
|
+
section: SettingSection,
|
|
65
|
+
projects: readonly ProjectRecord[]
|
|
66
|
+
): number {
|
|
67
|
+
if (section.rowCount) return section.rowCount(projects)
|
|
68
|
+
return Array.isArray(section.rows) ? section.rows.length : section.rows(projects).length
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Rows of the given section, or an empty list when the id is unknown. */
|
|
72
|
+
export function getSectionRows(
|
|
73
|
+
sectionId: string,
|
|
74
|
+
projects: readonly ProjectRecord[]
|
|
75
|
+
): readonly SettingRow[] {
|
|
76
|
+
const section = getSection(sectionId)
|
|
77
|
+
return section ? sectionRows(section, projects) : []
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function getSectionRowCount(sectionId: string, projects: readonly ProjectRecord[]): number {
|
|
81
|
+
const section = getSection(sectionId)
|
|
82
|
+
return section ? sectionRowCount(section, projects) : 0
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** The row with this id, dynamic ones included. */
|
|
86
|
+
export function findSettingRow(
|
|
87
|
+
id: string,
|
|
88
|
+
projects: readonly ProjectRecord[]
|
|
89
|
+
): SettingRow | undefined {
|
|
90
|
+
for (const section of SETTING_SECTIONS) {
|
|
91
|
+
const row = sectionRows(section, projects).find((entry) => entry.id === id)
|
|
92
|
+
if (row) return row
|
|
93
|
+
}
|
|
94
|
+
return undefined
|
|
95
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { SettingSection } from '../types'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* What aimux writes into other tools' configuration. Installing Claude's hooks is
|
|
5
|
+
* a one-way write at startup with no uninstall, so it cannot honestly claim to
|
|
6
|
+
* take effect before the next launch.
|
|
7
|
+
*/
|
|
8
|
+
export const INTEGRATIONS_SECTION: SettingSection = {
|
|
9
|
+
id: 'integrations',
|
|
10
|
+
label: 'Integrations',
|
|
11
|
+
rows: [
|
|
12
|
+
{
|
|
13
|
+
description: 'Drive per-tab activity from Claude Code events instead of reading the screen.',
|
|
14
|
+
fallback: false,
|
|
15
|
+
fromConfig: (config) => config.integrations?.claudeHooks,
|
|
16
|
+
id: 'integrations.claudeHooks',
|
|
17
|
+
kind: 'toggle',
|
|
18
|
+
label: 'Claude Code hooks',
|
|
19
|
+
restart: true,
|
|
20
|
+
storage: 'settings',
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { SettingSection } from '../types'
|
|
2
|
+
|
|
3
|
+
import { dispatchGlobal } from '../../state/dispatch-ref'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Bars and their widths. Every row here is a view over something `AppState`
|
|
7
|
+
* already owns, so each one dispatches the action its keybinding dispatches —
|
|
8
|
+
* which is also what gets the value persisted on the next autosave. The toggles
|
|
9
|
+
* flip rather than set, which is safe because `writeRow` never hands a row the
|
|
10
|
+
* value it already has.
|
|
11
|
+
*/
|
|
12
|
+
export const LAYOUT_SECTION: SettingSection = {
|
|
13
|
+
id: 'layout',
|
|
14
|
+
label: 'Layout',
|
|
15
|
+
rows: [
|
|
16
|
+
{
|
|
17
|
+
description: 'The tab strip above the terminal.',
|
|
18
|
+
id: 'layout.projectBar',
|
|
19
|
+
kind: 'toggle',
|
|
20
|
+
label: 'Tab bar',
|
|
21
|
+
read: (ctx) => ctx.state.projectBar.visible,
|
|
22
|
+
storage: 'app',
|
|
23
|
+
write: () => dispatchGlobal({ type: 'toggle-project-bar' }),
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: 'layout.leftBar',
|
|
27
|
+
kind: 'toggle',
|
|
28
|
+
label: 'Left bar',
|
|
29
|
+
read: (ctx) => ctx.state.bars.left.visible,
|
|
30
|
+
storage: 'app',
|
|
31
|
+
write: () => dispatchGlobal({ side: 'left', type: 'toggle-bar' }),
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
description: 'Columns. Clamped to what the terminal can spare.',
|
|
35
|
+
id: 'layout.leftBarWidth',
|
|
36
|
+
kind: 'number',
|
|
37
|
+
label: 'Left bar width',
|
|
38
|
+
max: 80,
|
|
39
|
+
min: 10,
|
|
40
|
+
read: (ctx) => ctx.state.bars.left.width,
|
|
41
|
+
step: 2,
|
|
42
|
+
storage: 'app',
|
|
43
|
+
write: (value) => {
|
|
44
|
+
if (typeof value !== 'number') return
|
|
45
|
+
dispatchGlobal({ side: 'left', type: 'set-bar-width', width: value })
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: 'layout.rightBar',
|
|
50
|
+
kind: 'toggle',
|
|
51
|
+
label: 'Right bar',
|
|
52
|
+
read: (ctx) => ctx.state.bars.right.visible,
|
|
53
|
+
storage: 'app',
|
|
54
|
+
write: () => dispatchGlobal({ side: 'right', type: 'toggle-bar' }),
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
description: 'Columns. Clamped to what the terminal can spare.',
|
|
58
|
+
id: 'layout.rightBarWidth',
|
|
59
|
+
kind: 'number',
|
|
60
|
+
label: 'Right bar width',
|
|
61
|
+
max: 80,
|
|
62
|
+
min: 10,
|
|
63
|
+
read: (ctx) => ctx.state.bars.right.width,
|
|
64
|
+
step: 2,
|
|
65
|
+
storage: 'app',
|
|
66
|
+
write: (value) => {
|
|
67
|
+
if (typeof value !== 'number') return
|
|
68
|
+
dispatchGlobal({ side: 'right', type: 'set-bar-width', width: value })
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
}
|