@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.
Files changed (233) hide show
  1. package/README.md +27 -25
  2. package/package.json +2 -2
  3. package/src/app-runtime/auto-commit-driver.ts +22 -27
  4. package/src/app-runtime/backend-attach-runtime.ts +43 -36
  5. package/src/app-runtime/backend-runtime-events.ts +107 -88
  6. package/src/app-runtime/editor-actions.ts +201 -0
  7. package/src/app-runtime/git-actions.ts +197 -0
  8. package/src/app-runtime/measure-ref.ts +19 -0
  9. package/src/app-runtime/navigation-actions.ts +213 -0
  10. package/src/app-runtime/project-actions.ts +188 -0
  11. package/src/app-runtime/prompt-injection.ts +123 -0
  12. package/src/app-runtime/selection.ts +29 -0
  13. package/src/app-runtime/settings-actions.ts +156 -0
  14. package/src/app-runtime/setup-actions.ts +237 -0
  15. package/src/app-runtime/side-effect-context.ts +33 -0
  16. package/src/app-runtime/side-effects.ts +305 -1589
  17. package/src/app-runtime/snippet-actions.ts +3 -2
  18. package/src/app-runtime/split-drag-controller.ts +4 -8
  19. package/src/app-runtime/tab-actions.ts +218 -0
  20. package/src/app-runtime/tab-runtime-timeouts.ts +1 -1
  21. package/src/app-runtime/use-auto-commit-driver.ts +14 -13
  22. package/src/app-runtime/use-backend-runtime.ts +12 -11
  23. package/src/app-runtime/use-directory-search.ts +5 -4
  24. package/src/app-runtime/use-mouse-handlers.ts +28 -42
  25. package/src/app-runtime/{use-workspace-autosave.ts → use-project-autosave.ts} +4 -4
  26. package/src/app-runtime/use-renderer-bindings.ts +2 -1
  27. package/src/app-runtime/use-setup-runner.ts +96 -0
  28. package/src/app-runtime/use-terminal-resize.ts +28 -33
  29. package/src/app-runtime/workspace-actions.ts +366 -0
  30. package/src/app-runtime/workspace-activity.ts +138 -0
  31. package/src/app-runtime/workspace-naming.ts +90 -0
  32. package/src/app.tsx +102 -85
  33. package/src/assets/sounds/bell.wav +0 -0
  34. package/src/assets/sounds/ding.wav +0 -0
  35. package/src/assets/sounds/plane.wav +0 -0
  36. package/src/cli/client/daemon-client.ts +2 -2
  37. package/src/cli/client/project-resolver.ts +112 -0
  38. package/src/cli/commands/project/close.ts +32 -0
  39. package/src/cli/commands/project/create.ts +93 -0
  40. package/src/cli/commands/project/list.ts +25 -0
  41. package/src/cli/commands/project/show.ts +32 -0
  42. package/src/cli/commands/{workspace → project}/switch.ts +19 -19
  43. package/src/cli/commands/tab/await.ts +2 -2
  44. package/src/cli/commands/tab/close.ts +3 -3
  45. package/src/cli/commands/tab/create.ts +74 -73
  46. package/src/cli/commands/tab/focus.ts +3 -3
  47. package/src/cli/commands/tab/list.ts +6 -6
  48. package/src/cli/commands/tab/run.ts +2 -2
  49. package/src/cli/commands/tab/send.ts +2 -2
  50. package/src/cli/commands/tab/snapshot.ts +2 -2
  51. package/src/cli/commands/tab/tail.ts +2 -2
  52. package/src/cli/commands/tab/wait.ts +2 -2
  53. package/src/cli/commands/worker/await.ts +3 -3
  54. package/src/cli/commands/worker/doctor.ts +30 -30
  55. package/src/cli/commands/worker/list.ts +15 -15
  56. package/src/cli/commands/worker/prompt.ts +3 -3
  57. package/src/cli/commands/worker/run.ts +23 -23
  58. package/src/cli/commands/worker/shared.ts +53 -59
  59. package/src/cli/commands/worker/stop.ts +29 -29
  60. package/src/cli/commands/worker/submit.ts +3 -3
  61. package/src/cli/commands/{worktree → workspace}/create-core.ts +26 -26
  62. package/src/cli/commands/workspace/create.ts +30 -68
  63. package/src/cli/commands/workspace/list.ts +52 -9
  64. package/src/cli/commands/workspace/remove.ts +81 -0
  65. package/src/cli/completion/plan.ts +2 -2
  66. package/src/cli/completion/sources.ts +18 -18
  67. package/src/cli/context.ts +13 -13
  68. package/src/cli/flags.ts +30 -10
  69. package/src/cli/index.ts +26 -16
  70. package/src/cli/output.ts +1 -1
  71. package/src/cli/registry.ts +12 -12
  72. package/src/config/loader.ts +16 -5
  73. package/src/config.ts +149 -101
  74. package/src/daemon/catalog-writer.ts +67 -67
  75. package/src/daemon/daemon.ts +205 -201
  76. package/src/daemon/session-manager.ts +39 -39
  77. package/src/daemon/session-registry.ts +14 -14
  78. package/src/git/divergence.ts +33 -2
  79. package/src/git/git-poller.ts +1 -1
  80. package/src/git/git-status.ts +2 -2
  81. package/src/git/{move-worktree.ts → move-workspace.ts} +5 -5
  82. package/src/git/pr-merge.ts +64 -0
  83. package/src/git/pr-status-poller.ts +57 -0
  84. package/src/git/pr-status.ts +227 -0
  85. package/src/git/repo-discovery.ts +1 -1
  86. package/src/git/use-repo-discovery.ts +1 -1
  87. package/src/git/workspace-branch-poller.ts +54 -0
  88. package/src/git/workspace-divergence-poller.ts +65 -0
  89. package/src/git/worktree.ts +29 -0
  90. package/src/index.tsx +11 -6
  91. package/src/input/keymap/help-entries.ts +7 -5
  92. package/src/input/modes/bridge.ts +13 -12
  93. package/src/input/modes/handlers/shared.ts +1 -1
  94. package/src/input/modes/transitions.ts +34 -21
  95. package/src/input/modes/types.ts +48 -31
  96. package/src/ipc/manager-protocol.ts +44 -44
  97. package/src/ipc/protocol.ts +192 -166
  98. package/src/platform/open-url.ts +39 -0
  99. package/src/platform/play-sound.ts +184 -0
  100. package/src/platform/project-search.ts +8 -8
  101. package/src/platform/worktree-paths.ts +8 -6
  102. package/src/pty/assistant-question-extractor.ts +1 -1
  103. package/src/pty/assistant-status-detection-loop.ts +79 -55
  104. package/src/pty/assistant-status-detector.ts +40 -15
  105. package/src/pty/command-registry.ts +81 -1
  106. package/src/restart-terminal-manager.ts +1 -1
  107. package/src/services/ai-usage/provider.ts +9 -2
  108. package/src/services/ai-usage/spawn.ts +3 -1
  109. package/src/session-backend/bootstrap.ts +4 -4
  110. package/src/session-backend/local-session-backend.ts +78 -75
  111. package/src/session-backend/remote-session-backend.ts +64 -52
  112. package/src/session-backend/types.ts +43 -32
  113. package/src/settings/live.ts +90 -0
  114. package/src/settings/search.ts +43 -0
  115. package/src/settings/sections/about.ts +49 -0
  116. package/src/settings/sections/appearance.ts +54 -0
  117. package/src/settings/sections/automation.ts +105 -0
  118. package/src/settings/sections/commands.ts +72 -0
  119. package/src/settings/sections/editor.ts +56 -0
  120. package/src/settings/sections/experimental.ts +57 -0
  121. package/src/settings/sections/git.ts +112 -0
  122. package/src/settings/sections/index.ts +95 -0
  123. package/src/settings/sections/integrations.ts +23 -0
  124. package/src/settings/sections/layout.ts +72 -0
  125. package/src/settings/sections/notifications.ts +99 -0
  126. package/src/settings/sections/setup.ts +54 -0
  127. package/src/settings/sections/status-bar.ts +63 -0
  128. package/src/settings/settings-store.ts +207 -0
  129. package/src/settings/types.ts +108 -0
  130. package/src/snippets/run-shell-var.ts +48 -22
  131. package/src/state/actions.ts +342 -0
  132. package/src/state/app-store.ts +2 -1
  133. package/src/state/bars.ts +75 -0
  134. package/src/state/dispatch-ref.ts +1 -1
  135. package/src/state/git-pane-sizing.ts +0 -9
  136. package/src/state/layout-resize.ts +2 -2
  137. package/src/state/pr-status-store.ts +39 -0
  138. package/src/state/project-catalog.ts +275 -0
  139. package/src/state/project-data.ts +143 -0
  140. package/src/state/{session-persistence.ts → project-persistence.ts} +77 -73
  141. package/src/state/project-save.ts +46 -0
  142. package/src/state/project-workspaces.ts +392 -0
  143. package/src/state/reducers/auto-commit-state.ts +11 -10
  144. package/src/state/reducers/git-commit-modal-state.ts +122 -0
  145. package/src/state/reducers/git-mode-state.ts +1 -1
  146. package/src/state/reducers/git-panel-state.ts +10 -53
  147. package/src/state/reducers/modal-state.ts +290 -681
  148. package/src/state/reducers/multi-repo-state.ts +3 -6
  149. package/src/state/reducers/project-state.ts +312 -0
  150. package/src/state/reducers/settings-state.ts +80 -0
  151. package/src/state/reducers/tab-state.ts +107 -60
  152. package/src/state/reducers/ui-state.ts +86 -15
  153. package/src/state/selectors.ts +49 -37
  154. package/src/state/store.ts +83 -57
  155. package/src/state/types.ts +261 -417
  156. package/src/state/validation.ts +14 -12
  157. package/src/terminal-manager/manager-client.ts +32 -32
  158. package/src/terminal-manager/terminal-manager.ts +24 -24
  159. package/src/ui/components/git/diff-renderer/pierre-diff.tsx +2 -1
  160. package/src/ui/components/git/git-panel.tsx +2 -2
  161. package/src/ui/components/git/git-view.tsx +12 -12
  162. package/src/ui/components/git/image-diff/image-diff-view.tsx +1 -1
  163. package/src/ui/components/git/pane/git-pane-header.tsx +105 -39
  164. package/src/ui/components/git/pane/git-pane-widget.tsx +37 -23
  165. package/src/ui/components/git/pane/pr-checks-panel.tsx +197 -0
  166. package/src/ui/components/git/pane/pr-state-row.tsx +103 -0
  167. package/src/ui/components/layout/bar.tsx +191 -0
  168. package/src/ui/components/layout/sidebar/project-list.tsx +444 -0
  169. package/src/ui/components/layout/sidebar/tab-item.tsx +27 -12
  170. package/src/ui/components/layout/sidebar/use-sidebar-auto-scroll.ts +1 -1
  171. package/src/ui/components/layout/sidebar/workspace-row.tsx +253 -0
  172. package/src/ui/components/layout/status-bar.tsx +6 -2
  173. package/src/ui/components/layout/terminal-pane.tsx +34 -29
  174. package/src/ui/components/layout/top-tab-bar.tsx +48 -37
  175. package/src/ui/components/modals/git/git-commit-modal.tsx +8 -8
  176. package/src/ui/components/modals/{sessions/create-session-modal.tsx → projects/create-project-modal.tsx} +12 -12
  177. package/src/ui/components/modals/{sessions/session-name-modal.tsx → projects/project-name-modal.tsx} +2 -2
  178. package/src/ui/components/modals/{sessions/session-picker-modal.tsx → projects/project-picker-modal.tsx} +33 -35
  179. package/src/ui/components/modals/settings/settings-search-modal.tsx +70 -0
  180. package/src/ui/components/modals/shared/form.tsx +34 -22
  181. package/src/ui/components/modals/shared/picker.tsx +10 -10
  182. package/src/ui/components/modals/shared/{worktree-delete-confirm.tsx → workspace-delete-confirm.tsx} +9 -9
  183. package/src/ui/components/modals/tabs/new-tab-modal.tsx +30 -324
  184. package/src/ui/components/modals/workspace/create-workspace-modal.tsx +101 -0
  185. package/src/ui/components/modals/{worktree/worktree-move-confirm-modal.tsx → workspace/workspace-move-confirm-modal.tsx} +5 -5
  186. package/src/ui/components/modals/{worktree/worktree-move-modal.tsx → workspace/workspace-move-modal.tsx} +29 -27
  187. package/src/ui/components/primitives/input-field.tsx +7 -3
  188. package/src/ui/components/primitives/surface.tsx +3 -0
  189. package/src/ui/components/settings/row-value.tsx +74 -0
  190. package/src/ui/components/settings/settings-row.tsx +78 -0
  191. package/src/ui/components/settings/settings-view.tsx +169 -0
  192. package/src/ui/components/setup/setup-widget.tsx +152 -0
  193. package/src/ui/flash/build-labels.ts +27 -27
  194. package/src/ui/hooks/use-activity-sprite.ts +73 -0
  195. package/src/ui/hooks/use-scroll-active-into-view.ts +26 -0
  196. package/src/ui/{session-ordering.ts → project-ordering.ts} +4 -4
  197. package/src/ui/root.tsx +164 -230
  198. package/src/ui/status-bar-model.ts +48 -34
  199. package/src/ui/terminal-graphics/kitty.ts +28 -2
  200. package/src/ui/terminal-graphics/sprites/done@330/0.png +0 -0
  201. package/src/ui/terminal-graphics/sprites/done@330/1.png +0 -0
  202. package/src/ui/terminal-graphics/sprites/idle@1000/0.png +0 -0
  203. package/src/ui/terminal-graphics/sprites/idle@1000/1.png +0 -0
  204. package/src/ui/terminal-graphics/sprites/waiting@170/0.png +0 -0
  205. package/src/ui/terminal-graphics/sprites/waiting@170/1.png +0 -0
  206. package/src/ui/terminal-graphics/sprites/working@150/0.png +0 -0
  207. package/src/ui/terminal-graphics/sprites/working@150/1.png +0 -0
  208. package/src/ui/terminal-graphics/sprites/working@150/2.png +0 -0
  209. package/src/ui/terminal-graphics/sprites/working@150/3.png +0 -0
  210. package/src/ui/terminal-graphics/sprites.ts +246 -0
  211. package/src/ui/truncate.ts +7 -0
  212. package/src/ui/widgets/registry.tsx +21 -0
  213. package/src/ui/widgets/widget-context-menu.ts +85 -0
  214. package/src/update.ts +2 -2
  215. package/src/app-runtime/session-actions.ts +0 -187
  216. package/src/cli/client/workspace-resolver.ts +0 -110
  217. package/src/cli/commands/workspace/close.ts +0 -32
  218. package/src/cli/commands/workspace/show.ts +0 -32
  219. package/src/cli/commands/worktree/create.ts +0 -55
  220. package/src/cli/commands/worktree/list.ts +0 -68
  221. package/src/cli/commands/worktree/remove.ts +0 -81
  222. package/src/git/worktree-branch-poller.ts +0 -54
  223. package/src/git/worktree-divergence-poller.ts +0 -59
  224. package/src/state/reducers/session-state.ts +0 -262
  225. package/src/state/session-catalog.ts +0 -143
  226. package/src/state/session-worktrees.ts +0 -260
  227. package/src/state/workspace-save.ts +0 -45
  228. package/src/ui/components/git/pane/git-pane-context-menu.ts +0 -26
  229. package/src/ui/components/layout/sidebar/sidebar.tsx +0 -163
  230. package/src/ui/components/layout/sidebar/use-top-tab-bar-auto-scroll.ts +0 -30
  231. package/src/ui/components/layout/sidebar/workspace-list.tsx +0 -418
  232. package/src/ui/components/layout/sidebar/worktree-row.tsx +0 -149
  233. /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 { IPC_CAPABILITY_WORKSPACE_LIFECYCLE } from '../../../ipc/protocol'
