@brimveyn/aimux 1.20.4 → 1.22.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/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 +305 -1589
- package/src/app-runtime/snippet-actions.ts +3 -2
- package/src/app-runtime/split-drag-controller.ts +4 -8
- 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 +28 -42
- 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 +28 -33
- 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 +102 -85
- 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 +149 -101
- 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-merge.ts +64 -0
- package/src/git/pr-status-poller.ts +57 -0
- package/src/git/pr-status.ts +227 -0
- 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 +11 -6
- 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/open-url.ts +39 -0
- 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 +3 -1
- 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 +75 -0
- package/src/state/dispatch-ref.ts +1 -1
- package/src/state/git-pane-sizing.ts +0 -9
- package/src/state/layout-resize.ts +2 -2
- package/src/state/pr-status-store.ts +39 -0
- 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} +77 -73
- 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 -53
- 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 +86 -15
- package/src/state/selectors.ts +49 -37
- package/src/state/store.ts +83 -57
- package/src/state/types.ts +261 -417
- 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/diff-renderer/pierre-diff.tsx +2 -1
- 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-header.tsx +105 -39
- package/src/ui/components/git/pane/git-pane-widget.tsx +37 -23
- package/src/ui/components/git/pane/pr-checks-panel.tsx +197 -0
- package/src/ui/components/git/pane/pr-state-row.tsx +103 -0
- package/src/ui/components/layout/bar.tsx +191 -0
- 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 +48 -37
- 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 +164 -230
- 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 +21 -0
- package/src/ui/widgets/widget-context-menu.ts +85 -0
- 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 -45
- package/src/ui/components/git/pane/git-pane-context-menu.ts +0 -26
- package/src/ui/components/layout/sidebar/sidebar.tsx +0 -163
- 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
package/src/cli/index.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { DaemonClient } from './client/daemon-client'
|
|
|
2
2
|
import type { CliContext } from './context'
|
|
3
3
|
|
|
4
4
|
import { connectToDaemon, DaemonUnreachableError } from './client/bootstrap'
|
|
5
|
-
import {
|
|
5
|
+
import { listProjects, resolveProjectWithOrigin } from './client/project-resolver'
|
|
6
6
|
import { type ArgSpec, CliUsageError, type FlagSpec, parseArgs, SHARED_FLAGS } from './flags'
|
|
7
7
|
import {
|
|
8
8
|
EXIT_DAEMON_UNREACHABLE,
|
|
@@ -22,7 +22,7 @@ const EXIT_CODES_BLOCK = [
|
|
|
22
22
|
' 4 daemon unreachable (socket missing and autostart failed)',
|
|
23
23
|
' 10 question (tab run / tab await: worker is blocked on a question/permission)',
|
|
24
24
|
' 11 pending submit (worker holds an unsubmitted prompt — see `worker submit`)',
|
|
25
|
-
' 124 timeout (tab run, tab await, tab wait, tab tail --timeout,
|
|
25
|
+
' 124 timeout (tab run, tab await, tab wait, tab tail --timeout, project switch --wait)',
|
|
26
26
|
].join('\n')
|
|
27
27
|
|
|
28
28
|
const OUTPUT_CONTRACT_BLOCK = [
|
|
@@ -100,12 +100,12 @@ function printHelp(): void {
|
|
|
100
100
|
'',
|
|
101
101
|
'Env:',
|
|
102
102
|
' AIMUX_PROFILE Runtime profile (state dir, socket paths); --profile overrides.',
|
|
103
|
-
'
|
|
103
|
+
' AIMUX_PROJECT Pin the target project (id or name); --project overrides.',
|
|
104
104
|
'',
|
|
105
105
|
'Agent recipe:',
|
|
106
106
|
' # create an isolated named worker, dispatch, and await one structured outcome',
|
|
107
|
-
' # --
|
|
108
|
-
' aimux worker run --
|
|
107
|
+
' # --project pins the target repo so a UI project switch cannot redirect it',
|
|
108
|
+
' aimux worker run --project myrepo --name fixup --assistant claude "explain this repo"',
|
|
109
109
|
'',
|
|
110
110
|
'Maintenance:',
|
|
111
111
|
' aimux doctor | update | restart-daemon | restart-terminal-manager | version',
|
|
@@ -172,6 +172,17 @@ export async function runCli(argv: readonly string[]): Promise<number> {
|
|
|
172
172
|
const group = argv[0] ?? ''
|
|
173
173
|
const verb = argv[1] ?? ''
|
|
174
174
|
|
|
175
|
+
// `aimux worktree` was renamed to `aimux workspace`, and the old
|
|
176
|
+
// `aimux workspace` became `aimux project`. Aliasing the groups would
|
|
177
|
+
// silently change what `aimux workspace create` does, so refuse loudly for
|
|
178
|
+
// a release instead.
|
|
179
|
+
if (group === 'worktree') {
|
|
180
|
+
writeError(
|
|
181
|
+
'`aimux worktree` was renamed to `aimux workspace`, and the old `aimux workspace` is now `aimux project`. See docs/reference/cli.md'
|
|
182
|
+
)
|
|
183
|
+
return EXIT_USAGE
|
|
184
|
+
}
|
|
185
|
+
|
|
175
186
|
// `aimux <group> --help` (or bare `aimux <group>`) — group-scoped help. Print
|
|
176
187
|
// the verb list without erroring on the missing verb, so agents can drill
|
|
177
188
|
// down step by step.
|
|
@@ -225,17 +236,16 @@ export async function runCli(argv: readonly string[]): Promise<number> {
|
|
|
225
236
|
|
|
226
237
|
const state: {
|
|
227
238
|
daemon: DaemonClient | null
|
|
228
|
-
|
|
239
|
+
project: ReturnType<typeof resolveProjectWithOrigin> | null
|
|
229
240
|
} = {
|
|
230
241
|
daemon: null,
|
|
231
|
-
|
|
242
|
+
project: null,
|
|
232
243
|
}
|
|
233
|
-
const resolveOnce = (): ReturnType<typeof
|
|
234
|
-
if (state.
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
state.
|
|
238
|
-
return state.workspace
|
|
244
|
+
const resolveOnce = (): ReturnType<typeof resolveProjectWithOrigin> => {
|
|
245
|
+
if (state.project) return state.project
|
|
246
|
+
const projectFlag = typeof parsed.flags.project === 'string' ? parsed.flags.project : undefined
|
|
247
|
+
state.project = resolveProjectWithOrigin(projectFlag)
|
|
248
|
+
return state.project
|
|
239
249
|
}
|
|
240
250
|
const ctx: CliContext = {
|
|
241
251
|
args: parsed,
|
|
@@ -244,9 +254,9 @@ export async function runCli(argv: readonly string[]): Promise<number> {
|
|
|
244
254
|
state.daemon = await connectToDaemon()
|
|
245
255
|
return state.daemon
|
|
246
256
|
},
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
257
|
+
getProject: () => resolveOnce().record,
|
|
258
|
+
getProjectOrigin: () => resolveOnce().origin,
|
|
259
|
+
getProjects: () => listProjects(),
|
|
250
260
|
}
|
|
251
261
|
|
|
252
262
|
try {
|
package/src/cli/output.ts
CHANGED
|
@@ -25,7 +25,7 @@ export function writeError(message: string): void {
|
|
|
25
25
|
// 10 question (`tab run`: worker is blocked on a question/permission)
|
|
26
26
|
// 11 pending submit (`worker run --detach`: prompt is in the composer but no
|
|
27
27
|
// turn started — recoverable with `worker submit`, unlike a real error)
|
|
28
|
-
// 124 timeout (`tab wait`, `tab tail --timeout`, `
|
|
28
|
+
// 124 timeout (`tab wait`, `tab tail --timeout`, `project switch --wait`)
|
|
29
29
|
export const EXIT_OK = 0
|
|
30
30
|
export const EXIT_USAGE = 2
|
|
31
31
|
export const EXIT_RUNTIME = 3
|
package/src/cli/registry.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { CliContext } from './context'
|
|
2
2
|
import type { ArgSpec, FlagSpec } from './flags'
|
|
3
3
|
|
|
4
|
+
import { projectClose } from './commands/project/close'
|
|
5
|
+
import { projectCreate } from './commands/project/create'
|
|
6
|
+
import { projectList } from './commands/project/list'
|
|
7
|
+
import { projectShow } from './commands/project/show'
|
|
8
|
+
import { projectSwitch } from './commands/project/switch'
|
|
4
9
|
import { tabAwait } from './commands/tab/await'
|
|
5
10
|
import { tabClose } from './commands/tab/close'
|
|
6
11
|
import { tabCreate } from './commands/tab/create'
|
|
@@ -18,14 +23,9 @@ import { workerPrompt } from './commands/worker/prompt'
|
|
|
18
23
|
import { workerRun } from './commands/worker/run'
|
|
19
24
|
import { workerStop } from './commands/worker/stop'
|
|
20
25
|
import { workerSubmit } from './commands/worker/submit'
|
|
21
|
-
import { workspaceClose } from './commands/workspace/close'
|
|
22
26
|
import { workspaceCreate } from './commands/workspace/create'
|
|
23
27
|
import { workspaceList } from './commands/workspace/list'
|
|
24
|
-
import {
|
|
25
|
-
import { workspaceSwitch } from './commands/workspace/switch'
|
|
26
|
-
import { worktreeCreate } from './commands/worktree/create'
|
|
27
|
-
import { worktreeList } from './commands/worktree/list'
|
|
28
|
-
import { worktreeRemove } from './commands/worktree/remove'
|
|
28
|
+
import { workspaceRemove } from './commands/workspace/remove'
|
|
29
29
|
|
|
30
30
|
export interface CliCommand {
|
|
31
31
|
group: string
|
|
@@ -47,14 +47,14 @@ export const COMMANDS: readonly CliCommand[] = [
|
|
|
47
47
|
tabSnapshot,
|
|
48
48
|
tabTail,
|
|
49
49
|
tabWait,
|
|
50
|
+
projectList,
|
|
51
|
+
projectShow,
|
|
52
|
+
projectCreate,
|
|
53
|
+
projectSwitch,
|
|
54
|
+
projectClose,
|
|
50
55
|
workspaceList,
|
|
51
|
-
workspaceShow,
|
|
52
56
|
workspaceCreate,
|
|
53
|
-
|
|
54
|
-
workspaceClose,
|
|
55
|
-
worktreeList,
|
|
56
|
-
worktreeCreate,
|
|
57
|
-
worktreeRemove,
|
|
57
|
+
workspaceRemove,
|
|
58
58
|
workerRun,
|
|
59
59
|
workerPrompt,
|
|
60
60
|
workerSubmit,
|
package/src/config/loader.ts
CHANGED
|
@@ -6,11 +6,22 @@ import { getProfileConfigDir } from '../profile-paths'
|
|
|
6
6
|
|
|
7
7
|
const CONFIG_FILENAMES = ['aimux.config.ts', 'aimux.config.js']
|
|
8
8
|
|
|
9
|
+
export interface LoadedUserConfig {
|
|
10
|
+
resolved: ResolvedConfig
|
|
11
|
+
/**
|
|
12
|
+
* The file as written, before defaults are merged in. The settings screen needs
|
|
13
|
+
* this one: `resolved` has a value for every key, so it cannot tell a setting
|
|
14
|
+
* the user declared from one that merely defaulted — and only a declared
|
|
15
|
+
* setting outranks what the screen has written.
|
|
16
|
+
*/
|
|
17
|
+
user: AimuxUserConfig
|
|
18
|
+
}
|
|
19
|
+
|
|
9
20
|
/**
|
|
10
|
-
* Load the user's aimux.config.ts (or .js)
|
|
11
|
-
*
|
|
21
|
+
* Load the user's aimux.config.ts (or .js) and merge it with defaults. Falls back
|
|
22
|
+
* to pure defaults if no config file exists.
|
|
12
23
|
*/
|
|
13
|
-
export async function loadUserConfig(): Promise<
|
|
24
|
+
export async function loadUserConfig(): Promise<LoadedUserConfig> {
|
|
14
25
|
const configDir = getProfileConfigDir()
|
|
15
26
|
|
|
16
27
|
for (const filename of CONFIG_FILENAMES) {
|
|
@@ -24,7 +35,7 @@ export async function loadUserConfig(): Promise<ResolvedConfig> {
|
|
|
24
35
|
const userConfig: AimuxUserConfig = mod.default ?? mod
|
|
25
36
|
|
|
26
37
|
logDebug('config.user.loaded', { path: configPath })
|
|
27
|
-
return resolveConfig(userConfig)
|
|
38
|
+
return { resolved: resolveConfig(userConfig), user: userConfig }
|
|
28
39
|
} catch (error) {
|
|
29
40
|
const message = error instanceof Error ? error.message : String(error)
|
|
30
41
|
logDebug('config.user.error', { error: message, path: configPath })
|
|
@@ -32,5 +43,5 @@ export async function loadUserConfig(): Promise<ResolvedConfig> {
|
|
|
32
43
|
}
|
|
33
44
|
}
|
|
34
45
|
|
|
35
|
-
return resolveConfig({})
|
|
46
|
+
return { resolved: resolveConfig({}), user: {} }
|
|
36
47
|
}
|
package/src/config.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
2
2
|
|
|
3
|
-
import type {
|
|
4
|
-
import type { GitFileListMode,
|
|
3
|
+
import type { StoredSettings } from './settings/types'
|
|
4
|
+
import type { GitFileListMode, ProjectSnapshotV1 } from './state/types'
|
|
5
5
|
|
|
6
6
|
import { logDebug } from './debug/input-log'
|
|
7
7
|
import { getProfileConfigDir } from './profile-paths'
|
|
8
|
-
import {
|
|
8
|
+
import { isProjectSnapshotV1 } from './state/validation'
|
|
9
9
|
import { migrateThemeId as resolveLegacyThemeId, type ThemeId, type ThemeMode } from './ui/themes'
|
|
10
10
|
|
|
11
11
|
function migrateThemeId(value: unknown): ThemeId | undefined {
|
|
@@ -22,42 +22,46 @@ export function getConfigPath(): string {
|
|
|
22
22
|
return `${getProfileConfigDir()}/aimux.json`
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* `visible` / `mode` / `position` / `*Ratio` are legacy placement fields, read
|
|
27
|
+
* once by `deriveBarsFromLegacy` and no longer written. They must stay
|
|
28
|
+
* optional: rejecting the whole block would silently drop the git *content*
|
|
29
|
+
* preferences (`fileListMode`, `diffModeRatio`, `treeCompaction`).
|
|
30
|
+
*/
|
|
25
31
|
export interface PersistedGitPane {
|
|
26
32
|
diffModeRatio?: number
|
|
27
33
|
fileListMode?: GitFileListMode
|
|
28
34
|
treeCompaction?: boolean
|
|
29
35
|
prefetchRadius?: number
|
|
30
|
-
visible
|
|
31
|
-
mode
|
|
32
|
-
position
|
|
36
|
+
visible?: boolean
|
|
37
|
+
mode?: 'embedded' | 'pane'
|
|
38
|
+
position?: 'top' | 'bottom' | 'left' | 'right'
|
|
33
39
|
paneRatio?: number
|
|
34
40
|
embeddedRatio?: number
|
|
35
41
|
ratio?: number
|
|
36
42
|
}
|
|
37
43
|
|
|
44
|
+
/** Legacy; still written for downgrade safety, read only as a bars fallback. */
|
|
38
45
|
export interface PersistedSidebar {
|
|
39
46
|
visible: boolean
|
|
40
47
|
width: number
|
|
41
48
|
}
|
|
42
49
|
|
|
43
|
-
export interface
|
|
50
|
+
export interface PersistedBarWidget {
|
|
44
51
|
id: string
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
direction?: SplitDirection
|
|
48
|
-
ratio?: number
|
|
49
|
-
send?: string
|
|
52
|
+
grow: number
|
|
53
|
+
visible: boolean
|
|
50
54
|
}
|
|
51
55
|
|
|
52
|
-
export interface
|
|
53
|
-
|
|
56
|
+
export interface PersistedBar {
|
|
57
|
+
visible: boolean
|
|
58
|
+
width: number
|
|
59
|
+
widgets: PersistedBarWidget[]
|
|
54
60
|
}
|
|
55
61
|
|
|
56
|
-
export interface
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
description?: string
|
|
60
|
-
tabs: WorktreeTemplateTab[]
|
|
62
|
+
export interface PersistedBars {
|
|
63
|
+
left: PersistedBar
|
|
64
|
+
right: PersistedBar
|
|
61
65
|
}
|
|
62
66
|
|
|
63
67
|
export interface AimuxConfig {
|
|
@@ -67,20 +71,28 @@ export interface AimuxConfig {
|
|
|
67
71
|
themeTransparent?: boolean
|
|
68
72
|
themeMode?: ThemeMode
|
|
69
73
|
gitPane?: PersistedGitPane
|
|
74
|
+
bars?: PersistedBars
|
|
70
75
|
sidebar?: PersistedSidebar
|
|
71
|
-
|
|
72
|
-
|
|
76
|
+
projectBarVisible?: boolean
|
|
77
|
+
projectSnapshot?: ProjectSnapshotV1
|
|
78
|
+
/**
|
|
79
|
+
* What the settings screen has written, keyed by row id. Sparse: an absent key
|
|
80
|
+
* means the user never touched that row, which is not the same as `false`.
|
|
81
|
+
* Keys this build doesn't know are kept as-is so a downgrade doesn't erase
|
|
82
|
+
* settings a newer one wrote.
|
|
83
|
+
*/
|
|
84
|
+
settings?: StoredSettings
|
|
73
85
|
skippedUpdateVersion?: string
|
|
74
86
|
/** One-shot guard: orphan `aimux/` branches were pruned from existing repos. */
|
|
75
87
|
prunedOrphanAimuxBranches?: boolean
|
|
76
|
-
worktreeTemplates?: WorktreeTemplate[]
|
|
77
88
|
}
|
|
78
89
|
|
|
79
90
|
function isPersistedGitPane(value: unknown): value is PersistedGitPane {
|
|
80
91
|
if (typeof value !== 'object' || value === null) return false
|
|
81
92
|
const v = value as Record<string, unknown>
|
|
82
|
-
const modeOk = v.mode === 'embedded' || v.mode === 'pane'
|
|
93
|
+
const modeOk = v.mode === undefined || v.mode === 'embedded' || v.mode === 'pane'
|
|
83
94
|
const positionOk =
|
|
95
|
+
v.position === undefined ||
|
|
84
96
|
v.position === 'top' ||
|
|
85
97
|
v.position === 'bottom' ||
|
|
86
98
|
v.position === 'left' ||
|
|
@@ -106,7 +118,7 @@ function isPersistedGitPane(value: unknown): value is PersistedGitPane {
|
|
|
106
118
|
Number.isFinite(v.diffModeRatio) &&
|
|
107
119
|
v.diffModeRatio > 0 &&
|
|
108
120
|
v.diffModeRatio < 1)
|
|
109
|
-
const visibleOk = typeof v.visible === 'boolean'
|
|
121
|
+
const visibleOk = v.visible === undefined || typeof v.visible === 'boolean'
|
|
110
122
|
const fileListModeOk =
|
|
111
123
|
v.fileListMode === undefined || v.fileListMode === 'tree' || v.fileListMode === 'flat'
|
|
112
124
|
const treeCompactionOk = v.treeCompaction === undefined || typeof v.treeCompaction === 'boolean'
|
|
@@ -130,81 +142,89 @@ function isPersistedGitPane(value: unknown): value is PersistedGitPane {
|
|
|
130
142
|
) {
|
|
131
143
|
return false
|
|
132
144
|
}
|
|
133
|
-
// cross-field coherence: embedded => top|bottom; pane => left|right
|
|
134
|
-
if (v.mode === 'embedded' && v.position !== 'top' && v.position !== 'bottom') return false
|
|
135
|
-
if (v.mode === 'pane' && v.position !== 'left' && v.position !== 'right') return false
|
|
136
145
|
return true
|
|
137
146
|
}
|
|
138
147
|
|
|
139
|
-
function
|
|
140
|
-
return typeof value === 'number' && Number.isFinite(value) && value > 0.15 && value < 0.85
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function isWorktreeTemplateTab(value: unknown): value is WorktreeTemplateTab {
|
|
148
|
+
function isPersistedBar(value: unknown): value is PersistedBar {
|
|
144
149
|
if (typeof value !== 'object' || value === null) return false
|
|
145
150
|
const v = value as Record<string, unknown>
|
|
146
|
-
if (
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
if (typeof
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if (typeof pane.splitFrom !== 'string' || !seenIds.has(pane.splitFrom)) return false
|
|
162
|
-
if (pane.splitFrom === pane.id) return false
|
|
163
|
-
if (pane.direction !== 'horizontal' && pane.direction !== 'vertical') return false
|
|
164
|
-
if (pane.ratio !== undefined && !isRatioValid(pane.ratio)) return false
|
|
165
|
-
}
|
|
166
|
-
if (pane.send !== undefined && typeof pane.send !== 'string') return false
|
|
167
|
-
}
|
|
168
|
-
return true
|
|
151
|
+
if (typeof v.visible !== 'boolean') return false
|
|
152
|
+
if (typeof v.width !== 'number' || !Number.isFinite(v.width) || v.width <= 0) return false
|
|
153
|
+
if (!Array.isArray(v.widgets)) return false
|
|
154
|
+
return v.widgets.every((raw) => {
|
|
155
|
+
if (typeof raw !== 'object' || raw === null) return false
|
|
156
|
+
const widget = raw as Record<string, unknown>
|
|
157
|
+
return (
|
|
158
|
+
typeof widget.id === 'string' &&
|
|
159
|
+
widget.id.length > 0 &&
|
|
160
|
+
typeof widget.grow === 'number' &&
|
|
161
|
+
Number.isFinite(widget.grow) &&
|
|
162
|
+
widget.grow > 0 &&
|
|
163
|
+
typeof widget.visible === 'boolean'
|
|
164
|
+
)
|
|
165
|
+
})
|
|
169
166
|
}
|
|
170
167
|
|
|
171
|
-
export function
|
|
168
|
+
export function isPersistedBars(value: unknown): value is PersistedBars {
|
|
172
169
|
if (typeof value !== 'object' || value === null) return false
|
|
173
170
|
const v = value as Record<string, unknown>
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
171
|
+
return isPersistedBar(v.left) && isPersistedBar(v.right)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* One-time upgrade from the pre-bars layout (a left sidebar plus a git pane
|
|
176
|
+
* with its own mode/position). Runs whenever `bars` is absent so an existing
|
|
177
|
+
* install keeps the layout it had.
|
|
178
|
+
*/
|
|
179
|
+
export function deriveBarsFromLegacy(
|
|
180
|
+
sidebar: PersistedSidebar | undefined,
|
|
181
|
+
gitPane: PersistedGitPane | undefined
|
|
182
|
+
): PersistedBars {
|
|
183
|
+
const gitVisible = gitPane?.visible ?? true
|
|
184
|
+
const embeddedRatio = gitPane?.embeddedRatio ?? gitPane?.ratio ?? 0.5
|
|
185
|
+
const gitGrow = Math.max(1, Math.round(embeddedRatio * 100))
|
|
186
|
+
const git = { grow: gitGrow, id: 'git', visible: gitVisible }
|
|
187
|
+
const projects = { grow: Math.max(1, 100 - gitGrow), id: 'projects', visible: true }
|
|
188
|
+
|
|
189
|
+
const left: PersistedBar = {
|
|
190
|
+
visible: sidebar?.visible ?? true,
|
|
191
|
+
widgets: gitPane?.position === 'top' ? [git, projects] : [projects, git],
|
|
192
|
+
width: sidebar?.width ?? 28,
|
|
180
193
|
}
|
|
181
|
-
|
|
194
|
+
const right: PersistedBar = { visible: false, widgets: [], width: 40 }
|
|
195
|
+
|
|
196
|
+
// A legacy `pane` git panel on the right was already its own right-hand
|
|
197
|
+
// column — it maps straight onto the right bar. On the left it shared the
|
|
198
|
+
// edge with the sidebar, which the single-bar model expresses as a stack.
|
|
199
|
+
if (gitPane?.mode === 'pane' && gitPane.position === 'right') {
|
|
200
|
+
left.widgets = [{ ...projects, grow: 100 }]
|
|
201
|
+
right.visible = true
|
|
202
|
+
right.widgets = [{ ...git, grow: 100 }]
|
|
203
|
+
right.width = Math.round((gitPane.paneRatio ?? gitPane.ratio ?? 0.5) * 80)
|
|
204
|
+
}
|
|
205
|
+
return { left, right }
|
|
182
206
|
}
|
|
183
207
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
return
|
|
208
|
+
/**
|
|
209
|
+
* Keeps the entries it understands and drops the rest, rather than rejecting the
|
|
210
|
+
* whole block: this record grows one independent key per setting, so one bad
|
|
211
|
+
* value has no business resetting every other one.
|
|
212
|
+
*/
|
|
213
|
+
function parseStoredSettings(value: unknown): { settings: StoredSettings; dropped: number } {
|
|
214
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
215
|
+
return { dropped: 0, settings: {} }
|
|
192
216
|
}
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
for (const entry of value) {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
if (
|
|
201
|
-
|
|
202
|
-
continue
|
|
203
|
-
}
|
|
204
|
-
seen.add(entry.id)
|
|
205
|
-
valid.push(entry)
|
|
217
|
+
const settings: StoredSettings = {}
|
|
218
|
+
let dropped = 0
|
|
219
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
220
|
+
const ok =
|
|
221
|
+
typeof entry === 'boolean' ||
|
|
222
|
+
typeof entry === 'string' ||
|
|
223
|
+
(typeof entry === 'number' && Number.isFinite(entry))
|
|
224
|
+
if (ok) settings[key] = entry
|
|
225
|
+
else dropped++
|
|
206
226
|
}
|
|
207
|
-
return
|
|
227
|
+
return { dropped, settings }
|
|
208
228
|
}
|
|
209
229
|
|
|
210
230
|
function isPersistedSidebar(value: unknown): value is PersistedSidebar {
|
|
@@ -259,13 +279,19 @@ export function loadConfigResult(): ConfigLoadResult {
|
|
|
259
279
|
themeTransparent?: unknown
|
|
260
280
|
themeMode?: unknown
|
|
261
281
|
gitPane?: unknown
|
|
282
|
+
bars?: unknown
|
|
262
283
|
sidebar?: unknown
|
|
263
284
|
gitPanelVisible?: unknown
|
|
264
285
|
gitPanelRatio?: unknown
|
|
286
|
+
projectBarVisible?: unknown
|
|
287
|
+
/** ponytail: pre-rename spelling, honoured for one release. */
|
|
265
288
|
sessionBarVisible?: unknown
|
|
266
|
-
|
|
289
|
+
projectSnapshot?: unknown
|
|
290
|
+
settings?: unknown
|
|
267
291
|
skippedUpdateVersion?: unknown
|
|
268
292
|
prunedOrphanAimuxBranches?: unknown
|
|
293
|
+
/** Removed; still detected so the doctor can say so. */
|
|
294
|
+
workspaceTemplates?: unknown
|
|
269
295
|
worktreeTemplates?: unknown
|
|
270
296
|
}
|
|
271
297
|
|
|
@@ -305,6 +331,11 @@ export function loadConfigResult(): ConfigLoadResult {
|
|
|
305
331
|
issues.push('ignored invalid sidebar')
|
|
306
332
|
}
|
|
307
333
|
|
|
334
|
+
const validBars = isPersistedBars(parsed.bars) ? parsed.bars : undefined
|
|
335
|
+
if (parsed.bars !== undefined && validBars === undefined) {
|
|
336
|
+
issues.push('ignored invalid bars')
|
|
337
|
+
}
|
|
338
|
+
|
|
308
339
|
// Legacy migration: previous schema stored gitPanelVisible/gitPanelRatio at
|
|
309
340
|
// top level. If the new `gitPane` field is absent, synthesize it from legacy
|
|
310
341
|
// keys so users don't lose their toggle/ratio on upgrade.
|
|
@@ -330,17 +361,28 @@ export function loadConfigResult(): ConfigLoadResult {
|
|
|
330
361
|
}
|
|
331
362
|
}
|
|
332
363
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
364
|
+
// `sessionBarVisible` was the pre-rename key; without the fallback the bar
|
|
365
|
+
// would silently reset to its default on the first launch after upgrading.
|
|
366
|
+
const rawProjectBarVisible = parsed.projectBarVisible ?? parsed.sessionBarVisible
|
|
367
|
+
const validProjectBarVisible =
|
|
368
|
+
typeof rawProjectBarVisible === 'boolean' ? rawProjectBarVisible : undefined
|
|
369
|
+
if (rawProjectBarVisible !== undefined && validProjectBarVisible === undefined) {
|
|
370
|
+
issues.push('ignored invalid projectBarVisible')
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (parsed.projectSnapshot !== undefined && !isProjectSnapshotV1(parsed.projectSnapshot)) {
|
|
374
|
+
issues.push('ignored invalid projectSnapshot')
|
|
337
375
|
}
|
|
338
376
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
377
|
+
const storedSettings = parseStoredSettings(parsed.settings)
|
|
378
|
+
const settingsIsRecord =
|
|
379
|
+
typeof parsed.settings === 'object' &&
|
|
380
|
+
parsed.settings !== null &&
|
|
381
|
+
!Array.isArray(parsed.settings)
|
|
382
|
+
if (parsed.settings !== undefined && !settingsIsRecord) {
|
|
383
|
+
issues.push('ignored invalid settings')
|
|
384
|
+
} else if (storedSettings.dropped > 0) {
|
|
385
|
+
issues.push(`ignored ${String(storedSettings.dropped)} invalid setting(s)`)
|
|
344
386
|
}
|
|
345
387
|
|
|
346
388
|
const validSkippedUpdateVersion =
|
|
@@ -351,7 +393,12 @@ export function loadConfigResult(): ConfigLoadResult {
|
|
|
351
393
|
issues.push('ignored invalid skippedUpdateVersion')
|
|
352
394
|
}
|
|
353
395
|
|
|
354
|
-
|
|
396
|
+
// Templates are gone, and an unknown key parses fine — so a config that
|
|
397
|
+
// still declares them would silently stop doing anything. The doctor is the
|
|
398
|
+
// only place that can say so.
|
|
399
|
+
if (parsed.workspaceTemplates !== undefined || parsed.worktreeTemplates !== undefined) {
|
|
400
|
+
issues.push('workspaceTemplates was removed — use a per-project setup script instead')
|
|
401
|
+
}
|
|
355
402
|
|
|
356
403
|
if (issues.length > 0) {
|
|
357
404
|
logDebug('config.load.validationIssue', { issues, path: configPath })
|
|
@@ -359,20 +406,21 @@ export function loadConfigResult(): ConfigLoadResult {
|
|
|
359
406
|
|
|
360
407
|
return {
|
|
361
408
|
config: {
|
|
409
|
+
bars: validBars ?? deriveBarsFromLegacy(validSidebar, validGitPane),
|
|
362
410
|
customCommands: isCustomCommandsRecord(parsed.customCommands) ? parsed.customCommands : {},
|
|
363
411
|
gitPane: validGitPane,
|
|
412
|
+
projectBarVisible: validProjectBarVisible,
|
|
413
|
+
projectSnapshot: isProjectSnapshotV1(parsed.projectSnapshot)
|
|
414
|
+
? parsed.projectSnapshot
|
|
415
|
+
: undefined,
|
|
364
416
|
prunedOrphanAimuxBranches: parsed.prunedOrphanAimuxBranches === true ? true : undefined,
|
|
365
|
-
|
|
417
|
+
settings: storedSettings.settings,
|
|
366
418
|
sidebar: validSidebar,
|
|
367
419
|
skippedUpdateVersion: validSkippedUpdateVersion,
|
|
368
420
|
themeId: migrateThemeId(parsed.themeId),
|
|
369
421
|
themeMode: validThemeMode,
|
|
370
422
|
themeTransparent: validThemeTransparent,
|
|
371
423
|
version: 2,
|
|
372
|
-
workspaceSnapshot: isWorkspaceSnapshotV1(parsed.workspaceSnapshot)
|
|
373
|
-
? parsed.workspaceSnapshot
|
|
374
|
-
: undefined,
|
|
375
|
-
worktreeTemplates: validWorktreeTemplates,
|
|
376
424
|
},
|
|
377
425
|
issues,
|
|
378
426
|
source: 'file',
|