@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
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type { ProjectRecord, WorkspaceRecord } from '../../state/types'
|
|
2
|
+
|
|
3
|
+
import { findMostRecentProject, loadProjectCatalog } from '../../state/project-catalog'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Where a command's target project came from. An orchestrator needs this to
|
|
7
|
+
* tell "I asked for pragma-once" from "aimux guessed, and the UI had moved on":
|
|
8
|
+
* `active` is the only origin that can silently follow the UI to another repo.
|
|
9
|
+
*/
|
|
10
|
+
export type ProjectOrigin = 'flag' | 'env' | 'active'
|
|
11
|
+
|
|
12
|
+
export interface ResolvedProject {
|
|
13
|
+
origin: ProjectOrigin
|
|
14
|
+
record: ProjectRecord
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Env pin for headless orchestrators — `--project` still wins over it. */
|
|
18
|
+
export const PROJECT_ENV_VAR = 'AIMUX_PROJECT'
|
|
19
|
+
/** ponytail: pre-rename spelling, honoured for one release. Drop with --workspace. */
|
|
20
|
+
const LEGACY_PROJECT_ENV_VAR = 'AIMUX_WORKSPACE'
|
|
21
|
+
|
|
22
|
+
/** The project's primary (root) workspace, i.e. the repository it is about. */
|
|
23
|
+
export function findPrimaryWorkspace(project: ProjectRecord): WorkspaceRecord | undefined {
|
|
24
|
+
return project.workspaces?.find((workspace) => workspace.source === 'primary')
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The repo every fresh workspace for this project is cut from. Surfaced in
|
|
29
|
+
* every worker envelope so an agent can see *which project* it just acted on
|
|
30
|
+
* instead of inferring it from a workspace path hash.
|
|
31
|
+
*/
|
|
32
|
+
export function projectRepoRoot(project: ProjectRecord): string | null {
|
|
33
|
+
return findPrimaryWorkspace(project)?.repoRoot ?? project.projectPath ?? null
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Stable project identity block embedded in command output. */
|
|
37
|
+
export function projectIdentity(project: ProjectRecord): {
|
|
38
|
+
id: string
|
|
39
|
+
name: string
|
|
40
|
+
repoRoot: string | null
|
|
41
|
+
} {
|
|
42
|
+
return { id: project.id, name: project.name, repoRoot: projectRepoRoot(project) }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Resolve `--project W` to a project record from the catalog, reporting where
|
|
47
|
+
* the choice came from. Precedence: the explicit flag, then `AIMUX_PROJECT`,
|
|
48
|
+
* then the most recently opened project. Throws when the catalog is empty (no
|
|
49
|
+
* project has ever been created) or when the explicit name/id doesn't match.
|
|
50
|
+
*
|
|
51
|
+
* Matching: exact id wins; otherwise exact name (case-sensitive); otherwise
|
|
52
|
+
* unique case-insensitive name match.
|
|
53
|
+
*/
|
|
54
|
+
export function resolveProjectWithOrigin(name: string | undefined): ResolvedProject {
|
|
55
|
+
const projects = loadProjectCatalog()
|
|
56
|
+
if (projects.length === 0) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
'no projects found — create one from the aimux UI first (or pass --project once created)'
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const flag = name !== undefined && name !== '' ? name : undefined
|
|
63
|
+
const env = process.env[PROJECT_ENV_VAR] ?? process.env[LEGACY_PROJECT_ENV_VAR]
|
|
64
|
+
const fromEnv = env != null && env !== '' ? env : undefined
|
|
65
|
+
const selector = flag ?? fromEnv
|
|
66
|
+
if (selector === undefined) {
|
|
67
|
+
const active = findMostRecentProject(projects)
|
|
68
|
+
if (!active) {
|
|
69
|
+
throw new Error('no active project and the catalog is empty')
|
|
70
|
+
}
|
|
71
|
+
return { origin: 'active', record: active }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
origin: flag !== undefined ? 'flag' : 'env',
|
|
76
|
+
record: matchProject(projects, selector),
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function resolveProject(name: string | undefined): ProjectRecord {
|
|
81
|
+
return resolveProjectWithOrigin(name).record
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function matchProject(projects: ProjectRecord[], name: string): ProjectRecord {
|
|
85
|
+
const byId = projects.find((project) => project.id === name)
|
|
86
|
+
if (byId) return byId
|
|
87
|
+
|
|
88
|
+
const exactNameMatches = projects.filter((project) => project.name === name)
|
|
89
|
+
if (exactNameMatches.length > 1) {
|
|
90
|
+
throw new Error(
|
|
91
|
+
`project "${name}" matches multiple projects: ${exactNameMatches.map((s) => s.id).join(', ')}`
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
const exactOnly = exactNameMatches[0]
|
|
95
|
+
if (exactOnly) return exactOnly
|
|
96
|
+
|
|
97
|
+
const lower = name.toLowerCase()
|
|
98
|
+
const ciMatches = projects.filter((project) => project.name.toLowerCase() === lower)
|
|
99
|
+
if (ciMatches.length > 1) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
`project "${name}" matches multiple projects: ${ciMatches.map((s) => s.id).join(', ')}`
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
const ciOnly = ciMatches[0]
|
|
105
|
+
if (ciOnly) return ciOnly
|
|
106
|
+
|
|
107
|
+
throw new Error(`project not found: ${name}`)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function listProjects(): ProjectRecord[] {
|
|
111
|
+
return loadProjectCatalog()
|
|
112
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { CliCommand } from '../../registry'
|
|
2
|
+
|
|
3
|
+
import { IPC_CAPABILITY_PROJECT_LIFECYCLE } from '../../../ipc/protocol'
|
|
4
|
+
import { resolveProject } from '../../client/project-resolver'
|
|
5
|
+
import { SHARED_FLAGS } from '../../flags'
|
|
6
|
+
import { EXIT_OK, writeJson } from '../../output'
|
|
7
|
+
|
|
8
|
+
export const projectClose: CliCommand = {
|
|
9
|
+
args: [{ complete: { kind: 'dynamic', source: 'project' }, name: 'project', required: true }],
|
|
10
|
+
flags: SHARED_FLAGS,
|
|
11
|
+
group: 'project',
|
|
12
|
+
run: async (ctx) => {
|
|
13
|
+
const target = ctx.args.positionals[0]
|
|
14
|
+
if (typeof target !== 'string' || target.length === 0) {
|
|
15
|
+
throw new Error('target project is required (id or name)')
|
|
16
|
+
}
|
|
17
|
+
const project = resolveProject(target)
|
|
18
|
+
|
|
19
|
+
const daemon = await ctx.getDaemon()
|
|
20
|
+
if (!daemon.hasCapability(IPC_CAPABILITY_PROJECT_LIFECYCLE)) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
'daemon predates projectLifecycle capability — restart aimux to pick up the new daemon'
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
await daemon.expectOk('closeProject', { targetProjectId: project.id })
|
|
27
|
+
writeJson({ closedProjectId: project.id, name: project.name })
|
|
28
|
+
return EXIT_OK
|
|
29
|
+
},
|
|
30
|
+
summary: 'Close a project (via the UI when attached, otherwise the catalog)',
|
|
31
|
+
verb: 'close',
|
|
32
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { resolve as resolvePath } from 'node:path'
|
|
2
|
+
|
|
3
|
+
import type { CliCommand } from '../../registry'
|
|
4
|
+
|
|
5
|
+
import { IPC_CAPABILITY_PROJECT_LIFECYCLE } from '../../../ipc/protocol'
|
|
6
|
+
import { SHARED_FLAGS } from '../../flags'
|
|
7
|
+
import { EXIT_OK, EXIT_TIMEOUT, writeJson } from '../../output'
|
|
8
|
+
|
|
9
|
+
const DEFAULT_WAIT_TIMEOUT_MS = 30_000
|
|
10
|
+
|
|
11
|
+
export const projectCreate: CliCommand = {
|
|
12
|
+
args: [{ complete: { kind: 'none' }, name: 'name', required: true }],
|
|
13
|
+
flags: [
|
|
14
|
+
...SHARED_FLAGS,
|
|
15
|
+
{
|
|
16
|
+
complete: { kind: 'file' },
|
|
17
|
+
description: 'project path to associate with the project',
|
|
18
|
+
kind: 'string',
|
|
19
|
+
name: 'project',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
description: 'immediately switch the running UI to the new project',
|
|
23
|
+
kind: 'boolean',
|
|
24
|
+
name: 'switch',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
description: 'with --switch, wait until the UI confirms the switch completed',
|
|
28
|
+
kind: 'boolean',
|
|
29
|
+
name: 'wait',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
description: 'timeout for --wait, in milliseconds (default 30000)',
|
|
33
|
+
kind: 'number',
|
|
34
|
+
name: 'timeout',
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
group: 'project',
|
|
38
|
+
run: async (ctx) => {
|
|
39
|
+
const name = ctx.args.positionals[0]
|
|
40
|
+
if (typeof name !== 'string' || name.length === 0) {
|
|
41
|
+
throw new Error('project name is required')
|
|
42
|
+
}
|
|
43
|
+
const projectRaw =
|
|
44
|
+
typeof ctx.args.flags.project === 'string' ? ctx.args.flags.project : undefined
|
|
45
|
+
const projectPath = projectRaw === undefined ? undefined : resolvePath(projectRaw)
|
|
46
|
+
const doSwitch = ctx.args.flags.switch === true
|
|
47
|
+
const wait = ctx.args.flags.wait === true
|
|
48
|
+
const timeoutMs =
|
|
49
|
+
typeof ctx.args.flags.timeout === 'number' ? ctx.args.flags.timeout : DEFAULT_WAIT_TIMEOUT_MS
|
|
50
|
+
|
|
51
|
+
if (wait && !doSwitch) {
|
|
52
|
+
// Without --switch, the UI never emits an ack event, so there's
|
|
53
|
+
// nothing meaningful for --wait to wait on.
|
|
54
|
+
throw new Error('--wait requires --switch (nothing to wait for otherwise)')
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const daemon = await ctx.getDaemon()
|
|
58
|
+
if (!daemon.hasCapability(IPC_CAPABILITY_PROJECT_LIFECYCLE)) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
'daemon predates projectLifecycle capability — restart aimux to pick up the new daemon'
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!wait) {
|
|
65
|
+
await daemon.expectOk('createProject', { name, projectPath, switch: doSwitch })
|
|
66
|
+
writeJson({ name, projectPath, switch: doSwitch })
|
|
67
|
+
return EXIT_OK
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Subscribe BEFORE sending the request. The UI's create+switch path
|
|
71
|
+
// relays as `projectSwitched` once handleCreateProjectEffect has
|
|
72
|
+
// dispatched load-project. Match on name+projectPath since the CLI
|
|
73
|
+
// doesn't know the id the UI will assign to the new project.
|
|
74
|
+
const settled = new Promise<number>((resolve) => {
|
|
75
|
+
const off = daemon.on('projectSwitched', (payload) => {
|
|
76
|
+
off()
|
|
77
|
+
clearTimeout(timer)
|
|
78
|
+
writeJson({ name, projectId: payload.projectId, projectPath, switch: doSwitch })
|
|
79
|
+
resolve(EXIT_OK)
|
|
80
|
+
})
|
|
81
|
+
const timer = setTimeout(() => {
|
|
82
|
+
off()
|
|
83
|
+
writeJson({ error: 'timed out waiting for projectSwitched', name, projectPath })
|
|
84
|
+
resolve(EXIT_TIMEOUT)
|
|
85
|
+
}, timeoutMs)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
await daemon.expectOk('createProject', { name, projectPath, switch: doSwitch })
|
|
89
|
+
return settled
|
|
90
|
+
},
|
|
91
|
+
summary: 'Create a new project (via the UI when attached, otherwise the catalog)',
|
|
92
|
+
verb: 'create',
|
|
93
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { CliCommand } from '../../registry'
|
|
2
|
+
|
|
3
|
+
import { listProjects } from '../../client/project-resolver'
|
|
4
|
+
import { SHARED_FLAGS } from '../../flags'
|
|
5
|
+
import { EXIT_OK, writeJson } from '../../output'
|
|
6
|
+
|
|
7
|
+
export const projectList: CliCommand = {
|
|
8
|
+
args: [],
|
|
9
|
+
flags: SHARED_FLAGS,
|
|
10
|
+
group: 'project',
|
|
11
|
+
run: async () => {
|
|
12
|
+
const projects = listProjects()
|
|
13
|
+
writeJson({
|
|
14
|
+
projects: projects.map((project) => ({
|
|
15
|
+
id: project.id,
|
|
16
|
+
lastOpenedAt: project.lastOpenedAt,
|
|
17
|
+
name: project.name,
|
|
18
|
+
projectPath: project.projectPath,
|
|
19
|
+
})),
|
|
20
|
+
})
|
|
21
|
+
return EXIT_OK
|
|
22
|
+
},
|
|
23
|
+
summary: 'List known projects in the profile catalog',
|
|
24
|
+
verb: 'list',
|
|
25
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { CliCommand } from '../../registry'
|
|
2
|
+
|
|
3
|
+
import { SHARED_FLAGS } from '../../flags'
|
|
4
|
+
import { EXIT_OK, writeJson } from '../../output'
|
|
5
|
+
|
|
6
|
+
export const projectShow: CliCommand = {
|
|
7
|
+
args: [],
|
|
8
|
+
flags: SHARED_FLAGS,
|
|
9
|
+
group: 'project',
|
|
10
|
+
run: async (ctx) => {
|
|
11
|
+
const project = ctx.getProject()
|
|
12
|
+
writeJson({
|
|
13
|
+
activeWorkspaceId: project.activeWorkspaceId,
|
|
14
|
+
createdAt: project.createdAt,
|
|
15
|
+
id: project.id,
|
|
16
|
+
lastOpenedAt: project.lastOpenedAt,
|
|
17
|
+
name: project.name,
|
|
18
|
+
projectPath: project.projectPath,
|
|
19
|
+
workspaces:
|
|
20
|
+
project.workspaces?.map((w) => ({
|
|
21
|
+
branch: w.branch,
|
|
22
|
+
id: w.id,
|
|
23
|
+
name: w.name,
|
|
24
|
+
path: w.path,
|
|
25
|
+
source: w.source,
|
|
26
|
+
})) ?? [],
|
|
27
|
+
})
|
|
28
|
+
return EXIT_OK
|
|
29
|
+
},
|
|
30
|
+
summary: 'Show the active project (or the one named via --project)',
|
|
31
|
+
verb: 'show',
|
|
32
|
+
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import type { CliCommand } from '../../registry'
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { IPC_CAPABILITY_PROJECT_LIFECYCLE } from '../../../ipc/protocol'
|
|
4
|
+
import { resolveProject } from '../../client/project-resolver'
|
|
5
5
|
import { SHARED_FLAGS } from '../../flags'
|
|
6
6
|
import { EXIT_OK, EXIT_TIMEOUT, writeJson } from '../../output'
|
|
7
7
|
|
|
8
8
|
const DEFAULT_WAIT_TIMEOUT_MS = 30_000
|
|
9
9
|
|
|
10
|
-
export const
|
|
11
|
-
args: [{ complete: { kind: 'dynamic', source: '
|
|
10
|
+
export const projectSwitch: CliCommand = {
|
|
11
|
+
args: [{ complete: { kind: 'dynamic', source: 'project' }, name: 'project', required: true }],
|
|
12
12
|
flags: [
|
|
13
13
|
...SHARED_FLAGS,
|
|
14
14
|
{
|
|
@@ -22,54 +22,54 @@ export const workspaceSwitch: CliCommand = {
|
|
|
22
22
|
name: 'timeout',
|
|
23
23
|
},
|
|
24
24
|
],
|
|
25
|
-
group: '
|
|
25
|
+
group: 'project',
|
|
26
26
|
run: async (ctx) => {
|
|
27
27
|
const target = ctx.args.positionals[0]
|
|
28
28
|
if (typeof target !== 'string' || target.length === 0) {
|
|
29
|
-
throw new Error('target
|
|
29
|
+
throw new Error('target project is required (id or name)')
|
|
30
30
|
}
|
|
31
|
-
const
|
|
31
|
+
const project = resolveProject(target)
|
|
32
32
|
const wait = ctx.args.flags.wait === true
|
|
33
33
|
const timeoutMs =
|
|
34
34
|
typeof ctx.args.flags.timeout === 'number' ? ctx.args.flags.timeout : DEFAULT_WAIT_TIMEOUT_MS
|
|
35
35
|
|
|
36
36
|
const daemon = await ctx.getDaemon()
|
|
37
|
-
if (!daemon.hasCapability(
|
|
37
|
+
if (!daemon.hasCapability(IPC_CAPABILITY_PROJECT_LIFECYCLE)) {
|
|
38
38
|
throw new Error(
|
|
39
|
-
'daemon predates
|
|
39
|
+
'daemon predates projectLifecycle capability — restart aimux to pick up the new daemon'
|
|
40
40
|
)
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
if (!wait) {
|
|
44
|
-
await daemon.expectOk('
|
|
45
|
-
writeJson({ name:
|
|
44
|
+
await daemon.expectOk('switchProject', { targetProjectId: project.id })
|
|
45
|
+
writeJson({ name: project.name, targetProjectId: project.id })
|
|
46
46
|
return EXIT_OK
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
// Subscribe BEFORE sending the request so we don't miss the broadcast for
|
|
50
|
-
// the no-UI path (the daemon emits `
|
|
50
|
+
// the no-UI path (the daemon emits `projectSwitched` synchronously in
|
|
51
51
|
// that branch).
|
|
52
52
|
const settled = new Promise<number>((resolve) => {
|
|
53
|
-
const off = daemon.on('
|
|
54
|
-
if (payload.
|
|
53
|
+
const off = daemon.on('projectSwitched', (payload) => {
|
|
54
|
+
if (payload.projectId !== project.id) return
|
|
55
55
|
off()
|
|
56
56
|
clearTimeout(timer)
|
|
57
|
-
writeJson({ name:
|
|
57
|
+
writeJson({ name: project.name, targetProjectId: project.id })
|
|
58
58
|
resolve(EXIT_OK)
|
|
59
59
|
})
|
|
60
60
|
const timer = setTimeout(() => {
|
|
61
61
|
off()
|
|
62
62
|
writeJson({
|
|
63
|
-
error: 'timed out waiting for
|
|
64
|
-
|
|
63
|
+
error: 'timed out waiting for projectSwitched',
|
|
64
|
+
targetProjectId: project.id,
|
|
65
65
|
})
|
|
66
66
|
resolve(EXIT_TIMEOUT)
|
|
67
67
|
}, timeoutMs)
|
|
68
68
|
})
|
|
69
69
|
|
|
70
|
-
await daemon.expectOk('
|
|
70
|
+
await daemon.expectOk('switchProject', { targetProjectId: project.id })
|
|
71
71
|
return settled
|
|
72
72
|
},
|
|
73
|
-
summary: 'Switch the UI (or catalog when headless) to another
|
|
73
|
+
summary: 'Switch the UI (or catalog when headless) to another project',
|
|
74
74
|
verb: 'switch',
|
|
75
75
|
}
|
|
@@ -45,7 +45,7 @@ export const tabAwait: CliCommand = {
|
|
|
45
45
|
const timeoutMs =
|
|
46
46
|
typeof ctx.args.flags.timeout === 'number' ? ctx.args.flags.timeout : DEFAULT_TIMEOUT_MS
|
|
47
47
|
|
|
48
|
-
const
|
|
48
|
+
const project = ctx.getProject()
|
|
49
49
|
const daemon = await ctx.getDaemon()
|
|
50
50
|
if (
|
|
51
51
|
!daemon.hasCapability(IPC_CAPABILITY_THIN_ATTACH) ||
|
|
@@ -57,7 +57,7 @@ export const tabAwait: CliCommand = {
|
|
|
57
57
|
)
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
const attach = await daemon.attach({ cols: 0,
|
|
60
|
+
const attach = await daemon.attach({ cols: 0, projectId: project.id, rows: 0, thin: true })
|
|
61
61
|
const tab = attach.tabs.find((t) => t.id === tabId)
|
|
62
62
|
if (!tab) {
|
|
63
63
|
// Exit 3 (runtime) via runCli — NOT 4, which is reserved for
|
|
@@ -14,7 +14,7 @@ export const tabClose: CliCommand = {
|
|
|
14
14
|
throw new Error('tabId is required')
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
const
|
|
17
|
+
const project = ctx.getProject()
|
|
18
18
|
const daemon = await ctx.getDaemon()
|
|
19
19
|
|
|
20
20
|
if (!daemon.hasCapability(IPC_CAPABILITY_THIN_ATTACH)) {
|
|
@@ -22,12 +22,12 @@ export const tabClose: CliCommand = {
|
|
|
22
22
|
'daemon predates thinAttach capability — restart aimux to pick up the new daemon'
|
|
23
23
|
)
|
|
24
24
|
}
|
|
25
|
-
await daemon.attach({ cols: 0,
|
|
25
|
+
await daemon.attach({ cols: 0, projectId: project.id, rows: 0, thin: true })
|
|
26
26
|
await daemon.expectOk('closeTab', { tabId })
|
|
27
27
|
|
|
28
28
|
writeJson({ ok: true })
|
|
29
29
|
return EXIT_OK
|
|
30
30
|
},
|
|
31
|
-
summary: 'Close a tab in the
|
|
31
|
+
summary: 'Close a tab in the project',
|
|
32
32
|
verb: 'close',
|
|
33
33
|
}
|