4
- import { resolveWorkspace } from '../../client/workspace-resolver'
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 workspaceSwitch: CliCommand = {
11
- args: [{ complete: { kind: 'dynamic', source: 'workspace' }, name: 'workspace', required: true }],
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: 'workspace',
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 workspace is required (id or name)')
29
+ throw new Error('target project is required (id or name)')
30
30
  }
31
- const session = resolveWorkspace(target)
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(IPC_CAPABILITY_WORKSPACE_LIFECYCLE)) {
37
+ if (!daemon.hasCapability(IPC_CAPABILITY_PROJECT_LIFECYCLE)) {
38
38
  throw new Error(
39
- 'daemon predates workspaceLifecycle capability — restart aimux to pick up the new daemon'
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('switchWorkspace', { targetSessionId: session.id })
45
- writeJson({ name: session.name, targetSessionId: session.id })
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 `workspaceSwitched` synchronously in
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('workspaceSwitched', (payload) => {
54
- if (payload.sessionId !== session.id) return
53
+ const off = daemon.on('projectSwitched', (payload) => {
54
+ if (payload.projectId !== project.id) return
55
55
  off()
56
56
  clearTimeout(timer)
57
- writeJson({ name: session.name, targetSessionId: session.id })
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 workspaceSwitched',
64
- targetSessionId: session.id,
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('switchWorkspace', { targetSessionId: session.id })
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 workspace',
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 workspace = ctx.getWorkspace()
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, rows: 0, sessionId: workspace.id, thin: true })
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 workspace = ctx.getWorkspace()
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, rows: 0, sessionId: workspace.id, thin: true })
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 workspace',
31
+ summary: 'Close a tab in the project',
32
32
  verb: 'close',
33
33
  }