@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
package/src/cli/context.ts
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ProjectRecord } from '../state/types'
|
|
2
2
|
import type { DaemonClient } from './client/daemon-client'
|
|
3
|
-
import type {
|
|
3
|
+
import type { ProjectOrigin } from './client/project-resolver'
|
|
4
4
|
import type { ParsedArgs } from './flags'
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Per-command context handed to `CliCommand.run`. Commands lazily resolve
|
|
8
|
-
* the
|
|
9
|
-
* only reads flags (e.g. `
|
|
8
|
+
* the project + daemon via the helpers on this object so a command that
|
|
9
|
+
* only reads flags (e.g. `project list`) doesn't have to open a socket.
|
|
10
10
|
*/
|
|
11
11
|
export interface CliContext {
|
|
12
12
|
args: ParsedArgs
|
|
13
13
|
/** Already-running daemon client, populated on first call to `daemon()`. */
|
|
14
14
|
getDaemon: () => Promise<DaemonClient>
|
|
15
|
-
/** Resolved
|
|
16
|
-
|
|
15
|
+
/** Resolved project, populated on first call to `project()`. */
|
|
16
|
+
getProject: () => ProjectRecord
|
|
17
17
|
/**
|
|
18
|
-
* Every catalogued
|
|
19
|
-
* gone, or did the active
|
|
18
|
+
* Every catalogued project. Commands that must answer "is my worker really
|
|
19
|
+
* gone, or did the active project move?" need the whole catalog, not just
|
|
20
20
|
* the resolved record. Optional so test fixtures can inject one.
|
|
21
21
|
*/
|
|
22
|
-
|
|
22
|
+
getProjects?: () => ProjectRecord[]
|
|
23
23
|
/**
|
|
24
|
-
* Where `
|
|
25
|
-
* (`
|
|
26
|
-
* `active` can drift under a command while the UI switches
|
|
24
|
+
* Where `getProject()` came from — `flag` (`--project`), `env`
|
|
25
|
+
* (`AIMUX_PROJECT`), or `active` (most recently opened project). Only
|
|
26
|
+
* `active` can drift under a command while the UI switches projects, so
|
|
27
27
|
* commands that must not follow the UI branch on this. Optional so test
|
|
28
28
|
* fixtures can build a minimal context.
|
|
29
29
|
*/
|
|
30
|
-
|
|
30
|
+
getProjectOrigin?: () => ProjectOrigin
|
|
31
31
|
}
|
package/src/cli/flags.ts
CHANGED
|
@@ -15,8 +15,8 @@ export type DynamicCompletionSource =
|
|
|
15
15
|
| 'git-ref'
|
|
16
16
|
| 'tab'
|
|
17
17
|
| 'worker'
|
|
18
|
+
| 'project'
|
|
18
19
|
| 'workspace'
|
|
19
|
-
| 'worktree'
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* Where a flag value or positional draws its shell-completion candidates.
|
|
@@ -67,6 +67,23 @@ export function parseArgs(
|
|
|
67
67
|
): ParsedArgs {
|
|
68
68
|
const flagByName = new Map<string, FlagSpec>()
|
|
69
69
|
for (const spec of flagSpecs) flagByName.set(spec.name, spec)
|
|
70
|
+
// Deprecated aliases from the project/workspace rename. Unlike the command
|
|
71
|
+
// groups these are safe to map silently: --workspace named a project and
|
|
72
|
+
// --worktree named what is now a workspace, so each old name still points at
|
|
73
|
+
// the same object.
|
|
74
|
+
// ponytail: drop with the aimux-sessions.json fallback.
|
|
75
|
+
// Resolved against the declared specs, never against another alias —
|
|
76
|
+
// otherwise --worktree would chain through the --workspace alias onto
|
|
77
|
+
// --project, which is a different object entirely.
|
|
78
|
+
for (const [old, current] of [
|
|
79
|
+
['workspace', 'project'],
|
|
80
|
+
['worktree', 'workspace'],
|
|
81
|
+
['new-worktree', 'new-workspace'],
|
|
82
|
+
] as const) {
|
|
83
|
+
if (flagByName.has(old)) continue
|
|
84
|
+
const spec = flagSpecs.find((entry) => entry.name === current)
|
|
85
|
+
if (spec) flagByName.set(old, spec)
|
|
86
|
+
}
|
|
70
87
|
|
|
71
88
|
const flags: Record<string, string | number | boolean> = {}
|
|
72
89
|
const positionals: string[] = []
|
|
@@ -85,18 +102,21 @@ export function parseArgs(
|
|
|
85
102
|
if (!spec) {
|
|
86
103
|
throw new CliUsageError(`unknown flag: --${name}`)
|
|
87
104
|
}
|
|
105
|
+
// Deprecated aliases resolve to the current spec, so results are always
|
|
106
|
+
// keyed by the canonical name regardless of which spelling was typed.
|
|
107
|
+
const key = spec.name
|
|
88
108
|
if (spec.kind === 'boolean') {
|
|
89
109
|
if (eq !== -1) {
|
|
90
110
|
throw new CliUsageError(`flag --${name} does not take a value`)
|
|
91
111
|
}
|
|
92
|
-
flags[
|
|
112
|
+
flags[key] = true
|
|
93
113
|
continue
|
|
94
114
|
}
|
|
95
115
|
if (spec.kind === 'optional-string') {
|
|
96
116
|
// Value binds ONLY in the `=` form (`--flag=value`). A bare `--flag`
|
|
97
117
|
// must not swallow the next token (it may be a positional), so it
|
|
98
118
|
// parses as boolean `true`.
|
|
99
|
-
flags[
|
|
119
|
+
flags[key] = eq === -1 ? true : token.slice(eq + 1)
|
|
100
120
|
continue
|
|
101
121
|
}
|
|
102
122
|
const raw = eq === -1 ? argv[++i] : token.slice(eq + 1)
|
|
@@ -108,9 +128,9 @@ export function parseArgs(
|
|
|
108
128
|
if (!Number.isFinite(parsed)) {
|
|
109
129
|
throw new CliUsageError(`flag --${name} must be a number (got: ${raw})`)
|
|
110
130
|
}
|
|
111
|
-
flags[
|
|
131
|
+
flags[key] = parsed
|
|
112
132
|
} else {
|
|
113
|
-
flags[
|
|
133
|
+
flags[key] = raw
|
|
114
134
|
}
|
|
115
135
|
continue
|
|
116
136
|
}
|
|
@@ -129,17 +149,17 @@ export function parseArgs(
|
|
|
129
149
|
}
|
|
130
150
|
|
|
131
151
|
/**
|
|
132
|
-
* Shared flag set — every command takes these. `--
|
|
133
|
-
* target
|
|
152
|
+
* Shared flag set — every command takes these. `--project` selects the
|
|
153
|
+
* target project (by id or name); `--profile` overrides `AIMUX_PROFILE`
|
|
134
154
|
* before any runtime path is resolved; `--json` is a no-op kept for
|
|
135
155
|
* consistency with future formats.
|
|
136
156
|
*/
|
|
137
157
|
export const SHARED_FLAGS: readonly FlagSpec[] = [
|
|
138
158
|
{
|
|
139
|
-
complete: { kind: 'dynamic', source: '
|
|
140
|
-
description: '
|
|
159
|
+
complete: { kind: 'dynamic', source: 'project' },
|
|
160
|
+
description: 'project id or name',
|
|
141
161
|
kind: 'string',
|
|
142
|
-
name: '
|
|
162
|
+
name: 'project',
|
|
143
163
|
},
|
|
144
164
|
{
|
|
145
165
|
complete: { kind: 'none' },
|
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 {
|
|
@@ -64,26 +64,6 @@ export interface PersistedBars {
|
|
|
64
64
|
right: PersistedBar
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
export interface WorktreeTemplatePane {
|
|
68
|
-
id: string
|
|
69
|
-
assistant: string
|
|
70
|
-
splitFrom?: string
|
|
71
|
-
direction?: SplitDirection
|
|
72
|
-
ratio?: number
|
|
73
|
-
send?: string
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export interface WorktreeTemplateTab {
|
|
77
|
-
panes: WorktreeTemplatePane[]
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export interface WorktreeTemplate {
|
|
81
|
-
id: string
|
|
82
|
-
name: string
|
|
83
|
-
description?: string
|
|
84
|
-
tabs: WorktreeTemplateTab[]
|
|
85
|
-
}
|
|
86
|
-
|
|
87
67
|
export interface AimuxConfig {
|
|
88
68
|
version: 2
|
|
89
69
|
customCommands: Record<string, string>
|
|
@@ -93,12 +73,18 @@ export interface AimuxConfig {
|
|
|
93
73
|
gitPane?: PersistedGitPane
|
|
94
74
|
bars?: PersistedBars
|
|
95
75
|
sidebar?: PersistedSidebar
|
|
96
|
-
|
|
97
|
-
|
|
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
|
|
98
85
|
skippedUpdateVersion?: string
|
|
99
86
|
/** One-shot guard: orphan `aimux/` branches were pruned from existing repos. */
|
|
100
87
|
prunedOrphanAimuxBranches?: boolean
|
|
101
|
-
worktreeTemplates?: WorktreeTemplate[]
|
|
102
88
|
}
|
|
103
89
|
|
|
104
90
|
function isPersistedGitPane(value: unknown): value is PersistedGitPane {
|
|
@@ -198,11 +184,11 @@ export function deriveBarsFromLegacy(
|
|
|
198
184
|
const embeddedRatio = gitPane?.embeddedRatio ?? gitPane?.ratio ?? 0.5
|
|
199
185
|
const gitGrow = Math.max(1, Math.round(embeddedRatio * 100))
|
|
200
186
|
const git = { grow: gitGrow, id: 'git', visible: gitVisible }
|
|
201
|
-
const
|
|
187
|
+
const projects = { grow: Math.max(1, 100 - gitGrow), id: 'projects', visible: true }
|
|
202
188
|
|
|
203
189
|
const left: PersistedBar = {
|
|
204
190
|
visible: sidebar?.visible ?? true,
|
|
205
|
-
widgets: gitPane?.position === 'top' ? [git,
|
|
191
|
+
widgets: gitPane?.position === 'top' ? [git, projects] : [projects, git],
|
|
206
192
|
width: sidebar?.width ?? 28,
|
|
207
193
|
}
|
|
208
194
|
const right: PersistedBar = { visible: false, widgets: [], width: 40 }
|
|
@@ -211,7 +197,7 @@ export function deriveBarsFromLegacy(
|
|
|
211
197
|
// column — it maps straight onto the right bar. On the left it shared the
|
|
212
198
|
// edge with the sidebar, which the single-bar model expresses as a stack.
|
|
213
199
|
if (gitPane?.mode === 'pane' && gitPane.position === 'right') {
|
|
214
|
-
left.widgets = [{ ...
|
|
200
|
+
left.widgets = [{ ...projects, grow: 100 }]
|
|
215
201
|
right.visible = true
|
|
216
202
|
right.widgets = [{ ...git, grow: 100 }]
|
|
217
203
|
right.width = Math.round((gitPane.paneRatio ?? gitPane.ratio ?? 0.5) * 80)
|
|
@@ -219,75 +205,26 @@ export function deriveBarsFromLegacy(
|
|
|
219
205
|
return { left, right }
|
|
220
206
|
}
|
|
221
207
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
const seenIds = new Set<string>()
|
|
231
|
-
for (let i = 0; i < v.panes.length; i++) {
|
|
232
|
-
const rawPane = v.panes[i]
|
|
233
|
-
if (typeof rawPane !== 'object' || rawPane === null) return false
|
|
234
|
-
const pane = rawPane as Record<string, unknown>
|
|
235
|
-
if (typeof pane.id !== 'string' || pane.id.length === 0) return false
|
|
236
|
-
if (seenIds.has(pane.id)) return false
|
|
237
|
-
seenIds.add(pane.id)
|
|
238
|
-
if (typeof pane.assistant !== 'string' || pane.assistant.length === 0) return false
|
|
239
|
-
if (i === 0) {
|
|
240
|
-
if (pane.splitFrom !== undefined) return false
|
|
241
|
-
if (pane.direction !== undefined) return false
|
|
242
|
-
if (pane.ratio !== undefined) return false
|
|
243
|
-
} else {
|
|
244
|
-
if (typeof pane.splitFrom !== 'string' || !seenIds.has(pane.splitFrom)) return false
|
|
245
|
-
if (pane.splitFrom === pane.id) return false
|
|
246
|
-
if (pane.direction !== 'horizontal' && pane.direction !== 'vertical') return false
|
|
247
|
-
if (pane.ratio !== undefined && !isRatioValid(pane.ratio)) return false
|
|
248
|
-
}
|
|
249
|
-
if (pane.send !== undefined && typeof pane.send !== 'string') return false
|
|
250
|
-
}
|
|
251
|
-
return true
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
export function isWorktreeTemplate(value: unknown): value is WorktreeTemplate {
|
|
255
|
-
if (typeof value !== 'object' || value === null) return false
|
|
256
|
-
const v = value as Record<string, unknown>
|
|
257
|
-
if (typeof v.id !== 'string' || v.id.length === 0) return false
|
|
258
|
-
if (typeof v.name !== 'string' || v.name.length === 0) return false
|
|
259
|
-
if (v.description !== undefined && typeof v.description !== 'string') return false
|
|
260
|
-
if (!Array.isArray(v.tabs) || v.tabs.length === 0) return false
|
|
261
|
-
for (const tab of v.tabs) {
|
|
262
|
-
if (!isWorktreeTemplateTab(tab)) return false
|
|
263
|
-
}
|
|
264
|
-
return true
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
export function parseWorktreeTemplates(
|
|
268
|
-
value: unknown,
|
|
269
|
-
issues: string[]
|
|
270
|
-
): WorktreeTemplate[] | undefined {
|
|
271
|
-
if (value === undefined) return undefined
|
|
272
|
-
if (!Array.isArray(value)) {
|
|
273
|
-
issues.push('ignored invalid worktreeTemplates (not an array)')
|
|
274
|
-
return undefined
|
|
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: {} }
|
|
275
216
|
}
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
for (const entry of value) {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
if (
|
|
284
|
-
|
|
285
|
-
continue
|
|
286
|
-
}
|
|
287
|
-
seen.add(entry.id)
|
|
288
|
-
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++
|
|
289
226
|
}
|
|
290
|
-
return
|
|
227
|
+
return { dropped, settings }
|
|
291
228
|
}
|
|
292
229
|
|
|
293
230
|
function isPersistedSidebar(value: unknown): value is PersistedSidebar {
|
|
@@ -346,10 +283,15 @@ export function loadConfigResult(): ConfigLoadResult {
|
|
|
346
283
|
sidebar?: unknown
|
|
347
284
|
gitPanelVisible?: unknown
|
|
348
285
|
gitPanelRatio?: unknown
|
|
286
|
+
projectBarVisible?: unknown
|
|
287
|
+
/** ponytail: pre-rename spelling, honoured for one release. */
|
|
349
288
|
sessionBarVisible?: unknown
|
|
350
|
-
|
|
289
|
+
projectSnapshot?: unknown
|
|
290
|
+
settings?: unknown
|
|
351
291
|
skippedUpdateVersion?: unknown
|
|
352
292
|
prunedOrphanAimuxBranches?: unknown
|
|
293
|
+
/** Removed; still detected so the doctor can say so. */
|
|
294
|
+
workspaceTemplates?: unknown
|
|
353
295
|
worktreeTemplates?: unknown
|
|
354
296
|
}
|
|
355
297
|
|
|
@@ -419,17 +361,28 @@ export function loadConfigResult(): ConfigLoadResult {
|
|
|
419
361
|
}
|
|
420
362
|
}
|
|
421
363
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
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')
|
|
426
375
|
}
|
|
427
376
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
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)`)
|
|
433
386
|
}
|
|
434
387
|
|
|
435
388
|
const validSkippedUpdateVersion =
|
|
@@ -440,7 +393,12 @@ export function loadConfigResult(): ConfigLoadResult {
|
|
|
440
393
|
issues.push('ignored invalid skippedUpdateVersion')
|
|
441
394
|
}
|
|
442
395
|
|
|
443
|
-
|
|
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
|
+
}
|
|
444
402
|
|
|
445
403
|
if (issues.length > 0) {
|
|
446
404
|
logDebug('config.load.validationIssue', { issues, path: configPath })
|
|
@@ -451,18 +409,18 @@ export function loadConfigResult(): ConfigLoadResult {
|
|
|
451
409
|
bars: validBars ?? deriveBarsFromLegacy(validSidebar, validGitPane),
|
|
452
410
|
customCommands: isCustomCommandsRecord(parsed.customCommands) ? parsed.customCommands : {},
|
|
453
411
|
gitPane: validGitPane,
|
|
412
|
+
projectBarVisible: validProjectBarVisible,
|
|
413
|
+
projectSnapshot: isProjectSnapshotV1(parsed.projectSnapshot)
|
|
414
|
+
? parsed.projectSnapshot
|
|
415
|
+
: undefined,
|
|
454
416
|
prunedOrphanAimuxBranches: parsed.prunedOrphanAimuxBranches === true ? true : undefined,
|
|
455
|
-
|
|
417
|
+
settings: storedSettings.settings,
|
|
456
418
|
sidebar: validSidebar,
|
|
457
419
|
skippedUpdateVersion: validSkippedUpdateVersion,
|
|
458
420
|
themeId: migrateThemeId(parsed.themeId),
|
|
459
421
|
themeMode: validThemeMode,
|
|
460
422
|
themeTransparent: validThemeTransparent,
|
|
461
423
|
version: 2,
|
|
462
|
-
workspaceSnapshot: isWorkspaceSnapshotV1(parsed.workspaceSnapshot)
|
|
463
|
-
? parsed.workspaceSnapshot
|
|
464
|
-
: undefined,
|
|
465
|
-
worktreeTemplates: validWorktreeTemplates,
|
|
466
424
|
},
|
|
467
425
|
issues,
|
|
468
426
|
source: 'file',
|